第十一章 模板、类型转化、C++新特性
模板(template)
- 泛型,是一种将类型参数化以达到代码复用的技术,C++中使用模板来实现泛型
- 模板的使用格式如下
template <typename\class T>
- typename和class是等价的
- C++提供两种模板:函数模板,类模板
函数模板
-
代码举例:
/* void swapValues(int &v1, int &v2) { int tem = v1; v1 = v2; v2 = tem; } void swapValues(double &v1, double &v2) { double tem = v1; v1 = v2; v2 = tem; } */ //从上面可看出除了数据的类型之外其他都一样,那么我们可不可以将这个类型抽出来,作为可变参数简化代码呢? //typename也可以是class ,T是TYPE的简称,当然也可以写其他任意字符,比如A等 //这么写,类型也成为可变参数了 //外面只要指定T的类型,函数中所有的T都会成这种类型。 //<>中可以有n个类型,即<typename T,typename T2,typename T3..> template <typename T> void swapValues(T &v1, T &v2) { T tem = v1; v1 = v2; v2 = tem; } int main() { int a = 10; int b = 20; swapValues(a, b); cout << "a=" << a << "b=" << b << endl; double c = 10.3; double d = 20.5; //调用时指定类型<double> //swapValues<double>(c, d); //即使不传<double>,他也可以自动识别类型 swapValues(c, d); cout << "c=" << c << "d=" << d << endl; getchar(); return 0; }
- 泛型的本质:
- 编译器会根据你调用这个泛型时用的实际类型生成相应的函数,比如上面代码,编译器会生成类型为int、double的2个函数。而其他类型的不会生成,因为没有实际使用
- 模板没有被使用时,是不会被实例化出来的
- 模板的声明和实现如果分离到.h和.cpp中,会导致链接错误
- 原因分析如下:
- 如果分开后,编译器会将main.cpp,与swap.cpp(包含模板实现的cpp文件)单独编译
- 但是模板只会在使用的时候编译器才会生成相应的实现
- 上面main.cpp,swap.cpp是2个互不相干的文件分别编译,所以编译器根本不会自动生成swap.cpp中的函数实现。
- 因此链接的时候,找不到相应的模板实现,报错
- 因此:模板不能单独存放到cpp中,可以直接将实现放在.h文件中或者放到.hpp中
- 原因分析如下:
- 一般将模板的声明和实现统一放到一个.hpp文件(C++专门提供存放模板的头文件)中
-
新建一个.hpp文件
//.hpp文件 template <typename T> void swapValues(T &v1, T &v2) { T tem = v1; v1 = v2; v2 = tem; }
-
-
多参数模板
template <class T1,class T2> void display(const T1 &v1,const T2 &v2) { cout << v1 << endl; cout << v2 << endl; } display(20,1.7);
- 函数模板的总结
- 函数模板就是建立一个通用的函数,其函数返回类型和形参类型不具体指定,而是用虚拟的类型来代表。
- 凡是函数体相同的函数都可以用函数模板来代替,不必定义多个函数,只需在模板中定义一次即可。
- 在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
类模板
- 模仿一个数组Aarray
- 动态数组的实现原理:
- 先申请一块足够大的堆空间,用于存放数组元素-容量
- 当不断的动态添加数组元素,直到开始申请的容量不够时
- 重新申请一块更大的堆空间,将之前的堆空间数据迁移到这个新的堆空间
- 释放掉原来的堆空间
-
举例:
//Item.hpp文件 //定义一个数组 //声明 template <class Item> class Array { int m_size = 0; int m_capacity = 0; //容量 Item *m_data = NULL; public: Array(int capacity); ~Array(); void add(Item value); Item get(int index); int size(); Item operator[](int index); }; //实现: //1. 每一个实现前都得加:template <class Item> //2. 命名空间后面加类模板:Array<Item>:: template <class Item> Array<Item>:: Array(int capacity) { if (capacity <= 0) return; this->m_data = new Item[capacity]{}; this->m_capacity = capacity; } template <class Item> Array<Item>:: ~Array() { if (!this->m_data) return; delete[] this->m_data; this->m_data = NULL; } template <class Item> void Array<Item>::add(Item value) { if (this->m_size == this->m_capacity) { cout << "数组已满,需要扩容" << endl; return; } this->m_data[this->m_size++] = value; } template <class Item> Item Array<Item>:: get(int index) { return (*this)[index]; } template <class Item> int Array<Item>:: size() { return this->m_size; } template <class Item> Item Array<Item>:: operator[](int index) { if (index < 0 || index >= this->m_size) return 0; return this->m_data[index]; } //使用 #include<iostream> #include"Item.hpp" using namespace std; int main() { Array<int> array(5); array.add(10); array.add(20); array.add(30); array.add(40); array.add(50); array.add(60); Array<double> array2(5); cout << array[3] << endl; getchar(); return 0; }
-
类模板的继承
//swap2.hpp文件,定义一个类模板TestClass1 #include<iostream> using namespace std; //TestClass1类模板的定义 template <class It1, class It2> class TestClass1 { It1 m_data; It2 m_data2; public: It1 getdata(); void setdata(It1); It2 getdata2(); void setdata2(It2); }; template <class It1, class It2> It1 TestClass1<It1, It2>::getdata() { return this->m_data; } template <class It1, class It2> void TestClass1<It1, It2>::setdata(It1 value) { this->m_data = value; } template <class It1, class It2> It2 TestClass1<It1, It2>::getdata2() { return this->m_data2; } template <class It1, class It2> void TestClass1<It1, It2>::setdata2(It2 value) { this->m_data2 = value; } //main.cpp文件 #include<iostream> #include"swap2.hpp" using namespace std; //继承:如果父类是一个类模板,那么继承时必须指定模板的类型,才可以继承 class TestClass2 :public TestClass1<int, double> { public: void display() { cout << this->getdata()<<"===="<<this->getdata2() << endl; } }; //使用 int main() { TestClass2 testObj; testObj.setdata(10); testObj.setdata2(20.1); testObj.display(); getchar(); return 0; }
- 如果父类是一个类模板,那么继承时必须指定模板的类型,才可以继承
- 类模板的总结
- 和函数模板一样,类模板就是建立一个通用类,其数据成员的类型、成员函数的返回类型和参数类形都可以不具体指定,而用虚拟的类型来代表
- 当使用类模板建立对象时,系统会根据实参的类型取代类模板中的虚拟类型,从而实现不同类的功能。
- 注意:一旦是类模板,那么在通过该类初始化一个对象的时候样式:
类<类型1,类型2> 变量名
了。
类模板中的友元函数
类型转换
- C++中建议使用C++的类型转换符取代C风格的强制类型转换
- C语言风格的类型转换符
- (type)expression
- type(expression)
- C语言风格的类型转换符
- C++中有4个类型转换符:
static_cast、dynamic_cast、reinterpret_cast、const_cast
- 使用格式:
xx_cast<type>(expression)
- 使用格式:
const_cast
-
一般用于去除const属性,将const转换成非const
//c语言的强制转换 int a = 10; double b1 = (double)a; double b2 = double(a); cout << b1 << endl; cout << b2 << endl; //const类型 const Person *p1 = new Person(); //将const 转换为非 const Person *p2 = const_cast<Person *> (p1); p2->m_age = 20; //c语言风格:也可以使用。与const_cast效果一样 Person *p3 = (Person *)p1; p3->m_age = 30;
dynamic_cast
-
一般用于多态类型的转换,有运行时安全检测
class Person { public: int m_age = 0; //必须有虚函数才能实现多态 virtual void test(){ } }; class Student: public Person{ public: int m_score=0; }; class Car { }; int main() { Person *p1 = new Person(); Person *p2 = new Student(); //必须是多态才可以,多态的条件:父类必须有虚函数 //这个转换很显然是错误的:父类强制转换为子类,会检查安全,st1 直接赋值为NULL Student *st1 =dynamic_cast<Student*> (p1); //正确 Student *st2 = dynamic_cast<Student*> (p2); //会检查安全,car1 直接赋值为NULL Car *car1 = reinterpret_cast<Student *>(p2); //那么在使用的时候,dynamic_cast会动态检测强制转换右边到左边是否是安全的 //如果不安全,那么就会直接赋值为NULL //如果直接使用C语言的强转也能成功,但是不会自动检测安全。 //下面打印结果: //00000000 cout << st1<< endl; //002E6158 cout << st2 << endl; getchar(); return 0; }
static_cast
- 对比dynamic_cast,缺乏运行时安全检测
- 不能交叉转换(不是同一继承体系的,无法转换)
- 常用于基本数据类型的转换、非const转成const
-
使用范围较广
Person *p1 = new Person(); Person *p2 = new Student(); Student *st1 = static_cast<Student *>(p1); Student *st2 = static_cast<Student *>(p2); //不能交叉转换 //Car *car1 = static_cast<Car *>(p2); //效果相同 /*Student *st1 = (Student *)p1; Student *st2 = (Student *)p2;*/ cout << st1 << endl; cout << st2 << endl; //基本数据类型转换 int i = 10; double w = static_cast<double>(i);
reinterpret_cast
- 属于比较底层的强制转换,没有任何类型检查和格式转换,仅仅是简单的二进制数据拷贝
- 可以交叉转换
-
可以将指针和整数互相转换
Person *p1 = new Person(); Person *p2 = new Student(); Student *st1 = reinterpret_cast<Student *>(p1); Student *st2 = reinterpret_cast<Student *>(p2); //能交叉转换 Car *car1 = reinterpret_cast<Car *>(p2); //直接将整形转换为指针 int *p = reinterpret_cast<int *>(100); //直接将地址转换为整形 int num = reinterpret_cast<int>(p); //0a 00 00 00 内存中的二进制 int i = 10; //必须转化为引用<double &> //0a 00 00 00 cc cc cc cc 转化:cc为内存的初始值,直接将i的二进制复制过来,肯定不是10了 double m = reinterpret_cast<double &>(i); //此时的m就不是10了,是0a 00 00 00 cc cc cc cc,仅仅是二进制的拷贝
C++新特性
C++11
- C++标准的发展
- C++11新特性
- auto
- 可以从初始化表达式中推断出变量的类型,大大简化编程工作
-
属于编译器特性,不影响最终的机器码质量,不影响运行效率
auto i = 10; // int auto str = "C++";//const char * auto person = new Person(); //Person*
- decltype
-
可以获取变量的类型
int a = 10; decltype(a) b = 20; //int
-
- nullptr
-
可以解决NULL的二义性问题
void func(int p) { cout << "void func(int)" << p << endl; } void func(int* p) { cout << "void func(int*)"<< p << endl; } //使用 int i1 = 0; //null就是0 int i2 = NULL; //此时调用的是void func(int),但是我们想调用的是void func(int*) func(NULL); //等价于func(0); //这么调用就会调用void func(int*) //nullptr代表空指针 func(nullptr); //不允许直接打印 //cout << nullptr << endl;
-
-
快速遍历
//快速遍历 int arr[] = { 10,20,12,12,14 }; for (int item : arr) { cout << item << endl; } //快速初始化 int arr2[]{ 10,20,12,12,14 };
- Lambda表达式
- 有点类似于JavaScript中的闭包、iOS中的Block,本质就是函数
- 完整结构:
[capture list] (params list) mutable exception-> return type { function body }
- capture list:捕获外部变量列表
- params list:形参列表,不能使用默认参数,不能省略参数名
- mutable:用来说明是否可以修改捕获的变量
- exception:异常设定
- return type:返回值类型
- function body:函数体
-
有时可以省略部分结构
[capture list] (params list) -> return type {function body} [capture list] (params list) {function body} [capture list] {function body}
-
代码举例:
//5. 定义函数 //这个函数有3个参数,2个整形,一个函数指针类型 int exec(int a, int b, int(*p)(int a, int b)) { if (p == nullptr) return 0; return p(a, b); } int main() { //Lambda就是一个函数,可以用一个函数指针指向 //1. [capture list] (params list) -> return type {function body} int (*p)(int , int)= [](int a, int b)->int { return a + b; }; cout << p(20, 30) << endl; //2. [capture list] (params list) {function body} auto p2 = [](int a, int b) { return a + b; }; cout << p2(20, 20) << endl; //3. [capture list] {function body} auto p3 = [] { cout << "test" << endl; }; p3(); //4. 创建时直接调用 auto p4 = [](int a, int b) { return a + b; }(10, 20); cout << p4 << endl; //5. 使用:函数指针参数直接可以传入表达式 cout << exec(20, 10, [](int v1, int v2) {return v1 + v2; }) << endl; cout << exec(20, 10, [](int v1, int v2) {return v1 - v2; }) << endl; cout << exec(20, 10, [](int v1, int v2) {return v1 * v2; }) << endl; getchar(); return 0; }
- Lambda表达式 - 外部变量捕获
-
代码举例:
//0.引出 int a = 10; int b = 20; auto p = [] { //Lambda表达式是不能直接访问外部变量的 //cout << a << endl; }; //要访问外部变量的值,必须先捕获外部变量的值 //1. 值捕获 auto p2 = [a, b] { cout << a << endl; cout << b << endl; //只能访问,不能修改 //a = 20; }; //外部改掉 a = 11; b = 12; //调用会发现打印的值仍为10,20,没有改掉 //说明Lambda捕获的仅仅是a,b的值 p2(); //2. 引用捕获:a是引用(地址)捕获,b是值捕获 auto p3 = [&a, b] { cout << a << endl; cout << b << endl; }; a = 21; b = 22; //打印21,12,a改变,b没有改变 p3(); //3. 隐式捕获(值捕获) auto p4 = [=] { cout << a << endl; cout << b << endl; }; a = 22; b = 23; //打印21,22,a,b没有改变 p4(); //4. 隐式捕获(地址捕获) auto p5 = [&] { cout << a << endl; cout << b << endl; //可以修改 //a = 30; }; a = 32; b = 33; //打印32,33,a,b都改变 p5(); //5. 隐式捕获 //a是值捕获(因为只确定了a的捕获类型),其他都是地址捕获 auto p6 = [&,a] { cout << a << endl; cout << b << endl; }; a = 42; b = 43; //打印32,43 p6(); //6. 隐式捕获 //a是地址捕获(因为只确定了a的捕获类型),其他都是值捕获 auto p7 = [=, &a] { cout << a << endl; cout << b << endl; }; a = 52; b = 53; //打印52,43 p7();
-
注意:
- 值捕获在Lambda表达式内部只能访问不能修改
- 地址捕获在Lambda表达式内部可以访问也可以修改
-
- Lambda表达式 - mutable
-
Lambda表达式希望修改外面捕获的值,但是外面的值不能变
//用引用捕获,内部可以修改外部的值,但是外部的值也被修改掉了 int b = 10; auto p1 = [&]() { b = 30; }; p1(); //30 cout << b << endl; //但是有时候,想内部修改外部捕获的值,但是外部的值仍然没有被修改 //使用mutable int a = 10; auto func = [a]() mutable{ cout << ++a << endl; }; func();//11 //10,还是原来的值。 cout << a << endl;
-
- auto
C++14
- 泛型Lambda表达式
- Lambda表达式之前传的参数类型必须确定
-
但是C++14类型直接可以传auto
auto pp = [](auto a, auto b) {return a + b; }; cout << pp(10, 12.5) << endl;
-
对捕获的变量进行初始化
int x; auto ppp = [x = 10](){ cout << x << endl; }; ppp(); //这里仍然是未初始化的值 cout << x << endl;
C++17
-
设置C++标准
-
可以进行初始化的if、switch语句