C++梁哥笔记day18 | 我的日常分享

C++梁哥笔记day18

类与对象

一、C++模板

1.1 模板概论

C++提供了函数模板,所谓函数模板,实际上是建立一个通用函数,其函数类型和参数类型不具体制定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。C++提供两种模板机制:函数模板和类模板,类属-类型参数化,又称参数模板。

总结:模板把函数或要处理的数据类型参数化,表现为参数的多态性,称为类属。模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

二、函数模板

C++特点:封装、继承、多态

C++特点:面向对象编程(封装继承多态)、泛型编程(模板)

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void swapInt(int &a,int &b){
int tmp = a;
a = b;
b = tmp;
return;
}
void swapChar(char &a,char &b){
char tmp = a;
a = b;
b = tmp;
return;
}
void test01(void){
int data1 = 10,data2 = 20;
cout<<"data1="<<data1<<",data2"<<data2<<endl;
swapInt(data1,data2);
cout<<"data1="<<data1<<",data2"<<data2<<endl;

char data3='a',data4='b';
cout<<"data3="<<data3<<",data4"<<data4<<endl;
swapChar(data3,data4);
cout<<"data3="<<data3<<",data4"<<data4<<endl;
}

运行结果:

图片

上面两个交换数据的函数只是数据类型不同,其他的都是一样的,这个时候我们可以使用函数模板,简化代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//函数模板template是关键字
//class 和 typename 是一样的
//T 系统自动推导 或 用户指定
template<typename T>
void mySwap(T &a,T &b){
T tmp = a;
a = b;
b = tmp;
return;
}
void test02(){
int data1 = 10,data2 = 20;
cout<<"data1="<<data1<<",data2"<<data2<<endl;
mySwap(data1,data2);//自动推导为int
//mySwap<int>(data1,data2);//用户显示指定为int
cout<<"data1="<<data1<<",data2"<<data2<<endl;

char data3='a',data4='b';
cout<<"data3="<<data3<<",data4"<<data4<<endl;
mySwap(data3,data4);//自动推导为char
cout<<"data3="<<data3<<",data4"<<data4<<endl;
}

运行结果:

图片

用模板是为了实现泛型,可以减轻编程的工作量,增加函数的重用性。

2.1 函数模板和普通函数的区别

  • 当函数模板与普通函数同名时,优先使用普通函数;可以通过<>指定使用函数模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template<typename T>
void mySwap(T &a,T &b){
cout<<"函数模板"<<endl;
T tmp = a;
a = b;
b = tmp;
return;
}

void mySwap(int &a, int &b){
cout<<"普通函数"<<endl;
int tmp = a;
a = b;
b = tmp;
return;
}
void test01(void){
//当普通函数与函数模板同名时,优先使用普通函数
int data1 = 10,data2 = 20;
mySwap(data1,data2);

//当普通函数与函数模板同名时,优先使用普通函数,可以指定使用模板函数
int data3 = 10,data4 = 20;
mySwap<>(data3,data4);

return;
}

运行结果:

图片

  • 函数模板的参数类型不能进行自动类型转换;如果想让函数模板进行类型转换,可以认为加<类型>指定要进行转换的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
template<typename T>
void mySwap(T a,T b){
cout<<"函数模板"<<endl;
cout<<a<<","<<b<<endl;
return;
}

void mySwap(int a, int b){
cout<<"普通函数"<<endl;
cout<<a<<","<<b<<endl;
return;
}
void test01(void){
int a = 100;
char b = 'a';

//函数模板不能进行自动类型转换,char 本质也是int,
//所以匹配到了普通函数,普通函数在传参时对b进行了自动类型转换,转换为了int
mySwap(a,b);

//由于函数模板不能进行自动类型转换,但是可以人为指定类型要转换的类型
mySwap<int>(a,b);
mySwap<char>(a,b);

return;
}

运行结果:

图片

2.2 函数模板总结

  1. 函数模板与普通函数的区别:函数模板不允许自动类型转换,普通函数能够进行自动类型转换
  2. 函数模板和普通函数在一起(同名)调用规则:C++编译器优先考虑普通函数,可以通过空模板实参列表的语法限定编译器只能通过模板匹配,函数模板可以像普通函数那样被重载,如果函数模板可以产生一个更好的匹配,那么选择模板。
  3. 模板实现机制:编译器并不是把函数模板处理成任何类型的函数,函数模板通过具体类型产生不同的函数,编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

2.3 函数模板的局限性

