所谓 “继承(inheritance)” 就是在一个已经存在的类基础上,再建立一个新类
从已有的类派生出新的类,派生类就继承了原有类(基类)的特征,包括成员和方法
通过继承可以完成以下的一些功能:
注:继承机制只需提供新特性,甚至不需要访问源码就可以派生出类(即如果购买的类库只提供了类的方法的头文件和编译后的代码,仍可以使用库中的类派生出的新类,而且可以在不公开实现的情况下将自己的类分发给其他人,同时允许他们在类中添加新特性)
使用继承的优点:
注:
语法:class 子类 : 继承方式 父类
基类的公有成员和受保护成员,在派生类中保持原来的访问属性,其私有成员仍为基类所独有
私有继承(private inheritance)
基类的公有成员和受保护成员,在派生类中成为了私有成员,私有成员仍为基类独有
受保护继承(private inheritance)
基类公有成员和受保护成员,在派生类中成了受保护成员,私有成员仍为基类独有
派生类的成员访问有三种方式:
继承方式供给程序员对类进行封装的机制:
1.全部继承,不封装基类,那么用公有继承
2.全部继承,完全封装基类,那么使用私有继承
3.全部继承,有选择封装基类,那么使用受保护继承
不管是哪种继承,派生类都不能访问基类的私有成员(除非改成protected)
继承的C++实现
class Base1 {
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
//公有继承
class Son1 : public Base1 {
public:
void func() {
m_a = 10;//基类中的公共权限成员,在子类中依然是公共权限
m_b = 20;//基类中的保护权限成员,在子类中依然是保护权限
//m_c = 30;//基类中的私有成员,在子类中不可访问
}
};
void test01() {
Son1 s1;
s1.m_a = 10;
//s1.m_b = 10;//到son1中,m_b是保护权限,类外不可访问
}
//保护继承
class Son2 : protected Base1 {
public:
void func() {
m_a = 10;//基类中的公共权限成员,在子类中变为保护权限
m_b = 10;//基类中的保护权限成员,在子类中依然是保护权限
//m_c = 10;//基类中的私有成员,在子类中不可访问
}
};
void test02() {
Son2 s2;
//s2.m_a = 10;//到son2中,m_a变为保护权限,类外不可访问
//s2.m_b = 10;//到son2中,m_b变为保护权限,类外不可访问
}
//私有继承
class Son3 : private Base1 {
public:
void func() {
m_a = 10;//基类中的公共权限成员,在子类中变为私有成员
m_b = 10;//基类中的保护权限成员,在子类中变为私有成员
//m_c = 10;//基类中的私有成员,在子类中不可访问
}
};
void test03() {
Son3 s3;
//s3.m_a = 10;//到son3中,m_a变为私有成员,类外不可访问
//s3.m_b = 10;//到son3中,m_b变为私有成员,类外不可访问
}
基类中所有非静态成员属性都会被子类继承下去
基类中私有成员属性,是被编译器隐藏了,因此是不可访问的,但是确实是被继承了
在继承中,构造的顺序:先构造基类,在构造子类,析构则相反
访问子类同名成员,直接访问即可
访问父类同名成员,需要加作用域
class Base {
public:
Base() {
m_a = 10;
}
void func() {
cout << "Base 的func()调用" << endl;
}
void func(int a) {
cout << "Base 的func(int a)调用" << endl;
}
int m_a;
};
class Son : public Base {
public:
Son() {
m_a = 20;
}
void func() {
cout << "Son 的func()调用" << endl;
}
int m_a;
};
//同名成员函数的处理
void test() {
Son s1;
s1.func();//直接调用是调用子类中的同名成员函数
s1.Base::func();
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
//s1.func(100);
//解决方案:加作用域
s1.Base::func(10);
}
int main() {
Son s;
cout << "Son中的m_a = " << s.m_a << endl;
//通过子类对象访问父类的同名成员,加作用域
cout << "Base中的m_a = " << s.Base::m_a << endl;
test();
}
总结:
继承中同名静态成员处理方式与非静态成员同名处理方式一致
静态成员属性特点:编译阶段分配内存;所有对象共享同一份数据;类内声明,类外初始化
class Base {
public:
static void func() {
cout << "Base 的func()调用" << endl;
}
static void func(int a) {
cout << "Base 的func(int a)调用" << endl;
}
static int m_a;//静态成员属性特点:编译阶段分配内存;所有对象共享同一份数据;类内声明,类外初始化
};
int Base::m_a = 10;
class Son : public Base {
public:
static void func() {
cout << "Son 的func()调用" << endl;
}
static int m_a;
};
int Son::m_a = 5;
//同名静态成员属性
void test1() {
//1.通过对象访问
Son s;
cout << "通过对象访问" << endl;
cout << "Son中的m_a = " << s.m_a << endl;
cout << "Base中的m_a = " << s.Base::m_a << endl;
//2.通过类名访问
cout << "通过类名访问" << endl;
cout << "Son中的m_a = " << Son::m_a << endl;
//第一个::代表通过类名方访问,第二个::代表访问父类作用域下
cout << "Base中的m_a = " << Son::Base::m_a << endl;
}
//同名静态成员函数
void test2() {
Son s1;
//通过对象访问
cout << "通过对象访问" << endl;
s1.func();
s1.Base::func();
//通过类名访问
cout << "通过类名访问" << endl;
//子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
//如果想访问父类中被隐藏同名成员,需要加作用域
Son::func();
Son::Base::func();
Son::Base::func(10);
}
int main() {
test1();
test2();
return 0;
}
c++可一个类继承多个类
语法:class 子类 : 继承方式 父类1 , 继承方式 父类2…
注:当多继承引发父类中有同名成员出现,需要加作用域区分
概念:两个派生类继承同一个类,又有某个类同时继承两个派生类,这种继承称为菱形继承,或者钻石继承
class Animal {//动物类
public:
int m_age;
};
//利用虚继承(关键字virtual),解决菱形继承导致数据有两份,资源浪费的问题
//在继承前加上virtual
//Animal类称为虚基类
class Sheep :virtual public Animal {};//羊类
class Tuo : virtual public Animal {};//驼类
class SheepTuo : public Sheep, public Tuo {};//羊驼类
void test() {
SheepTuo st;
st.Sheep:: m_age = 1;
st.Tuo::m_age = 5;
cout << "st.Sheep::m_age = " << st.Sheep::m_age << endl;
cout << "st.Tuo::m_age = " << st.Tuo::m_age << endl;
//加上虚继承之后:
cout << "st.m_age = " << st.m_age << endl;
}
int main() {
test();
}
单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承。(一对一的关系)
没有继承时,成员变量和成员函数会分开存储:
有继承关系时的内存模型:
在初始化时,先初始化基类成员,在初始化派生类成员。释放时,先释放派生类成员,在释放基类成员
注:
派生类及基类中存在同名函数时调用问题:
情况1:派生类中如果不实现Move方法,默认会调用基类实现
情况2:派生类实现了Move方法(相当于覆盖了基类实现),那么就会调用子类实现
小结:
refHero.Move() //基类引用调用函数
ptrHero->Move(); //基类指针调用函数
3.派生类对象可以赋值给基类对象,程序将使用隐式重载赋值运算符
注:
Hero& refHero = warrior1;
Hero * ptrHero = &warrior; //基类指针也可以指向派生类对象
Warrior& warrior2 = (Warrior&)refHero;//父类引用/指针需要强转成子类引用/指针
面向对象编程的多态性包括:
向不同的对象发送同一条消息(函数调用);不同的对象在接收时会产生不同的行为(不用的实现,即执行不同的函数。函数名相同,单执行的具体细节不同)
多态分为两类:
静态多态:函数重载和运算符重载属于静态多态
动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
静态多态的函数地址早绑定——编译阶段确定函数地址
动态多态的函数地址晚绑定——运行阶段确定函数地址
特点:
函数名相同;
函数参数个数不同;
如果参数个数相同,那么参数类型不能相同;
如果参数个数相同,并且类型也相同,那么必须是顺序不同
函数重载与函数返回值无关
函数重写:函数返回值类型、函数名、参数列表要完全相同
要实现C++函数重写,必须要先把父类的成员函数设定为虚函数。
C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类定义的虚函数进行重写时,需要在派生类中声明该方法为虚方法。
动态多态满足条件:
多态使用条件:父类指针或引用指向子类对象
virtual 返回值 函数名();
注:派生类重写基类方法Move时,可以加上override关键字表示重写;override关键字为C++11标准后新加入的,用来明确派生类重写基类继承来的虚函数
class Animal {//动物类
public:
//虚函数,基类加上virtual之后,子类可加可不加
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Cat : public Animal {
public:
virtual void speak() {
cout << "小猫在说话" << endl;
}
};
class Dog : public Animal {
virtual void speak() {
cout << "小狗在说话" << endl;
}
};
//执行说话的函数
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定—地址晚绑定
void doSpeak(Animal& animal) {
animal.speak();
}
void test1() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main() {
test1();
}
多态案例:利用多态实现两个操作数进行运算的计算器类
class AbstractCalculator {
public:
virtual int getResult() {
return 0;
}
int m_Num1;//操作数1
int m_Num2;//操作数2
};
class AddCalculator : public AbstractCalculator {//加法计算器类
public:
virtual int getResult() {
return m_Num1 + m_Num2;
}
};
class SubCalculator : public AbstractCalculator {//减法计算器类
public:
virtual int getResult() {
return m_Num1 - m_Num2;
}
};
class MulCalculator : public AbstractCalculator {//乘法计算器类
public:
virtual int getResult() {
return m_Num1 * m_Num2;
}
};
void test02() {
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
//减法运算
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
//乘法
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
}
int main() {
test02();
}
纯虚函数语法:virtual 返回值类型 函数名(参数列表) = 0;
当类中有了这个纯虚函数,这个类也称为抽象类
抽象类特点:
using namespace std;
class Base {
public:
virtual void func() = 0;
};
class Son : public Base {
public:
void func() {
cout << "func()的调用" << endl;
}
};
int main() {
Base * base = new Son;
base->func();
delete base;
}
多态使用时,如果子类有属性开辟在堆区,父类指针在释放时无法调用子类的析构函数;解决方式:将父类中的析构函数改为虚析构或纯虚析构
虚析构和纯虚析构共性:
1.可以解决父类指针释放子类对象
2.都需要有具体的函数实现
区别:如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名() {}
纯虚析构语法:
virtual ~类名() = 0;
using namespace std;
class Animal {
public:
Animal() {
cout << "Animal构造调用" << endl;
}
/*virtual ~Animal() {
cout << "Animal析构调用" << endl;
}*/
virtual ~Animal() = 0;//纯虚析构,需要实现
virtual void speak() = 0;
};
//Animal:: ~Animal() {//纯虚析构
// cout << "Animal纯虚析构调用" << endl;
//}
class Cat : public Animal {
public:
Cat(string name) {
cout << "Cat构造调用" << endl;
m_Name = new string(name);
}
void speak() {
cout << *m_Name << "小猫在说话" << endl;
}
~Cat() {
if (m_Name != NULL) {
cout << "Cat析构调用" << endl;
delete m_Name;
}
}
string* m_Name;
};
void testCat() {
//父类指针在析构时,不会调用子类中析构,导致子类如果有堆区属性,出现内存泄漏
//解决方案:在父类析构前加virtual
Animal* animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main() {
testCat();
}
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类
当B是A的子类型时,就意味着所有对A的操作都可以对B对象,即B重用A的操作来实现自己的操作
向上转型:把子类型对象转换为父类型对象,有三个注意点:
·
例如:Warrior w;
w.XiaoQuanQuan();
Warrior warrior; //子类型对象
Hero& hero = warrior;//父类型引用指向了子类型对象 - 向上转型
hero.XiaoQuanQuan(); //丢失了子类型信息,编译器会报错
如果还想调用子类型方法,那么就需要在进行强制转换 - 向下转型
Warrior& newWarrior = (Warrior&)hero;//向下转型不安全,因为hero对象有可能是Hero父类型的另一个子类型
假设程序是这样的:
Archmage warrior;
Hero& hero = warrior;
Warrior& newWarrior = (Warrior&)hero;
注:
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放;通过文件可以将数据持久化
c++中对文件操作需要包含头文件:<fstream>
文件类型分为两种:
1.文本文件——文件以ASCLL码形式存储在计算机中
2.二进制文件——文件以文本的二进制形式存储在计算机中
操作文件的三大类:
1.ofstream:写操作
2.ifstream:读操作
3.fstream:读写操作
写文件的步骤:
1.包含头文件:#include <fstream>
2.创建流对象:ofstream ofs;
3.打开文件:ofs.open("文件路径", 打开方式);
4.写数据:ofs<<"写入数据";
5.关闭文件:ofs.close();
文件打开方式:
注:文件打开方式可以配合使用,用 | 操作符
例如:用二进制方式写文件ios::binary | ios::out
#include
#include
using namespace std;
int main() {
//创建流对象
ofstream ofs;
//指定打开方式
ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs << "姓名:李四" << endl;
ofs << "性别:男" << endl;
ofs.close();
}
读文件的步骤:
1.包含头文件:#include <fstream>
2.创建流对象:ifstream ifs;
3.打开文件并判断文件是否打开成功:ifs.open("文件路径", 打开方式);
4.读数据:有四种
5.关闭文件:ifs.close();
#include
#include
#include
using namespace std;
void test() {
//1.包含头文件
//2.创建流对象
ifstream ifs;
//3.指定打开方式并判断是否打开成功
ifs.open("test.txt", ios::in);
if ( !ifs.is_open()) {
cout << "文件打开失败!" << endl;
return;
}
//4.读数据
//第一种:
/*char buf[2014] = { 0 };
while (ifs >> buf) {
cout << buf << endl;
}*/
//第二种
/*char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf))) {
cout << buf << endl;
}*/
//第三种
/*string buf;
while (getline(ifs, buf)) {
cout << buf << endl;
}*/
//第四种
char c;
while ((c = ifs.get()) != EOF) { //EOF 文件结尾
cout << c;
}
//5.关闭文件
ifs.close();
}
int main() {
test();
}
以二进制的方式进行读写操作;打开方式要指定为:ios::binary
二进制方式写文件主要利用流对象调用成员函数writer
函数原型
ostream& writer(const char * buffer, int len);
字符指针buffer指向内存中一段空间,len是读写的字节数
#include
#include
using namespace std;
class Person {
public:
char m_Name[64];
int m_age;
};
void test() {
//创建流对象
ofstream ofs("person.text", ios::out | ios::binary);
//打开文件,可以和创建流对象和成一步操作
//ofs.open("person.text", ios::out | ios::binary);
//写文件
Person p = { "张三", 18 };
ofs.write((const char*)&p, sizeof(Person));
ofs.close();
}
int main() {
test();
}
二进制方式读文件主要利用流对象调用成员函数read
函数原型
istream& read(插入 * buffer, int len):
字符指针buffer指向内存中一段存储空间,len是读写的字节数
#include
#include
#include
using namespace std;
class Person {
public:
char m_Name[640];
int m_age;
};
void test() {
//创建流对象
ifstream ifs;
//打开文件,判断文件是否打开成功
ifs.open("person.text", ios::in | ios::binary);
if (!ifs.is_open()) {
cout << "文件打开失败!" << endl;
}
//读文件
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << "姓名:" << p.m_Name << "年龄" << p.m_age << endl;
//关闭文件
ifs.close();
}
int main() {
test();
}
模板的概念:模板就是建立通用的模具,大大提高复用性
模板的特点:
1.模板不可以直接使用,他只是一个框架
2.模板的通用并不是万能的
c++中的泛型编程思想,主要利用的技术就是模板,c++提供的两种模板机制就是函数模板和类模板
函数模板的作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
函数模板语法
template<typename T>
函数声明或定义
//typename - 表明其后的符号是一种数据类型,可以使用class代替
//T - 通用数据类型,名称可以替换
使用模板的两种方法:
1.自动类型推导
2.显示指定类型
//声明模板,T是通用的数据类型
template<typename T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
void test() {
int a = 2, b = 3;
//利用函数模板交换
//两种方式使用函数模板
//1.自动类型推导
mySwap(a, b);
//2.显示指定类型
//mySwap(a, b);
cout << a << '\t' << b << endl;
double c = 2.1, d = 3.1;
mySwap(c, d);
cout << c << '\t' << d << endl;
}
int main() {
test();
}
注:
案例描述:
template<class T>//函数交换模板
void Swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
template<typename T>
void mySort(T array[], int len) {
for (int i = 0; i < len; i++) {
int max = i;//认定最大值的下标
for (int j = i + 1; j < len; j++) {
//认定的最大值比遍历出的数值要小,说明j下标的元素才是真正的最大值
if (array[max] < array[j]) {
max = j;
}
}
if (max != i) {//交换max和i下标的元素
Swap(array[max], array[i]);
}
}
}
template<class T>//打印结果模板
void Print(T array[], int len) {
for (int i = 0; i < len; i++) {
cout << array[i] << ' ';
}
cout << endl;
}
void test1() {
char charArray[] = "asdfghj";
int lenght = sizeof(charArray) / sizeof(char);
mySort(charArray, lenght);
Print(charArray, lenght);
}
void test2() {
int intArray[] = { 1,3,4,55,77,11,33,10,35 };
int lenght = sizeof(intArray) / sizeof(int);
mySort(intArray, lenght);
Print(intArray, lenght);
}
int main() {
test1();
cout << "------------" << endl;
test2();
}
隐式类型转换参考:https://blog.csdn.net/liunan199481/article/details/85251197
区别:
1.普通函数调用时可以发生自动类型转换(隐式类型转换)
2.函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
3.如果利用显示指定类型的方式,可以发生隐式类型转换
//普通函数调用
int myAdd(int a, int b) {
return a + b;
}
//函数模板
template<typename T>
T myAdd1(T a, T b) {
return a + b;
}
void test() {
int a = 10;
int b = 2;
char c = 'c';
cout << myAdd(a, c) << endl;
//函数模板用自动类型推导不可发生自动类型转换
//cout << myAdd1(a, c) << endl;//错误
//显示指定类型
cout << myAdd1<int>(a, c) << endl;
}
int main() {
test();
}
调用规则:
1.如果函数模板和普通函数都可以实现,优先调用普通函数
2.可以通过空模板参数列表来强制调用函数模板
3.函数模板也可以发生重载
4.如果函数模板可以产生更好的匹配,优先调用函数模板
void myPrint(int a, int b) {
cout << "调用普通函数" << endl;
}
template<typename T>
void myPrint(T a, T b) {
cout << "函数模板调用" << endl;
}
template<typename T>
void myPrint(T a, T b, T c) {
cout << "重载的函数模板调用" << endl;
}
void test() {
int a = 10;
int b = 1;
int c = 3;
myPrint(a, b);
myPrint<>(a, b);//通过空模板参数列表,强制调用函数模板
myPrint(a, b, c);//重载
//函数模板产生更好的匹配,优先调用函数模板
char c1 = 'a';
char c2 = 'b';
myPrint(c1, c2);
}
int main() {
test();
}
注:模板并不是万能的
template<typename T>
void fun(T a, T b){
a = b;
}
如果传入的是两个数组就无法实现
利用具体化的模板,可以解决自定义类型的通用化
template<typename T>//比较模板
bool Comper(T& a, T& b) {
if (a == b) return true;
else false;
}
class Person {
public:
Person(string name, int age) {
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
//利用具体化Person的版本实现代码,具体化优先调用
template<> bool Comper(Person& a, Person& b) {
if (a.m_name == b.m_name && a.m_age == b.m_age)return true;
else return false;
}
int main() {
Person person("张三", 10);
Person person1("张三", 10);
bool ret = Comper(person, person1);
if (ret)cout << "p1 == p2" << endl;
else cout << "p1 != p2 " << endl;
}
作用:建立一个通用类,类中的成员,数据类型可以不具体制定,用一个虚拟的类型来代表
语法:
template<typename T>
类
//类模板
template<class NameType, class AgeType>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_name = name;
this->m_age = age;
}
NameType m_name;
AgeType m_age;
};
int main() {
Person<string, int> p1("张三", 10);
cout << p1.m_name << '\t' << p1.m_age << endl;
}
注:类模板与函数模板的区别:
1.类模板没有自动类型推导的使用方式
2.类模板在模板参数列表中可以有默认参数
类模板中成员函数和普通类中成员函数创建时机的区别
1.普通类中的成员函数一开始就可以创建
2.类模板中的成员函数在使用时才创建
类模板实例化处的对象向函数传参的方式有三种:
1.指定传入的类型 — — 直接显示对象的数据类型
2.参数模板化 — — 将对象中的参数变为模板进行传递
3.整个类模板化 — — 将这个对象类型模板进行传递
template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
void ShowPerson() {
cout << "姓名:" << this->m_Name << endl;
cout << "年龄:" << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//1.指定传入类型
void PrintPerson1(Person<string, int>&p) {
p.ShowPerson();
}
void test1() {
Person<string, int>p("张三", 10);
PrintPerson1(p);
}
//2.参数模板化
template<class T1, class T2>
void PrintPerson2(Person<T1, T2>& p1) {
p1.ShowPerson();
}
void test2() {
Person<string, int>p("李四", 10);
PrintPerson2(p);
}
//3.整个类模板化
template<class T>
void PrintPerson3(T &p) {
p.ShowPerson();
}
void test3() {
Person<string, int>p("王五", 20);
PrintPerson3(p);
}
int main() {
test1();
test2();
test3();
}
当类模板碰到继承时,注:
1.当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型
2.如果不指定,编译器无法给子类分配内存
3.如果想灵活指出父类中T的类型,子类也需变为类模板
template<class T>
class Base {
public:
T m_a = 'a';
};
//calss Son:public Base//必须要知道父类中的T类型,才能继承给子类
class Son:public Base<int> {};
template<class T1, class T2>
class Son1 :public Base<T2> {
public:
//T2为继承的父类成员,T1为子类成员
T1 m_b = 'w';
};
int main() {
Son1<int, char>s2;
cout << s2.m_a << endl;
cout << s2.m_b << endl;
}
template<class T1, class T2>
class Base {
public:
Base(T1 a, T2 b);
void show();
T1 m_a;
T2 m_b;
};
template<class T1, class T2>
Base<T1, T2>::Base(T1 a, T2 b) {//构造函数类外实现
this->m_a = a;
this->m_b = b;
}
template<class T1, class T2>
void Base<T1, T2>::show() {//成员函数类外实现
cout << this->m_a << ' ' << this->m_b << endl;
}
int main() {
Base<int, int> base(1, 2);
base.show();
}
注:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决方式1.直接包含.cpp文件;
解决方式2.将声明和实现写在同一个文件中,并更改后缀名为.hpp,hpp是约定的名称并不是强制的
//Person.h文件, 第二种方法将后缀名改为.hpp
#pragma once
#include
#include
using namespace std;
template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age);
T1 m_name;
T2 m_age;
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
//Person.cpp文件
//#include "Person.h"
//template
//Person::Person(T1 name, T2 age) {
// this->m_name = name;
// this->m_age = age;
//}
//main.cpp文件
#include
//#include "Person.cpp"//第一种解决方式,直接包含源文件
#include "Person.hpp"
//第二种方式,把.h和.cpp中的内容写到一起,将后缀名改为.hpp文件
int main() {
Person<string, int>p("张三", 10);
cout << p.m_name << " " << p.m_age << endl;
}
全局函数类内实现,直接在类内声明友元即可
全局函数类外实现,需要让提前让编译器知道全局函数的存在
template<class T1, class T2>//提前让编译器知道Person类
class Person;
//全局函数,类外实现
template<class T1, class T2>
void PrintPerson2(Person<T1, T2>p) {
cout << "类外实现的:" << p.m_name << " " << p.m_age << endl;
}
template<class T1, class T2>
class Person {
//全局函数,类内实现
friend void PrintPerson(Person<T1, T2>p) {
cout << "类内实现:" << p.m_name << " " << p.m_age << endl;
}
//全局函数,类外实现
//加上空模板的参数列表,如果全局函数是类外实现,需要让编译器提前知道这个函数的存在
friend void PrintPerson2<>(Person<T1, T2>p);
public:
Person(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
T1 m_name;
T2 m_age;
};
int main() {
Person<string, int>p("张三", 3);
PrintPerson(p);
PrintPerson2(p);
}