10继承

10.1 概念:

所谓 “继承(inheritance)” 就是在一个已经存在的类基础上,再建立一个新类

从已有的类派生出新的类,派生类就继承了原有类(基类)的特征,包括成员和方法

通过继承可以完成以下的一些功能:

  1. 可以在已有类的基础上添加新功能,如对于数组类,可以添加数学计算
  2. 可以给类添加数据成员,如对于字符串类,可以派生出一个类,并添加指定成员表示颜色
  3. 可以修改类方法的行为,如对于普通英雄类,可以派生出拥有更丰富技能的近战英雄类

注:继承机制只需提供新特性,甚至不需要访问源码就可以派生出类(即如果购买的类库只提供了类的方法的头文件和编译后的代码,仍可以使用库中的类派生出的新类,而且可以在不公开实现的情况下将自己的类分发给其他人,同时允许他们在类中添加新特性)

使用继承的优点

  1. 基类定义公共内容,方便统一修改
  2. 重定义基类的成员函数
  3. 添加新类、新成员(职业)方便

注:

  1. 派生类对象存储了积累的数据成员,即:派生类继承了基类的实现
  2. 派生类对象可以使用基类的非私有函数(私有的不能访问),即:派生类继承了基类的接口
  3. 派生类需要自己的构造函数
  4. 派生类可以根据需要添加额外的数据成员和函数

10.2 继承方式:

语法:class 子类 : 继承方式 父类


公有继承(public inheritance)

基类的公有成员和受保护成员,在派生类中保持原来的访问属性,其私有成员仍为基类所独有

私有继承(private inheritance)

基类的公有成员和受保护成员,在派生类中成为了私有成员,私有成员仍为基类独有

受保护继承(private inheritance)

基类公有成员和受保护成员,在派生类中成了受保护成员,私有成员仍为基类独有

派生类的成员访问有三种方式:

  1. 公有访问:公有权限下,基类、派生类及外部都可以访问
  2. 私有访问: 私有权限下,只能基类访问,派生类及外部都无法访问
  3. 保护访问:受保护权限下,基类和派生类可以访问,外部无法访问

继承方式供给程序员对类进行封装的机制:

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变为私有成员,类外不可访问
}

10.3 继承下的C++对象模型

基类中所有非静态成员属性都会被子类继承下去

基类中私有成员属性,是被编译器隐藏了,因此是不可访问的,但是确实是被继承了

在继承中,构造的顺序:先构造基类,在构造子类,析构则相反

10.4 继承同名成员处理方式

访问子类同名成员,直接访问即可
访问父类同名成员,需要加作用域

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();
}

总结:

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

继承中同名静态成员处理方式与非静态成员同名处理方式一致

静态成员属性特点:编译阶段分配内存;所有对象共享同一份数据;类内声明,类外初始化

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;
}

10.5 多继承

c++可一个类继承多个类
语法:class 子类 : 继承方式 父类1 , 继承方式 父类2…

注:当多继承引发父类中有同名成员出现,需要加作用域区分

10.6 菱形继承

概念:两个派生类继承同一个类,又有某个类同时继承两个派生类,这种继承称为菱形继承,或者钻石继承

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();
}

10.7.1单继承1

单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承。(一对一的关系)

没有继承时,成员变量和成员函数会分开存储:

  1. 对象的内存中只包含成员变量,存储在栈区或堆区(使用new创建对象时)
  2. 成员函数与对象内存分离,存储在代码区


注:

  1. 编译器会将成员变量和成员函数分开存储:分别为每个对象的成员变量分配内存,但是所有对象都共享同一段代码
  2. 类可以看做是一种复杂的数据类型,如果使用sizeof求类所占空间的大小,会发现,只是计算了成员变量的大小,并没有把成员函数也包含在内

10.7.1单继承2

有继承关系时的内存模型:

  1. 有继承关系时,派生类的内存模型可以看成是基类成员变量和新增成员变量的总和;
  2. 所有的成员函数仍然存储在另外一个区域——代码区,由所有对象共享

(上图为实现继承关系的战士对象所占的内存)

在初始化时,先初始化基类成员,在初始化派生类成员。释放时,先释放派生类成员,在释放基类成员

注:

  1. 实例化派生类对象时,首先会创建基类对象(调用基类构造)
  2. 派生类构造应通过成员初始化列表将基类信息传递给基类构造
  3. 应该在派生类构造中初始化派生类新增的数据成员

