C++复习的长文指南(二)
一、面向对象基础知识
5. 文件操作
程序运行时产生的数据都属于临时数据
,程序—旦运行结束
都会被释放
通过文件
可以将数据持久化
C++中对文件操作需要包含头文件<fstream >
文件类型分为两种:1.
文本文件:文件以文本的ASCII码形式存储在计算机中2.
二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:1.
ofstream:写操作2.
ifstream:读操作3.
fstream :读写操作
5.1文本文件
5.1.1写文件
写文件步骤如下:1.
包含头文件
#include <fstream>
2.
创建流对象
ofstream ofs;3.
打开文件
ofs.open(“文件路径",打开方式);4.
写数据
ofs <<“写入的数据”;5.
关闭文件注意:
文件打开方式可以配合使用,利用|操作符
例如:
用二进制方式写文件ios : : binary | ios : : out总结:
文件操作必须包含头文件fstream
读文件可以利用ofstream ,或者fstream类
打开文件时候需要指定操作文件的路径,以及打开方式
利用<<
可以向文件中写数据
操作完毕,要关闭
文件
#include<iostream> using namespace std; #include<fstream> // 头文件的包含 // 文本文件 写文件 void test01() { // 1.包含头文件 // 2.创建流对象 ofstream ofs; // 创建输出流对象 // 3.指定打开方式 ofs.open("test.txt", ios::out); // 4.写内容 ofs << "姓名:张三" << endl; ofs << "性别:男" << endl; ofs << "年龄:18" << endl; // 5.关闭文件 ofs.close(); } int main() { test01(); system("pause"); return 0; }
5.1.2读文件
读文件与写文件步骤相似,但是读取方式相对于比较多
读文件步骤如下:1.
包含头文件
#include <fstream>
2.
创建流对象
ifstream ifs;3.
打开文件并判断文件是否打开成功ifs.open(“文件路径”,打开方式);4.
读数据
四种方式读取5.
关闭文件ifs.close();
#include<iostream> using namespace std; #include<fstream> // 头文件的包含 #include<string> // 文本文件 读文件 void test01() { // 1.包含头文件 // 2.创建流对象 ifstream ifs; // 创建输入流对象 // 3.打开文件 并且判断是否打开成功 ifs.open("test.txt", ios::in); if (! ifs.is_open()) { cout << "文件打开失败" << endl; return; } // 4.读数据 // 第一种 /*char buf[1024] = { 0 }; while (ifs >> buf) { cout << buf << endl; }*/ // 第二种 /*char buf[1024] = { 0 }; while (ifs.getline(buf, sizeof(buf))) { cout << buf << endl; }*/ // 第三种 /*string buf; while (getline(ifs, buf)) { cout << buf << endl; }*/ // 第四种,1次只读1个字符 char c; while ( (c = ifs.get()) != EOF) // EOF end of file 文件尾 { cout << c; } // 5.关闭文件 ifs.close(); } int main() { test01(); system("pause"); return 0; }
总结:
1.
读文件可以利用ifstream ,或者stream类2.
利用is_open函数可以判断文件是否打开成功3.
close关闭文件
5.2 二进制文件
以二进制
的方式对文件进行读写操作
打开方式要指定为ios:binary
5.2.1 二进制文件
二进制方式写文件主要利用流对象调用成员函数write
函数原型:
ostream& write(const char * buffer ,int len); 参数解释:
字符指针buffer指向内存中一段存储空间。len是读写的字节数
#include<iostream> using namespace std; #include<fstream> // 头文件的包含 #include<string> // 二进制文件 写文件 class Person { public: char m_Name[64]; // 姓名 int m_Age; // 年龄 }; void test01() { // 1.包含头文件 // 2.创建流对象 ofstream ofs; // 或者直接 //ofstream ofs("person.txt", ios::out | ios::binary); // 3.打开文件 ofs.open("person.txt", ios::out | ios::binary); // 4.写文件 Person p = { "张三", 18 }; // 如果直接&p,返回的应该是Person*,所以再强转const char* ofs.write((const char*)&p, sizeof(Person)); // 5.关闭文件 ofs.close(); } int main() { test01(); system("pause"); return 0; }
总结:
1.
文件输出流对象可以通过write函数
,以二进制
方式写数据
5.2.2 二进制读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型:
istream& read(char *buffer,int len);参数解释:
字符指针buffer指向内存中一段存储空间。len是读写的字节数
#include<iostream> using namespace std; #include<fstream> // 头文件的包含 #include<string> // 二进制文件 读文件 class Person { public: char m_Name[64]; // 姓名 int m_Age; // 年龄 }; void test01() { // 1.包含头文件 // 2.创建流对象 ifstream ifs; // 3.打开文件,并判断文件是否正常打开 ifs.open("person.txt", ios::in | ios::binary); if (! ifs.is_open()) { cout << "文件打开失败" << endl; return; } // 4.写文件 // 将数据读到Person中,因为Person是自定义数据类型, Person p; ifs.read((char*)&p, sizeof(p)); cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl; // 5.关闭文件 ifs.close(); } int main() { test01(); system("pause"); return 0; }
总结:
文件输入流对象可以通过read函数,以二进制方式读数据
6. c++面向对象的个人心得
开发流程
6.1
1.
一般进行分文件
编写,.h头文件
进行类申明
,.cpp文件
进行类的具体实现
。
6.2
2.
一般进行面向对象的编程,封装、继承、多态
都用到。比如以经典的“职工管理系统”为例,那么该系统有:职工、经理和老板3
种身份,以及“职工管理系统”的增删改查等用户功能操作
需要单独定义1个管理系统类
(也是分开申明和实现)。
6.3
3.
那既然要用到面向对象
,多态
肯定定义一个职工父类(基类
),然后分别定义职工、经理和老板3种身份的子类继承
父类,并重写
父类的虚函数
(父类指针指向子类
,不同子类调用相同的函数实现不同的功能。4.
也就是说,最终分别进行基类
、3种身份的子类
、1个管理系统类
的.h头文件申明
,再进行各自的.cpp文件实现
。职工间的父子关系用到多态,其余的职工数据的增删改查、文件保存等全部放在管理系统类实现功能。最后,还有1个main函数入口
,通常主函数实例化类对象进行调用对应不同功能的函数接口。
6.4
5.
最后根据选择判断语句,进行调用管理系统类对象
的不同成员函数就行(增删改查等)
ps:职工父类(基类
)、3种身份的子类
的类,说到底在c++中还是不同的自定义类型
,最终还是通过父类指针指向子类等操作进行职工维护的。
6.5
6.
最后,因为程序运行时产生的数据都属于临时数据
,哪怕是堆区数据
,程序—旦运行结束都会被释放
,我们,通过文件
可以将数据持久化
。
注意细节
6.1
一般,像管理系统类
,肯定要有1个记录当前系统已经存放的职工人数
的整型成员变量
,以及职工父类(基类
)的自定义数据类型
的数组指针
(一般是数组指针,涉及多个职工,肯定用数组
进行维护,并实现不同职工的多态
)ps:
每次进行完增删改查操作后,一定要对类内成员变量进行数据更新
,比如这里的m_Empnum进行+1,数组指针指向进行更新等。
6.2
一般,虽然类的构造函数
和析构函数
,编译器会自动提供空实现
,但是一般我们进行重写
;
很明显,用构造函数进行变量初始化
,比如整型变量置0
,指针指向空地址
;
析构函数就是用来释放
开辟到堆区
的数据,一般不会将数据放在栈区
,因为当函数执行结束,编译器会自动回收
,下次再执行就会出问题。
6.3
正常,在类内,成员属性等建议使用this指针
这个习惯。
二、泛型编程
主要针对C++泛型编程
和STL
技术做详细讲解,探讨C++更深层的使用
1. 模板
1.1 模板的概念
模板就是建立通用
的模具,大大提高复用性
例如生活中的模板
—寸照片模板:
模板的特点:
1.
模板的通用性很强,但是不可以直接使用,只是一个框架
。2.
也并非万能,比如证件照只能针对证件照,做不了别的风格照片。
1.2 函数模板
1.
C++另一种编程思想称为泛型编程,主要利用的技术就是模板2.
C++提供两种模板机制:函数模板
和类模板
1.2.1 函数模板语法
函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定
,用一个虚拟
的类型来代表。语法:
template<typename T> 函数申明或定义
解释:
template :声明创建模板
typename :表面其后面的符号是一种数据类型,可以用class代替
T:通用的数据类型,名称可以替换,通常为大写字母
#include<iostream> using namespace std; // 交换整型数据 void swapInt(int& a, int& b) { int temp = a; a = b; b = temp; } // 交换浮点型数据 void swapDouble(double& a, double& b) { double temp = a; a = b; b = temp; } // 利用模板提供通用的交换函数 template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型 void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } void test01() { int a = 10; int b = 20; swapInt(a, b); // 利用模板实现交换 // 1.自动类型推导 mySwap(a, b); // 2.显示指定类型 mySwap<int>(a, b); double c = 10.2; double d = 20.2; swapDouble(c, d); } int main() { system("pause"); return 0; }
总结:
1.
函数模板利用关键字template2.
使用函数模板有两种方式:自动类型推导、显示指定类型3.
模板的目的是为了提高复用性,将数据类型参数化
1.2.2 函数模板注意事项
注意事项:
1.
自动类型推导,必须推导出一致的数据类型T,才可以使用2.
模板必须要确定出T的数据类型,才可以使用
#include<iostream> using namespace std; // 函数模板注意事项: template<class T> // typename可以替换成class void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } // 1、自动类型推导,必须推导出一致的数据类型T才可以使用 void test01() { int a = 10; int b = 20; char c = 'c'; mySwap(a, b); // 正确 ///mySwap(a, c); // 错误, 推导不出一致的T类型 } // 2、模板必须要确定出T的数据类型,才可以使用 template<class T> void func() { cout << "func()的调用" << endl; } void test02() { //func(); // 错误 func<int>(); // 正确,因为编译器自动推导不出类型,只能进行显示指定类型 } int main() { system("pause"); return 0; }
总结:
1.
使用模板时必须确定出通用数据类型T,并且能够推导出一致的类型。
1.2.3 函数模板案例
案例描述:
1.利用函数模板封装—个排序的函数,可以对不同数据类型数组进行排序
2.排序规则从大到小,排序算法为选择排序
3.分别利用char数组和int数组进行测试
#include<iostream> using namespace std; template<typename T> void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } // 排序算法 template<typename T> void mySort(T arr[], int len) { for (int i = 0; i < len; i++) { int max = i; // 认定最大值的下标 for (int j = i + 1; j < len; j++) { // 认定的最大值比遍历出来的小 if (arr[max] < arr[j]) { max = j; //更新最大值下标 } } if (max != i) { // 交换max和i元素 mySwap(arr[max], arr[i]); } } } // 提供打印模板 template<typename T> void printArray(T arr[], int len) { for (int i = 0; i < len; i++) { cout << arr[i] << " "; } cout << endl; } void test01() { // 测试char数组 char charArr[] = "badcfe"; mySort(charArr, sizeof(charArr) / sizeof(charArr[0])); printArray(charArr, sizeof(charArr) / sizeof(charArr[0])); } void test02() { // 测试int数组 int IntArr[] = { 17,1, 2,5,7,10 }; mySort(IntArr, sizeof(IntArr) / sizeof(IntArr[0])); printArray(IntArr, sizeof(IntArr) / sizeof(IntArr[0])); } int main() { test01(); test02(); system("pause"); return 0; }
1.2.4 普通函数和函数模板的区别
普通函数与函数模板区别:
1.
普通函数调用时可以发生自动类型转换(隐式类型转换)2.
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换3.
如果利用显示指定类型的方式,可以发生隐式类型转换
#include<iostream> using namespace std; // 普通函数与函数模板区别 // 1、普通函数调用可以发生隐式类型转换 // 2、函数模板用自动类型推导,不可以发生隐式类型转换 // 3、函数模板用显示指定类型,可以发生隐式类型转换 // 普通函数 int myAdd01(int a, int b) { return a + b; } // 函数模板 template<typename T> T myAdd02(T a, T b) { return a + b; } void test01() { int a = 10; int b = 20; char c = 'c'; //asicc码,a-97 cout << myAdd01(a, b) << endl; cout << myAdd01(a, c) << endl; // 10 + 99 // 自动类型推导, 不会发生隐式类型转换 cout << myAdd02(a, b) << endl; //cout << myAdd02(a, c) << endl; //显示指定类型, 会发生隐式类型转换 cout << myAdd02<int>(a, c) << endl; } int main() { test01(); system("pause"); return 0; }
总结:
建议使用显示指定类型
的方式,调用函数模板,因为可以自己确定通用类型T
1.2.5 普通函数与函数模板的调用规则
调用规则如下:
1.如果函数模板和普通函数都可以实现,优先调用普通
函数
2.可以通过空模板参数列表来强制调用函数模板
3.函数模板也可以发生重载
4.如果函数模板可以产生更好的匹配,优先调用函数模板
#include<iostream> using namespace std; // 普通函数与函数模板调用规则 //void myPrint(int a, int b); void myPrint(int a, int b) { cout << "普通函数的调用" << endl; } template<typename T> void myPrint(T a, T b) { cout << "模板的调用" << endl; } template<typename T> void myPrint(T a, T b, T c) { cout << "模板重载的调用" << endl; } void test01() { int a = 10; int b = 20; //myPrint(a, b); // 如果myPrint()函数只留下申明,没有实现会报错 // 通过空模板参数列表,强制调用函数模板 // 哪怕是myPrint()函数有了实现,空模板参数列表也会强制调用函数模板 myPrint<>(a, b); myPrint<>(a, b, 100); // 如果函数模板产生更好的匹配,优先调用函数模板 char c1 = 'a'; char c2 = 'b'; myPrint(c1, c2); } int main() { test01(); system("pause"); return 0; }
总结:
既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。
1.2.6 模板的局限性
局限性:1.
模板的通用性并不是万能的
例如:
template<typename T> void f(T a, T b) { a = b; }
在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了。
再例如:
template<typename T> void f(T a, T b) { if (a > b) { // } }
在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行。
因此,C++为了解决这种问题,提供模板的重载
,可以为这些特定的类型
提供具体化的模板
。
#include<iostream> using namespace std; class Person { public: Person(int age, string name) { this->m_Age = age; this->m_Name = name; } int m_Age; string m_Name; }; template<typename T> bool Compare(T& a, T& b) { if (a == b) { return true; } else { return false; } } // 利用具体化的Person的版本代码,具体化优先调用 template<> bool Compare(Person& p1, Person& p2) { if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name) { return true; } else { return false; } } void test01() { Person p1(10, "Tom"); Person p2(10, "Tom"); bool ret = Compare(p1, p2); } int main() { test01(); system("pause"); return 0; }
总结:
1.
利用具体化的模板,可以解决自定义类型的通用化2.
学习模板并不是为了写模板,而是在STL
能够运用系统提供的模板
1.3 类模板
1.3.1 类模板语法
类模板作用:
建立一个通用类
,类中的成员数据类型可以不具体制定,用一个虚拟
的类型
来代表。语法:
template<typename T> 类
解释:
template:声明创建模板
typename:表面其后面的符号是一种数据类型,可以用class代替
T:通用的数据类型,名称可以替换,通常为大写字母
#include<iostream> using namespace std; #include<string> template<class NameType, class AgeType = int> class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; void test01() { Person<string, int> p("张三", 19); p.showPerson(); } int main() { test01(); system("pause"); return 0; }
总结:
类模板和函数模坂语法相似,在声明模板template后面加类,此类称为类模板
1.3.2 类模板和普通模板区别
类模板与函数模板区别主要有两点:1.
类模板没有自动类型推导的使用方式2.
类模板在模板参数列表中可以有默认参数
#include<iostream> using namespace std; #include<string> // 类模板和普通模板区别 template<class NameType, class AgeType = int> class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; // 1.类模板没有自动类型推导的使用方式 void test01() { //Person p("张三", 19); // 错误,无法用自动类型推导 Person<string, int> p("张三", 19); // 正确,只能用显示指定类型 p.showPerson(); } // 2.类模板在模板参数列表中可以有默认参数 void test02() { Person<string> p2("张三", 19); p2.showPerson(); } int main() { //test01(); test02(); system("pause"); return 0; }
总结:
1.
类模板使用只能用显示指定类型方式2.
类模板中的模板参数列表可以有默认参数
1.3.3 类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:1.
普通类中的成员函数一开始就可以创建2.
类模板中的成员函数在调用时才创建
#include<iostream> using namespace std; #include<string> // 类模板中成员函数创建时机 // 类模板中成员函数在调用时才去创建 class Person1 { public: void showPerson1() { cout << "Person1 show" << endl; } }; class Person2 { public: void showPerson2() { cout << "Person2 show" << endl; } }; template<class T> // 类模板中成员函数在调用时才去创建,是因为创建时编译器压根不知道T这个数据类型 class MyClass { public: T obj; // 类模板中成员函数 void func1() { obj.showPerson1(); } void func2() { obj.showPerson2(); } }; void test01() { MyClass<Person1> m1; m1.func1(); MyClass<Person2> m2; m2.func2(); } int main() { test01(); system("pause"); return 0; }
总结:
类模板中的成员函数并不是—开始就创建的,在调用时才去创建
1.3.4 类模板对象做参数
学习目标:
类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:1.
指定传入的类型:直接显示对象的数据类型2.
参数模板化:将对象中的参数变为模板进行传递3.
整个类模板化:将这个对象类型模板化进行传递
#include<iostream> using namespace std; #include<string> // 类模板对象做函数参数 template<class NameType, class AgeType = int> class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; // 1、指定传入类型 void printPerson1(Person<string, int>& p) { p.showPerson(); } void test01() { Person<string, int> p("张三", 19); printPerson1(p); } // 2、参数模板化 template<class T1, class T2> void printPerson2(Person<T1, T2>& p) { p.showPerson(); cout << "T1 的类型为:" << typeid(T1).name() << endl; cout << "T2 的类型为:" << typeid(T2).name() << endl; } void test02() { Person<string, int> p("李四", 29); printPerson2(p); } // 3、整个类模板化 template<class T> void printPerson3(T& p) { p.showPerson(); cout << "T 的类型为:" << typeid(T).name() << endl; } void test03() { Person<string, int> p("王五", 39); printPerson3(p); } int main() { //test01(); //test02(); test03(); system("pause"); return 0; }
总结:
1.
通过类模板创建的对象,可以有三种方式向函数中进行传参2.
使用比较广泛是第一种:指定传入的类型
1.3.5 类模板与继承
当类模板碰到继承时,需要注意一下几点:1.
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型2.
如果不指定,编译器无法给子类分配内存3.
如果想灵活指定出父类中T的类型,子类也需变为类模板
#include<iostream> using namespace std; #include<string> // 类模板与继承 template<class T> class Base { T m; }; //class Son : public Base // 错误,必须要知道父类中的T类型,才能继承给子类 class Son : public Base<int> { }; void test01() { Son s1; } // 如果想灵活指定父类中T类型,子类也需要变类模板 template<class T1, class T2> class Son2 : public Base<T2> { public: Son2() { cout << "T1 的类型:" << typeid(T1).name() << endl; cout << "T2 的类型:" << typeid(T2).name() << endl; } T1 obj; }; void test02() { Son2<int, char> s2; } int main() { //test01(); test02(); system("pause"); return 0; }
总结:
如果父类是类模板,子类需要指定出父类中T的数据类型
1.3.6 类模板成员函数类外实现
学习目标:能够掌握类模板中的成员函数类外实现
#include<iostream> using namespace std; #include<string> template<class T1, class T2> class Person { public: Person(T1 name, T2 age); void showPerson(); T1 m_Name; T2 m_Age; }; // 构造函数类外实现 template<class T1, class T2> Person<T1, T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } // 成员函数类外实现 template<class T1, class T2> void Person<T1, T2>::showPerson() { cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl; } void test01() { Person<string, int> p1("张三", 19); p1.showPerson(); } int main() { test01(); system("pause"); return 0; }
总结:
类模板中成员函数类外实现时,需要加上模板参数列表
1.3.7 类模板分文件编写
学习目标:
1.掌握类模板成员函数分文件编写产生的问题以及解决方式问题:
1.类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到解决:
1.解决方式1:直接包含.cpp源文件
2.解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp
,hpp是约定的名称,并不是强制示例:
首先,新建person.h头文件,
#pragma once #include<iostream> using namespace std; #include<string> template<class T1, class T2> class Person { public: Person(T1 name, T2 age); void showPerson(); T1 m_Name; T2 m_Age; };
接着,新建person.cpp源文件,
#include"person.h" // 构造函数类外实现 template<class T1, class T2> Person<T1, T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } // 成员函数类外实现 template<class T1, class T2> void Person<T1, T2>::showPerson() { cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl; }
上述代码看似没问题,会报错的。因为,类模板中成员函数一开始是不会创建,类模板对象调用时创建,所以包含"person.h"头文件时,编译器没有见到过T这些数据类型,所以链接不到。解决方法1:
main函数中更改为:
直接包含源文件
#include<iostream> using namespace std; #include<string> #include"person.cpp" void test01() { Person<string, int> p1("张三", 19); p1.showPerson(); } int main() { test01(); system("pause"); return 0; }
解决方法2:
将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件
#pragma once #include<iostream> using namespace std; #include<string> template<class T1, class T2> class Person { public: Person(T1 name, T2 age); void showPerson(); T1 m_Name; T2 m_Age; }; // 构造函数类外实现 template<class T1, class T2> Person<T1, T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } // 成员函数类外实现 template<class T1, class T2> void Person<T1, T2>::showPerson() { cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl; }
总结:
主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp
1.3.8 类模板与友元
学习目标:
1.
掌握类模板配合友元函数的类内和类外实现
全局函数类内实现:直接在类内声明友元即可
全局函数类外实现:需要提前让编译器知道全局函数的存在
#include<iostream> using namespace std; #include<string> // 通过全局函数打印Person信息 // 提前让编译器知道Person类存在 template<class T1, class T2> class Person; // 类外实现 template<class T1, class T2> void printPerson2(Person<T1, T2> p) { cout << "类外实现--姓名:" << p.m_Name << "年龄:" << p.m_Age << endl; } template<class T1, class T2> class Person { public: // 全局函数类内实现 friend void printPerson(Person<T1, T2>& p) { cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl; } // 全局函数类外实现 // 需要加1个空模板参数列表<>,因为不加这就是一个普通函数申明,而下方类外实现是函数模板的实现 // 如果全局函数是类外实现,需要让编译器提前知道这个函数的存在 friend void printPerson2<>(Person<T1, T2>p); Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } private: T1 m_Name; T2 m_Age; }; // 全局函数在类内实现的测试 void test01() { Person<string, int> p1("张三", 19); printPerson(p1); } // 全局函数在类外实现的测试 void test02() { Person<string, int> p2("李四", 29); printPerson2(p2); } int main() { test01(); system("pause"); return 0; }
总结:
建议全局函数做类内实现,用法简单,而且编译器可以直接识别