# C++基础14-C++中其他代码重用

本文是《C++ Primer Plus》的笔记,本文中的案例均自己实践过,如需转发请在转发开头贴上原文地址,谢谢

# 概要

实现代码重用有几种方式:

  1. 公有继承
  2. 一个对象作为另一个类的成员
  3. 私有或保护继承,实现has-a的关系
  4. 多重继承
  5. 类模板

# 包含对象成员的类

这种方式常用来实现has-a关系,像上篇文章所提,就是午餐和水果的关系,在设计模式中可被形容为:包含containment,组合composition,层次化layering。看实例:


Score.h 文件内容:

#pragma once
#include "pch.h"

class Score
{
private:
    double math = 0;
    double science = 0;
public:
    Score();
    Score(int mathT, int scienceT);
    void GetScore();
    ~Score();
};

Score.cpp 文件内容:

#include "Score.h"

Score::Score()
{
    math = 100;
    science = 100;
}

Score::Score(int mathT, int scienceT)
{
    math = mathT;
    science = scienceT;
    cout << "Score is initted!" << endl;
}

void Score::GetScore()
{
    cout << "math:" << math << "science:" << science << endl;
}


Score::~Score()
{
}

personInfo.h 文件内容:

#pragma once
class personInfo
{
private:
    int age;
public:
    personInfo();
    personInfo(int ageT);
    ~personInfo();
};

personInfo.cpp 文件内容:

#include "pch.h"
#include "personInfo.h"

personInfo::personInfo():age(10)
{
}

personInfo::personInfo(int ageT):age(ageT)
{
    cout << "personInfo is initted!" << endl;
}


personInfo::~personInfo()
{
}

student.h 文件内容:

#pragma once
#include "Score.h"
#include "personInfo.h"

class Student
{
private:
    personInfo info;
    Score score;
public:
    Student();
    Student(int ageT, int mathScore, int scienceScore);
    ~Student();
    void GetScore();
};

student.cpp 文件内容:

#include "Student.h"

Student::Student()
{
    score = Score();
}

Student::Student(int ageT, int mathScore,int scienceScore):score(mathScore,scienceScore),info(ageT)
{
    cout << "student is initted!" << endl;
}


Student::~Student()
{
}

void Student::GetScore()
{
    score.GetScore();
}


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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

在student类中,score是私有的,也就是类的外部访问不到,只能通过student暴露的成员函数来操作,这种情况被描述为student类获得了Socer的实现,但没有继承接口。student可以调用score的方法,再次封装暴露给外部。

在使用公有继承时,类可以继承接口,这些接口可能有实现(基类的纯虚函数提供接口,但不提供实现),获得接口是is-a关系的组成部分;使用组合时,类可以获得实现,但不能获得接口,这是has-a关系的组成部分。

类对象作为类的成员变量时初始化和其他基本类型是一样的,它们的初始化顺序和在构造函数的初始化列表中的顺序没有关系,和在类中声明的前后顺序有关。

# 私有继承

私有继承提供和特性和包含有点类似,获得实现,但不获得接口。所以私有继承可用来实现has-a关系,但是大部分人倾向用包含来实现has-a关系,因为它更易于理解,类声明中包含目标对象,通过类名称来引用这些对象,而使用继承则使关系更抽象,其次使用多继承会有很多问题。另外使用包含能包括多个同类子对象,而继承则只能使用一个这样的对象。

然而,私有继承提供的特性比包含多,比如能继承类的保护成员。还有如果需要重新定义虚函数,也得需要私有继承。

使用保护继承或私有继承时,基类的公有成员和保护成员成为派生类的保护成员或私有成员,假如要让基类的方法声明为公有方法可使用using 成员名(没有括号,函数特征标,返回类型),让该成员公有。还有一种老式方法,就是把成员名放在派生类的公有部分,不使用using关键字,但是这种方法已经被摒弃。看下面实例:

Person.h 文件内容:

#pragma once
class Person
{
private:
    int age;
public:
    int workage;
    Person();
    Person(int ageT);
    ~Person();

    int GetAge();
 
    friend ostream & operator<<(ostream & os, const Person & p1);
};

Person.cpp 文件内容:

#include "pch.h"
#include "Person.h"

Person::Person()
{
    cout << "Person is generatted with none parameter!" << endl;
}

Person::Person(int ageT)
{
    age = ageT;
    workage = age + 10;
    cout << "generate person with age:" << age << endl;
}

