当前位置: 首页 > news >正文

C++复习的长文指南(二)

C++复习的长文指南(二)

  • 一、面向对象基础知识
    • 5. 文件操作
      • 5.1文本文件
        • 5.1.1写文件
        • 5.1.2读文件
      • 5.2 二进制文件
        • 5.2.1 二进制文件
        • 5.2.2 二进制读文件
    • 6. c++面向对象的个人心得
      • 开发流程
      • 6.1
      • 6.2
      • 6.3
      • 6.4
      • 6.5
      • 注意细节
      • 6.1
      • 6.2
      • 6.3
  • 二、泛型编程
    • 1. 模板
      • 1.1 模板的概念
      • 1.2 函数模板
        • 1.2.1 函数模板语法
        • 1.2.2 函数模板注意事项
        • 1.2.3 函数模板案例
        • 1.2.4 普通函数和函数模板的区别
        • 1.2.5 普通函数与函数模板的调用规则
        • 1.2.6 模板的局限性
      • 1.3 类模板
        • 1.3.1 类模板语法
        • 1.3.2 类模板和普通模板区别
        • 1.3.3 类模板中成员函数创建时机
        • 1.3.4 类模板对象做参数
        • 1.3.5 类模板与继承
        • 1.3.6 类模板成员函数类外实现
        • 1.3.7 类模板分文件编写
        • 1.3.8 类模板与友元

一、面向对象基础知识

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.函数模板利用关键字template
2.使用函数模板有两种方式:自动类型推导、显示指定类型
3.模板的目的是为了提高复用性,将数据类型参数化

1.2.2 函数模板注意事项

注意事项:
1.自动类型推导,必须推导出一致的数据类型T,才可以使用
2.模板必须要确定出T的数据类型,才可以使用

#include<iostream>
using namespace std;// 函数模板注意事项:
template<class T> // typename可以替换成classvoid 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-97cout << 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;
}

总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 智慧矿山 | 选矿工艺可视化
  • 论文中的流程图参考图片
  • ADI - 通过5 V至24 V输入提供双极性、双向DC-DC流入和流出电流
  • JavaScript之typeof运算符
  • [PM]面试题-综合问题
  • 什么情况?我代码没了
  • 《基于FPGA的数字信号处理》专栏的导航与说明
  • MySQL update set语句中 逗号与and的区别
  • 配置第三方软件仓库
  • 24暑假2C
  • 深度学习:Head、Neck和Backbone的含义与作用
  • 云计算 Logstash 配置管理
  • word预览方式---插件,vue-office-docx、docx-preview、mammoth.js
  • Redis和Mysql如何保持数据一致性
  • python opencv 绘制多边形 闭合
  • 2017 前端面试准备 - 收藏集 - 掘金
  • CSS中外联样式表代表的含义
  • laravel with 查询列表限制条数
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • spring + angular 实现导出excel
  • SpringBoot 实战 (三) | 配置文件详解
  • SQLServer之创建数据库快照
  • 讲清楚之javascript作用域
  • 精彩代码 vue.js
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 面试遇到的一些题
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 为什么要用IPython/Jupyter?
  • 我这样减少了26.5M Java内存!
  • 详解移动APP与web APP的区别
  • nb
  • ​520就是要宠粉,你的心头书我买单
  • ​什么是bug?bug的源头在哪里?
  • ​水经微图Web1.5.0版即将上线
  • # 移动硬盘误操作制作为启动盘数据恢复问题
  • #if #elif #endif
  • (多级缓存)缓存同步
  • (二)Kafka离线安装 - Zookeeper下载及安装
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (附源码)计算机毕业设计大学生兼职系统
  • (面试必看!)锁策略
  • (一)Kafka 安全之使用 SASL 进行身份验证 —— JAAS 配置、SASL 配置
  • (转)h264中avc和flv数据的解析
  • (转)一些感悟
  • **python多态
  • .NET Core Web APi类库如何内嵌运行?
  • .NET Micro Framework初体验
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • .NET 中使用 Mutex 进行跨越进程边界的同步
  • .NET8 动态添加定时任务(CRON Expression, Whatever)
  • .Net插件开发开源框架
  • .NET的数据绑定
  • .Net接口调试与案例
  • @JSONField或@JsonProperty注解使用