C++ 复习
VS 修改 C++ 语言标准
右键项目-属性
输入输出
//引用头文件,用<>包裹起来的一般是系统提供的写好的代码 编译器会在专门的系统路径中去进行查找
#include <iostream>
//自己写的代码文件一般都用""包裹起来 编译器会在当前文件所在的目录中査找,找不到才会去系统路径重查找
//#include "xxx"int main()
{// std:: 命名空间// cout 该命名空间下的一个对象// << 是插入运算符,将字符串插入到 std::cout 对象中,以便输出到控制台std::cout << "Hello World!\n";//换行std::cout << "等待输入" << std::endl;//声明一装载输入内容的容器std::string input;//等待玩家输入,必须输入一些内容后回车才认为结束std::cin >> input;//主函数可以省略//return 0;
}
命名空间
#include <iostream>
//引用命名空间
using namespace std;int main()
{cout << "Hello World" << endl;
}
变量
#include <iostream>
using namespace std;int main()
{
#pragma region MyRegion//折叠代码
#pragma endregion//long 类型范围 -21亿 ~ 21亿 或 -9223亿亿 ~ 9223亿亿//不同操作系统(windows,UNIX,Linux)上所占的空间不同//windows 上 long 和 int 的存储空间一样long l = 10L;cout << "长整型: " << l << endl;cout << sizeof(int) << " " << sizeof(long) << endl;//无符号unsigned long ul = 20UL;cout << ul << endl;//范围 -9223亿亿 ~ 9223亿亿long long ll = 200000000000LL;cout << ll << endl;//6~7位有效数字float f = 1.5f;cout << f << endl;//15~16位有效数字double d = 4.6;cout << d << endl;//有效数字 18~21,或 33 ~ 36,不同操作系统不同long double ld = 1.8L;cout << ld << endl;//R"(内容)",包裹不想被转义的字符串string str = R"(""\""\n)";cout << str << endl;char c = 'A';//两个 char 相加,会被转为 int 进行加法运算,要避免这种写法str = c + "Q";cout << str << endl;
}
字符串操作
#include <iostream>
#include <string>using namespace std;int main()
{//其他类型转字符串string str = to_string(123);str.append("456");cout << str << endl;//字符串转其他类型string str2 = "234";int i = stoi(str2);cout << i << endl;long l = stol(str2);cout << l << endl;unsigned long ul = stoul(str2);cout << ul << endl;
}
异常捕获
#include <iostream>
#include <string>using namespace std;int main()
{try {int i2 = stoi("测试");}catch (const exception& e){cout << e.what() << endl;}
}
数组
一维数组
#include <iostream>
using namespace std;int main()
{//3种数组定义方式int arr[5];arr[0] = 1;//赋值之前可能是任意数cout << arr[0] << endl;int arr2[5] = { 0, 1, 2, 3, 4 };cout << arr2[2] << endl;int arr3[] = { 0, 1, 2, 3, 4 };cout << arr3[3] << endl;//获取数组元素个数int count = sizeof(arr) / sizeof(arr[0]);cout << count << endl;//数组首地址,16进制cout << arr << endl;//转为10进制cout << (int)arr << endl;//数组第一个元素地址cout << &arr[0] << endl;//数组名是常量,不可以进行赋值操作//arr = 100;
}
二维数组
#include <iostream>
using namespace std;int main()
{//4种定义二位数组的方式//数组名[行数][列数]int arr[2][3];arr[0][0] = 1;cout << arr[0][0] << endl;int arr2[2][3] = { {1,2,3}, {4,5,6} };cout << arr2[0][1] << endl;int arr3[2][3] = { 1,2,3,4,5,6 };cout << arr2[0][2] << endl;int arr4[][3] = { 1,2,3,4,5,6 };cout << arr4[1][0] << endl;
}
函数
函数声明
#include <iostream>
using namespace std;//函数声明,告诉编译器有这么个函数,写在 main 函数之后的函数需要声明
int Max(int a, int b);int main()
{int c = Max(1, 2);cout << c << endl;
}int Max(int a, int b)
{return a > b ? a : b;
}
函数分文件编写
创建后缀名为 .h 的头文件,在头文件中写函数的声明
创建后缀名为 .cpp 的源文件,在源文件中写函数的定义
创建 Max.h 头文件,头文件里尽量少 include 其他头文件,不要使用 using namespace,防止代代相传
int Max(int a, int b);
创建 Max.cpp 源文件
#include "Max.h"int Max(int a, int b)
{return a > b ? a : b;
}
其他文件中调用
#include <iostream>
#include "Max.h"using namespace std;int main()
{int c = Max(1, 2);cout << c << endl;
}
指针
#include <iostream>
using namespace std;int main()
{int a = 10;//取地址int * p = &a;cout << p << endl;//解引用,找到指针指向内存中的数据cout << *p << endl;*p = 20;cout << a << " " << *p << endl;//32位下4字节,64位下8字节,不管什么类型的指针cout << sizeof(p) << endl;
}
空指针
#include <iostream>
using namespace std;void Fun(int * p)
{cout << "指针" << endl;
}void Fun(int p)
{cout << "整数" << endl;
}int main()
{//空指针用于给变量初始化int * p = NULL;//空指针不能访问//*p = 10;//输出为整数,违反直觉Fun(NULL);
}
查看 NULL 的定义,void * 是无类型指针或通用指针。它可以指向任何类型的数据,但在解引用之前需要将其转换为具体的类型。
当 NULL 被用作指针时,它可能会被隐式转换为整数类型,这可能导致难以发现的错误。
C++11 引入了 nullptr 关键字,专门用来区分空指针和 0,替代 NULL
int * p = nullptr;
野指针
指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针
出现野指针的常见操作:
- 指针声明之后没有初始化
- 变量内存释放后,指针变量还保存着该变量的内存地址
常量指针,指针常量
常量指针:const 在 * 之前,指向的值不能修改(不能通过解引用修改值),但指向可以改变。可以理解为常量的指针,指向常量的指针,const 限定了 * ,那么 * 操作不能用
int a = 1;
int b = 2;
const int * p = &a;
p = &b; //指向可以修改
*p = 3; //错误,指向值不能修改
b = 4; //可以修改原始变量值
指针常量:const 在 * 之后,指向的值可以修改,但指向不能改变。可以理解为指针类型的常量,const 限定了指针,那么指针不能操作
int a = 1;
int b = 2;
int * const p = &a;
p = &b; //错误,指向不能修改
*p = 3; //指向的值可以修改
const 修饰指针和常量,指向和值都不能修改
int a = 1;
const int* const p = &a;
指针访问数组
int arr[5] = { 1,2,3,4,5 };
//数组名是首地址
int * p = arr;
cout << "第一个元素:" << *p << endl;for (int i = 0; i < 5; i++)
{cout << *p << endl;p++; //向后偏移一个类型长度
}
结构体
定义
struct Student
{string name;int age;
}s3; //在定义结构体的时候创建变量 s3//函数参数改为指针,实现引用传递,避免值传递复制新的副本,减少内存空间
//常量指针,防止修改值
void PrintStu(const Student* s)
{s->age = 20; //错误,不能修改cout << s->name << endl;
}int main()
{Student s1;s1.name = "张三";s1.age = 10;Student s2 = { "李四", 20 };s3.name = "王五";Student s4[2] ={{"张三", 10},{"李四", 20},};//指针操作Student * p = &s2;cout << p->name << p->age << endl;PrintStu(&s3);
}
内存四区
代码区 | 存放函数体的二进制代码,特点是共享和只读 |
---|---|
全局区 | 存放全局变量,静态变量以及常量(字符串常量和 const 修饰的全局常量),程序结束后由操作系统释放 |
栈区 | 存放局部变量,函数参数,返回地址,由编译器自动分配和释放 |
堆区 | 由程序员分配和释放,如不释放,程序结束后由操作系统回收 |
四区的意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
new 运算符
//堆上创建整型数据,new 返回该数据类型的指针
int * p = new int(10);
//释放堆中的数据
delete p;int * p2 = new int[10];
//释放数组要加[]
delete[] p2;
引用
作用: 给变量起别名
int a = 10;
//语法: 数据类型 &别名 = 原名
//引用必须初始化,且初始化后不能更改
int &b = a;cout << a << " " << b << endl; //10 10
b = 100;
cout << a << " " << b << endl; //100 100int c = 20;
&b = c; //错误,不能更改
引用做函数参数,和地址传递效果一样,语法更简单
//值传递,形参不会修饰实参
void Swap01(int a, int b)
{int temp = a;a = b;b = temp;
}//地址传递,形参会修饰实参
void Swap02(int * a, int * b)
{int temp = *a;*a = *b;*b = temp;
}//引用传递,形参会修饰实参,这里的参数是原变量的别名
void Swap03(int &a, int &b)
{int temp = a;a = b;b = temp;
}
引用作为函数返回值
//不要返回局部变量的引用,栈上会自动释放
int& Test1()
{int a = 10;return a;
}int& Test2()
{static int a = 10; //静态变量,在全局区return a;
}int main()
{int& ref = Test2();cout << ref << endl; //10//函数调用可以作为左值Test2() = 20;cout << ref << endl; //20
}
引用的本质是 C++ 内部实现的一个指针常量
int a = 10;
//自动转换为 int * const ref = 10
int & ref = a;
//自动转换为 *ref = 20
ref = 20;
常量引用
void Test(const int & val)
{val = 20; //错误,防止修改
}int main()
{//编译器转换为 int temp = 10; const int & ref = 10;const int & ref = 10;ref = 20; //错误,加 const 之后变成只读
}
类和对象
C++ 面向对象三大特征:封装,继承,多态
封装:把属性和行为作为一个整体,通过 public,protect,private 对属性和行为加以权限控制。
struct 和 class 的区别
唯一区别就是默认的访问权限不一样,struct 默认权限为 public,class 默认权限为 private
构造函数,拷贝构造,析构函数
默认情况下,编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符operator=,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,编译器不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,编译器不会再提供其他构造函数
class Person
{
public://无参构造Person() {cout << "默认构造" << endl;}//有参构造Person(int a) {age = a;cout << "有参构造" << endl;}//拷贝构造函数,用于复制对象,const阻止修改原对象Person(const Person& p){//拷贝所有属性age = p.age;cout << "拷贝构造" << endl;}//析构函数,不能有参数,因此不能重载~Person(){cout << "析构" << endl;}int age;
};//值传递,会调用拷贝构造,实参传递给形参
void Test1(Person p) {}//RVO(Return Value Optimization)返回值优化,是编译器对函数返回对象时的拷贝操作的优化
//值方式返回局部对象,RVO关闭会拷贝一个新的对象返回,调用拷贝构造,RVO开启则不会
Person Test2()
{Person p1;return p1;
}int main()
{//调用方式//1.括号法Person p1; //调用默认无参构造Person p2(10); //调用有参构造Person p3(p2); //调用拷贝构造,使用已经创建的对象来初始化新对象//2.显示法Person p4 = Person(10); //有参构造Person p5 = Person(p4); //拷贝构造//3.隐式转换法Person p6 = 10; //相当于 Person p6 = Person(10);Person p7 = p6; //拷贝构造Test1(p1); //拷贝构造Person p8 = Test2(); //可能触发拷贝构造,看编辑器是否优化
}
深拷贝,浅拷贝
浅拷贝:简单的赋值拷贝操作。问题:可能导致堆区内存重复释放
深拷贝:在堆区重新申请空间,进行拷贝操作,如果有属性在堆区开辟内存,一定要自己提供拷贝构造函数
class Person
{
public:Person(int age){//在堆上创建内存pAge = new int(age);}~Person(){if (pAge != nullptr) {delete pAge;pAge = nullptr;}cout << "析构" << endl;}int * pAge;
};int main()
{Person p1(10);Person p2(p1); //拷贝构造//p1 p2的 pAge 指向同一个内存地址,p2析构释放内存,p1析构再释放就会报错
}
初始化列表
class Person
{
public://传统初始化操作//Person(int a, int b, int c)//{// m_a = a;// m_b = b;// m_c = c;//}//初始化列表,初始化属性Person(int a, int b, int c) : m_a(a), m_b(b), m_c(c){//和上面构造函数效果一样}int m_a;int m_b;int m_c;
};
类对象成员构造,析构顺序
class A{};class B
{A a;
};
创建 B 对象时,构造顺序:先 A 后 B,销毁 B 对象时,析构顺序:先 B 后 A
静态成员变量,静态成员函数
class Person
{
public://静态函数只能访问静态变量,static void func(){m_a = 30;//m_b = 10; //错误,不能确定m_b属于哪个对象,因此不能访问cout << "静态函数" << endl;}//静态成员变量,所有对象共享一份数据//编译阶段分配内存,类内声明static int m_a;int m_b;
};//类外初始化
int Person::m_a = 10;int main()
{Person p1;p1.m_a = 20;cout << "通过对象访问" << p1.m_a << endl;cout << "通过类名访问" << Person::m_a << endl;//通过对象调用p1.func();//通过类名调用Person::func();
}
成员变量和成员函数分开存储
class Person{};class Animal
{
public://非静态成员变量占用对象内存空间,其他的都不占用int age;
};int main()
{Person p;Animal a;//编辑器会给空对象分配一个字节空间,是为了区分空对象占内存的位置cout << sizeof(p) << endl; //1cout << sizeof(a) << endl; //4
}
this 指针
this 指针的本质是指针常量,指向不能修改
class Person
{
public:Person(int age){//this指针是隐含在每一个非静态成员函数内的指针,指向调用这个函数所属的对象this->age = age;}Person& AddAge(Person & p){this->age += p.age;//this是指向调用对象的指针,*this就是对象本体return *this;}int age;
};int main()
{Person p1(10);Person p2(10);//链式编程思想p2.AddAge(p1).AddAge(p1).AddAge(p1);cout << p2.age << endl; //40
}
常函数,常对象
class Person
{
public://常函数,const 修饰的是 this 指向,让 this 指向的值也不能修改void Test() const{//m_a = 10; //错误,常函数中不能修改普通成员变量m_b = 20;}void Test2(){}int m_a;mutable int m_b; //mutable 是可变的,常函数中也能修改
};int main()
{//常对象,不能修改成员变量const Person p;//p.m_a = 10; //错误,不能修改p.Test(); //常对象只能调用常函数//p.Test2(); //错误,不能调用普通成员函数,因为普通成员函数可以修改属性
}
友元
友元的目的是让一个函数或类访问另一个类中的私有成员
1.全局函数做友元
class Person
{//申明这个全局函数可以访问私有成员friend void GlobeFun(Person& p);
private:int m_a;
};//全局函数
void GlobeFun(Person& p)
{p.m_a = 10;
}
2.类做友元
class Person
{//申明这个类可以方法私有成员friend class FriendClass;
private:int m_a;
};class FriendClass
{
public:void Visit(){p = new Person();p->m_a = 10;}Person* p;
};
3.成员函数做友元
注意声明类和方法的顺序,否则访问不到私有成员
//前向声明依赖的类
class Person;class FriendClass
{
public://定义方法但不实现void Visit(Person& p);Person* p;
};class Person
{//申明这个类的成员函数可以方法私有成员friend void FriendClass::Visit(Person& p);private:int m_a;
};// 实现友元方法
void FriendClass::Visit(Person& p)
{p.m_a = 10;
}
运算符重载
加法重载
class Person
{
public:int m_a;//1.成员函数重载//Person operator+(Person& p)//{// Person temp;// temp.m_a = this->m_a + p.m_a;// return temp;//}
};//2.全局函数重载
Person operator+(Person& p1, Person& p2)
{Person temp;temp.m_a = p1.m_a + p2.m_a;return temp;
}int main()
{Person p1;Person p2;Person P3 = p1 + p2;
}
左移运算符重载,可以配合友元使用
class Person
{friend ostream& operator<<(ostream& cout, Person& p);public:Person(int a){m_a = a;}private:int m_a;
};//只能利用全局函数重载左移运算符
ostream& operator<<(ostream& cout, Person& p)
{cout << "m_a: " << p.m_a << endl;return cout;
}int main()
{Person p(10);cout << p << endl;
}
递增运算符重载
class MyInteger
{//前置++,返回引用,为了一直对一个数据操作MyInteger& operator++(){m_Num++;return *this;}//后置++,返回临时值,int 是个占位参数,用于区分前置和后置++MyInteger operator++(int){//先记录当前值返回,然后加1MyInteger temp = *this;m_Num++;return temp;}private:int m_Num;
};
赋值运算符重载
class Person
{
public:Person(int a){m_a = new int(a);}//重载赋值运算符Person& operator=(Person& p){//有属性在堆区,先释放,再深拷贝if (m_a != nullptr){delete m_a;m_a = nullptr;}m_a = new int(*p.m_a);return *this;}int* m_a;
};int main()
{Person p1(10);Person p2(20);Person p3(30);p3 = p2 = p1;cout << *p1.m_a << endl; //10cout << *p2.m_a << endl; //10cout << *p3.m_a << endl; //10
}
关系运算符重载(>, <, >=. <=, ==)
class Person
{
public:Person(string name, int age){m_Name = name;m_Age = age;}//重载关系运算符bool operator==(Person& p){return this->m_Name == p.m_Name && this->m_Age == p.m_Age;}string m_Name;int m_Age;
};int main()
{Person p1("Tom", 10);Person p2("Tom", 10);if (p1 == p2) {cout << "相等" << endl;}else{cout << "不相等" << endl;}
}
函数调用运算符()重载
由于重载后的使用方式非常像函数调用,因此也成为仿函数
class MyPrint
{
public://重载函数调用运算符void operator()(string test){cout << test << endl;}
};int main()
{MyPrint m;m("hello");
}
继承
减少重复代码
class Base
{
public:int m_A;
protected:int m_B;
private:int m_C; //私有成员也会继承下去
};class Son : public Base
{
public:int m_D;
};int main()
{//父类所有的非静态成员属性都会被子类继承,private 成员访问不到也会继承cout << sizeof(Son) << endl; //16
}
继承中的构造和析构顺序
构造顺序:先父类后子类
析构顺序:先子类后父类
多继承
实际开发中不建议使用
class Base1
{
public:Base1(){m_A = 20;}int m_A;
};class Base2
{
public:Base2(){m_A = 30;}int m_A;
};class Son : public Base1, public Base2
{
public:Son(){m_B = 10;}int m_B;
};int main()
{Son s;cout << sizeof(Son) << endl; //12//父类中出现同名成员,加作用域区分cout << s.Base1::m_A << endl; //20cout << s.Base2::m_A << endl; //30
}
菱形继承
class Animal
{
public:int m_A;
};//加 virtual 变成虚继承,公共父类为虚基类
//解决菱形继承导致的数据重复问题
class Sheep : virtual public Animal{};class Tuo : virtual public Animal{};//类内有个虚基类指针 vbptr, 指向虚基类表 vbtable,通过偏移值找到对应属性
class SheepTuo : public Sheep, public Tuo {};int main()
{SheepTuo st;st.m_A = 10;cout << st.m_A << endl; //10cout << st.Sheep::m_A << endl; //10cout << st.Tuo::m_A << endl; //10
}
多态
静态多态:函数重载,运算符重载。编译阶段确定函数地址
动态多态:子类覆写虚函数。运行时确定函数地址
class Animal
{
public://虚函数virtual void Speak(){cout << "动物说话" << endl;}
};class Cat : public Animal
{
public://重写虚函数void Speak(){cout << "猫说话" << endl;}
};void DoSpeak(Animal& animal)
{animal.Speak();
}int main()
{Cat c;DoSpeak(c);cout << sizeof(Cat) << endl; //8,64位机器指针占8字节
}
原理:类内有虚函数指针 vfptr,指向虚函数表 vftable,表内记录虚函数地址
纯虚函数
//有纯虚函数就是抽象类,无法实例化对象
class Animal
{
public://纯虚函数,子类必须重写纯虚函数,否则也属于抽象类virtual void Speak() = 0;
};
虚析构,纯虚析构
class Animal
{
public:Animal(){cout << "Animal 构造" << endl;}//虚析构,父类指针释放子类对象时,调用子类的析构函数//virtual ~Animal()//{// cout << "Animal 析构" << endl;//}//纯虚析构,有纯虚函数的类就是抽象类virtual ~Animal() = 0;
};//纯虚析构的实现
Animal::~Animal()
{cout << "Animal 纯虚析构" << endl;
}class Cat : public Animal
{
public:Cat(string name){m_Name = new string(name);cout << *m_Name << "Cat 构造" << endl;}~Cat(){cout << "Cat 析构" << endl;if (m_Name != nullptr){delete m_Name;m_Name = nullptr;}}string * m_Name;
};int main()
{Animal* animal = new Cat("Tom");//Animal 不是虚析构的话,不会调用子类的析构函数delete animal;
}
文件操作
文件操作三大类:
- ofstream 写操作
- ifstream 读操作
- fstream 读写操作
打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios:app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
写文本文件
#include <iostream>
#include<fstream> //头文件
using namespace std;int main()
{//创建流对象ofstream ofs;//指定打开方式ofs.open("test.txt", ios::out);//写内容ofs << "姓名: 张三" << endl;ofs << "年龄: 10" << endl;//关闭ofs.close();
}
读文本文件
#include <string>
#include <iostream>
#include<fstream>
using namespace std;int main()
{//创建流对象ifstream ifs;//指定打开方式ifs.open("test.txt", ios::in);if (!ifs.is_open()){cout << "文件打开失败" << endl;ifs.close();return 0;}//读内容//第1种方式//char buf[1024] = { 0 };//while (ifs >> buf)//{// cout << buf << endl;//}//第2种方式//char buf[1024] = { 0 };//while (ifs.getline(buf, sizeof(buf)))//{// cout << buf << endl;//}//第3种方式,getline 全局函数string buf;while (getline(ifs, buf)){cout << buf << endl;}//第4种方式,不推荐//char c;//while ((c = ifs.get()) != EOF)//{// cout << c;//}//关闭ifs.close();
}
写二进制文件
#include <string>
#include <iostream>
#include<fstream> //头文件
using namespace std;class Person
{
public:char m_Name[64];int m_Age;
};int main()
{//创建流对象fstream fs;//指定打开方式fs.open("person.txt", ios::out | ios::binary);//写内容Person p = { "张三", 20 };fs.write((const char*)&p, sizeof(p));//关闭fs.close();
}
读二进制文件
#include <string>
#include <iostream>
#include<fstream> //头文件
using namespace std;class Person
{
public:char m_Name[64];int m_Age;
};int main()
{//创建流对象ifstream ifs;//指定打开方式ifs.open("person.txt", ios::in | ios::binary);if (!ifs.is_open()){cout << "文件打开失败" << endl;ifs.close();return 0;}//写内容Person p;ifs.read((char*)&p, sizeof(p));cout << p.m_Name << " " << p.m_Age << endl;//关闭ifs.close();
}
模板
函数模板
template<typename T> //typename 可以替换成 class
void MySwap(T& a, T& b)
{T temp = a;a = b;b = temp;
}int main()
{int a = 10;int b = 20;//自动类型推导MySwap(a, b);//显示指定类型MySwap<int>(a, b);
}
普通函数与函数模板区别:
- 普通函数调用时可以发生自动类型转换(隐式类型转换,比如 char 转 int)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换,如果利用显示指定类型的方式,可以发生隐式类型转换
模板局限性:自定义数据类型不一定能跑通
class Person
{
public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age;
};template<typename T>
bool MyCompare(T& a, T& b)
{return a == b;
}//具体化模板,解决自定义类型的通用化
template<> bool MyCompare(Person & p1, Person& p2)
{return p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age;;
}int main()
{Person p1("Tom", 10);Person p2("Tom", 10);if (MyCompare(p1, p2)){cout << "相等" << endl;}else {cout << "不相等" << endl;}
}
类模板
类模板与函数模板区别主要有两点:
- 类模板必须显示指定类型
- 类模板在模板参数列表中可以有默认参数
template<typename NameType, typename AgeType = int>
class Person
{
public:Person(NameType name, AgeType age){this->m_Name = name;this->m_Age = age;}NameType m_Name;AgeType m_Age;
};int main()
{Person<string, int> p1("张三", 10);Person<string> p2("李四", 10); //int可以省略
}
类模板中成员函数和通类中成员函数创建时机是有区别的:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
template<typename T>
class MyClass
{
public://类模板中的成员函数,不是一开始就创建,而是调用时生成的void Show(){obj.Test();}T obj
};class Person
{
public:void Test(){cout << "test" << endl;}
};int main()
{MyClass<Person> m;m.Show();
}
类模板与继承
template<typename T>
class Base
{
public:T m;
};//子类继承需要指定类型,知道类型才能给子类分配内存
class Son1 : public Base<int>{};//如果想要灵活指定父类中的T类型,子类也需要变成类模板
template<typename T1, typename T2>
class Son2 : public Base<T1>
{
public:Son2(){cout << typeid(T1).name() << endl;}T2 obj;
};
类模板分文件编写
如果类模板写在其他文件中,引用其头文件 .h 文件,链接不到模板函数的具体实现
解决办法:
- 直接引用源文件 .cpp,如 #include “person.cpp”,一般不这样做
- 将 .h 和 .cpp 文件写到一起,文件后缀改成 .hpp
创建 person.hpp 文件
#include <string>
#include <iostream>
using namespace std;template<typename T1, typename T2>
class Person
{
public:Person(T1 name, T2 age);T1 m_Name;T2 m_Age;
};template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)
{this->m_Name = name;this->m_Age = age;
}
主文件中引用
#include "person.hpp"int main()
{Person<string, int> p("Jerry", 10);
}