第十一章 模板、类型转化、C++新特性

模板(template)

  1. 泛型,是一种将类型参数化以达到代码复用的技术,C++中使用模板来实现泛型
  2. 模板的使用格式如下
    1. template <typename\class T>
    2. typename和class是等价的
  3. C++提供两种模板:函数模板,类模板

函数模板

  1. 代码举例:

     /*
     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;
     }
    
  2. 泛型的本质:
    1. 编译器会根据你调用这个泛型时用的实际类型生成相应的函数,比如上面代码,编译器会生成类型为int、double的2个函数。而其他类型的不会生成,因为没有实际使用
  3. 模板没有被使用时,是不会被实例化出来的
  4. 模板的声明和实现如果分离到.h和.cpp中,会导致链接错误
    1. 原因分析如下:
      1. 如果分开后,编译器会将main.cpp,与swap.cpp(包含模板实现的cpp文件)单独编译
      2. 但是模板只会在使用的时候编译器才会生成相应的实现
      3. 上面main.cpp,swap.cpp是2个互不相干的文件分别编译,所以编译器根本不会自动生成swap.cpp中的函数实现。
      4. 因此链接的时候,找不到相应的模板实现,报错
      5. 因此:模板不能单独存放到cpp中,可以直接将实现放在.h文件中或者放到.hpp中
  5. 一般将模板的声明和实现统一放到一个.hpp文件(C++专门提供存放模板的头文件)中
    1. 新建一个.hpp文件

       //.hpp文件
       template <typename T> void swapValues(T &v1, T &v2) {
       	T tem = v1;
       	v1 = v2;
       	v2 = tem;
       }
      
  6. 多参数模板

    template <class T1,class T2> 
     void display(const T1 &v1,const T2 &v2) {
     cout << v1 << endl;
     cout << v2 << endl;
     }
        
     display(20,1.7);
    
  7. 函数模板的总结
    1. 函数模板就是建立一个通用的函数,其函数返回类型和形参类型不具体指定,而是用虚拟的类型来代表。
    2. 凡是函数体相同的函数都可以用函数模板来代替,不必定义多个函数,只需在模板中定义一次即可。
    3. 在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

类模板

  1. 模仿一个数组Aarray
  2. 动态数组的实现原理:
    1. 先申请一块足够大的堆空间,用于存放数组元素-容量
    2. 当不断的动态添加数组元素,直到开始申请的容量不够时
    3. 重新申请一块更大的堆空间,将之前的堆空间数据迁移到这个新的堆空间
    4. 释放掉原来的堆空间
  3. 举例:

     //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;
     }
    
  4. 类模板的继承

     //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. 如果父类是一个类模板,那么继承时必须指定模板的类型,才可以继承
  5. 类模板的总结
    1. 和函数模板一样,类模板就是建立一个通用类,其数据成员的类型、成员函数的返回类型和参数类形都可以不具体指定,而用虚拟的类型来代表
    2. 当使用类模板建立对象时,系统会根据实参的类型取代类模板中的虚拟类型,从而实现不同类的功能。
    3. 注意:一旦是类模板,那么在通过该类初始化一个对象的时候样式:类<类型1,类型2> 变量名了。

类模板中的友元函数

图1

类型转换

  1. C++中建议使用C++的类型转换符取代C风格的强制类型转换
    1. C语言风格的类型转换符
      1. (type)expression
      2. type(expression)
  2. C++中有4个类型转换符:static_cast、dynamic_cast、reinterpret_cast、const_cast
    1. 使用格式:xx_cast<type>(expression)

const_cast

  1. 一般用于去除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

  1. 一般用于多态类型的转换,有运行时安全检测

    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

  1. 对比dynamic_cast,缺乏运行时安全检测
  2. 不能交叉转换(不是同一继承体系的,无法转换)
  3. 常用于基本数据类型的转换、非const转成const
  4. 使用范围较广

     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

  1. 属于比较底层的强制转换,没有任何类型检查和格式转换,仅仅是简单的二进制数据拷贝
  2. 可以交叉转换
  3. 可以将指针和整数互相转换

     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

  1. C++标准的发展 图1
  2. C++11新特性
    1. auto
      1. 可以从初始化表达式中推断出变量的类型,大大简化编程工作
      2. 属于编译器特性,不影响最终的机器码质量,不影响运行效率

         auto i = 10; // int 
         auto str = "C++";//const char *
         auto person = new Person(); //Person*
        
    2. decltype
      1. 可以获取变量的类型

         int a = 10;
         decltype(a) b = 20; //int
        
    3. nullptr
      1. 可以解决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;
        
    4. 快速遍历

       //快速遍历
       int arr[] = { 10,20,12,12,14 };
       for (int item : arr) {
       	cout << item << endl;
       }
       //快速初始化
       int arr2[]{ 10,20,12,12,14 };
      
    5. Lambda表达式
      1. 有点类似于JavaScript中的闭包、iOS中的Block,本质就是函数
      2. 完整结构: [capture list] (params list) mutable exception-> return type { function body }
        1. capture list:捕获外部变量列表
        2. params list:形参列表,不能使用默认参数,不能省略参数名
        3. mutable:用来说明是否可以修改捕获的变量
        4. exception:异常设定
        5. return type:返回值类型
        6. function body:函数体
      3. 有时可以省略部分结构

         [capture list] (params list) -> return type {function body}
         [capture list] (params list) {function body}
         [capture list] {function body}
        
      4. 代码举例:

         //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;
         }
        
      5. Lambda表达式 - 外部变量捕获
        1. 代码举例:

           //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();
          
        2. 注意:

          1. 值捕获在Lambda表达式内部只能访问不能修改
          2. 地址捕获在Lambda表达式内部可以访问也可以修改
      6. Lambda表达式 - mutable
        1. 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;
          

C++14

  1. 泛型Lambda表达式
    1. Lambda表达式之前传的参数类型必须确定
    2. 但是C++14类型直接可以传auto

       auto pp = [](auto a, auto b) {return a + b; };
       cout << pp(10, 12.5) << endl;
      
  2. 对捕获的变量进行初始化

     int x;
     auto ppp = [x = 10](){
     cout << x << endl;
     };
     ppp();
     //这里仍然是未初始化的值
     cout << x << endl;
    

C++17

  1. 设置C++标准

    图1

  2. 可以进行初始化的if、switch语句 图1

Table of Contents