重新系统学习c++语言,并将学习过程中的知识在这里抄录、总结、沉淀。同时希望对刷到的朋友有所帮助,一起加油哦!
生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉悦!
系列文章列表:
写在前面,本篇章主要针对泛型编程学习。
1 模版
1.1 模版的概念
模版就是建立通用模具,减少重复工作量。
生活中也有很多使用模版的例子。例如:
ppt模版: 年度总结ppt模版
报表模版: 月度数据报表模版
模版的特点:
- 模版不可以直接使用,它只是一个框架。使用时需要自己填充业务特性的东西。
- 模版的通用并不是万能的,只适用于某些特定类型的场景。
1.2 函数模版
c++另一种编程思想称为 泛型编程,主要利用的技术就是模版。
c++提供两种模版机制: 函数模版 和 类模版
1.2.1 函数模版语法
函数模版的作用:
给定一个通用函数,其函数返回值类型和形参类型都可以不具体给定,用一个虚拟的类型来代替。
语法:
template<typename T>
函数声明或定义
解释:
- template:声明要创建模版
- typename:表明其后的符号是一种数据类型。告诉编译器不要报错。typename可用class代替
- T:通用数据类型。可以写成其他,通常为大写字母,例如 X或Y等。
调用使用模版函数的方式:
- 自动类型推导
- 显式指定数据类型
示例:
#include <iostream>
#include <string>
using namespace std;
// 交换两个整型数据
void swapInt(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
// 交换两个浮点型数据
void swapDouble(double& a, double& b) {
double tmp = a;
a = b;
b = tmp;
}
// 使用模版函数
template<typename T>
void mySwap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
void test() {
int a = 1;
int b = 2;
swapInt(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
double c = 1.1;
double d = 2.2;
swapDouble(c, d);
cout << "c = " << c << endl;
cout << "d = " << d << endl;
}
void test2() {
cout << "------------使用模版函数--------------" << endl;
int a = 1;
int b = 2;
// 两种调用模版函数的方式:
// 1 自动类型推导
//mySwap(a, b);
// 2 显式指定数据类型
mySwap<int>(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
int main() {
test();
test2();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.2.2 函数模版注意事项
注意事项:
- 自动类型推导,必须推导出一致的数据类型T,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
示例:
#include <iostream>
#include <string>
using namespace std;
// 使用模版函数
template<typename T>
void mySwap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
template<typename T>
void func() {
cout << "func调用" << endl;
}
void test() {
int a = 1;
int b = 2;
double c = 3.3;
mySwap(a, b);// 正确
// mySwap(a, c);// 错误,必须推导出一致的数据类型T
}
void test2() {
// func(); // 错误,无法确定出T的数据类型
func<int>(); // 正确
func<string>(); // 正确
}
int main() {
test();
test2();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.2.3 函数模版案例
案例:
- 利用模版的技术封装一个排序函数,可以对不同数据类型进行排序;
- 排序规则从大到小,排序算法为选择排序;
- 用char数组和int数组进行测试;
示例:
#include <iostream>
#include <string>
using namespace std;
// 交换函数
template<class T>
void mySwap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
// 选择排序算法
template<class T>
void mySort(T arr[], int len) {
for (int i = 0; i < len; i++)
{
// 选定i下标的元素值为当前轮排序的最大值下标
int max = i;
for (int k = i + 1; k < len; k++) {
// 若认定的最大值 比 遍历出的值 小,说明k下标的元素才是最大值
if (arr[max] < arr[k])
{
max = k; // 更新最大值下标
}
}
if (max != i) {
mySwap(arr[max],arr[i]); // 交换max和i元素值
}
}
}
// 打印数组
template<class T>
void printArr(T arr[], int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
// 测试char数组
void test() {
char arr[] = { 'd','e','a','b','c' };
int len = sizeof(arr) / sizeof(char);
mySort(arr, len);
printArr(arr, len);
}
// 测试int数组
void testInt() {
int arr[] = { 4,2,3,1,6,5,7 };
int len = sizeof(arr) / sizeof(int);
mySort(arr, len);
printArr(arr, len);
}
int main() {
test();
testInt();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.2.4 普通函数与函数模版的区别
区别:
- 普通函数 调用可以发生隐式类型转换
- 函数模版 用自动类型推导,不可以发生隐式类型转换
- 函数模版 用显式指定类型,可以发生隐式类型转换
示例:
# include <iostream>
# include <string>
using namespace std;
// 普通函数
int add1(int a, int b) {
return a + b;
}
// 模版函数
template<class T>
T add2(T a, T b) {
return a + b;
}
void test1() {
int a = 10;
int b = 20;
char c = 'c'; // a-97 c-99
cout << add1(a, b) << endl;
cout << add1(a, c) << endl;
}
void test2() {
int a = 10;
int b = 20;
char c = 'c';
cout << add2(a, b) << endl;
// 报错, 用自动类型推导,不可以发生隐式类型转换
// cout << add2(a, c) << endl;
// 用显式指定类型,可以发生隐式类型转换
cout << add2<int>(a, c) << endl;
}
int main() {
test1();
test2();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.2.5 普通函数与函数模版的调用规则
调用规则:
- 如果普通函数和函数模版都有实现,优先调用普通函数;
- 可以通过空模版参数列表来强制调用函数模版;
- 函数模版也可以发生重载;
- 如果模版函数可以产生更好的匹配,优先调用函数模版;
示例:
#include <iostream>
#include<string>
using namespace std;
void myPrint(int a) {
cout << "调用普通函数" << endl;
}
template<class T>
void myPrint(T a) {
cout << "调用模版函数" << endl;
}
// 函数模版也可以发生重载;
template<class T>
void myPrint(T a, T b) {
cout << "调用重载的模版函数" << endl;
}
void test() {
int a = 10;
// 如果普通函数和函数模版都有实现,优先调用普通函数;
myPrint(a);
// 可以通过空模版参数列表来强制调用函数模版;
myPrint<>(a);
// 如果模版函数可以产生更好的匹配,优先调用函数模版;
// 因为普通函数需要先做隐式类型转换,模版函数不需要,所以编译器认为模版函数更匹配
char c = 'c';
myPrint(c);
}
int main() {
test();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.2.6 模版的局限性
模版并不是万能的,有些特定的数据类型,需要用具体化模版方式来实现。
例如:
template<class T>
void f(T a,T b)
{
a=b;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
在上述a=b;的赋值代码中,如果传入的a和b是数组,就无法实现,编译会报错。
示例:
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
Person(string name, int age) {
m_name = name;
m_age = age;
}
bool operator==(Person &p) {
if (this->m_name == p.m_name) {
return true;
}
else
{
return false;
}
}
string m_name;
int m_age;
};
template<class T>
bool myCompare(T &a, T &b) {
if (a == b) {
return true;
}
else {
return false;
}
}
// 利用具体化Person的版本实现,具体化会优先调用
// template<> 模版定义成空模版列表
// 模版函数参数列表T需要换成具体化的数据类型Person
template<> bool myCompare(Person& p1, Person& p2) {
if (p1.m_name == p2.m_name && p1.m_age == p2.m_age) {
return true;
}
else {
return false;
}
}
void test01() {
int a = 10;
int b = 20;
bool ret = myCompare(a, b);
if (ret) {
cout << "a == b" << endl;;
}
else
{
cout << "a != b" << endl;
}
}
void test02() {
Person p1 = Person("echo", 18);
Person p2 = Person("echo", 18);
bool ret = myCompare(p1, p2);
if (ret) {
cout << "p1 == p2" << endl;;
}
else
{
cout << "p1 != p2" << endl;
}
}
int main() {
test01();
test02();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.3 类模版
1.3.1 类模版语法
作用:建立一个通用类,类中的成员数据类型可以不固定,用虚拟类型来指定。
语法:
template<typename T>
class 类名{
……
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
示例:
#include <iostream>
#include <string>
using namespace std;
//也可以这样定义 template<typename NameType, typename AgeType>
template<class NameType,class AgeType>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_name = name;
this->m_age = age;
}
void PrintPerson() {
cout << "name:" << m_name << ",age:" << m_age << endl;
}
NameType m_name;
AgeType m_age;
};
void test() {
Person<string,int> p1("echo", 18);
p1.PrintPerson();
Person<string, string> p2("bala", "不知道");
p2.PrintPerson();
}
int main() {
test();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.3.2 类模版与函数模版的区别
- 类模版没有自动堆导使用方式
- 类模版中的模版参数列表可以有默认数据类型
示例:
#include <iostream>
#include <string>
using namespace std;
template<typename NameType,typename AgeType = int>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_name = name;
this->m_age = age;
}
void PrintPerson() {
cout << "name:" << m_name << ",age:" << m_age << endl;
}
NameType m_name;
AgeType m_age;
};
void test() {
// 1 无法用自动类型推导,只能用显式指定数据类型
// Person p1("echo", 18); // 无法编译通过,错误
Person<string, int> p1("echo", 18);
p1.PrintPerson();
// 2 类模版的模版参数列表可以指定默认数据类型
Person<string> p2("tom", 30);
p2.PrintPerson();
}
int main() {
test();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.3.3 类模版中成员函数创建时机
普通类和类模版中成员函数的创建时机不同:
- 普通类中的成员函数在 一开始 就创建;
- 类模版中的成员函数在 调用时 才创建;
示例:
#include <iostream>
#include <string>
using namespace std;
class Person1 {
public:
void showPerson1() {
cout << "show Person1" << endl;
}
};
class Person2 {
public:
void showPerson2() {
cout << "show Person2" << endl;
}
};
template<typename T>
class MyPerson {
public:
void fun1() {
obj.showPerson1();
}
void fun2() {
obj.showPerson2();
}
T obj;
};
void test1() {
MyPerson<Person1> p;
p.fun1();
//p.fun2(); // 错误 因为:调用时确认obj是Person1数据类型,fun2成员未创建
}
void test2() {
MyPerson<Person2> p;
//p.fun1(); //错误 因为:调用时确认obj是Person2数据类型,fun1成员未创建
p.fun2();
}
int main() {
test1();
test2();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.3.4 类模版对象做函数参数
类模版对象做函数参数共有三种形式:
- 指定传入类型--直接显式的使用数据类型做函数参数 (最常用的形式)
- 参数模版化--将类对象中的参数模版化来做函数参数
- 整个类模版化--将类对象模版化来做函数参数
示例:
#include <iostream>
#include <string>
using namespace std;
template<class T1,class T2>
class Person {
public:
Person(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
void showPerson() {
cout << "name:" << m_name << ",age:" << m_age << endl;
}
T1 m_name;
T2 m_age;
};
void showPerson1(Person<string, int> &p) {
p.showPerson();
}
//1 指定传入类型--直接显式的使用数据类型做函数参数
void test1() {
Person<string, int> p("echo1", 18);
showPerson1(p);
}
template<class T1,class T2>
void showPerson2(Person<T1, T2>& p) {
p.showPerson();
}
//2 参数模版化--将类对象中的参数模版化来做函数参数
//3 整个类模版化--将类对象模版化来做函数参数
void test2() {
Person<string, int> p("echo2", 19);
showPerson2(p);
}
template<class T>
void showPerson3(T& p) {
p.showPerson();
}
//3 整个类模版化--将类对象模版化来做函数参数
void test3() {
Person<string, int> p("echo3", 20);
showPerson3(p);
}
int main() {
test1();
test2();
test3();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.3.5 类模版与继承
要继承类模版,需要注意:
- 当子类继承的父类是一个类模版时,子类在声明时,需要指定父类中T的数据类型。——如果不指定父类T的数据类型,编译器无法给子类分配内存,会编译不通过。
- 如果想灵活继承父类中T的数据类型,子类也需要定义成类模版。
示例:
#include <iostream>
#include <string>
using namespace std;
template<class T>
class Base {
T b;
};
// 错误写法
// 子类继承父类是类模版时,子类在声明时,需要指定父类中T的类型
//class Son :public Base {
//};
// 正确写法如下,指定T为int
class Son :public Base<int> {
};
void test1() {
Son s;
}
// 如果想灵活指定父类中T的数据类型,子类也需变为模版
// T 是base中数据类型,Y是Son2中数据类型
template<class T,class Y>
class Son2 :public Base<T> {
Y y;
};
void test2() {
Son2<int, string> s;
}
int main() {
test1();
test2();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.3.6 类模版成员函数类外实现
示例:
#include <iostream>
#include <string>
using namespace std;
// 类模版中的成员函数在类外实现
template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age);
void PrintPerson();
T1 m_name;
T2 m_age;
};
// 类模版中的成员函数在类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{
m_name = name;
m_age = age;
}
template<class T1, class T2>
void Person<T1, T2>::PrintPerson()
{
cout << "name:" << m_name << ",age:" << m_age << endl;
}
void test() {
Person<string, int> p1("echo", 18);
p1.PrintPerson();
}
int main() {
test();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)
1.3.7 类模版分文件编写
问题:
普通类分文件编写,会直接将类分成头文件a.h文件和源文件a.cpp文件。然后在调用程序中添加#include "a.h",程序就直接调用类成员函数。但是,如果是类模版,该方式分文件编写及调用会出现编译问题。
原因:
类模版中成员函数是在调用时才创建,直接添加 .h会找不到函数。
解决办法:
方法1:直接包含.cpp源文件。
方法2:将声明和实现写在一起,不要分文件编写,并将文件名后缀写成.hpp。.hpp是大家约 定的类模版文件名写法,不是强制的。——常用方式
1.3.8 类模版与友元
全局函数类内实现:
直接在类内声明是友元即可;
全局函数类外实现:
(1) 需要提前让编译器知道类模版;
(2) 需要提前让编译器知道全局函数存在;
示例:
#include <iostream>
#include <string>
using namespace std;
// 要提前让编译器知道Person类存在
template<class T1, class T2>
class Person;
// 全局函数类外实现
template<class T1, class T2>
void showPerson2(Person < T1, T2>& p) {
cout << "类外实现--name:" << p.m_name << ",age:" << p.m_age << endl;
}
template<class T1,class T2>
class Person {
// 全局函数类内实现
friend void showPerson(Person < T1, T2>& p ){
cout << "类内实现--name:" << p.m_name << ",age:" << p.m_age << endl;
}
// 全局函数类外实现
// 注意:要加空模版参数列表
// 如果全局函数是类外实现,需要让编译器提前知道这个函数存在,不然编译不通过
friend void showPerson2<>(Person < T1, T2>& p);
public:
Person(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
private:
T1 m_name;
T2 m_age;
};
void test() {
Person<string, int> p("echo", 18);
showPerson(p);
}
void test2() {
Person<string, int> p("echo", 18);
showPerson2(p);
}
int main() {
test();
test2();
system("pause");
return 0;
}
![点击并拖拽以移动](/i/l/?n=22&i=blog/506926/202211/506926-20221124113236074-145121859.gif)