如果代码实现时定义了赋值操作,a=b,但是T为数据,这种假设就不成立了同样,如果里面的语句为判断语句if(a>b),但T如果是结构体,该假设也不成立,另外如果传入的是数组,数组名为地址,因此它比较的是滴啊之,而这也不是我们所希望的操作。总之,编写的模板函数很可能无法处理某些类型,另一方面,有时候通用化是有意义的,但C++语法不允许这样做。为了解决这种问题,C++提供模板函数的重载,为这些特定的类型提供具体化的模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class Person{
public:
int a;
int b;
public:
Person(int a,int b){
cout<<"Person构造函数"<<endl;
this->a = a;
this->b = b;
}
};

template<typename T>
T mySwap(T &a,T &b){
return a > b ? a : b;
}

ostream& operator<<(ostream &out,Person &ob){
out<<ob.a<<" "<<ob.b;
return out;
}

void test01(){
//正常
int data1 = 10,data2 = 20;
int t1 = mySwap(data1,data2);
cout<<t1<<endl;

Person ob1 = Person(10,20);
Person ob2 = Person(30,40);
//报错
/*Person t = mySwap(ob1,ob2);
cout<<t<<endl;*/
}class Person{
public:
int a;
int b;
public:
Person(int a,int b){
cout<<"Person构造函数"<<endl;
this->a = a;
this->b = b;
}
};

template<typename T>
T myMax(T &a,T &b){
return a > b ? a : b;
}

ostream& operator<<(ostream &out,Person &ob){
out<<ob.a<<" "<<ob.b;
return out;
}

void test01(){
//正常
int data1 = 10,data2 = 20;
int t1 = myMax(data1,data2);
cout<<t1<<endl;

Person ob1 = Person(10,20);
Person ob2 = Person(30,40);
//报错
/*Person t = myMax(ob1,ob2);
cout<<t<<endl;*/
}

运行结果:

上述问题的解决办法有两种:提供函数模板具体化;重载>运算符

方法1:

1
2
3
4
// 方法一:提供函数模板具体化
template<> Person myMax(Person &ob1,Person &ob2){
return ob1.a > ob2.b ? ob1 : ob2;
}

方法2:推荐这种,各自的管理各自的

1
2
3
4
//方法二: 重载>运算符
bool operator>(Person &ob){
return this->a > ob.b ? true : false;
}

三、类模板

3.1 基本概念

类模板和函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同。类模板用于实现类所需数据的类型参数化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <string>
using namespace std;
template<class T1,class T2>
class Person{
public:
T1 name;
T2 num;
public:
Person(T1 name,T2 num){
this->name = name;
this->num = num;
cout<<"有参构造函数"<<endl;
}
~Person(){
cout<<"析构函数"<<endl;
}
void showPerson(){
cout<<"name="<<this->name<<",num="<<this->num<<endl;
}
};
void test01(){
string name = "小花";
int num = 100;
Person<string,int> ob1 = Person<string,int>(name,num);
ob1.showPerson();

Person<string,int> ob2 = Person<string,int>("小明",200);
ob2.showPerson();

Person<string,int> ob3("小王",300);
ob3.showPerson();
//ob1 ob2 ob3是不同的类 Person与<string,int>结合表示一个类。
}

int main(int argc, char *argv[])
{
test01();
return 0;
}

运行结果:

注意:ob1 ob2 ob3是不同的类 Person与<string,int>结合表示一个类。

3.2 类模板做函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template<class T1,class T2>
class Person{
public:
T1 name;
T2 num;
public:
Person(T1 name,T2 num){
this->name = name;
this->num = num;
cout<<"有参构造函数"<<endl;
}
~Person(){
cout<<"析构函数"<<endl;
}
void showPerson(){
cout<<"name="<<this->name<<",num="<<this->num<<endl;
}
};
void fun(Person<string,int> &ob){
ob.name += "vip";
ob.num += 1000;
return;
}
void test02(){
Person<string,int> ob("小花",200);
fun(ob);
ob.showPerson();
}

Person类模板作为参数必须要具体化加<string, int>,可以理解为Person <string,int>才是一个类

3.3 类模板派生普通类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
template<class T1,class T2>
class Person{
public:
T1 name;
T2 num;
public:
Person(T1 name,T2 num){
this->name = name;
this->num = num;
cout<<"有参构造函数"<<endl;
}
~Person(){
cout<<"析构函数"<<endl;
}
void showPerson(){
cout<<"name="<<this->name<<",num="<<this->num<<endl;
}
};
class Student:public Person<string,int>{
public:
int score;
public:
Student(string name,int num,int score):Person<string,int>(name,num){
this->score = score;
cout<<"Student有参构造函数"<<endl;
}
~Student(){
cout<<"Student析构函数"<<endl;
}
};
void test03(){
Student ob1("小明",300,70);
ob1.showPerson();
}

运行结果:

Person类模板作为基类必须要具体化加<string, int>,可以理解为Person <string,int>才是一个类