Person::~Person()
{
}

int Person::GetAge()
{
    return age;
}

ostream & operator<<(ostream & os, const Person & p1)
{
    // TODO: 在此处插入 return 语句
    os << "<< person :" << p1.age << endl;
    return os;
}

Student.h 文件内容:

#pragma once
#include "Person.h"
class Student :
    private Person
{
public:
    Student();
    Student(int ageT);
    ~Student();

    void GetAge();
    void GetInfo();
    const Person & PersonInfo() const;
    using Person::workage;
};

Student.cpp 文件内容:

#include "pch.h"
#include "Student.h"


Student::Student()
{
    cout << "student is generatted with none parameter!" << endl;
}

Student::Student(int ageT) :Person(ageT)
{
    cout << "generate student with age:"<< ageT << endl;
}


Student::~Student()
{
}

void Student::GetAge()
{
    cout << "student's age:" << Person::GetAge() << endl;
}

void Student::GetInfo()
{
    cout << "<< Person from Student:" << endl;
    cout << (const Person &)*this << endl;
}

const Person & Student::PersonInfo() const
{
    // TODO: 在此处插入 return 语句
    return (const Person &)*this;
}

main 函数:

int main()
{
    Student stu1(10);
    stu1.GetAge();
    //Person * p1 = &stu1;
    //在私有继承中,在不显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针
    cout << "Person workage:" << stu1.PersonInfo().workage << "     get from Studen Object:"<< stu1.workage <<endl;
    //stu1.workage 该成员通过using重新定义了访问权限,否则显示不可访问
    stu1.GetInfo();

}

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
  1. 使用私有继承,要初始化父类的构造函数,需要在派生类的构造函数列表中调用父类构造函数。使用基类的其他方法则需要使用基类类名和作用域解析运算符来调用方法。
  2. 要访问基类的对象,需要使用(基类类名 &)*this来得到,类似的使用基类友元函数需要类型转换成相应的形式。
  3. 在私有继承中,在不显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。
  4. workage通过使用using关键字重新定义了访问权限,可通过对象来访问,但是如果成员变量在父类是私有或者保护的,那么using也没办法将该成员变量公开。

# 多重继承

多重继承(multiple inheritance,MI)面临着两个主要问题,一是从两个不同的基类继承同名方法;二是从两个或更多基类那里继承同一个类的多个实例。

与单继承相比,多继承更困难和出问题,甚至有些用户强烈反对使用MI,甚至希望删除MI,而有一些人认为对于特殊的工程,MI更有用,甚至是必不可少的,还有一些人建议谨慎,适度地使用MI。看下面实例:

People.h 文件内容:
#pragma once
class People
{
private:
    int id;
public:
    People();
    People(int idT);
    ~People();

    virtual void Work();
    void GetPeopleAddr();
};

People.cpp 文件内容:
#include "pch.h"
#include "People.h"


People::People()
{
    cout << "people is generatted with none parameter!" << endl;
}

People::People(int idT)
{
    id = idT;
    cout << "generate a people with id:" << id << endl;
}


People::~People()
{
}

void People::Work()
{
    cout << "a people is working!" << endl;
}

void People::GetPeopleAddr()
{
    cout << "People addr is :" << this;
    cout << endl;
}

Singer.h 文件内容:

#pragma once
#include "People.h"
class Singer :
    public People
{
public:
    Singer();
    ~Singer();

    void Work();
};

Singer.cpp 文件内容:

#include "pch.h"
#include "Singer.h"


Singer::Singer()
{
    cout << "singer is generatted with none parameter!" << endl;
}


Singer::~Singer()
{
}

void Singer::Work()
{
    cout << "a singer sing a song!" << endl;
}

Writer.h 文件内容:
#pragma once
#include "People.h"
class Writer:
    public People 
{
public:
    Writer();
    ~Writer();

    void Work();
};

Writer.cpp 文件内容:
#include "pch.h"
#include "Writer.h"


Writer::Writer()
{
    cout << "writer is generatted with none parameter!" << endl;
}

Writer::~Writer()
{
}

void Writer::Work()
{
    cout << "a writer write a book!" << endl;
}

SingerWriter.h 文件内容:
#pragma once
#include "Singer.h"
#include "Writer.h"
class SingerWriter :
    public Singer,public Writer
{
public:
    SingerWriter();
    ~SingerWriter();
};