派生类及基类中存在同名函数时调用问题:

情况1:派生类中如果不实现Move方法,默认会调用基类实现
情况2:派生类实现了Move方法(相当于覆盖了基类实现),那么就会调用子类实现

小结:

  1. 派生类对象可以使用基类的非私有成员函数
  2. 基类指针可以在不进行显式类型转换的情况下指向派生类对象(基类指针可以直接指向派生类对象)
refHero.Move()   //基类引用调用函数
ptrHero->Move(); //基类指针调用函数

3.派生类对象可以赋值给基类对象,程序将使用隐式重载赋值运算符

注:

  • 基类指针或引用只能调用基类函数,并不能调用派生类函数
  • 如果使用基类引用指向派生类对象,那么基类引用就不能调用派生类中定义的方法了
Hero& refHero = warrior1;
Hero * ptrHero = &warrior; //基类指针也可以指向派生类对象
Warrior& warrior2 = (Warrior&)refHero;//父类引用/指针需要强转成子类引用/指针
  • 不可以将基类对象和地址赋值给派生类引用和对象,即不能够做逆操作

11 多态

面向对象编程的多态性包括:

向不同的对象发送同一条消息(函数调用);不同的对象在接收时会产生不同的行为(不用的实现,即执行不同的函数。函数名相同,单执行的具体细节不同)

多态分为两类:

静态多态:函数重载和运算符重载属于静态多态
动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态的区别:

静态多态的函数地址早绑定——编译阶段确定函数地址
动态多态的函数地址晚绑定——运行阶段确定函数地址

11.1 静态多态——函数重载

特点:

函数名相同;
函数参数个数不同;
如果参数个数相同,那么参数类型不能相同;
如果参数个数相同,并且类型也相同,那么必须是顺序不同
函数重载与函数返回值无关

11.2 动态多态——函数重写

函数重写:函数返回值类型、函数名、参数列表要完全相同

要实现C++函数重写,必须要先把父类的成员函数设定为虚函数。

C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类定义的虚函数进行重写时,需要在派生类中声明该方法为虚方法。

动态多态满足条件:

  1. 有继承关系
  2. 子类重写父类的虚函数

多态使用条件:父类指针或引用指向子类对象

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;//操作数1int 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();
}

11.3 纯虚函数和抽象类

纯虚函数语法: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;
}

11.4 虚析构和纯虚析构

多态使用时,如果子类有属性开辟在堆区,父类指针在释放时无法调用子类的析构函数;解决方式:将父类中的析构函数改为虚析构或纯虚析构

虚析构和纯虚析构共性:

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() {//父类指针在析构时,不会调用子类中析构,导致子类如果有堆区属性,出现内存泄漏//解决方案:在父类析构前加virtualAnimal* animal = new Cat("Tom");animal->speak();delete animal;
}
int main() {testCat();
}

总结:

1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类

11.5 向上转型和向下转型

当B是A的子类型时,就意味着所有对A的操作都可以对B对象,即B重用A的操作来实现自己的操作

向上转型:把子类型对象转换为父类型对象,有三个注意点:

  1. 向上转型是安全的
  2. 向上转型可以自动类型转换
  3. 向上转型的过程中会丢失子类型信息

·

例如: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;

注:

  1. 构造函数不能是虚函数
  2. 析构函数应该是虚函数(除非不用做基类,通常应该为基类提供一个虚析构函数,即使它不需要析构函数)
  3. 友元不能是虚函数,因为友元不是类成员
  4. 如果基类析构不加virtual关键字,那么派生类对象在释放时,只会调用基类构造;加上virtual关键字后,会先调用派生类构造,在调用基类构造;意味着即使基类不需要显示析构提供服务,也应该提供虚析构函数,即使它不执行任何任务

12 C++文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放;通过文件可以将数据持久化

c++中对文件操作需要包含头文件:<fstream>

文件类型分为两种:

1.文本文件——文件以ASCLL码形式存储在计算机中
2.二进制文件——文件以文本的二进制形式存储在计算机中

操作文件的三大类:

1.ofstream:写操作
2.ifstream:读操作
3.fstream:读写操作

12.1 文本文件操作 - 写文件

写文件的步骤:

