c++远征06之模板篇

C++远征之模板篇_C++视频教程-慕课网

本课程将带领大家体会更为奇妙的C++,讲述了静态、友元、运算符重载的定义及使用方法,其中重点讲述函数模板、类模板以及标准模板库的相关知识,知识内容更重实用性,本门课程对于面向对象的语言的学习将大有裨益,所有知识均以实践的方式讲解到操作层面,力求即学即会。

1 课程简介

2 友元函数和友元类

说明: 无论时友元类还是友元函数

  • 友元的声明不受访问限定符的影响,可以声明载类中的任何位置。
  • 友元关系不可传递,A 是 B 的友元,B 是 C 的友元,不代表 A 是 C 的友元。
  • 友元声明的形式及数量不受限制。
  • 友元具有单向性,A是B的友元,B不一定是A的友元。
  • 友元函数和友元类必须使用关键字friend定义。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
using namespace std;

/**
* 定义Coordinate类
* 友元函数:display
* 数据成员:m_iX、m_iY
*/

class Coordinate
{
// 友元函数
friend void display(Coordinate &coor);
public:
Coordinate(int x, int y)
{
m_iX = x;
m_iY = y;
}
public:
int m_iX;
int m_iY;
};

/**
* display函数用于显示m_iX、m_iY的值
*/

void display(Coordinate &coor)
{

cout << "m_iX:" << coor.m_iX << endl;
cout << "m_iY:" << coor.m_iY << endl;
}

int main(void)
{

// 实例化Coordinate对象
Coordinate coor(3, 4);
// 调用display函数
display(coor);
return 0;
}

友元成员函数

说明:在类 A 中声明类 B 的成员函数 fun 为友元,则类 B 的 fun 函数就可以访问类 A 中的私有成员。

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
#include <iostream>
using namespace std;

class Coordinate;
class Circle
{
public:
void printXY(Coordinate &c);
};

class Coordinate
{
// 声明成员友元函数
friend void Circle::printXY(Coordinate &c);
public:
Coordinate(int x, int y){}
private:
int m_iX;
int m_iY;
};

void Circle::printXY(Coordinate &c)
{
cout << "Circle -- printXY" << endl;
}

int main(void)
{

Coordinate coor(5, 6);
Circle circle;
circle.printXY(coor);
return 0;
}

2.2 友元类

说明: 在类 A 中将类 B 声明为友元,则类 B 可以访问 B 类中类 A 型属性的私有成员 。
注意: 由于编译器不同,友元类有两种声明形式:1、friend class 类名; 2、friend 类名
建议:友元只是封装的补充,事实上对封装性是一种破坏,设计良好的程序应该避免使用友元。

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
#include <iostream>
using namespace std;
class Watch;

/**
* 定义Time类
* 数据成员:m_iHour, m_iMinute,m_iSecond
* 成员函数:构造函数
* 友元类:Watch
*/

class Time
{
// 友元类
friend class Match;
public:
Time(int hour, int min, int sec)
{
m_iHour = hour;
m_iMinute = min;
m_iSecond = sec;
}
public:
int m_iHour;
int m_iMinute;
int m_iSecond;
};

/**
* 定义Watch类
* 数据成员:m_tTime
* 成员函数:构造函数
* display用于显示时间
*/

class Watch
{
public:
Watch(Time &t):m_tTime(t){}
void display()
{

// 访问 Time 类型属性的私有成员
cout << m_tTime.m_iHour << endl;
cout << m_tTime.m_iMinute << endl;
cout << m_tTime.m_iSecond << endl;
}
public:
Time m_tTime;
};

int main()
{

Time t(6, 30, 20);
Watch w(t);
w.display();

return 0;
}

3 static

说明: 包括静态数据成员静态成员函数

  • 定义静态成员函数和静态数据成员都需要 static 关键字
  • 公有静态成员函数可以被类直接调用
  • 静态数据成员不能在构造函数初始化,必须单独初始化
  • 静态成员函数不能调用非静态成员函数和非静态数据成员
  • 静态数据成员只有一份,且不依赖对象而存在

限制:静态成员函数不能用 const 修饰,因为 const 本质上是修饰的隐式提供给成员函数的 this ,但静态成员函数实际上没有 this。

访问:从内部和外部两个角度看

  • 类外部访问静态成员: 可以通过类直接访问,也可以通过实例化的对象访问。
  • 类内部访问静态成员: 只有静态方法可以访问静态成员,且只能访问静态成员。

3.2 综合 DEMO

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
#include <iostream>
using namespace std;

/*
* 坦克类
* 每实例化一个坦克对象静态计数器加一
* 每销毁一个坦克对象静态计数器减一
*/

class Tank
{
public:
Tank(){s_iCount++;}
~Tank(){s_iCount--;}
// 静态成员函数
static int getCount(){return s_iCount;}
// 静态数据成员
static int s_iCount;
private:
string m_strCode;
};

// 初始化静态数据成员的值(不再需要 static 关键字)
int Tank::s_iCount = 0;