SingerWriter.cpp 文件内容:
#include "pch.h"
#include "SingerWriter.h"


SingerWriter::SingerWriter()
{
    cout << "singerWriter is generatted with none parameter!" << endl;
}


SingerWriter::~SingerWriter()
{
}

main 函数内容:

#include "Student.h"
#include "Singer.h"
#include "Writer.h"
#include "SingerWriter.h"

int main()
{
    Singer s1;
    Writer w1;
    People * p1=&s1;
    People * p2=&w1;
    p1->Work();
    p2->Work();
    SingerWriter sw1;
    People * p3;
    //p3 = &sw1; //基类People不明确
    p3 = (Singer *)&sw1;
    p3->Work();
    p3->GetPeopleAddr();
    p3 = (Writer *)&sw1;
    p3->Work();
    p3->GetPeopleAddr();
    p3 = (Singer *)&sw1;
    p3->GetPeopleAddr();
}
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
  1. 多重继承中,需要为每个基类指明访问级别关键字,如果没有指明则默认为private。比如

    class SingerWriter :
        public Singer,Writer
    {
    }
    
    Writer就是私有继承。
    
    1
    2
    3
    4
    5
    6
  2. 多重继承派生类要使得基类指针指向派生对象,需要明确基类指向转换,比如

    p3 = (Singer *)&sw1;
    p3 = (Writer *)&sw1;
    
    1
    2
  3. 通过GetPeopleAddr()获取People的地址和初始化信息发现,sw1继承了两个People实例,也就是Singer和Writer中各有一个。但是这与我们最初的初衷相背,我们希望只有一个People实例,这个时候就要用到虚基类。

# 虚基类

虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。使用方法是在声明类继承基类时加上virtual关键字,virtual和访问级别权限声明符无先后顺序。比如

将Singer和Writer的定义修改如下:

class Singer :
    public virtual People{}

class Writer:
    virtual public People {}

SingerWriter.h 文件添加:
    void Work();