1.包含头文件:#include <fstream>
2.创建流对象:ofstream ofs;
3.打开文件:ofs.open("文件路径", 打开方式);
4.写数据:ofs<<"写入数据";
5.关闭文件:ofs.close();

文件打开方式:

注:文件打开方式可以配合使用,用 | 操作符

例如:用二进制方式写文件ios::binary | ios::out

#include <iostream>
#include <fstream>
using namespace std;
int main() {//创建流对象ofstream ofs;//指定打开方式ofs.open("test.txt", ios::out);ofs << "姓名:张三" << endl;ofs << "姓名:李四" << endl;ofs << "性别:男" << endl;ofs.close();
}

12.2 文本文件操作 - 读文件

读文件的步骤:

1.包含头文件:#include <fstream>
2.创建流对象:ifstream ifs;
3.打开文件并判断文件是否打开成功:ifs.open("文件路径", 打开方式);
4.读数据:有四种
5.关闭文件:ifs.close();
#include <iostream>
#include <fstream>
#include <string>
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();
}

12.3 二进制文件 - 写文件

以二进制的方式进行读写操作;打开方式要指定为:ios::binary

二进制方式写文件主要利用流对象调用成员函数writer

函数原型
ostream& writer(const char * buffer, int len);
字符指针buffer指向内存中一段空间,len是读写的字节数
#include <iostream>
#include <fstream>
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();
}

12.4 二进制文件 - 读文件

二进制方式读文件主要利用流对象调用成员函数read

函数原型
istream& read(插入 * buffer, int len):
字符指针buffer指向内存中一段存储空间,len是读写的字节数
#include <iostream>
#include <fstream>
#include <string>
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();
}

13 函数模板

模板的概念:模板就是建立通用的模具,大大提高复用性

模板的特点:

1.模板不可以直接使用,他只是一个框架
2.模板的通用并不是万能的

13.1 函数模板

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<int>(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();
}

注:

  • 自动类型推导,必须推导出一致的数据类型才可以使用
  • 模板必须要确定出T的数据类型才可以使用

13.2 函数模板案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别用char数组和int数组进行测试
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();
}

13.3 普通函数与函数模板的区别

隐式类型转换参考: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();
}

13.4 普通函数与函数模板的调用规则

调用规则:

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();
}

13.5 模板的局限性

注:模板并不是万能的

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;
}

13.6 类模板

作用:建立一个通用类,类中的成员,数据类型可以不具体制定,用一个虚拟的类型来代表

语法:

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.类模板在模板参数列表中可以有默认参数

13.7 类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机的区别

1.普通类中的成员函数一开始就可以创建
2.类模板中的成员函数在使用时才创建

13.8 类模板对象做函数参数

类模板实例化处的对象向函数传参的方式有三种:

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();
}

13.9 类模板与继承

当类模板碰到继承时,注:

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;
}

13.10 类模板成员函数类外实现

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();
}

13.11 类模板分文件编写

注:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决方式1.直接包含.cpp文件;
解决方式2.将声明和实现写在同一个文件中,并更改后缀名为.hpp,hpp是约定的名称并不是强制的

//Person.h文件, 第二种方法将后缀名改为.hpp
#pragma once
#include <iostream>
#include <string>
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<class T1, class T2>
//Person<T1, T2>::Person(T1 name, T2 age) {//  this->m_name = name;
//  this->m_age = age;
//}
//main.cpp文件
#include <iostream>
//#include "Person.cpp"//第一种解决方式,直接包含源文件
#include "Person.hpp"
//第二种方式,把.h和.cpp中的内容写到一起,将后缀名改为.hpp文件
int main() {Person<string, int>p("张三", 10);cout << p.m_name << " " << p.m_age << endl;
}

13.12 类模板与友元

全局函数类内实现,直接在类内声明友元即可
全局函数类外实现,需要让提前让编译器知道全局函数的存在

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);
}

