本阶段主要针对C++泛型编程(利用模板实现)和STL技术做详细讲解,探讨C++更深层的使用。
模板就是通过建立通用的模具,从而提高程序复用性。模板主要可以分为函数模板与类模板。
函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表(类型参数化)。
template
//后紧跟着函数的定义或声明
| 函数模板 | 说明 |
|---|---|
| template | 声明创建模板 |
| typename | 表明其后面的符号是一种数据类型,可以用class替代 |
| T | 通用的数据类型,名称可以替换(通常为大写的字母) |
#include
using namespace std;//1.声明一个函数模板告诉编译器T是一个通用的数据类型
template
void mySwap(T &a, T &b){T temp = a;a = b;b = temp;
}void test01(){int a = 10;int b = 20;//2.两种方式使用函数模板//(1)自动类型推导mySwap(a, b);cout << "a = " << a << endl;cout << "b = " << b << endl;//(2)显示指定类型double c = 3.14;double d = 6.28;mySwap(c, d);cout << "c = " << c << endl;cout << "d = " << d << endl;
}int main(){test01();system("pause");return 0;
}

注意:
函数模板中,自动类型推导必须推导出一致的数据类型T,才可以使用
模板必须要求确定出T的数据类型,才可以使用(如下例中函数没有指出
T的数据类型,调用时需要使用func)() templatevoid func(){cout << "func的调用" << endl; }
利用函数模板封装一个排序的函数,可以对不同数据类型的数组进行排序(排序规则从大到小,排序算法为选择排序)
#include
using namespace std;//1.mySort交换函数
template
void mySwap(T &a, T &b){T temp = a;a = b;b = temp;
}//2.mySort选择排序函数
template
void mySort(T arr[], int len){for(int i = 0; i < len; ++i){//(1)认定最大值的下标int max = i;//(2)遍历数组中的元素尝试更新最大值maxfor(int j = i + 1; j < len; ++j){if(arr[max] < arr[j]) max = j;}//(3)如果认定的最大值与遍历后的最大值不相等(最大值max是否被更新),则交换max与i下标的元素值if(max != i) mySwap(arr[max], arr[i]);}
}//3.printArray数组输出函数
template
void printArray(T arr[], int len){for(int i = 0; i < len; ++i){cout << arr[i] << " ";}cout << endl;
}void test01(){char charArr[] = "badcfe";int len = sizeof(charArr) / sizeof(char);mySort(charArr, len);printArray(charArr, len);
}void test02(){int intArr[] = {7, 5, 4, 8, 10, 1, 6, 4};int len = sizeof(intArr) / sizeof(int);mySort(intArr, len);printArray(intArr, len);
}int main(){test01();test02();system("pause");return 0;
}

普通函数与函数模板的区别:
#include
using namespace std;//1.普通函数
int myAdd01(int a, int b){return a + b;
}//2.函数模板
template
T myAdd02(T a, T b){return a + b;
}void test01(){int a = 10;int b = 20;char c = 'c';//1.普通函数调用cout << myAdd01(a, b) << endl;cout << myAdd01(a, c) << endl;//2.函数模板自动类型推导调用cout << myAdd02(a, b) << endl;//cout << myAdd02(a, c) << endl;(报错不会发生隐式类型转换)//3.函数模板显示指定类型调用cout << myAdd02(a, b) << endl;cout << myAdd02(a, c) << endl;
}int main(){test01();system("pause");return 0;
}
总结:建议使用显示指定类型的方式调用函数模板,可以自己确定通用类型T
普通函数与函数模板的调用规则:
注意:函数模板也可以发生重载
#include
using namespace std;void myPrint(int a, int b){cout << "调用的普通函数" << endl;
}template
void myPrint(T a, T b){cout << "调用的函数模板" << endl;
}void test01(){cout << "test01:如果普通函数和函数模板都可以调用,则优先调用普通函数" << endl;int a = 10;int b = 10;myPrint(a, b);
}void test02(){cout << "test02:如果普通函数和函数模板都可以调用,通过空模板参数列表来强制调用函数模板" << endl;int a = 10;int b = 20;myPrint<>(a, b);
}void test03(){cout << "test03:如果函数模板可产生更好的匹配,则优先调用函数模板" << endl;char c1 = 'a';char c2 = 'b';myPrint(c1, c2);
}int main(){test01();test02();test03();system("pause");return 0;
}

总结:如果提供了函数模板,就最好不要再提供普通函数,否则容易出现二义性。
当传入的T数据类型为数组、类(例如:Person)等数据类型时,template模板就无法使用了。
c++为了解决这种问题,使其提供的模板template可以实现重载,可以为这些特定的数据类型提供更具体的模板。
#include
#include
using namespace std;class Person{
public:Person(string n, int a){this->name = n;this->age = a;}string name;int age;
};template
bool myCompare(T &sub1, T &sub2){if(sub1 == sub2){cout << "sub1 == sub2" << endl;return true;}else{cout << "sub1 != sub2" << endl;return false;}
}//在函数头前添加template<>具体化Person的版本实现模板函数,具体化优先调用
template<> bool myCompare(Person &sub1, Person &sub2){if(sub1.name == sub2.name && sub1.age == sub2.age){cout << "sub1 == sub2" << endl;return true;}else{cout << "sub1 != sub2" << endl;return false;}
}void test01(){int a = 10;int b = 20;myCompare(a, b);
}void test02(){Person p1("Tom", 10);Person p2("John", 10);Person p3("Tom", 10);/*1.需要重载Person类型之间的比较运算符==2.再提供可以接收Person数据类型的函数模板*/myCompare(p1, p2);myCompare(p1, p3);
}int main(){test01();test02();system("pause");return 0;
}

总结:
- 利用具体化的模板,可以解决自定义类型的通用化。
- 在STL中系统提供了大量的模板。
类模板的作用,建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表。
template
//后紧跟着类定义
| 函数模板 | 说明 |
|---|---|
| template | 声明创建模板 |
| typename | 表明其后面的符号是一种数据类型,可以用class替代 |
| T | 通用的数据类型,名称可以替换(通常为大写的字母) |
#include
using namespace std;template
class Person{
public:Person(NameType n, AgeType a){this->name = n;this->age = a;}void showPerson(){cout << "name:" << this->name << "\tage:" << this->age << endl;}NameType name;AgeType age;
};void test01(){Person p1("lch", 20);p1.showPerson();
}int main(){test01();system("pause");return 0;
}

总结:类模板与函数模板语法非常相似,在声明模板template后面加类,称为模板类。


#include
using namespace std;template
class Person{
public:Person(NameType n, AgeType a){this->name = n;this->age = a;}void showPerson(){cout << "name:" << this->name << "\tage:" << this->age << endl;}NameType name;AgeType age;
};void test01(){//Person p1("lch", 20);//1.类模板没有自动类型推导的使用方式只有显示指定类型Person p1("lch", 21);p1.showPerson();Person p2("luochenhao", 21);//2.类模板在模板参数列表中可以有默认参数p2.showPerson();
}int main(){test01();system("pause");return 0;
}

普通类中的成员函数开始就能够创建,类模板中的成员函数在被调用时才能够创建。
#include
using namespace std;class Cock{
public:void showCock(){cout << "It's a Cock, bukbukbuk!" << endl;}
};class Cat{
public:void showCat(){cout << "It's a Cat, miaomiaomiao~" << endl;}
};template
class Animal{
public:T obj;void Cock(){obj.showCock();}void Cat(){obj.showCat();}
};void test01(){Animal animal1;animal1.Cock();//animal1.Cat();无法调用Animal animal2;//animal2.Cock();无法调用animal2.Cat();
}int main(){test01();system("pause");return 0;
}

总结:
由于无法确定类模板T的数据类型(Cock or Cat),故类模板中的成员函数不会创建;
直到成员函数被调用时(数据类型已经确定),才能够创建成员函数。
类模板实例化出的对象,向函数传递参数的方式有3种:
#include
using namespace std;template
class Person{
public:Person(T1 n, T2 a){this->name = n;this->age = a;}void showPerson(){cout << "姓名:" << this->name << "\t年龄:" << this->age << endl;}T1 name;T2 age;
};//1.指定传入类型
void printPerson1(Person &p){p.showPerson();
}
void test01(){Person p("lch", 21);printPerson1(p);
}//2.参数模板化
template
void printPerson2(Person &p){p.showPerson();cout << "T1的类型为:" << typeid(T1).name() << endl;cout << "T2的类型为:" << typeid(T2).name() << endl;
}
void test02(){Person p("lch", 21);printPerson2(p);
}//3.整个类模板化
template
void printPerson3(T &p){p.showPerson();cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03(){Person p("lch", 21);printPerson3(p);
}int main(){test01();test02();test03();system("pause");return 0;
}

注意:指定参数传入的类型是使用最为广泛的一种传参方式。
当类模板遇到继承问题时,应注意:

#include
using namespace std;//类模板与继承
template
class Base{
public:T m;
};//1.class Son:public Base{};报错,必须要知道父类中T的数据类型才能继承给子类
class Son1:public Base{};//2.如果想灵活指定父类中T的类型,子类也需要变成类模板
template
class Son2:public Base{
public:Son2(){cout << "T1的数据类型为:" << typeid(T1).name() << endl;cout << "T2的数据类型为:" << typeid(T2).name() << endl;}T2 obj;
};int main(){Son1 s1;Son2 s2;system("pause");return 0;
}

总结:如果父类是类模板,子类需要指定出父类中T的数据类型
类模板的成员函数的类内实现:
template
class Person{
public:Person(T1 n, T2 a){this->name = n;this->age = a;}void showPerson(){cout << "name:" << this->name << "age:" << this->age << endl;}T1 name;T2 age;
};
类模板的成员函数的类外实现:
#include
using namespace std;template
class Person{
public:Person(T1 n, T2 a);void showPerson();T1 name;T2 age;
};//1.构造函数的类外实现
template
Person::Person(T1 n, T2 a){this->name = n;this->age = a;
}//2.成员函数的类外实现
template
void Person::showPerson(){cout << "name:" << this->name << "\tage:" << this->age << endl;
}int main(){Person p("lch", 21);p.showPerson();system("pause");return 0;
}

注意:类模板中的成员函数类外实现时,需要加上模板参数列表,例
void Person。::showPerson()
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到:

解决方案有2种:

.h声明和.cpp实现写到同一个文件中,并更改后缀名为hpp(hpp是约定的名称/非强制)
#ifndef PERSON_H
#define PERSON_H
#pragma once
#include
using namespace std;template
class Person{
public:Person(T1 n, T2 a);void showPerson();T1 name;T2 age;
};//1.构造函数的类外实现
template
Person::Person(T1 n, T2 a){this->name = n;this->age = a;
}//2.成员函数的类外实现
template
void Person::showPerson(){cout << "name:" << this->name << "\tage:" << this->age << endl;
}#endif // PERSON_H
#include
#include "Person.hpp"
using namespace std;int main(){Person p("lch", 18);p.showPerson();system("pause");return 0;
}

程序成功运行
实现类模板配合友元函数的类内和类外实现。
#include
using namespace std;//通过全局函数打印Person信息
template
class Person{//全局函数类内实现friend void printPerson(Person p){cout << "name:" << p.name << "age:" << p.age << endl;}
public:Person(T1 n, T2 a){this->name = n;this->age = a;}
private:T1 name;T2 age;
};int main(){Person p("lch", 21);printPerson(p);system("pause");return 0;
}
#include
using namespace std;template
class Person;//全局函数类外实现
template
void printPerson(Person p){cout << "name:" << p.name << "\tage:" << p.age << endl;
}template
class Person{//全局函数类外实现(加一个空模板的参数列表)friend void printPerson<>(Person p);
public:Person(T1 n, T2 a){this->name = n;this->age = a;}
private:T1 name;T2 age;
};int main(){Person p("lch", 21);printPerson(p);system("pause");return 0;
}

operator=防止浅拷贝问题。

#ifndef MYARRAY_H
#define MYARRAY_H
#pragma once
#include
using namespace std;template
class MyArray{public://实现有参构造MyArray(int capa){cout << "MyArray有参构造调用" << endl;this->capacity = capa;this->size = 0;this->pAddress = new T[this->capacity];}//防止浅拷贝问题需要重写拷贝构造函数MyArray(const MyArray &arr){cout << "MyArray拷贝构造调用" << endl;this->capacity = arr.capacity;this->size = arr.size;//this->pAddress = arr.pAddress;指针不能这样直接赋值,会导致浅拷贝问题堆区内存重复释放//(1)重新在堆区开辟一个数组空间,让指针进行维护this->pAddress = new T[arr.capacity];//(2)将arr中原有的数据拷贝到新开辟的空间中for(int i = 0; i < this->size; ++i) this->pAddress[i] = arr.pAddress[i];}//重载operator=防止浅拷贝问题MyArray& operator=(const MyArray &arr){cout << "MyArray的operator=调用" << endl;//(1)先判断原来堆区是否有数据,如果有先释放if(this->pAddress != NULL){delete[] this->pAddress;this->pAddress = NULL;this->capacity = 0;this->size = 0;}//(2)深拷贝this->capacity = arr.capacity;this->size = arr.size;this->pAddress = new T[arr.capacity];for(int i = 0; i < this->size; ++i) this->pAddress[i] = arr.pAddress[i];return *this;}//堆区开辟的数据需要析构函数手动释放~MyArray(){if(this->pAddress != NULL){cout << "MyArray析构函数调用" << endl;delete[] this->pAddress;//清空数组中的数据this->pAddress = NULL;//将指针置空防止野指针}}private:T *pAddress;//指针指向堆区开辟的真实数组int capacity;//数组容量int size;//数组元素个数
};#endif // MYARRAY_H
#include
#include "MyArray.hpp"
using namespace std;void test01(){MyArray arr1(10);//测试有参构造功能MyArray arr2(arr1);//测试拷贝构造功能MyArray arr3(100);arr3 = arr1;//测试=运算符重载
}int main(){test01();system("pause");return 0;
}

[]实现访问操作)。#ifndef MYARRAY_H
#define MYARRAY_H
#pragma once
#include
using namespace std;template
class MyArray{public://1.实现有参构造MyArray(int capa){this->capacity = capa;this->size = 0;this->pAddress = new T[this->capacity];}//2.防止浅拷贝问题需要重写拷贝构造函数MyArray(const MyArray &arr){this->capacity = arr.capacity;this->size = arr.size;//this->pAddress = arr.pAddress;指针不能这样直接赋值,会导致浅拷贝问题堆区内存重复释放//(1)重新在堆区开辟一个数组空间,让指针进行维护this->pAddress = new T[arr.capacity];//(2)将arr中原有的数据拷贝到新开辟的空间中for(int i = 0; i < this->size; ++i) this->pAddress[i] = arr.pAddress[i];}//3.重载operator=防止浅拷贝问题MyArray& operator=(const MyArray &arr){//(1)先判断原来堆区是否有数据,如果有先释放if(this->pAddress != NULL){delete[] this->pAddress;this->pAddress = NULL;this->capacity = 0;this->size = 0;}//(2)深拷贝this->capacity = arr.capacity;this->size = arr.size;this->pAddress = new T[arr.capacity];for(int i = 0; i < this->size; ++i) this->pAddress[i] = arr.pAddress[i];return *this;}//4.尾插法实现数据增加void Push_Back(const T &val){//(1)判断容量已经满if(this->capacity == this->size) return;//(2)进行数据插入this->pAddress[this->size] = val;this->size++;}//5.尾删法实现数据删除void Pop_Back(){//(1)判断容量已经空if(this->size == 0) return;//(2)进行数据删除this->size--;}//6.用户通过下标的方式访问数组中的元素(如果函数调用想作为等号的左值存在arr[0]=100;需要返回T&)T& operator[](int index){return this->pAddress[index];}//7.返回数组的容量int getCapacity(){return this->capacity;}//8.返回数组的个数int getSize(){return this->size;}//9.堆区开辟的数据需要析构函数手动释放~MyArray(){if(this->pAddress != NULL){delete[] this->pAddress;//清空数组中的数据this->pAddress = NULL;//将指针置空防止野指针}}private:T *pAddress;//指针指向堆区开辟的真实数组int capacity;//数组容量int size;//数组元素个数
};#endif // MYARRAY_H
#include
#include "MyArray.hpp"
using namespace std;void printArray(MyArray &arr){cout << ">>>数组内容打印如下:" << endl;for(int i = 0; i < arr.getSize(); ++i){cout << arr[i] << " ";}cout << endl;
}void test01(){//arr1数组测试MyArray arr1(10);for(int i = 0; i < 7; ++i) arr1.Push_Back(i);printArray(arr1);cout << "数组的容量为:" << arr1.getCapacity() << endl;cout << "数组中的元素个数为:" << arr1.getSize() << endl;//arr2数组测试MyArray arr2(arr1);arr2.Pop_Back();printArray(arr2);cout << "数组的容量为:" << arr2.getCapacity() << endl;cout << "数组中的元素个数为:" << arr2.getSize() << endl;
}class Person{
public:Person(){};Person(string n, int a){this->name = n;this->age = a;}string name;int age;
};void printPersonArray(MyArray &arr){cout << ">>>数组内容打印如下:" << endl;for(int i = 0; i < arr.getSize(); ++i){cout << "姓名:" << arr[i].name << "\t年龄:" << arr[i].age << endl;}
}void test02(){MyArray arr(10);Person p1("lch", 21);Person p2("cxk", 18);Person p3("kfc", 28);Person p4("gkd", 21);arr.Push_Back(p1);arr.Push_Back(p2);arr.Push_Back(p3);arr.Push_Back(p4);printPersonArray(arr);cout << "数组的容量为:" << arr.getCapacity() << endl;cout << "数组中的元素个数为:" << arr.getSize() << endl;
}int main(){test01();test02();system("pause");return 0;
}