int main(void)
{

// 通过类直接访问
cout << Tank::getCount() << endl;
cout << Tank::s_iCount << endl;

// 通过对象访问
Tank tank;
cout << tank.getCount() << endl;
cout << tank.s_iCount << endl;

return 0;
}

4 运算符重载

描述: 给原有运算符赋予新功能
关键字: operator

4.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
#include <iostream>
using namespace std;

/*
* 负号重载
* 当对 Coordinate 类对象进行负号操作时,对其两个数据成员取负。
*/

class Coordinate
{
public:
Coordinate(int x, int y);
// 重载负号(-)
Coordinate& operator-()
{
m_iX = -m_iX;
m_iY = -m_iY;
return *this;
}
private:
int m_iX;
int m_iY;
};

int main(void)
{

Coordinate coor1(3, 5);
-coor1; // coor1.operator-()

return 0;
}

友元函数运算符重载

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
#include <iostream>
using namespace std;

/*
* 负号重载
*/

class Coordinate
{
// 重载负号(-)
friend Coordinate& operator-(Coordinate &coor);
public:
Coordinate(int x, int y)
{
m_iX = x;
m_iY = y;
}
private:
int m_iX;
int m_iY;
};

/*
* 重载负号
* 当对 Coordinate 对象进行负号元算时,对取数据成员取负
*/

Coordinate& operator-(Coordinate &coor)
{
coor.m_iX = -coor.m_iX;
coor.m_iY = -coor.m_iY;
return coor;
}

int main(void)
{

Coordinate coor1(3, 5);
-coor1; // coor1.operator-(coor1)

return 0;
}

++

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
#include <iostream>
using namespace std;

/*
* 重载++
*/

class Coordinate
{
public:
Coordinate(int x, int y)
{
m_iX = x;
m_iY = y;
}
// 前置++
Coordinate& operator++()
{
m_iX++;
m_iY++;
return *this;
}
// 后置 ++(参数 int 表示这是后置重载)
Coordinate operator++(int)
{
// 保存当前值对象
Coordinate old(*this);
m_iX++;
m_iY++;

// 返回的时修改前的对象
return old;
}
private:
int m_iX;
int m_iY;
};

int main(void)
{

Coordinate coor1(3, 5);
coor1++; // coor1.operator++(0) // 参数0没有什么用,计算机以此识别这时一个后置运算
++coor1; // coor1.operator++()
return 0;
}

4.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
#include <iostream>
using namespace std;

/*
* 重载加号(成员函数重载)
*/

class Coordinate
{
public:
Coordinate(int x, int y) {
m_iX = x;
m_iY = y;
}
// 成员函数重载
Coordinate operator+(const Coordinate &coor)
{
Coordinate temp(0, 0);
temp.m_iX = this->m_iX + coor.m_iX;
temp.m_iY = this->m_iY + coor.m_iY;

return temp;
}
private:
int m_iX;
int m_iY;
};

int main(void)
{

Coordinate coor1(3, 5);
Coordinate coor2(1, 1);
Coordinate coor3(0, 0);
coor3 = coor1 + coor2; // coor1.operator+(coor2)

return 0;
}

友元函数重载

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
#include <iostream>
using namespace std;

/*
* 重载加号(成员函数重载)
*/

class Coordinate
{
// 声明友元函数运算符重载
friend Coordinate operator+(const Coordinate &c1, const Coordinate &c2);
public:
Coordinate(int x, int y) {
m_iX = x;
m_iY = y;
}
private:
int m_iX;
int m_iY;
};

// 友元函数+号重载
Coordinate operator+(const Coordinate &c1, const Coordinate &c2)
{
Coordinate temp(0, 0);
temp.m_iX = c1.m_iX + c2.m_iX;
temp.m_iY = c1.m_iY + c2.m_iY;

return temp;
}
int main(void)
{

Coordinate coor1(3, 5);
Coordinate coor2(1, 1);
Coordinate coor3(0, 0);
coor3 = coor1 + coor2; // coor1.operator+(coor2)

return 0;
}

<<

注意: 输出运算符不可以采用成员函数重载,因为采用成员函数实现重载第一个参数默认时当前对象,而重载输出运算符第一个参数必须是 ostream类型的。

友元函数运算符重载

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
#include <iostream>
using namespace std;

/*
* 重载加号(成员函数重载)
*/

class Coordinate
{
// 友元函数运算符重载声明
friend ostream& operator<<(ostream &out, const Coordinate &coor);
public:
Coordinate(int x, int y) {
m_iX = x;
m_iY = y;
}
private:
int m_iX;
int m_iY;
};

// 友元函数<<号重载
ostream& operator<<(ostream &out, const Coordinate &coor)
{
cout << coor.m_iX << ", " << coor.m_iY << endl;
return out;
}

int main(void)
{

Coordinate coor1(3, 5);
cout << coor1 << endl;

return 0;
}

[]

注意: 不能使用友元函数来重载索引运算符。
成员函数运算符重载

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
#include <iostream>
using namespace std;

/*
* 索引运算符重载
*/