老九学堂 学习C++ 第十天相关推荐

  1. 老九学堂 学习 C++ 第七、八天

    7.1 内联函数 内联(inline)函数:是C++为提高程序运行速度所做的一项改进:与常规函数的区别不在于编写方式,而在于被调用时的运行机制不同:编译使用函数代码替换 函数调用. 使用建议:如果执行 ...

  2. 老九学堂 学习C++ 第六天

    函数 自定义函数的完整写法: 注: 函数原型与函数定义的头部类似,最后以分号结尾 函数原型中的参数名称可以省略,只写参数类型 C++中返回值类型不能是数组,但可以是其他任何类型(可以将数组作为结构或对 ...

  3. 老九学堂之分布式设计教材

    老九学堂之分布式设计教材 作者:老九-技术大黍 原文:分布式系统设计教材 社交:知乎 公众号:老九学堂(新人有惊喜) 特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权 前言 本文由老 ...

  4. 【老九学堂】【C++】数组与指针

    不知道在通过前面的内容学习后,是否有很多小伙伴都会认为数组和指针是等价的,数组名表示数组的首地址呢?不幸的是,这是一种非常危险的想法,并不完全正确,前面我们将数组和指针等价起来是为了方便大家理解(在大 ...

  5. 【老九学堂】【C++】位运算符

    位运算是指按二进制进行的运算.在系统软件中,常常需要处理二进制位的问题.C语言提供了6个位操作运算符.这些运算符只能用于整型操作数,即只能用于带符号或无符号的char,short,int与long类型 ...

  6. 【老九学堂】【C++】C++的发展史

    为了让小伙伴们在学习过程中,能收获更多的知识,达到真正的零基础入门和深入了解C++,老九君特地收集了有关C++发展相关的一些资料供大家查阅和学习: C++语言发展大概可以分为三个阶段: 第一阶段从80 ...

  7. 【老九学堂】【初识C语言】C语言中的运算符

    1运算符和表达式 C语言运算符是说明特定操作的符号,它是构造C语言表达式的工具.C语言的运算异常丰富,除了控制语句和输入输出以外的几乎所有的基本操作都作为运算符处理.除了常见的三大类,算术运算符.关系 ...

  8. 【老九学堂】【初识C语言】C语言保留字(关键字)详解

    保留字(reserved word) 保留字又称关键字. 指在高级语言中已经定义过的字,使用者不能再将这些字作为变量名或过程名使用. 每种程序设计语言都规定了自己的一套保留字. 例如:BASIC语言规 ...

  9. 【老九学堂】【初识C语言】二维数组

    一维数组只有一个下标,称为一维数组,其数组元素也称为单下标变量.在实际问题中有很多量是二维的或多维的,因此C语言允许构造多维数组.多维数组元素有多个下标,以标识它在数组中的位置,所以也称为多下标变量. ...

最新文章

  1. 用python循环语句求素数_Python基础入门_3条件语句和迭代循环
  2. 2022图神经网络5篇最新的研究综述:双曲/图分类/联邦/等变/异质性
  3. 牛客华为机试第8题python
  4. 运维老鸟职场生活交友经验谈
  5. 转:java中static、final、static final的区别
  6. JavaScript-jQuery操作Dom元素
  7. Mybatis使用的9种设计模式,真是太有用了
  8. 平均15-16薪,汇量科技2021届秋招正式启动!
  9. 【英语学习】【Daily English】U06 Shopping L01 We are out of pasta.
  10. chromedriver不在路径的解决办法
  11. 数学建模算法与应用_《数学建模算法与应用》笔记【1】
  12. 麦克风阵列信号基础(十一)
  13. 7-2 查找指定字符 (15 分)
  14. Excel的数据导入到PB的DW中
  15. 实验四|Python 企业偿债能力分析
  16. windows下如何安装tomcat并设置开机自启
  17. Hexo博客使用LeanCloud统计页面访问次数
  18. 游戏运营的十二大组成
  19. auto uninstaller 9.3.28下载安装教程
  20. RTSP取流之海康威视

热门文章

  1. 零基础入门学习Python--永久存储:腌制一缸美味的泡菜
  2. oracle ebcdic 转换,使用sqlldr导入EBCDIC格式数据并新增Oracle字符集
  3. 小米温湿度计接入金桔通用蓝牙网关
  4. Missing parentheses in call to 'print'
  5. ubuntu20.02安装显卡驱动常见问题总结
  6. 分享 40 个免费的前端初学者视频教程
  7. 机器学习和人工智能发展简史
  8. 微信公众平台消息储存mysql php_使用PHP进行微信公众平台开发的示例
  9. 每天3分钟知晓天下事,一句话新闻资讯简报的公众号推荐
  10. CF - D. Letter Picking(博弈 + 区间dp)