一、C++之类模板
1.1、类模板
类模板相对于函数模板而言的话相对复杂很多,因为要考虑到不同文件实现对象而导致模板之间定义问题。
类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,所以将类中的类型进行泛化。
格式如下:
我们简单写个例子:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
template <class T>
class Student
{
public:
Student(T id, T height)
{
this->id = id;
this->height = height;
}
void print(void)
{
cout << "id : " << id << " height:" << height << endl;
}
private:
T id;
T height;
};
int main(void)
{
Student<int> s(10,20);
s.print();
return 0;
}
运行结果:
这种使用方法和函数模版是一样的,我们在定义一个多个参数的。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
template <class T1,class T2>
class Student
{
public:
Student(T1 id, T2 name)
{
this->id = id;
this->name = name;
}
void print(void)
{
cout << "id : " << id << " 名字:" << name << endl;
}
private:
T1 id;
T2 name;
};
int main(void)
{
Student<int,string> s(10,"张三");
s.print();
return 0;
}
运行结果:
1.2、类模板之派生类
当我们定义派生类的时候又该如何定义类模板呢?
如下面代码:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
template<class T>
class Person
{
private:
T id;
T age;
};
class child:public Person
{
};
int main(void)
{
return 0;
}
但是编译器提示出错了:
我们当然能理解,毕竟父类中存在一个T是不明确的,所以我么可以这样尝试,在子类上面申明是类模板
虽然语法上过得去,但是还是编译出错,其实可以这么理解,子类上面的T是属于子类的,可是因为你继承父类中也是类模板,但是在父类中的T并没有明确类型,也就是编译器根本没办法根据这个类型来给成员开辟空间,所以在继承过程中,必须明确父类的模板类型。
所以我们可以这样修改:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
template<class T>
class Person
{
public:
Person(T id, T age)
{
this->id = id;
this->age = age;
}
T id;
T age;
};
template<class T>
class child:public Person<int>
{
public:
child(T id, T age, T height) :Person(id, age)
{
this->height = height;
}
void print(void)
{
cout << "id:" << this->id << " age:" << this->age << " height:" << this->height << endl;
}
public:
T height;
};
int main(void)
{
child<int> c(1,2,3);
c.print();
return 0;
}
在继承过程中主要是明确父类的数据类型:
class child:public Person<int>
1.3、类模板之类内实现
所谓类的类内实现,主要是某些函数在类里面进行实现,比如我们重载+实现复数的加法。
#include <iostream>
using namespace std;
template<class T>
class Person
{
public:
Person()
{
this->real = 0;
this->imaginary = 0;
}
Person(T real, T imaginary)
{
this->real = real;
this->imaginary = imaginary;
}
Person operator+(Person &other)
{
this->real = this->real + other.real;
this->imaginary = this->imaginary + other.imaginary;
return *this;
}
void print(void)
{
cout << this->real << " + " << this->imaginary << "i" << endl;
}
private:
T real; //实数
T imaginary;//虚数
};
int main(void)
{
Person<int> p1(1,2);
Person<int> p2(1, 2);
Person<int> p3;
p3 = p1 + p2;
p3.print();
return 0;
}
运行结果:
在类模板内实现函数和平常使用的方式几乎一样。
1.4、类模板之类外实现(同一个.cpp)
我们将上面那个的例子重载 +定义为友元函数,然后将构造、析构、打印函数在类的外面实现:
#include <iostream>
using namespace std;
template<class T>
class Person
{
public:
friend Person<T> operator+(Person<T> &p1, Person<T> &p2);
Person();
Person(T real, T imaginary);
void print(void);
private:
T real; //实数
T imaginary;//虚数
};
template<class T>
Person<T>::Person()
{
this->real = 0;
this->imaginary = 0;
}
template<class T>
Person<T>::Person(T real, T imaginary)
{
this->real = real;
this->imaginary = imaginary;
}
template<class T>
void Person<T>::print(void)
{
cout << this->real << " + " << this->imaginary << "i" << endl;
}
template<class T>
Person<T> operator+(Person<T> &p1, Person<T> &p2)
{
Person<T> temp;
temp.real = p1.real + p2.real;
temp.imaginary = p1.imaginary + p2.imaginary;
return temp;
}
int main(void)
{
Person<int> p1(1, 2);
Person<int> p2(1, 2);
Person<int> p3;
p3 = p1 + p2;
p3.print();
return 0;
}
可是在编译完后发现提示一个错误:
其实就是无法解释友元函数的申明,那么我么加上一个模板定义看看。
在友元申明函数上面协商一个模板申明。
编译运行,最终结果:
虽然能够运行,但是你以为这种写法就是正确的么?
我们丢到linux下面去运行看看,如下图,已经在linux下编写完成:
进行编译后发现出错了:
也就是说不同编译器之间存在少许差异,这些差异我们暂且不谈(具体细节我也现在搞不清楚,暂时留下这个坑),我根据《轻松搞定C++》这本书籍上面描述暂且以VS2013为编译器进行描述:
1、在模板类中 如果有友元重载操作符<<或者>>需要在operator<< 和 参数列表之间加入 < T >。
2、如果说是不是<<和>> ,需要在在模板类中当友元函数在这个模板类 之前声明这个函数。
例如上面那个例子:
template<class T>
class Person;
template<class T>
Person<T> operator+(Person<T> &p1, Person<T> &p2);
在类前面对他进行申明。
运行结果:
其实我测试发现,哪怕不申明,只需要添加 < T >,也是可以运行的。
这里就有些混乱了,于是我去查看C++宝典,里面有这段话描述:
根据这段话的描述,其实就是我们第二点的描述,所以建议以后写类模板的友元函数一律按照这种方式来写,别偷懒。
我们折腾这么一大段,这个类模板的友元函数使用起来极其麻烦,不仅滥⽤用友元函数,而且本来可以当成员函数,却要⽤用友元函数。
所以在这里极其建议:模板类 不要轻易写友元函数, 要写的 就写<<
和>> 。
1.5、类模板之类外实现(一个.cpp,一个.h)
这里指的是创建一个类,进行实现函数。
我们创建一个类,具体创建方法不懂的可以百度。
这是我们的主函数:
#include <iostream>
#include "Person.h"
using namespace std;
int main(void)
{
Person<int> p(1,2);
p.print();
return 0;
}
这是我们的类的.h文件:
#pragma once
template<class T>
class Person
{
public:
Person(T id, T age);
~Person();
void print(void);
private:
T id;
T age;
};
这是类的.c文件
#include "Person.h"
template<class T>
Person<T>::Person(T id, T age)
{
this->id = id;
this->age = age;
}
template<class T>
Person<T>::~Person()
{
}
template<class T>
void Person<T>::print(void)
{
cout << "id:" << this->id << " age:" << this->age << endl;
}
其实我们的目的很简单,就是创建一个人的对象,然后将她的信息打印出来。
我们编译一下:
发现报错了,我解释一下这个错误的缘由。
之前我们讲函数模板的时候,说过模板会进行二次编译,第一次编译会检测语法是否过得去,第二次编译是根据实际的参数创建对应的函数。
而且我们也知道编译的过程:
编译器会单独对每个.c文件编译成目标文件,他里面的函数他会设定一个占位符,留着链接器根据所有的目标文件,进行替换。
结合上面这两点,我们就确定一下,语法上是没问题的,问题是出在链接器那里,为什么这么说呢?
因为类那个文件只是第一次编译成目标文件,因为你没有具体的参数,所以他不会生成具体的函数,也就是不会进行第二次编译。
而在主函数中这个占位符,链接器想去寻找这个函数的时候,发现并没有对应的函数(因为模板函数没有进行第二次编译),所以直接报错了。
那么怎么修改呢?
既然想要模板进行两次编译,只需要告诉他具体的类型参数进去即可,而且是在同一个文件内,因为你只要分开,他就单独编译了。
在主函数文件中,添加这个头文件:
#include "Person.cpp"
其实说白了,就是把类函数中定义的模板拿到和主函数一个文件内,那么在同一个文件,既有定义,又有真实传参,所以模板函数肯定会二次编译产生具体函数,这样就可以调用了。
运行结果如下:
其实为了区分,我们一般写专门定义类函数模板的地方的文件
这个后缀就代表他是类的函数模版,里面都是定义的模板,并没有具体实现,所以使用时候需要注意。
1.5、类模板之static
关于类模板和函数模板一个道理,就是一个模子生产实际事物,就是一个原型,生产实际函数。
那么关于static是否是类模板的共享资源,还是不同实现的类的共同资源呢?
毫无疑问,只有在第二次编译的时候才产生实际的类,利用类模板产生的类之间是不同的,所以对于里面的static成员,也是各自属于各自。
他们的关系如下面这个图: