• c++---模板篇


    1、模板

    概念:模板就是建立通用的模具,大大提高复用性

    特点:

    1. 模板不可以直接使用,它只是一个框架
    2. 模板的通用并不是万能的

    1.1、函数模板

    • C++另一种编程思想称为泛型编程,主要利用的技术就是模板
    • C++提供两种模板机制:函数模板类模板

    1.1.1、函数模板语法

    函数模板作用:

    建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型代表

    语法

    template
    函数声明或定义
    
    • 1
    • 2

    解释

    template — 声明创建模板

    typename — 表面其后面的符号是一种数据类型,可以用class代替

    T — 通用的数据类型,名称可以替换,通常为大写字母

    示例:

    //函数模板
    //声明一个模板,告诉编译器后面代码中紧跟着的T不要犯错,T是一个通用数据类0
    template
    //交换俩个数
    void mySwap(T& a, T& b) {
    	T temp = a;
    	a = b;
    	b = temp;
    }
    
    void test() {
    
    	//利用模板函数交换
    	//1、自动类型推导
    	int a = 10;
    	int b = 20;
    	mySwap(a, b);
    	cout << "a:" << a << endl;
    	cout << "b:" << b << endl;
    
    	//2、显示指定类型
    	double c = 20;
    	double d = 40;
    	mySwap(c, d);
    	cout << "c:" << c << endl;
    	cout << "d:" << d << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    image-20231001215022923

    总结:

    • 函数模板利用关键字 template

    • 使用函数模板有俩种方式:自动类型推导、显示指定类型

    • 模板的目的是为了提高复用性,将类型参数化

    1.1.2、函数模板注意事项

    注意事项:

    • 自动类型推导,必须推导出一致的数据类型T,才可以使用
    • 模板必须要确定出T的数据类型,才可以使用

    示例:

    //自动类型推导,必须推导出一致的数据类型T,才可以使用
    template
    //交换俩个数
    void mySwap(T& a, T& b) {
    	T temp = a;
    	a = b;
    	b = temp;
    }
    
    	int a = 10;
    	char c = 'c';
    	mySwap(a, c);//报错
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20231001220244750

    
    //模板必须要确定出T的数据类型,才可以使用
    template
    void func() {
    	cout << "func调用" << endl;
    }
    
    void test02() {
    	//func();//报错
        func();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20231001220611749

    1.1.3、函数模版案例 – 数组排序

    描述:

    • 利用函数模版封装一个排序的函数,可以对不同数据类型数组进行排序
    • 排序规则小到大
    • 测试:char数组和int数组进行测试
    //选择排序
    template
    void chooseSort(T arr[],int lenth) {
    	for (T i = lenth-1; i >=0; i--)
    	{
    		//最大值的下标
    		T max = 0;
    		for (T j = 0; j <=i; j++)
    		{
    			if (arr[j]>arr[max])
    			{
    				max = j;
    			}
    		}
    		T temp = arr[i];
    		arr[i] = arr[max];
    		arr[max] = temp;
    	}
    }
    
    //打印数组
    template
    void printArray(T arr[], int lenth) {
    	for (T i = 0; i < lenth; i++)
    	{
    		cout << arr[i]<<"  ";
    	}
    }
    void test01() {
    	//int arry[] = { 5,2,6,7,1,3 };
    
    	//int len = sizeof(arry) / sizeof(arry[0]);
    	//cout << len << endl;
    
    	//chooseSort(arry, len);
    	//printArray(arry, len);
    	
    	char charArry[] = "dcbae";
    
    	int len = sizeof(charArry) / sizeof(charArry[0]);
    	cout << len << endl;
    
    	chooseSort(charArry, len);
    	printArray(charArry, len);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    1.1.4、普通函数和函数模板区别

    • 普通函数调用时可以发生自动类型转换(隐式转换)
    • 函数模板调用时,如果利用自动类型推导,不会发生隐式转换
    • 如果利用显示指定类型的方式,可以发生隐式转换

    建议使用显示指定类型的方式,调用函数模版,因为可以自己确定通用类型T

    1. //普通函数调用时可以发生自动类型转换(隐式转换)
      int myAdd(int a, int b) {
      	return a + b;
      }
      
      void test01() {
      	int a = 10;
      	char c = 'c';
      
      	//这里会发生自动类型推导将char转换成int
      	int b = myAdd(a, c);
      	cout << "B:" << b << endl;
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      image-20231002174109662

    2. //函数模板调用时,如果利用自动类型推导,不会发生隐式转换
      template
      T myAdd2(T a, T b) {
      	return a + b;
      }
      
      //利用模版不会发生自动类型推导
      int d = myAdd2(a, c);
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      image-20231002174335575

    3. //正确 如果利用显示指定类型的方式,可以发生隐式转换
      int d = myAdd2(a, c);
      
      • 1
      • 2

    image-20231002174530885

    1.1.5、普通函数和函数模板的调用规则

    调用规则如下:

    1. 如果函数模板和普通函数都可以实现,优先调用普通模板

    2. 可以通过空模板参数列表来强制调用函数模板

    3. 函数模板也可以发生重载

    4. 如果函数模板可以产生更好的匹配,优先调用函数模板

    5. //如果函数模板和普通函数都可以实现,优先调用普通模板
      //普通函数
      void myPrint(int a, int b) {
      	cout << "调用的普通函数" << endl;
      }
      
      //函数模板
      template
      void myPrint(T a, T b) {
      	cout << "调用的模板函数" << endl;
      }
      
      void test01() {	
      	int a = 10;
      	int b = 20;
      	myPrint(a, b);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      image-20231002180201548

    6. //可以通过空模板参数列表来强制调用函数模板
      myPrint<>(a, b);
      
      • 1
      • 2

    image-20231002180342684

    1. //函数模板也可以发生重载
      template
      void myPrint(T a, T b) {
      	cout << "调用的模板函数" << endl;
      }
      
      template
      void myPrint(T a, T b,T c) {
      	cout << "调用的重载模板函数" << endl;
      }
      
      myPrint(a, b, 100);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      image-20231002180603990

    2. //如果函数模板可以产生更好的匹配,优先调用函数模板
      void myPrint(int a, int b) {
      	cout << "调用的普通函数" << endl;
      }
      
      //函数模板
      template
      void myPrint(T a, T b) {
      	cout << "调用的模板函数" << endl;
      }
      
      char a = 'a';
      char b = 'b';
      myPrint(a, b);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      这里是由于模板不需要推导直接命为char类型进行运算,所以不走上面的普通函数,普通函数需要将char转换成Int多走一步,所以优先选模板函数

    image-20231002180912841

    如果提供了函数模板,那么最好不要再提供普通函数,否则容易出现二义性

    1.1.6、模板的局限性

    局限性:

    如果T的数据类型传入的是我们自定义的数据类型,那么我们的代码也无法运行。

    image-20231003093021728

    因此为了解决这个问题C++提供了模板的重载,可以为这些特定的类型提供具体化的模板

    template
    void func(T &a, T &b) {
    	if (a>b)
    	{
    		cout << "A大" << endl;
    	}
    	else {
    		cout << "B大" << endl;
    	}
    }
    
    //特定的重载
    template<> void func(Person &p1, Person &p2) {
    	if (p1.a > p2.a)
    	{
    		cout << "A大" << endl;
    	}
    	else {
    		cout << "B大" << endl;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    image-20231003093624449

    1.2、类模板

    1.2.1、类模板语法

    类模板作用:

    • 建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表

    语法:

    template
    • 1
    • 2

    示例:

    template
    class Person {
    public:
    	Person(NameType name, Agetype age) {
    		this->m_name = name;
    		this->m_age = age;	 
    	}
    	void showPerson() {
    		cout << "name:" << this->m_name << "age:" << this->m_age << endl;
    	}
    
    	NameType m_name;
    	Agetype m_age;
    };
    
    void test01() {
        //赋值
    	Person p1("孙悟空", 999);
    	p1.showPerson();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    1.2.2、类模板与函数模板区别

    类模板与函数模板区别主要有两点:

    1. 类模板没有自动类型推导的使用方式
    2. 类模板在模板参数列表中可以有默认参数

    示例:

    	//1.类模板没有自动类型推导的使用方式
    	Person p("zyy", 1000);
    
    • 1
    • 2

    image-20231005163013817

    	//2、类模板在模板参数列表中可以有默认参数
    	template
        class Person {
        public:
            Person(NameType name, Agetype age) {
                this->m_name = name;
                this->m_age = age;	 
            }
            void showPerson() {
                cout << "name:" << this->m_name << "age:" << this->m_age << endl;
            }
    
            NameType m_name;
            Agetype m_age;
        };
            
    	Person p2("猪八戒", 99);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    1.2.3、类模板中成员函数创建时机

    类模板中成员函数和普通类中成员函数创建时机是有区别的:

    • 普通类中的成员函数一开始就可以创建
    • 类模板中的成员函数在调用时才创建
    //类模板中成员函数创建时机
    class Person1 {
    public:
    	void showPerson1() {
    		cout << "Person1" << endl;
    	}
    };
    
    class Person2 {
    public:
    	void showPerson2() {
    		cout << "Person2" << endl;
    	}
    };
    
    
    template
    class myClass {
    public:
    	T obj;
    	//类模板中的成员函数
    	void func1() {
    		obj.showPerson1();
    	}
    	void func2() {
    		obj.showPerson2();
    	}
    };
    
    void test01() {
    	myClassm;
    	m.func1();
    	//编译出错,说明函数调用才会去创建成员函数;
    	//m.func2();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    image-20231006103705719

    1.2.4、类模板对象做函数参数

    类模板实例化出的对象,向函数传参的方式

    三种传入方式:

    1. 指定传入的类型 ----直接显示对象的数据类型(常用)
    2. 参数模板化 ----将对象中的参数变为模板进行传递
    3. 整个类模板化 ----将这个对象类型模板化进行传递

    示例:

    #include
    #include
    using namespace std;
    
    template
    class Person {
    public:
    	Person(T1 name, T2 age) {
    		this->m_name = name;
    		this->m_age = age;
    	}
    
    	void showPerson() {
    		cout << "name:" << this->m_name << "age:" << this->m_age << endl;
    	}
    
    	T1 m_name;
    	T2 m_age;
    };
    
    //1.指定传入的类型
    //void printPerson1(Person& p) {
    //	p.showPerson();
    //}
    //
    //void test01() {
    //	Person p("孙悟空", 100);
    //	printPerson1(p);
    //}
    
    
    //2.参数模板化 
    //template
    //void printPerson2(Person& p) {
    //	p.showPerson();
    //};
    //
    //void test02() {
    //	Person p("猪八戒", 200);
    //	printPerson2(p);
    //}
    
    
    //3.整个类模板化
    template
    void printPerson3(T& p) {
    	p.showPerson();
    }
    
    
    void test03() {
    	Person p("唐僧", 20);
    	printPerson3(p);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    查看自动推断的数据类型到底是什么

    	cout << "T的数据类型为:" << typeid(T).name() << endl;
    
    • 1

    image-20231006155738154

    1.2.5、类模板与继承

    当类模板碰到继承时,需要注意以下几点:

    • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
    • 如果不指定,编译器无法给子类分配内存
    • 如果想灵活指定出父类中T的类型,子类也需变为类模板

    示例:

    template
    class Base {
    public:
    	T m_age;
    };
    
    //1.错误1 不能直接继承,需要指定数据类型
    //class Son : public Base {
    //
    //};
    
    class Son : public Base {
    public:
    	Son() {
    		cout << "T的数据类型:" << typeid(m_age).name() << endl;
    	}
    };
    
    
    //如果需要灵活指定子类数据类型 需要将子类也变成一个模板
    template
    class Son2 : public Base
    {
    public:
    	Son2() {
    		cout << "Son2 T1 类型:" << typeid(T1).name() << endl;
    		cout << "Base T2 类型:" << typeid(T2).name() << endl;
    	}
    public:
    	T1 obj;
    };
    
    void test01() {
    	Son s1;
    	Son2 s2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    image-20231006161843898

    1.2.6、类模板成员函数类外实现

    函数需要加上template模板声明

    示例:

    //类模板成员函数类外实现
    template
    class Person {
    public:
    	Person(T1 name, T2 age);
    	void showPerson();
    
    	T1 m_Name;
    	T2 m_Age;
    };
    
    //类外实现
    template
    Person::Person(T1 name,T2 age) {	
    		this->m_Name = name;
    		this->m_Age = age;	
    }
    
    template
    void Person::showPerson() {
    	cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
    }
    
    
    void test01() {
    	Person p1("zyy",18);
    	p1.showPerson();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    image-20231006162958336

    1.2.7、 类模板分文件编写

    问题:

    • 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

    解决:

    • 方法一:直接包含 .cpp 源文件
    • 方法二:将声明和实现写到同一个文件中,并更改后缀名为 .hpp , hpp 是约定的名称,并不是强制(推荐)

    示例:

    Person.cpp

    #include "Person.h"
    #include
    using namespace std;
    #include
    template
    Person::Person(T1 name,T2 age) {	
    		this->m_Name = name;
    		this->m_Age = age;	
    }
    
    template
    void Person::showPerson() {
    	cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Person.h

    #pragma once
    #include
    using namespace std;
    template
    class Person {
    public:
    	Person(T1 name, T2 age);
    	void showPerson();
    
    	T1 m_Name;
    	T2 m_Age;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    主文件

    #include
    #include "Person.h"
    using namespace std;
    // 类模板分文件编写
    
    void test01() {
    	Person p1("zyy",18);
    	p1.showPerson();
    }
    int main() {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ​ 这里如果直接执行的话就会报错,因为在一开始.h类模板中的成员函数是不会创建的,导致编译器编译失败。

    image-20231006173118745

    ​ 想使用的话需要在主文件中直接引用 .cpp 源码,但是不推荐这种方法

    #include "Person.cpp"
    
    • 1

    image-20231006200740971

    方法二:

    ​ 将源码和头文件合并,合并到同一个文件,并取后缀为.hpp文件

    Person.hpp

    #pragma once
    #include
    using namespace std;
    #include
    template
    class Person {
    public:
    	Person(T1 name, T2 age);
    	void showPerson();
    
    	T1 m_Name;
    	T2 m_Age;
    };
    
    template
    Person::Person(T1 name, T2 age) {
    	this->m_Name = name;
    	this->m_Age = age;
    }
    
    template
    void Person::showPerson() {
    	cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    主文件

    #include
    #include "Person.hpp"
    using namespace std;
    // 类模板分文件编写
    
    void test01() {
    	Person p1("zyy",18);
    	p1.showPerson();
    }
    int main() {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    image-20231006205519042

    1.2.8、类模板与友元

    全局函数类内实现 - 直接在类内声明友元即可(推荐)

    全局函数类外实现 - 需要提前让编译器知道全局函数的存在

    示例:

    //提前知道Person存在
    template
    class Person;
    
    //类外实现
    template
    void printPerson2(Person& p) {
    	cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
    }
    
    template
    class Person {
    	//全局函数类内实现
     	friend void printPerson(Person &p) {
    		cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
    	}
    
    	//类外实现
    	//加空模版参数列表,如果类外实现,需要让编译器提前知道函数存在
    	friend void printPerson2<>(Person &p);
    
    public:
    	Person(T1 name, T2 age){
    		this->m_Name = name;
    		this->m_Age = age;
    	}
    
    private:
    	T1 m_Name;
    	T2 m_Age;
    };
    
    
    
    //全局函数在类内实现
    void test01() {
    	Person p1("zyy", 18);
    	printPerson(p1);
    	Person p2("jerry", 20);
    	printPerson2(p2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    image-20231006214127656

  • 相关阅读:
    Vue--v-for
    web前端面试高频考点——Vue原理(diff算法、模板编译、组件渲染和更新、JS实现路由)
    网络安全——(黑客)自学
    SpringBoot2 +vue2 + shiro 集成山东通 auth2 方式单点登陆
    数组12— map() :转换数组元素
    Radius OTP完成堡垒机登录认证 安当加密
    Sentinel基础知识
    IDS(入侵检测系统)
    9、Java 成员方法详解
    【网络编程】
  • 原文地址:https://blog.csdn.net/NITIQ/article/details/133623569