SingerWriter.cpp 文件添加:
void SingerWriter::Work()
{
    cout << "singerwriter work function" << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

按上面修改完毕后运行发现People的地址一样了,他们都是同一个。注意需要在SingerWriter添加Work()方法,否则会提示People::Work重写不明确。

注意虚函数和虚基类之间并不存在联系,可能是因为C++用户强烈反对引入新的关键字,此外虚基类会额外消耗性能,这可能是不把虚行为作为默认MI准则的原因。

# 新的构造函数规则

在构造函数的初始化列表中调用基类构造函数,在基类构造函数的初始化列表中再调用基类的基类构造函数,可完成某些数据传递,但是现在是通过两条不同途径传递(Writer和Singer),就可能造成冲突,所以为了避免这冲突,C++规定在基类是虚基类时,禁止信息通过中间类自动传递给基类。但是编译器必须在构造派生对象之前构造基类对象组件,编译器将默认调用People的默认构造函数。如果需要调用People的其他构造函数,需要显式指定出来。

SingerWriter.h 文件内容添加:

    SingerWriter(int id);

SingerWriter.cpp 文件内容添加:

SingerWriter::SingerWriter(int id):Singer(id),Writer(id)
{
    cout << "call SingerWriter construction with id:" << id << endl;
}

Singer.h 文件内容添加:

    Singer(int id);

Singer.cpp 文件内容添加:

Singer::Singer(int id):People(id)
{
    cout << "call Singer construction with id:" << id << endl;
}

Writer.h 文件内容添加:

    Writer(int id);

Writer.cpp 文件内容添加:

Writer::Writer(int id):People(id)
{
    cout << "call writer construction with id:" << id << endl;
}

main 函数中:

int main()
{
    SingerWriter sw1(3);
}

运行结果为:

people is generatted with none parameter!
call Singer construction with id:3
call writer construction with id:3
call SingerWriter construction with id:3

如果把SingerWriter.cpp的构造函数修改为:

SingerWriter::SingerWriter(int id):People(id),Singer(id),Writer(id)
{
    cout << "call SingerWriter construction with id:" << id << endl;
}

运行结果为:

generate a people with id:3
call Singer construction with id:3
call writer construction with id:3
call SingerWriter construction with id: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
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

所以如果类有间接虚基类,除非只需使用该虚基类的默认构造函数,否则必须显式地调用该虚基类的某个构造函数。

# 多重继承方法的二义性

在讲前面虚基类时,提到如果没有在SingerWriter中定义Work方法,则认为该类应该继承两个基类的Work方法,这就导致了二义性。除了上面在SingerWriter中重新定义Work方法以外,调用时可明确指出是哪个基类的方法:

    SingerWriter sw1(3);
    sw1.Writer::Work();
    sw1.Singer::Work();
1
2
3

# 混合使用虚基类和非虚基类

如果基类是虚基类,派生类将包含基类的一个子对象,如果基类不是虚基类,派生类将包含多个子对象,当虚基类和非虚基类混合时怎么算?比如类A被用作类B,C的虚基类,同时被用作X和Y的非虚基类,一个类M从B,C和X,Y派生来,那么M包含几个A类子对象呢?3个。当类通过多条虚途径和非虚途径继承某个特定的基类时,该类包含一个表示所有的虚途径的基类子对象和分别从各条非虚途径的多个基类子对象。

# 虚基类方法的优先级

将上面的Writer中的Work给注释掉,那么SingerWriter中不定义Work也不会导致二义性。因为子类的同名方法优先于父类。另外二义性与访问级别没有关系,即使是Writer中的Work是私有的,在SingerWriter中还是会二义性的。

# 类模板

上面所说的继承(公有,私有和保护)和包含在代码重用中指的是逻辑部分的重用,如果一个类是针对int数据类型,如果它也能适用float类型,这个时候需要类模板,跟函数模板类似,它使得类能与类型无关,这种类也叫容器类(container class)。

有人认为使用typedef也能起到类似效果,但是它一方面是每次修改类型都需要修改头文件,而且不是运行时修改,另一方面它同一个程序同一时间只能生成一种类型,无法同时生成int和float的。

# 定义使用模板类

定义格式如下:

第一种:
template <class Type>
class test1{

}

第二种:
template <typename Type>
class test2{

}

在类的实现中方法定义为:
template <typename Type>
void test1<Type>::push(Type parameter){

}
如果是在类声明中定义了方法(内联定义),则可以省略模板前缀和类限定符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

关键字class并不意味着Type必须是一个类,只是表明Type是一个通用的类型说明符,使用模板时,Type被实际的类型替换。较新的C++使用新的关键字typename代替class。Type可以被替换为自己的泛型名,其命名规则与其他标识符相同。

注意这些模板不是类和成员函数定义,他们是编译器指令,说明了如何生成类和成员函数定义。模板的具体实现被称为实例化(instantiation)或具体化(specialization)。不能将模板成员函数放在独立的实现文件中,由于模板不是函数,它们不能单独编译。模板必须与特定的模板实例化请求一起使用。最好就是将所有模板信息放在一个头文件中,并在要使用这些模板的文件中包含该头文件。

看下面实例:

TemplateClass.h 文件内容:
#pragma once
template <class Type>
class TemplateClass
{
public:
    TemplateClass();
    ~TemplateClass();
    void CompareNum(Type a,Type b);
};

TemplateClass.cpp 文件内容:

#include "pch.h"
#include "TemplateClass.h"


template<class Type>
TemplateClass<Type>::TemplateClass()
{
}


template<class Type>
TemplateClass<Type>::~TemplateClass()
{
}

template<class Type>
void TemplateClass<Type>::CompareNum(Type a, Type b)
{
    if (a < b) {
        cout << "a is lower than b!" << endl;
    }
    else {
        cout << "a is bigger than b!" << endl;
    }

}

main 函数内容:

int main()
{
    TemplateClass<int> t1;
    t1.CompareNum(1, 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

上面运行会提示无法解析的外部符号等等。如果把TemplateClass.cpp中的内容拷贝到TemplateClass.h中则正常运行。

# 模板类练习

# 容器栈的实现

#pragma once
template <typename T>
class Stack {
private:
    int top=0;
    int len = 0;
    T * stp;
public:
    Stack();
    Stack(int n);
    ~Stack();
    bool isempty();
    void push(T data);
    bool pop(T & data);
};

template <typename T>
bool Stack<T>::isempty() {
    if (top == 0)return true;
    return false;
}

template <typename T>
Stack<T>::Stack(int n) {
    stp = new T[n];
    len = n;
}

template <typename T>
Stack<T>::Stack():Stack(10) {
}

template <typename T>
Stack<T>::~Stack() {
}


template <typename T>
bool Stack<T>::pop(T & data) {
    if (top > 0) {
        data = stp[--top];
    }
    return false;
}

template <typename T>
void Stack<T>::push(T data) {
    if (top < len) {
        stp[top++] = data;
    }
}

main 函数中:
int main()
{
    int temp;
    Stack<int> st1;
    st1.push(10);
    st1.push(20);
    st1.push(30);
    while (!st1.isempty()) {
        st1.pop(temp);
        cout << temp << endl;
    }
    Stack<const char *> st2;
    st2.push("10");
    st2.push("20");
    st2.push("30");
    const char * temp1;
    while (!st2.isempty()) {
        st2.pop(temp1);
        cout << temp1 << endl;
    }
}

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
69
70
71
72
73
74
75

注意如果不在该头文件中添加默认构造函数和析构函数,则会默认生成,提示和上面同样的信息无法解析的外部符号等等。

# 容器数组的实现

myArr.h 文件中:
#pragma once
template<typename T,int n>
class MyArray {
private:
    T * ptr;
public:
    MyArray();
    MyArray(const T initValue);
    ~MyArray();

    T & operator[](int i);
};


template<typename T,int n>
MyArray<T,n>::MyArray() {
    ptr = new T[n];
    cout << "MyArray is initted with none parameter" << endl;
}

template<typename T,int n>
MyArray<T,n>::MyArray(const T initValue) {
    ptr = new T[n];
    for (int i = 0; i < n; i++) {
        ptr[i] = initValue;
    }
    cout << "MyArray is initted with " << initValue << endl;
}

template<typename T,int n>
MyArray<T,n>::~MyArray() {

}

template<typename T,int n>
T & MyArray<T,n>::operator[](int i) {
    if (i >= 0 && i < n) {
        return ptr[i];
    }
}

main 函数中:
int main()
{
    MyArray<int, 20> myArr(30);
    cout << myArr[100] << endl;
}

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

# 模板多功能性

# 递归使用模板

模板可以被递归使用,模板可以被嵌套在模板中。用上面的MyArray模板类进行嵌套实例:


方式一:

int main()
{
    const int outlen=5;
    const int inlen=3;

    MyArray<MyArray< char *,inlen>, outlen> myArr;
    for (int i = 0; i < outlen; i++) {
        MyArray< char *, inlen> inner;
        for (int j = 0; j < inlen; j++) {
            stringstream ss;
            ss << "num:" << j;
            int len = std::strlen(ss.str().c_str());
            inner[j] = new char[len+1] ;
            strcpy_s(inner[j], len + 1,ss.str().c_str());
        }
        myArr[i] = inner;
    }
    for (int i = 0; i < outlen; i++) {
        for (int j = 0; j < inlen; j++) {
            cout << "元素值:" << myArr[i][j] << endl;
        }
    }
}

方式二:

int main()
{
    const int outlen=5;
    const int inlen=3;
    string stringArr[inlen*outlen];
    int index = 0;

    MyArray<MyArray<const char *,inlen>, outlen> myArr;
    for (int i = 0; i < outlen; i++) {
        MyArray<const char *, inlen> inner;
        for (int j = 0; j < inlen; j++) {
            stringstream ss;
            ss << "num:" << j;
            stringArr[index] = ss.str();
            inner[j] = stringArr[index].c_str();
            index++;
        }
        myArr[i] = inner;
    }
    for (int i = 0; i < outlen; i++) {
        for (int j = 0; j < inlen; j++) {
            cout << "元素值:" << myArr[i][j] << endl;
        }
    }
}

方法三:得不到结果,想想为什么?

int main()
{
    const int outlen=5;
    const int inlen=3;
    stringstream ssArr[inlen*outlen];
    int index = 0;

    MyArray<MyArray<const char *,inlen>, outlen> myArr;
    for (int i = 0; i < outlen; i++) {
        MyArray<const char *, inlen> inner;
        for (int j = 0; j < inlen; j++) {
            ssArr[index] << "num:" << j;
            inner[j] = ssArr[index].str().c_str();
            index++;
        }
        myArr[i] = inner;
    }
    for (int i = 0; i < outlen; i++) {
        for (int j = 0; j < inlen; j++) {
            cout << "元素值:" << myArr[i][j] << endl;
        }
    }
}

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
69
70
71
72
73
74
75
76
77
78
79
80
81

# 多模板参数

模板可以使用多个类型参数,可以为类型参数提供默认值。看下面实例:

OutPut.h 文件内容:

#pragma once
template<typename T1,typename T2=int>
class OutPut {
private:
    T1 valueA;
    T2 valueB;
public:
    OutPut();
    ~OutPut();
};


template<typename T1,typename T2>
OutPut<T1,T2>::OutPut() {
    cout << "valueA's type:" << typeid(valueA).name() << endl;
    cout << "valueB's type:" << typeid(valueB).name() << endl;
}


template<typename T1,typename T2>
OutPut<T1,T2>::~OutPut() {

}

main 函数文件:

template<typename T1,typename T2=int>
void OutPutFunction(T1 nameA) {
    T2 nameB;
    cout << "nameA's type:" << typeid(nameA).name() << endl;
    cout << "nameB's type:" << typeid(nameB).name() << endl;
}

int main()
{
    OutPut<float> o1;
    OutPut<double,float> o2;
    OutPutFunction<double>(1);
    OutPutFunction<double,float>(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
41
42
43

# 模板的具体化

类模板与函数模板很相似,因为可以有隐式实例化,显式实例化和显式具体化。模板以泛型的方式描述类,而具体化是使用具体的类型生成类声明。

# 显式隐式实例化

以上面的MyArray为例:

template class MyArray<float, 10>;
//显式实例化

int main()
{
    //隐式实例化
    MyArray<int, 10> arr1;
    MyArray<int, 10> * arrPt;
    cout << "init time:" << endl;
    arrPt = new MyArray<int, 10>;
    //编译器在生成对象之前不会生成类的隐式实例化

}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 显式具体化

显式具体化(explicit specialization)是特定类型(用于替换模板中的泛型)的定义。可能需要为特殊类型实例化时做一些小的修改,使其行为不同。

以OutPut类为例:

OutPut.h 文件中添加:

template<>
class OutPut<double, double> {

public:
    OutPut();
    ~OutPut();
};

OutPut<double, double>::OutPut() {
    cout << "double double template default constructor is called!" << endl;
}

OutPut<double, double>::~OutPut() {

}

main 函数文件中:

int main()
{
    OutPut<double, double> op1;
}


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

# 部分具体化

C++还允许部分具体化(partial specialization),部分限制模板的通用性。给其中一部分类型参数指定具体的类型:

OutPut.h 文件中添加:

template<typename T>
class OutPut<double, T> {

public:
    OutPut();
    ~OutPut();
};

template<typename T>
OutPut<double, T>::OutPut() {
    cout << "double T template default constructor is called!" << endl;
}

template<typename T>
OutPut<double, T>::~OutPut() {

}

main 函数中:
int main()
{
    OutPut<double, double> op1;
    OutPut<double, float> op2;
}
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>
class OutPut<T, T> {}

template<typename T>
class OutPut<T*, T*> {}

1
2
3
4
5
6

# 成员模板

模板可用作结构,类或模板类的成员。看实例:

方式一(内置定义)#pragma once
class Student{
private:
    template<typename U>
    class Score {
    private:
        U math;
    public:
        Score(U mathT) {
            math = mathT;
        }
        U GetScore() {
            return math;
        }
    };
    Score<T> myScore;

public:
    Student(T mathScore):myScore(mathScore) {
        cout << "Student is initted!" << endl;
    }
    ~Student() {};
    template<typename U>
    T GetScore(U params) {
        cout << typeid(params).name() << endl;
        return myScore.GetScore();
    };

};

方法二(外置定义)#pragma once
template<typename T>
class Student{
private:
    template<typename U>
    class Score;
    Score<T> myScore;

public:
    Student(T mathScore);
    ~Student();
    template<typename U>
    T GetScore(U params);

};

template<typename T>
    template<typename U>
class Student<T>::Score {
    private:
        U math;
    public:
        Score(U mathT) {
            math = mathT;
        }
        U GetScore() {
            return math;
        }
    };


template<typename T>
Student<typename T>::Student(T mathScore):myScore(mathScore) {
    cout << "Student is initted!" << endl;
}


template<typename T>
Student<typename T>::~Student() {

}

template<typename T>
    template<typename U>
T Student<typename T>::GetScore(U params) {
    cout << typeid(params).name() << endl;
    return myScore.GetScore();
}

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

# 模板作为参数

模板可以包含类型参数和非类型参数,模板可以包含本身就是模板的参数。看实例:

#pragma once
template<template<typename T,typename int> typename U,typename V,int n=10>
class PetrolStation{

private:
    U<V,n> u1;
public :
    PetrolStation();
    ~PetrolStation() {};
    U<V, n> & GetCar();
};

template<template<typename T,typename int> typename U,typename V,int n>
PetrolStation<U,V,n>::PetrolStation() {
    cout << typeid(u1).name() << endl;
}

template<template<typename T,typename int> typename U,typename V,int n>
U<V,n> & PetrolStation<U,V,n>::GetCar() {
    return u1;
}


main 函数中:

int main()
{
    PetrolStation<MyArray,const char *> c1;
    c1.GetCar()[6] = "宝马";
    cout <<  c1.GetCar()[6] << endl;
}
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

# 模板类和友元

# 非模板友元

Num.h 文件内容:

#pragma once
template<typename T>
class Num{
private:
    T value;
    static int totalNum;
public:
    Num(T valueT) {
        totalNum++;
        value = valueT;
    }
    ~Num() {
        totalNum--;
    }
    friend int Count(Num<T> &);
    friend T GetValue(Num<T> &);
};

template<typename T>
int Num<T>::totalNum = 0;

int Count(Num <int> & numT) {
    cout << "Num<int>:" << Num<int>::totalNum << endl;
    return Num<int>::totalNum;
}

int Count(Num <float> & numT) {
    cout << "Num<int>:" << Num<float>::totalNum << endl;
    return Num<float>::totalNum;
}

int GetValue(Num<int> & numT) {
    cout << "Num<int> Object value:" << numT.value << endl;
    return numT.value;
}

float GetValue(Num<float> & numT) {
    cout << "Num<float> Object value:"  << numT.value << endl;
    return numT.value;
}

main 中:

int main()
{
    Num<int> t1(10);
    Num<int> t3(30);
    Num<float> t2(20);
    Count(t1);
    Count(t2);

    GetValue(t1);
    GetValue(t2);
}


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

注意上面的Count和GetValue友元函数,他们本身并不是模板函数,而只是使用一个模板作参数,意味着必须要为使用的友元定义显式具体化。注意类的每个特定具体化都有自己的静态成员。

# 模板类的约束模板友元函数

将上面实例中的非模板友元函数修改成模板友元函数:

#pragma once
//头部声明开始
template<typename T>
class Num;
//注意这被称为前向声明(forward declaration)

template <typename T> int Count();
template <typename T> void GetValue(Num<T> &);
//头部声明结束

//template class
template<typename T>
class Num{
private:
    T value;
    static int totalNum;
public:
    Num(T valueT) {
        totalNum++;
        value = valueT;
    }
    ~Num() {
        totalNum--;
    }
    friend int Count<T>();
    friend void GetValue<>(Num<T> &);
    //friend void GetValue<T>(Num<T> &);    //这种形式也可以的
};

template<typename T>
int Num<T>::totalNum = 0;

template<typename T>
int Count() {
    cout << "Num<T>:" << Num<T>::totalNum << endl;
    return Num<T>::totalNum;
}

template<typename T>
void GetValue(Num<T> & numT) {
    cout << "Num<> Object value:" << numT.value << endl;
}

上面的头部声明可以改为:
template <typename T> int Count();
template <typename T> void GetValue(T &);

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

注意Count因为不像GetValue有参数可以推断T的类型,所以使用时需要明确指出T的类型,当然也可以把Count改成GetValue这种样式。

# 模板类的非约束模板友元函数

前面的约束模板友元函数是在类外声明的模板的具体化。Num<int>获取int类型的友元,其他类型获得对应的友元。现在通过在类内声明模板,来创建非约束友元函数,即该友元是该类所有具体化的友元。来看实例:

在Num类声明中public添加:

template<typename T1,typename T2>
friend void Count2(T1 &,T2 &);

在类声明外定义该函数:

template<typename T1,typename T2>
void Count2(T1 & t1, T2 & t2) {
    cout << "t1 value:" << t1.value << "    t2 value:" << t2.value << endl;
    cout << T1::totalNum << endl;
};

调用:
Count2(t1, t2);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意Count2能访问所有具体化的Num成员。

# 模板别名(C++11)

typedef Num<int> intNumber;

template<typename T>
    using numType = Num<T>;

    using numType2 = Num<int>;

使用模板时:

    intNumber t4(40);
    numType<float> t5(50);
    numType2 t6(60);

1
2
3
4
5
6
7
8
9
10
11
12
13