class Coordinate
{
public:
Coordinate(int x, int y) {
m_iX = x;
m_iY = y;
}
// 重载索引运算符
int operator[](int index)
{
if(0 == index)
{
return m_iX;
}
if(1 == index)
{
return m_iY;
}
}
private:
int m_iX;
int m_iY;
};

int main(void)
{

Coordinate coor(3, 5);
cout << coor[0]; // coor.operatorp[](0)
cout << coor[1]; // coor.operatorp[](1)

return 0;
}

5 模板函数和模板类

5.1 函数模板

5.1.1 基础

关键字 tymplatetypenameclass(不是定义类),其中 typename 和 class 可以相互替换。
说明: 当需要定义多个功能相同,数据类型不同的函数时,可以使用函数模板。

  • 无论是类型还是值都可以模板化处理,甚至可以混在一起使用。
  • 函数模板的参数个数可以时一个,也可以是多个。
  • 使用函数模板时,需要指定模板参数,此时的函数称为 模板函数
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
#include <string.h>
#include <iostream>
using namespace std;

// 单个类型
template <typename T>
void swapNum(T &a, T &b)
{

T tmp = a;
a = b;
b = tmp;
}

// 多个类型
template <typename T, typename C>
void display(T a, C b)
{

cout << a << " " << b << endl;
}

// 类型和数据
temolate <typename T, int size>
void display(T a)
{

for(int i = 0; i < size; i++)
{
cout << a;
}
cout << endl;
}

int main(void)
{

int x = 20, y = 30;
swapNum<int>(x, y);

int a = 1024;
string str = "hello world!";
display<int, string>(a, str);

display<int, 5>(15);
return 0;
}

5.1.2 函数模板与函数重载

说明: 函数模板也存在重载。

1
2
3
4
5
6
7
8
template <typename T>
void display(T a);

template <typename T>
void display(T a, T b);

template <typename T, int size>
void display(T a);

5.2 类模板

  • 类模板声明和实现要写在同一个文件中
  • 外联实现的成员函数都需要分别声明模板(虽然看起来有点多余…)
1
2
3
.
├── MyArray.h # 类模版声明和实现不能分离开,因此全部写在头文件
└── main.cpp

MyArray.h

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
#ifndef MYARRAY_H
#define MYARRAY_H

#include <iostream>
using namespace std;

template <typename T, int KSize, int KVal>
class MyArray
{
public:
MyArray();
~MyArray()
{
delete []m_pArr;
m_pArr = NULL;
}
void display();
private:
T *m_pArr;
};

template <typename T, int KSize, int KVal>
MyArray<T, KSize, KVal>::MyArray()
{
m_pArr = new T[KSize];
for(int i = 0; i < KSize; i++)
{
m_pArr[i] = KVal;
}
}

template <typename T, int KSize, int KVal>
void MyArray<T, KSize, KVal>::display()
{
for(int i = 0; i < KSize; i++)
{
cout << m_pArr[i] << endl;
}
}

#endif

main.cpp

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include "MyArray.h"
using namespace std;

int main(void)
{

MyArray<int, 5, 0> arr;
arr.display();

return 0;
}

6 标准模板类

说明: STL (Standard Template Lib),即标准模板库,下面介绍一些常用的。

6.1 vector 向量

说明: 对数组的封装,读取能在常数时间完成,可理解为一个可变长度的数组。

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
#include <iostream>
#include <vector>
using namespace std;

int main(void)
{

vector<int> vec; // 初始化
vec.push_back(3); // 从尾部插入一个数据
vec.push_back(5); // 从尾部插入一个数据
vec.push_back(7); // 从尾部插入一个数据
vec.pop_back(); // 从尾部删除一个数据
cout << vec.size() << endl; // 查看数据数量

// for 遍历
for(int k = 0; k < vec.size(); k++)
{
cout << vec[k] << endl;
}

// 使用迭代器遍历
vector<int>::iterator citer = vec.begin();
for(; citer != vec.end(); citer++)
{
cout << *citer << endl;
}

cout << vec.front() << endl; // 第一个数据
cout << vec.back() << endl; // 最后一个数据
return 0;
}

6.2 list 链表

特点: 数据插入数据快

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <list>
using namespace std;

int main(void)
{

list<int> l1;
l1.push_back(2);
l1.push_back(4);
l1.push_back(5);

// 使用迭代器遍历 list
list<int>::iterator itor = l1.begin();
for(; itor != l1.end(); itor++)
{
cout << *itor << endl;
}
return 0;
}

6.3 map 映射

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
#include <iostream>
#include <string>
#include <map>
using namespace std;


int main(void)
{

// 创建 map 实例
map<int, string> m;

// 创建键值对
pair<int, string> p1(10, "shanghai");
pair<int, string> p2(20, "beijing");

// 插入键值对
m.insert(p1);
m.insert(p2);

// 根据键访问值
cout << m[10] << endl;
cout << m[20] << endl;

// 迭代器遍历
map<int, string>::iterator itor = m.begin();
for(; itor != m.end(); itor++)
{
cout << itor->first << endl;
cout << itor->second << endl;
}
return 0;
}