精华内容
下载资源
问答
  • 2022-03-27 13:01:40

    一、模板概论

    c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函 数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数 就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函 数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板 中的虚拟类型,从而实现不同函数的功能。 c++提供两种模板机制:函数模板和类 模板 类属 - 类型参数化,又称参数模板 总结: 模板把函数或类要处理的数据类型 参数化,表现为参数的多态性,成为类属。 模板用于表达逻辑结构相同,但具体 数据元素类型不同的数据对象的通用行为。

    如何实现一个通用的交换函数呢?

    void Swap(int& left, int& right)
    {
     int temp = left;
     left = right;
     right = temp;
    }
    void Swap(double& left, double& right)
    {
     double temp = left;
     left = right;
     right = temp;
    }
    void Swap(char& left, char& right)
    {
     char temp = left;
     left = right;
     right = temp;
    }

    使用函数重载虽然可以实现,但是有一下几个不好的地方: 1. 重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数 2. 代码的可维护性比较低,一个出错可能所有的重载均出错 那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

    函数模板格式

    template<Typename T1, Typename T2, Typename T3....>

     返回值类型 函数名(参数列表){}

    template<typename T>
    void Swap( T& left, T& right)
    {
     T temp = left;
     left = right;
     right = temp;
    }

      

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

    函数模板不允许自动类型转化 普通函数能够自动进行类型转

    //函数模板
    template<class T>
    T MyPlus(T a, T b){
    T ret = a + b;
    return ret;
    }
    //普通函数
    int MyPlus(int a,char b){
    int ret = a + b;
    return ret;
    }
    void test02(){
    int a = 10;
    char b = 'a';
    //调用函数模板,严格匹配类型
    MyPlus(a, a);
    MyPlus(b, b);
    //调用普通函数
    MyPlus(a, b);
    //调用普通函数 普通函数可以隐式类型转换
    MyPlus(b, a);
    //结论:
    //函数模板不允许自动类型转换,必须严格匹配类型
    //普通函数可以进行自动类型转换
    }
    

    函数模板和普通函数 都识别 优先选择普通函数 

    /函数模板和普通函数 都识别 如果选择函数模板 加<>

    三、类模板 

    类模板和函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个 类,其功能是相同的,仅仅是数据类型不同。 类模板用于实现类所需数据的类型参数化

    template<class NameType, class AgeType>
    class Person
    {
    public:
    Person(NameType name, AgeType age)
    {
    this->mName = name;
    this->mAge = age;
    }
    void showPerson()
    {
    cout << "name: " << this->mName << " age: " << this->mAge <
    < endl;
    }
    public:
    NameType mName;
    AgeType mAge;
    };
    void test01()
    {
    //Person P1("德玛西亚",18); // 类模板不能进行类型自动推导
    Person<string, int>P1("德玛西亚", 18);
    P1.showPerson();
    }
    

    类模板做函数参数:

    template<class NameType, class AgeType>
    class Person{
    public:
    Person(NameType name, AgeType age){
    this->mName = name;
    this->mAge = age;
    }
    void PrintPerson(){
    cout << "Name:" << this->mName << " Age:" << this->mAge <<
    endl;
    }
    public:
    NameType mName;
    AgeType mAge;
    };
    //类模板做函数参数
    void DoBussiness(Person<string,int>& p){
    p.mAge += 20;
    p.mName += "_vip";
    p.PrintPerson();
    }
    int main(){
    Person<string, int> p("John", 30);
    DoBussiness(p);
    system("pause");
    return EXIT_SUCCESS;
    }

    类模板派生普通类

    //类模板
    template<class T>
    class MyClass{
    public:
    MyClass(T property){
    this->mProperty = property;
    }
    public:
    T mProperty;
    };
    //子类实例化的时候需要具体化的父类,子类需要知道父类的具体类型是什么样的
    //这样 c++编译器才能知道给子类分配多少内存
    //普通派生类
    class SubClass : public MyClass<int>{
    public:
    SubClass(int b) : MyClass<int>(20){
    this->mB = b;
    }
    public:
    int mB;
    };

     类模板派生类模板

    //父类类模板
    template<class T>
    class Base
    {
    T m;
    };
    template<class T >
    class Child2 : public Base<double> //继承类模板的时候,必须要确定基类的大
    小
    {
    public:
    T mParam;
    };
    void test02()
    {
    Child2<int> d2;
    }

     类模板类内实现

    template<class NameType, class AgeType>
    class Person
    {
    public:
    Person(NameType name, AgeType age)
    {
    this->mName = name;
    this->mAge = age;
    }
    void showPerson()
    {
    cout << "name: " << this->mName << " age: " << this->mAge <
    < endl;
    }
    public:
    NameType mName;
    AgeType mAge;
    };
    void test01()
    {
    //Person P1("德玛西亚",18); // 类模板不能进行类型自动推导
    Person<string, int>P1("德玛西亚", 18);
    P1.showPerson();
    }

     

    更多相关内容
  • 1.分离编译模式 ...下面的程序由三个文件组成:func.h用来对函数模板进行申明,func.cpp用来定义函数模板,main.cpp包含func.h头文件并调用相应的函数模板。 /***func.h***/ template<class> void
  • 所谓函数模板实际上是建立一个通用函数,其涵涵素类型额形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板。 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需要在模板...
  • 本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解。具体内容如下: 泛型编程(Generic Programming)是一种编程范式,通过将类型参数化来实现在同一份代码上...
  • 函数模板和普通函数区别结论:  函数模板不允许自动类型转化  普通函数能够进行自动类型转换  函数模板和普通函数在一起,调用规则:  1 函数模板可以像普通函数一样被重载  2 C++编译器优先考虑普通函数 ...
  • 练习 C++中函数模板、类模板的创建和使用方法。 (1) 理解模板的作用。 (2) 学习函数模板及其声明方法,掌握模板函数及其生成方法。 (3) 学习函数模板的两种不同的实例化方法。 (4) 学习类模板的声明与使用方法。
  • 本章将正式开始介绍C++中的模板,为了能让大家更好地体会到用模板多是件美事!我们将会举例说明,大家可以试着把自己带入到文章中,跟着思路去阅读和思考,真的会很有意思!如果你对网络流行梗有了解,读起来将会更...

       ​​​​​​          🤣 爆笑教程 👉  《C++要笑着学》 👈 火速订阅  🔥

    💭 写在前面

    本章将正式开始介绍C++中的模板,为了能让大家更好地体会到用模板多是件美事!我们将会举例说明,大家可以试着把自己带入到文章中,跟着思路去阅读和思考,真的会很有意思!如果你对网络流行梗有了解,读起来将会更有意思!


    Ⅰ.  泛型编程

    0x00  引入 - 通用的交换函数

    在C语言中,我们实现两数交换,不用花的方法(异或啥的),中规中矩的写法是通过 tmp 交换。

    💬 比如我们这里想交换 变量a变量b 的值,我们可以写一个 Swap 函数:

    void Swap(int* px, int* py) {
    	int tmp = *px;  // 创建临时变量,存储a的值
    	*px = *py;      // 将b的值赋给a
    	*py = tmp;      // 让b从tmp里拿到a的值
    }
    
    int main(void)
    {
    	int a = 0, b = 1;
    	Swap(&a, &b);   // 传址
    
    	return 0;
    }

    变量a变量b 是整型,如果现在有了是浮点型的 变量c 变量d

    还可以用我们这个整型的 Swap 函数交换吗?

    void Swap(int* px, int* py) {  
    	int tmp = *px; 
    	*px = *py;      
    	*py = tmp;      
    }
    
    int main(void)
    {
    	int a = 0, b = 1;
    	double c = 1.1, d = 2.2;  // 浮点型
    	Swap(&a, &b);
    	Swap(&c, &d);
    
    	return 0;
    }

    似乎不太行,因为我们实现的 Swap 函数接受的是整形数据,这里传的是浮点数了。

     我们可以再写一个浮点数版本的 Swap 函数…… 叫 SwapDouble

    void SwapDouble(double* px, double* py) {
    	double tmp = *px; 
    	*px = *py;      
    	*py = tmp;      
    }

    不错,问题是解决了。但是我现在又出现了字符型的 变量e 和 变量f 呢?

     ……

    那我现在又出现了各种乱七八糟的类型呢?

    SwapIntSwapDoubleSwapChar 真是乱七八糟的,

    ❓ 能不能实现一个通用的 Swap 函数呢?

     那我们不用C语言了!我们用C++,C++里面不是有 函数重载 嘛!

    用C++我们还能用引用的方法交换呢,直接传引用,取地址符号都不用打了,多好!

    💬 test.cpp: 

    于是咔咔咔,改成了C++之后 ——

    void Swap(int& rx, int& ry) {
    	int tmp = rx;
    	rx = ry;
    	ry = tmp;
    }
    void Swap(double& rx, double& ry) {
    	double tmp = rx;
    	rx = ry;
    	ry = tmp;
    }
    void Swap(char& rx, char& ry) {
    	char tmp = rx;
    	rx = ry;
    	ry = tmp;
    }
    
    int main(void)
    {
    	int a = 0, b = 1;
    	double c = 1.1, d = 2.2;
    	char e = 'e', f = 'f';
    
    	Swap(a, b);
    	Swap(c, d);
    	Swap(e, f);
    
    	return 0;
    }

     场面一度尴尬…… 

    好像靠函数重载来调用不同类型的 Swap,只是表面上看起来 "通用" 了 ,

    实际上问题还是没有解决,有新的类型,还是要添加对应的函数…… 

    ❌ 用函数重载解决的缺陷:

    ① 重载的函数仅仅是类型不同,代码的复用率很低,只要有新类型出现就需要增加对应的函数。

    ② 代码的可维护性比较低,一个出错可能导致所有重载均出错。

     哎!要是能像做表情包那样简单就好了……

    你看我做表情,有些是可以靠模板去制作的,比如这种 "狂粉举牌" 表情:

    这就是模板!如果在C++中也能够存在这样一个模板该有多好?

    就像这里,只要在板子上写上名字(类型),

    就可以做出不同的 "举牌表情"(生成具体类型的代码)。

     那将会节省很多头发!

    巧妙的是!C++里面有这种神器!!!

    而且大佬已经把神器打造好了,你只要学会如何使用就能爽到飞起!

    下面让我们开始函数模板的学习!在这之前我们再来科普一下什么是泛型编程。

    0x01  什么是泛型编程

     泛型,就是针对广泛的类型的意思。

    泛型编程: 编写与类型无关的调用代码,是代码复用的一种手段。 模板是泛型编程的基础。

    Ⅱ.  函数模板

    0x00  函数模板的概念

    上面我们提到了 "神器" ,现在我们来学会如何去使用它,我们先来介绍一下概念。

     📚 函数模板代表了一个函数家族,该函数模板与类型无关,

    在使用时被参数化,根据实参类型产生函数的特定类型版本。

    0x01  函数模板格式

    template<typename T1, typename T2,......,typename Tn>
    返回值类型 函数名(参数列表){}
    

    template 是定义模板的关键字,后面跟的是尖括号 < >

    typename 是用来定义模板参数的关键字

    T1, T2, ..., Tn 表示的是函数名,可以理解为模板的名字,名字你可以自己取。

     👈 就像这个表情包模板,我给他取名为 "狂粉举牌" 表情。

    💬 解决刚才的问题:

    ① 我们来定义一个叫 Swap 的函数,我们这不给具体的类型:

    void Swap();

    ② 然后在它的前面定义一个具体的类型:

    template<typename T>    // template + <typename 模板名>
    void Swap();

     ③ 这时候,我们就可以用这个模板名来做类型了:

    template<typename T>         // 模板参数列表 ———— 参数类型
    void Swap(T& rx, T& ry) {    // 函数参数列表 ———— 参数对象
    	T tmp = rx;
    	rx = ry;
    	ry = tmp;
    }
    

    这,就是函数模板!虽然参数的名字我们可以自己取 (你写成 TMD 也没人拦你 )

     但是我们一般喜欢给它取名为 T,因为 T 代表 Type(类型),

    有些地方也会叫 TPTYX ,或者 KV结构(key-value-store)我们还会给它取名为 KING

    💬 当然,如果你需要多个类型,也是可以定义多个类型的:

    template<typename T1, typename T2, typename T3>

    📌 注意事项:

    函数模板不是一个函数,因为它不是具体要调用的某一个函数,而是一个模板。就像 "好学生",主体是学生,"好" 是形容 "学生" 的;这里也一样,"函数模板" 是模板,所以 函数模板表达的意思是 "函数的模板" 。所以,我们一般不叫它模板函数,应当叫作函数模板。

    "函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。"  —— 《百度百科》

    ② 我们在用 template< > 定义模板的时候,尖括号里的 typename 其实还可以写成 class

    template<class T>     // 使用class充当typename (具体后面会说)
    void Swap(T& rx, T& ry) {
    	T tmp = rx;
    	rx = ry;
    	ry = tmp;
    }
    

    🚩 现在我们把完整的代码跑一下看看:

    template<typename T>
    void Swap(T& rx, T& ry) {
    	T tmp = rx;
    	rx = ry;
    	ry = tmp;
    }
    
    int main(void)
    {
    	int a = 0, b = 1;
    	double c = 1.1, d = 2.2;
    	char e = 'e', f = 'f';
    
    	Swap(a, b);
    	Swap(c, d);
    	Swap(e, f);
    
    	return 0;
    }

     (代码成功运行)

    🐞 调试,打开监视看看是否都成功交换了:

    搞定!我们使用模板成功解决了问题,实现了通用的 Swap 函数!

    如果是自定义类型,函数里面就要是拷贝构造,你要实现好就行。

    因为 T 没有规定是什么类型,所以任意类型都是可以的,内置类型和自定义类型都可以的。

     真是太香了!这,就是模板!

    0x02  模板函数的原理

    ❓ 思考:这下面三个调用调用的是同一个函数吗?

    🔑 不是同一个函数。这三个函数执行的指令是不一样的,你可以这么想,

    它们都需要建立栈帧,栈帧里面是要开空间的,你就要给 rx 开空间,

    rx 的类型都不一样(double int char)。所以当然调用的不是同一个函数了。

     我们来思考一下模板函数的原理是什么。

     比如说我现在想把杜甫写的《登高》做出一万份出来,怎么做?

     

     

    最后我们传递出去的也不是印诗的模具,而是印出来的纸,

    不管是手抄还是印刷,传递出去的都是纸。

    💬 所以我们再来看这里的代码:

    template<typename T>
    void Swap(T& rx, T& ry) {
    	T tmp = rx;
    	rx = ry;
    	ry = tmp;
    }
    
    int main(void)
    {
    	int a = 0, b = 1;
    	double c = 1.1, d = 2.2;
    	char e = 'e', f = 'f';
    
    	Swap(a, b);
    	Swap(c, d);
    	Swap(e, f);
    
    	return 0;
    }
    

    和上面说的一样,我们不会把印诗的模具传递出去,而是印出来的纸,

     所以这里调用的当然不是模板,而是这个模板造出来的东西。

    而函数模板造出 "实际要调用的" 的过程,叫做模板实例化。

    编译器在调用之前会干一件事情 —— 模板实例化。

    我们下面就来探讨一下模板实例化。

    Ⅲ.  函数模板实例化

    0x00  引入:这些不同类型的Swap函数是怎么来的

    int a = 0, b = 1;
    Swap(a, b);

    编译器在调用 Swap(a, b) 的时候,发现 a b 是整型的,编译器就开始找,

    虽然没有找到整型对应的 Swap,但是这里有一份模板 —— 

    template<typename T>   // 大家好我是模板,飘过~
    void Swap(T& rx, T& ry) {
    	T tmp = rx;
    	rx = ry;
    	ry = tmp;
    }

    这里要的是整型,编译器就通过这个模板,推出一个 T int 类型的函数。

    这时编译器就把这个模板里的 T 都替换成 int,生成出一份 Tint 的函数。

    char e = 'e', f = 'f';
    Swap(e, f);

    一样的,如果要调用 Swap(e, f) ,e f 是字符型,编译器就会去实例化出一个 char 的。

     

    你调的函数还是那些函数,只是你写一份模板出来,让编译器去用模板生成那些函数。

    前面注意事项那里我们说过,函数模板本身不是函数。

    它是是编译器使用方式产生特定具体类型函数的模具,在编译器编译阶段,

    对于模板函数的使用,编译器需要根据传入的实参类型来推演,生成对应类型的函数以供调用。

    比如:当用 double 类型使用函数模板时,编译器通过对实参类型的推演,

    T 确定为 double 类型,然后产生一份专门处理 double 类型的代码,对于字符类型也是如此。

    0x01  转到反汇编观察

    🐞 我们刚才调试的时候在监视窗口已经看到了,它们的值成功交换了。

    现在我们再调试一次,这次转到反汇编,去验证一下编译器通过模板生成函数这件事:

    0x01  模板实例化的定义

    模板将我们本来应该要重复做的活,交给了编译器去做。

    编译器不是人,它不会累,让编译器拿着模板实例化就完事了。

     用手搓衣服舒服,还是用洗衣机洗舒服?

     自己手写舒服,还是编译器自己去生成舒服?

    📚 用不同类型的参数使用模板参数时,成为函数模板的实例化。

    模板参数实例化分为:隐式实例化 显式实例化 ,下面我们来分别讲解一下这两种实例化。

    0x02  模板的隐式实例化

    📚 定义:让编译器根据实参,推演模板函数的实际类型。

    我们刚才讲的 Swap 其实都是隐式实例化,就是让编译器自己去推。

    💬 现在我们再举一个 Add 函数模板做参考:

    #include <iostream>
    using namespace std;
    
    template<class T>
    T Add(const T& x, const T& y) {
    	return x + y;
    }
    
    int main(void)
    {
    	int a1 = 10, a2 = 20;
    	double d1 = 10.1, d2 = 20.2;
    	cout << Add(a1, a2) << endl;
    	cout << Add(d1, d2) << endl;
    
    	return 0;
    }
    

    ❓ 现在思考一个问题,如果出现 a1 + d2 这种情况呢?实例化能成功吗?

    Add(a1, d2);

    这必然是失败的, 因为会出现冲突。一个要把它实例化成 int ,一个要把它实例成 double

    💡  解决方式

    ① 传参之前先进行强制类型转换,非常霸道的解决方式:

    template<class T>
    T Add(const T& x, const T& y) {
    	return x + y;
    }
    
    int main(void)
    {
    	int a1 = 10, a2 = 20;
    	double d1 = 10.1, d2 = 20.2;
    	cout << Add(a1, a2) << endl;
    	cout << Add(d1, d2) << endl;
    
    	cout << Add((double)a1, d2) << endl;
    
    	return 0;
    }

    ② 写两个参数,那么返回的参数类型就会起决定性作用:

    #include <iostream>
    using namespace std;
    
    template<class T1, class T2>
    T1 Add(const T1& x, const T2& y) {   // 那么T1就是int,T2就是double
    	return x + y;      // 范围小的会像范围大的提升,int会像double "妥协"
    } // 最后表达式会是一个double,但是最后返回值又是T1,是int,又会转
    
    int main(void)
    {
    	int a1 = 10, a2 = 20;
    	double d1 = 10.1, d2 = 20.2;
    
    	cout << Add(a1, d2) << endl;   // int,double  👆
    
    	return 0;
    }

    当然,这种问题严格意义上来说是不会用多个参数来解决的,

     这里只是想从语法上演示一下,我们还有更好地解决方式,我们继续往下看。

    ③ 我们还可以使用 "显式实例化" 来解决:

    Add<int>(a1, d2);     // 指定实例化成int
    Add<double>(a1, d2)   // 指定实例化成double

    我们下面先来详细介绍一下显式实例化,然后再回来看看它是如何解决的。

    0x03  模板的显式实例化

    📚 定义:在函数名后的 < > 里指定模板参数的实际类型。

    简单来说,显式实例化就是在中间加一个尖括号 < >  去指定你要实例化的类型。

    (在函数名和参数列表中间加尖括号)

    函数名 <类型> (参数列表);

    💬 代码:解决刚才的问题

    template<class T>
    T Add(const T& x, const T& y) {
    	return x + y;
    }
    
    int main(void)
    {
    	int a1 = 10, a2 = 20;
    	double d1 = 10.1, d2 = 20.2;
    	cout << Add(a1, a2) << endl;
    	cout << Add(d1, d2) << endl;
    
    	cout << Add<int>(a1, d2) << endl;     // 指定T用int类型
    	cout << Add<double>(a1, d2) << endl;  // 指定T用double类型
    
    	return 0;
    }
    

    🚩 运行结果:

    🔑  解读:

    像第一个 Add<int>(a1, a2)  ,a2 是 double,它就要转换成 int

    第二个 Add<double>(a1, a2),a1 是 int,它就要转换成 double

    这种地方就是类型不匹配的情况,编译器会尝试进行隐式类型转换。

    double int 这种相近的类型,是完全可以通过隐式类型转换的。

     如果无法成功转换,编译器将会报错。

    🔺 总结:

    函数模板你可以让它自己去推,但是推的时候不能自相矛盾。

    你也可以选择去显式实例化,去指定具体的类型。

    0x04  模板参数的匹配原则

    我们还是用刚才的 Add 函数模板来举例,现在我需要对整型的 a1 和 a2 进行加法操作:

    template<class T>
    T Add(const T& x, const T& y) {
    	return x + y;
    }
    
    int main(void)
    {
    	int a1 = 10, a2 = 20;	
    	cout << Add(a1, a2) << endl;
    
    	return 0;
    }

     我们是通过这个 Add 函数模板,生成 int 类型的加法函数的。

    💬 如果我们有一个现成的、专门用来处理 int 类型加法的函数:

    // 专门处理int的加法函数
    int Add(int x, int y) {
    	return x + y;
    }
    
    // 通用加法函数
    template<class T>
    T Add(const T& x, const T& y) {
    	return x + y;
    }
    
    int main(void)
    {
    	int a1 = 10, a2 = 20;	
    	cout << Add(a1, a2) << endl;
    
    	return 0;
    }

    ❓  思考:如果你是编译器,当 Add(a1, a2) 时你会选择用哪一个?

    是用函数模板印一个 int 类型的 Add 函数,还是用这现成的 Add 函数呢?

    我们继续往下看……

    📚 匹配原则:

    ① 一个非模板函数可以和一个同名的模板函数同时存在,

    而且该函数模板还可以被实例化为这个非模板函数:

    // 专门处理int的加法函数
    int Add(int x, int y) {
    	cout << "我是专门处理int的Add函数: ";
    	return x + y;
    }
    
    // 通用加法函数
    template<class T>
    T Add(const T& x, const T& y) {
    	cout << "我是模板参数生成的: ";
    	return x + y;
    }
    
    int main(void)
    {
    	int a1 = 10, a2 = 20;	
    	cout << Add(a1, a2) << endl;       // 默认用现成的,专门处理int的Add函数
    	cout << Add<int>(a1, a2) << endl;  // 指定让编译器用模板,印一个int类型的Add函数
    
    	return 0;
    }

    ② 对于非模板函数和同名函数模板,如果其他条件都相同,

    在调用时会优先调用非模板函数,而不会从该模板生成一个实例。

    如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

    // 专门处理int的加法函数
    int Add(int x, int y) {
    	cout << "我是专门处理int的Add函数: ";
    	return x + y;
    }
    
    // 通用加法函数
    template<class T1, class T2>
    T1 Add(const T1& x, const T2& y) {
    	cout << "我是模板参数生成的: ";
    	return x + y;
    }
    
    int main(void)
    {
    	cout << Add(1, 2) << endl;     // 用现成的
    	//(与非函数模板类型完全匹配,不需要函数模板实例化)
    
    	cout << Add(1, 2.0) << endl;   // 可以,但不是很合适,自己印更好
    	//(模板参数可以生成更加匹配的版本,编译器根据实参生产更加匹配的Add函数)
    
    	return 0;
    }

    Ⅳ.  类模板

    0x00  引入:和本篇开头本质上是一样的问题

    💬 就比如 Stack,如果我们定它是 int,那么它就是存整型的栈:

    class Stack {
    public:
    	Stack(int capacity = 4) 
    		: _top(0) 
    		, _capacity(capacity) {
    		_arr = new int[capacity];
    	}
    	~Stack() {
    		delete[] _arr;
    		_arr = nullptr;
    		_capacity = _top = 0;
    	}
    private:
    	int* _arr;
    	int _top;
    	int _capacity;
    };

    ❓ 如果我想改成存 double 类型的栈呢?

     当时我们在讲解数据结构的时候,是用 typedef 来解决的。

    typedef int STDataType;
    class Stack {
    public:
    	Stack(STDataType capacity = 4) 
    		: _top(0) 
    		, _capacity(capacity) {
    		_arr = new int[capacity];
    	}
    	~Stack() {
    		delete[] _arr;
    		_arr = nullptr;
    		_capacity = _top = 0;
    	}
    private:
    	STDataType* _arr;
    	int _top;
    	int _capacity;
    };
    

    如果需要改变栈的数据类型,直接改 typedef 那里就可以了。

    这依然是治标不治本,虽然看起来就像是支持泛型一样,

     它最大的问题是不能同时存储两个类型,你就算是改也没法解决:

    int main(void)
    {
    	Stack st1;   // 存int数据
    	Stack st2;   // 存double数据
    
    	return 0;
    }

    你只能做两个栈,如果需要更多的数据类型……

    那就麻烦了,你需要不停地CV做出各种数据类型版本的栈:

    class StackInt {...};
    class StackDouble {...};
    ……

    这和文章开头提到的问题(Swap)本质上是一个问题,就是不支持泛型。

    它们类里面的代码几乎是完全一样的,只是类型的不同。

     函数我们可以使用模板,类也是可以的,我们下面就来讲解一下类模板。

    0x01  类模板的定义格式

    📚 定义:和函数模板的定义方式是一样的,template 后面跟的是尖括号 < >

    template<class T1, class T2, ..., class Tn>
    class 类模板名 {
        类内成员定义
    }

    💬 代码:解决刚才的问题

    template<class T>
    class Stack {
    public:
    	Stack(T capacity = 4) 
    		: _top(0) 
    		, _capacity(capacity) {
    		_arr = new T[capacity];
    	}
    	~Stack() {
    		delete[] _arr;
    		_arr = nullptr;
    		_capacity = _top = 0;
    	}
    private:
    	T* _arr;
    	int _top;
    	int _capacity;
    };
    
    int main(void)
    {
    	Stack st1;   // 存储int
    	Stack st2;   // 存储double
    
    	return 0;
    }

      但是我们发现,类模板他好像不支持自动推出类型,

     它不像函数模板,不指定它也可以根据传入的实参去推出对应的类型的函数以供调用。

    函数模板之所以能推,是因为有实参传形参这么一个 "契机" ,让编译器能帮你推。

    你定义一个类,它能推吗?没这个能力你知道吧!

    所以这里只支持显示实例化,我们继续往下看。

    0x02  类模板实例化

     基于上面的原因,我们想要对类模板实例化,我们可以使用显示实例化。

     类模板实例化在类模板名字后跟 < >,然后将实例化的类型放在 < > 中即可。

    类名 <类型> 变量名;

    💬 代码演示:解决刚才的问题

    
    template<class T>
    class Stack {
    public:
    	Stack(T capacity = 4) 
    		: _top(0) 
    		, _capacity(capacity) {
    		_arr = new T[capacity];
    	}
    	~Stack() {
    		delete[] _arr;
    		_arr = nullptr;
    		_capacity = _top = 0;
    	}
    private:
    	T* _arr;
    	int _top;
    	int _capacity;
    };
    
    int main(void)
    {
    	Stack<int> st1;      // 指定存储int
    	Stack<double> st2;   // 指定存储double
    
    	return 0;
    }

    📌 注意事项:

    Stack 不是具体的类,是编译器根据被实例化的类型生成具体类的模具。

    template<class T>
    class Stack {...};

    类模板名字不是真正的类,而实例化的结果才是真正的类。

    Stack 是类名,Stack<int> 才是类型:

    Stack<int> s1;
    Stack<double> s2;

    0x03  类外定义类模板参数

    ❓ 思考问题:下面的 Push 为什么会报错?

    template<class T>
    class Stack {
    public:
    	Stack(T capacity = 4) 
    		: _top(0) 
    		, _capacity(capacity) {
    		_arr = new T[capacity];
    	}
    	// 这里我们让析构函数放在类外定义
    	void Push(const T& x);
    	~Stack();
    private:
    	T* _arr;
    	int _top;
    	int _capacity;
    };
    
    /* 类外 */
    
    void Stack::Push(const T& x) {   ❌
        ...
    }

    🔑 解答:

    ① Stack 是类名,Stack<int> 才是类型。这里要拿 Stack<T> 去指定类域才对。

    ② 类模板中的函数在类外定义,没加 "模板参数列表" ,编译器不认识这个 T 。类模板中函数放在类外进行定义时,需要加模板参数列表。

    这段代码第一个问题是没有拿 Stack<T> 去指定类域,

    最大问题其实是编译器压根就不认识这个T

    即使你用拿类型 Stack<T> 指定类域,编译器也一样认不出来:

    我们拿析构函数 ~Stack 来演示一下:

    template<class T>
    class Stack {
    public:
    	Stack(T capacity = 4) 
    		: _top(0) 
    		, _capacity(capacity) {
    		_arr = new T[capacity];
    	}
    
    	// 这里我们让析构函数放在类外定义
    	~Stack();
    private:
    	T* _arr;
    	int _top;
    	int _capacity;
    };
    
    /* 类外 */
    Stack<int>::~Stack() {    ❌  // 即使是指定类域也不行 
        ...
    }
    

    💬 代码演示:我们现在来看一下如何添加模板参数列表!

    template<class T>
    class Stack {
    public:
    	Stack(T capacity = 4) 
    		: _top(0) 
    		, _capacity(capacity) {
    		_arr = new T[capacity];
    	}
    
    	// 这里我们让析构函数放在类外定义
    	~Stack();
    private:
    	T* _arr;
    	int _top;
    	int _capacity;
    };
    
    // 类模板中函数放在类外进行定义时,需要加模板参数列表
    template <class T>
    Stack<T>::~Stack() {   // Stack是类名,不是类型! Stack<T> 才是类型,
    	delete[] _arr;
    	_arr = nullptr;
    	_capacity = _top = 0;
    }
    

     这样编译器就能认识了。

    本章完!


    📄 文章信息

    📌 [ 笔者 ]   王亦优
    📃 [ 更新 ]   2022.4.18
    ❌ [ 勘误 ]   暂无
    📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
                  本人也很想知道这些错误,恳望读者批评指正!
    

    📜 参考资料

    Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

    . C++reference[EB/OL]. []. http://www.cplusplus.com/reference/.

    百度百科[EB/OL]. []. https://baike.baidu.com/.

    比特科技. C++[EB/OL]. 2021[2021.8.31]. .

    展开全文
  • C++函数模板 我们知道,数据或数值可以通过函数参数传递,在函数定义时它们是未知的,只有在发生函数调用时才能确定其值。这就是数据的参数化。 其实,数据类型也可以通过参数来传递,在函数定义是可以不指明具体的...
  • 这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,终导致链接错误。  上面这句话有点抽象。要理解...
  • 目录前言C++函数模板的使用函数模板语法1.模板说明2.函数定义3.函数模板调用模板函数函数模板和函数重载 前言 C++提供了模板(template)编程的概念。所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的...

    前言

    C++提供了模板(template)编程的概念。所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的形参类型不具体指定,用一个虚拟的类型来代表。这种通用的方式称为模板。模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

    C++函数模板的使用

    为什么要有函数模板

    例如,在一个项目中,有个项目需求是能够实现多个函数用来返回两个数的最大值,要求能支持char类型、int类型、double类型变量。然后呢,根据这个需求,我们写了以下这个代码。

    #include <iostream>
    
    using namespace std;
    
    //比较int 类型
    int Max(int a, int b)
    {
    	return a > b ? a : b;
    }
    
    //比较char 类型
    char Max(char a, char b)
    {
    	return a > b ? a : b;
    }
    
    //比较float 类型
    float Max(float a, float b)
    {
    	return a > b ? a : b;
    }
    
    int main(void)
    {
    
    	int  n = 1;
    	int	 m = 2;
    	cout << "max(1, 2) = " << Max(n, m) << endl;
    
    	float a = 2.0;
    	float b = 3.0;
    	cout << "max(2.0, 3.0) = " << Max(a, b) << endl;
    
    	char i = 'a';
    	char j = 'b';
    	cout << "max('a', 'b') = " << Max(i, j) << endl;
    
    	return 0;
    }
    

    执行得到
    在这里插入图片描述
    我们看到,如果我们需要比较多个类型时,需要多个函数,而实际上我们只需要定义一个 “函数” 就能解决。

    #include <iostream>
    
    using namespace std;
    
    //template 关键字告诉C++编译器 要开始泛型编程了
    //T - 参数化数据类型
    template <typename T>
    T Max(T a, T b) {
    	return a > b ? a : b;
    }
    
    int main(void)
    {
    
    	int  n = 1;
    	int	 m = 2;
    	cout << "max(1, 2) = " << Max(n, m) << endl;
    
    	float a = 2.0;
    	float b = 3.0;
    	cout << "max(2.0, 3.0) = " << Max(a, b) << endl;
    
    	char i = 'a';
    	char j = 'b';
    	cout << "max('a', 'b') = " << Max(i, j) << endl;
    	
    	return 0;
    }
    

    得到的结果是一样的
    在这里插入图片描述
    我们在定义一个函数模板之后,,没有指定类型,编译器会实现参数类型的自动推导。
    在这里插入图片描述

    函数模板语法

    所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。

    凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

    函数模板定义形式
    由以下三部分组成: 模板说明 + 函数定义 + 函数模板调用

    template < 类型形式参数表 >
    类型 函数名 (形式参数表)
    {
    //语句序列
    }

    我们也可以定义多个类型

    template <typename T, typename T2>
    

    但是我们定义了一定要用,否则会报错
    在这里插入图片描述
    将代码改成

    #include <iostream>
    
    using namespace std;
    
    //template 关键字告诉C++编译器 要开始泛型编程了
    //T - 参数化数据类型
    template <typename T, typename T2>
    T Max(T a, T2 b) {
    	return a > b ? a : b;
    }
    
    int main(void){
    
    	int n = 6;
    	int m = 9;
    
    	cout << Max(n, m) << endl;
    
    	system("pause");
    	return 0;
    }
    

    在这里插入图片描述

    1.模板说明

    template < 类型形式参数表 >
    类型形式参数的形式:
    typename T1 , typename T2 , …… , typename Tn
    或 class T1 , class T2 , …… , class Tn
    注意: typenameclass 的效果是一样的,不过建议呢使用typename,为了防止用class有歧义)
    在这里插入图片描述

    2.函数定义

    类型 函数名 (形式参数表)
    {
    }
    注意: 模板说明的类属参数必须在函数定义中出现一次,函数参数表中可以使用类属类型参数,也可以使用一般类型参数

    3.函数模板调用

    Max<int,int>(a, b); //显式类型调用
    Max(a, b); //自动数据类型推导

    在这里插入图片描述
    我们再来看以下代码

    #include <iostream>
    
    using namespace std;
    
    //template 关键字告诉C++编译器 要开始泛型编程了
    //T - 参数化数据类型
    template <typename T, typename T2>
    T Max(T a, T2 b) {
    	return a > b ? a : b;
    }
    
    int main(void){
    
    	int n = 6;
    	int m = 9;
    	char a = 'c';	//'c' 对应的ascll码值是 99
    
    	cout << "Max<int, char>(m, a): " << Max<int, char>(m, a) << endl;	//显式类型调用
    	cout << "Max(m, a): " << Max(m, a) << endl;	//自动数据类型推导
    
    	cout << "Max(a, m): " << Max(a, m) << endl;	//自动数据类型推导
    	
    	return 0;
    }
    

    在这里插入图片描述
    为什么会这样呢?因为我们在定义函数模板的时候第一个类型作为了函数类型。
    在这里插入图片描述

    模板函数

    在这里插入图片描述
    也就是,在我们编译的时候,编译器内部会自动生成这些模板函数,然后再进行调用,当然,这些都是编译器内部生成的,我们可以不用去了解太深,我们知道这个概念就可以了。

    如果我们来比较类的两个对象

    #include <iostream>
    
    using namespace std;
    
    //定义一个类
    class Son {
    public:
    	Son(int age) { m_age = age; }
    	~Son(){}
    
    	int getAge() { return m_age; }
    
    
    private:
    	int m_age;
    };
    
    //template 关键字告诉C++编译器 要开始泛型编程了
    //T - 参数化数据类型
    template <typename T, typename T2>
    T Max(T a, T2 b) {
    	return a > b ? a : b;
    }
    
    int main(void){
    	Son s1(18);	
    	Son s2(21);
    
    	cout << "Max(s1, s2): " << Max(s1, s2).getAge() << endl;
    	/*
    	* 模板函数 在类的对象中, 不认识 > 这个符号
    	Son Max(Son s1, Son s2){
    		return a > b ? a : b;
    	}
    	*/
    	return 0;
    }
    

    就会出现这样的错误
    在这里插入图片描述
    因为在类对象的比较中,不认识这个 > 的符号,所以我们想要能成功执行,我们得在类中实现 > 的运算符重载

    #include <iostream>
    
    using namespace std;
    
    //定义一个类
    class Son {
    public:
    	Son(int age) { m_age = age; }
    	~Son(){}
    
    	int getAge() { return m_age; }
    
    	//重载 > 运算符
    	bool operator >(Son& res) {
    		if (this->m_age > res.m_age) return true;
    
    		return false;
    	}
    
    
    private:
    	int m_age;
    };
    
    //template 关键字告诉C++编译器 要开始泛型编程了
    //T - 参数化数据类型
    template <typename T, typename T2>
    T Max(T a, T2 b) {
    	return a > b ? a : b;
    }
    
    
    int main(void){
    	Son s1(18);	
    	Son s2(21);
    
    	cout << "Max(s1, s2): " << Max(s1, s2).getAge() << endl;
    	/*
    	* 模板函数 在类的对象中, 不认识 > 这个符号
    	Son Max(Son s1, Son s2){
    		return a > b ? a : b;
    	}
    	*/
    	return 0;
    }
    

    这样就可以执行得出结果了
    在这里插入图片描述

    函数模板和函数重载

    我们先来看一下这个demo

    #include <iostream>
    
    using namespace std;
    
    template <typename T>
    void Test(T& a, T& b) {
    	T c;
    	c = a;
    	a = b;
    	b = c;
    
    	cout << "Test 函数模板被调用了.." << endl;
    }
    
    void Test(int& a, int& b) {
    	int c;
    	c = a;
    	a = b;
    	b = c;
    
    	cout << "Test 普通函数被调用.." << endl;
    }
    
    
    int main(void) {
    	int n = 99;
    	int m = 65;
    
    	Test(n, m);
    	
    	return 0;
    }
    

    这是来观察,当存在函数模板和普通对应的函数的时候,会调用哪个函数
    在这里插入图片描述
    可以看到 当函数模板和普通函数并存的时候,参数类型会和普通重载函数更加匹配

    我们把普通函数注释掉,我们才能看到调用函数模板
    在这里插入图片描述
    如果普通函数和函数模板都在的情况下,我们非要使用函数模板,这个怎么实现呢,很简单,如果显式的使用函数模板,则使用<> 类型列表。
    在这里插入图片描述

    如果我们将一个传递的参数改为别的类型
    在这里插入图片描述
    不存在对应的普通函数,函数模板不会提供隐式的类型转换,必须是严格的类型匹配

    函数模板和普通函数区别结论:

    两者允许并存
    函数模板不允许自动类型转化

    如果有函数模板和普通函数的时候,普通函数的参数类型不不一致的时候,也会调用普通函数,因为会有隐式的转换,但是如果有函数模板的时候,函数模板会产生更好的匹配,使用函数模板。
    在这里插入图片描述
    有更好的函数模板的时候,会选择函数模板
    在这里插入图片描述

    嵌套使用函数模板

    在函数模板中,我们也可以嵌套使用函数模板。

    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    T Max(T a, T b)
    {
    	cout << "调用 T Max(T a, T b)" << endl;
    	return a > b ? a : b;
    }
    
    template <typename T>
    T Max(T a, T b, T c) {
    	cout << "调用 T Max(T a, T b, T c)" << endl;
    	return Max(Max(a, b), c);
    }
    
    int main(void) {
    	int a = 1;
    	int b = 2;
    	int c = 3;
    
    	Max(a, b, c);
    
    	system("pause");
    	return 0;
    }
    

    在这里插入图片描述
    注意:嵌套中的函数要声明在这个函数之前,否则编译器会找不到哪个函数,然后会报错

    函数模板和普通函数在一起,调用规则

    1 函数模板可以像普通函数一样被重载
    2 C++编译器优先考虑普通函数
    3 如果函数模板可以产生一个更好的匹配,那么选择模板
    4 可以通过空模板实参列表的语法限定编译器只通过模板匹配

    在Linux中反汇编查看函数模板被调用的机制

    在Linux创建一个cpp文件
    在这里插入图片描述
    保存退出之后编译生成指定的汇编的文件
    在这里插入图片描述
    然后查看这个汇编文件,找到我们熟悉的main函数
    在这里插入图片描述
    在这里插入图片描述
    再执行一个有函数模板的CPP文件
    在这里插入图片描述
    在main函数中调用了函数模板
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    从上面可以看出,我们只是定义了一个函数模板,而在调用的时候,内部会自动帮我们生成一个模板函数,然后再执行这个模板函数,得到我们想要的结果

    结论

    1. 编译器并不是把函数模板处理成能够处理任意类型的函数
    2. 编译器从函数模板通过具体类型产生不同的函数
    展开全文
  • 1.函数模板的声明和模板函数的生成 1.1函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函数的函数体重复设计。它的最大特点是把函数使用的数据类型作为参数。 函数模板的声明...
  • C++函数模板(模板函数)详解

    万次阅读 多人点赞 2019-07-04 16:03:01
    C++函数模板(模板函数)详解定义用法:函数模板的原理延申用法2.1为什么需要类模板2.2单个类模板语法2.3继承中的类模板语法案例1:案例2:2.4类模板的基础语法2.5类模板语法知识体系梳理1.所有的类模板函数写在类的...

    定义

    函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。(好吧,咱也听不懂,直接上用法吧?)

    用法:

    面向对象的继承和多态机制有效提高了程序的可重用性和可扩充性。在程序的可重用性方面,程序员还希望得到更多支持。举一个最简单的例子,为了交换两个整型变量的值,需要写下面的 Swap 函数:

    void Swap(int & x, int & y)
    {
        int tmp = x;
        x = y;
        y = tmp;
    }
    

    为了交换两个 double 型变量的值,还需要编写下面的 Swap 函数:

    void Swap (double & xr double & y)
    {
        double tmp = x;
        x = y;
        y = tmp;
    }
    

    如果还要交换两个 char 型变量的值,交换两个 CStudent 类对象的值……都需要再编写 Swap 函数。而这些 Swap 函数除了处理的数据类型不同外,形式上都是一样的。能否只写一遍 Swap 函数,就能用来交换各种类型的变量的值呢?继承和多态显然无法解决这个问题。因此,“模板”的概念就应运而生了。

    众所周知,有了“模子”后,用“模子”来批量制造陶瓷、塑料、金属制品等就变得容易了。程序设计语言中的模板就是用来批量生成功能和形式都几乎相同的代码的。有了模板,编译器就能在需要的时候,根据模板自动生成程序的代码。从同一个模板自动生成的代码,形式几乎是一样的。

    函数模板的原理

    C++ 语言支持模板。有了模板,可以只写一个 Swap 模板,编译器会根据 Swap 模板自动生成多个 Sawp 函数,用以交换不同类型变量的值。

    在 C++ 中,模板分为函数模板和类模板两种。

    • 函数模板是用于生成函数;
    • 类模板则是用于生成类的。

    函数模板的写法如下:

    template <class 类型参数1, class类型参数2, ...>
    返回值类型  模板名(形参表)
    {
        函数体
    }
    

    其中的 class 关键字也可以用 typename 关键字替换,例如:

    template <typename 类型参数1, typename 类型参数2, ...>
    

    函数模板看上去就像一个函数。前面提到的 Swap 模板的写法如下:

    template <class T>
    void Swap(T & x, T & y)
    {
        T tmp = x;
        x = y;
        y = tmp;
    }
    

    T 是类型参数,代表类型。编译器由模板自动生成函数时,会用具体的类型名对模板中所有的类型参数进行替换,其他部分则原封不动地保留。同一个类型参数只能替换为同一种类型。编译器在编译到调用函数模板的语句时,会根据实参的类型判断该如何替换模板中的类型参数。

    例如下面的程序:

    #include <iostream>
    using namespace std;
    template<class T>
    void Swap(T & x, T & y)
    {
        T tmp = x;
        x = y;
        y = tmp;
    }
    int main()
    {
        int n = 1, m = 2;
        Swap(n, m);  //编译器自动生成 void Swap (int &, int &)函数
        double f = 1.2, g = 2.3;
        Swap(f, g);  //编译器自动生成 void Swap (double &, double &)函数
        return 0;
    }
    

    编译器在编译到Swap(n, m);时找不到函数 Swap 的定义,但是发现实参 n、m 都是 int 类型的,用 int 类型替换 Swap 模板中的 T 能得到下面的函数:

    void Swap (int & x, int & y)
    {
        int tmp = x;
        x = y;
        y = tmp;
    }
    

    该函数可以匹配Swap(n, m);这条语句。于是编译器就自动用 int 替换 Swap 模板中的 T,生成上面的 Swap 函数,将该 Swap 函数的源代码加入程序中一起编译,并且将Swap(n, m);编译成对自动生成的 Swap 函数的调用。

    同理,编译器在编译到Swap(f, g);时会用 double 替换 Swap 模板中的 T,自动生成以下 Swap 函数:

    void Swap(double & x, double & y)
    {
        double tmp = x;
        x = y;
        y = tmp;
    }
    

    然后再将Swap(f, g);编译成对该 Swap 函数的调用。

    编译器由模板自动生成函数的过程叫模板的实例化。由模板实例化而得到的函数称为模板函数。在某些编译器中,模板只有在被实例化时,编译器才会检查其语法正确性。如果程序中写了一个模板却没有用到,那么编译器不会报告这个模板中的语法错误。

    编译器对模板进行实例化时,并非只能通过模板调用语句的实参来实例化模板中的类型参数,模板调用语句可以明确指明要把类型参数实例化为哪种类型。可以用:

    模板名<实际类型参数1, 实际类型参数2, ...>
    

    的方式告诉编译器应该如何实例化模板函数。例如下面的程序:

    #include <iostream>
    using namespace std;
    template <class T>
    T Inc(int n)
    {
        return 1 + n;
    }
    int main()
    {
        cout << Inc<double>(4) / 2;
        return 0;
    }
    

    Inc(4)指明了此处实例化的模板函数原型应为:

    double Inc(double);
    

    编译器不会因为实参 4 是 int 类型,就生成原型为 int Inc(int) 的函数。因此,上面程序输出的结果是 2.5 而非 2。

    函数模板中可以有不止一个类型参数。例如,下面这个函数模板的写法是合法的:

    template <class Tl, class T2>
    T2 print(T1 argl, T2 arg2)
    {
        cout << arg1 << " " << arg2 << endl;
        return arg2;
    }
    

    【实例】一个求数组中最大元素的函数模板
    例题:设计一个分数类 CFraction,再设计一个名为 MaxElement 的函数模板,能够求数组中最大的元素,并用该模板求一个 CFmction 数组中的最大元素。

    示例程序如下:

    #include <iostream>
    using namespace std;
    template <class T>
    T MaxElement(T a[], int size) //size是数组元素个数
    {
        T tmpMax = a[0];
        for (int i = 1; i < size; ++i)
            if (tmpMax < a[i])
                tmpMax = a[i];
        return tmpMax;
    }
    class CFraction //分数类
    {
        int numerator;   //分子
        int denominator; //分母
    public:
        CFraction(int n, int d) :numerator(n), denominator(d) { };
        bool operator <(const CFraction & f) const
        {//为避免除法产生的浮点误差,用乘法判断两个分数的大小关系
            if (denominator * f.denominator > 0)
                return numerator * f.denominator < denominator * f.numerator;
            else
                return numerator * f.denominator > denominator * f.numerator;
        }
        bool operator == (const CFraction & f) const
        {//为避免除法产生的浮点误差,用乘法判断两个分数是否相等
            return numerator * f.denominator == denominator * f.numerator;
        }
        friend ostream & operator <<(ostream & o, const CFraction & f);
    };
    ostream & operator <<(ostream & o, const CFraction & f)
    {//重载 << 使得分数对象可以通过cout输出
        o << f.numerator << "/" << f.denominator; //输出"分子/分母" 形式
        return o;
    }
    int main()
    {
        int a[5] = { 1,5,2,3,4 };
        CFraction f[4] = { CFraction(8,6),CFraction(-8,4),
            CFraction(3,2), CFraction(5,6) };
        cout << MaxElement(a, 5) << endl;
        cout << MaxElement(f, 4) << endl;
        return 0;
    }
    

    编译到第 41 行时,根据实参 a 的类型,编译器通过 MaxElement 模板自动生成了一个 MaxElement 函数,原型为:

    int MaxElement(int a[], int size);
    

    编译到第 42 行时,根据 f 的类型,编译器又生成一个 MaxElement 函数,原型为:

    CFraction MaxElement(CFraction a[], int size);
    

    在该函数中,用到了<比较两个 CFraction 对象的大小。如果没有对<进行适当的重载,编译时就会出错。

    从 MaxElement 模板的写法可以看出,在函数模板中,类型参数不但可以用来定义参数的类型,还能用于定义局部变量和函数模板的返回值。

    延申用法

    2.1为什么需要类模板

    • 类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:

      在这里插入图片描述

    • 类模板用于实现类所需数据的类型参数化

    • 类模板在表示如数组、表、图等数据结构显得特别重要,

    • 这些数据结构的表示和算法不受所包含的元素类型的影响

    2.2单个类模板语法

     1 //类的类型参数化 抽象的类
     2 //单个类模板
     3 template<typename T>
     4 class A 
     5 {
     6 public:
     7     A(T t)
     8     {
     9         this->t = t;
    10     }
    11 
    12     T &getT()
    13     {
    14         return t;
    15     }
    16 protected:
    17 public:
    18     T t;
    19 };
    20 void main()
    21 {
    22    //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
    23     A<int>  a(100); 
    24     a.getT();
    25     printAA(a);
    26     return ;
    27 }
    

    2.3继承中的类模板语法

    在这里插入图片描述

    案例1:

     1 //结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A<int> 
     2 //
     3 class B : public A<int>
     4 {
     5 public:
     6     B(int i) : A<int>(i)
     7     {
     8 
     9     }
    10     void printB()
    11     {
    12         cout<<"A:"<<t<<endl;
    13     }
    14 protected:
    15 private:
    16 };
    17 
    18 //模板与上继承
    19 //怎么样从基类继承  
    20 //若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数
    21 void pintBB(B &b)
    22 {
    23     b.printB();
    24 }
    25 void printAA(A<int> &a)  //类模板做函数参数 
    26 {
    27      //
    28     a.getT();
    29 }
    30 
    31 void main()
    32 {
    33     A<int>  a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 
    34     a.getT();
    35     printAA(a);
    36 
    37     B b(10);
    38     b.printB();
    39 
    40 
    41     cout<<"hello..."<<endl;
    42     system("pause");
    43     return ;
    44 }
    

    案例2:

     1 #include<iostream>
     2 using namespace std;
     3 //A编程模板类--类型参数化
     4 /*
     5 类模板的定义 类模板的使用 类模板做函数参数
     6 */
     7 template <typename T>
     8 class A
     9 {
    10 public:
    11     A(T a = 0)
    12     {
    13         this->a = a;
    14     }
    15 public:
    16     void printA()
    17     {
    18         cout << "a:" << a << endl;
    19     }
    20 protected:
    21     T a;
    22 private:
    23     
    24 };
    25 //从模板类派生时,需要具体化模板类,C++编译器需要知道父类的数据类型是什么样子的
    26 //要知道父类所占的内存多少
    27 class B :public A<int>
    28 {
    29 public:
    30     B(int a =10, int b =20):A<int>(a)
    31     {
    32         this->b = b;
    33     }
    34     void printB()
    35     {
    36         cout << "a:" << a << "b:" << b << endl;
    37     }
    38 protected:
    39 private:
    40     int b;
    41     
    42 };
    43 //从模板类派生模板类
    44 template <typename T>
    45 class C :public A<T>
    46 {
    47 
    48 public:
    49     C(T c,T a) : A<T>(a)
    50     {
    51         this->c = c;
    52     }
    53     void printC()
    54     {
    55         cout << "c:" << c << endl;
    56     }
    57 protected:
    58     T c;
    59 private:
    60     
    61 };
    62 
    63 void main()
    64 {
    65     //B b1(1, 2);
    66     //b1.printB();
    67     C<int> c1(1,2);
    68     c1.printC();
    69 }
    

    2.4类模板的基础语法

     1 #include<iostream>
     2 using namespace std;
     3 //A编程模板类--类型参数化
     4 /*
     5     类模板的定义 类模板的使用 类模板做函数参数
     6 */
     7 template <typename T>
     8 class A
     9 {
    10 public:
    11     A(T a = 0)
    12     {
    13         this->a = a;
    14     }
    15 public:
    16     void printA()
    17     {
    18         cout << "a:" << a << endl;
    19     }
    20 protected:
    21 private:
    22     T a;
    23 };
    24 //参数 C++编译器具体的类
    25 void UseA(A<int> &a)
    26 {
    27     a.printA();
    28 }
    29 void main()
    30 {
    31     //模板类本身就是抽象的,具体的类,具体的变量
    32     A<int> a1(11),a2(22),a3(33);//模板类是抽象的, 需要类型具体化
    33     //a1.printA();
    34 
    35     UseA(a1);
    36     UseA(a2);
    37     UseA(a3);
    38 }
    

    2.5类模板语法知识体系梳理

    1.所有的类模板函数写在类的内部

    代码:

    复数类:

     1 #include<iostream>
     2 using namespace std;
     3 template <typename T>
     4 class Complex
     5 {
     6 public:
     7     friend Complex MySub(Complex &c1, Complex &c2)
     8     {
     9         Complex tmp(c1.a-c2.a, c1.b-c2.b);
    10         return tmp;
    11     }
    12 
    13     friend ostream & operator<< (ostream &out, Complex &c3)
    14     {
    15         out << c3.a << "+" << c3.b <<"i"<< endl;
    16         return out;
    17     }
    18     Complex(T a, T b)
    19     {
    20         this->a = a;
    21         this->b = b;
    22     }
    23     Complex operator+(Complex &c2)
    24     {
    25         Complex tmp(a + c2.a, b + c2.b);
    26         return tmp;
    27     }
    28     void printCom()
    29     {
    30         cout << "a:" << a << " b:" << b << endl;
    31     }
    32 protected:
    33 private:
    34     T a;
    35     T b;
    36 };
    37 
    38 /*
    39     重载运算符的正规写法:
    40     重载左移<<  右移>> 只能用友元函数,其他的运算符重载都要用成员函数,不要滥用友元函数
    41 */
    42 //ostream & operator<< (ostream &out, Complex &c3)
    43 //{
    44 //    out<< "a:" << c3.a << " b:" << c3.b << endl;
    45 //    return out;
    46 //}
    47 void main()
    48 {
    49     Complex<int>     c1(1,2);
    50     Complex<int>     c2(3, 4);
    51 
    52     Complex<int> c3 = c1 + c2;//重载加号运算符
    53     
    54     c3.printCom();
    55 
    56     //重载左移运算符
    57     cout << c3 << endl;
    58     
    59     {
    60         Complex<int> c4 = MySub(c1 , c2);
    61         
    62         cout << c4 << endl;
    63     }
    64     system("pause");
    65 }
    

    2.所有的类模板函数写在类的外部,在一个cpp中

    注意:

    复制代码
    //构造函数 没有问题

    • 普通函数 没有问题

    • 友元函数:用友元函数重载 << >>

      friend ostream& operator<< (ostream &out, Complex &c3) ;

    • 友元函数:友元函数不是实现函数重载(非 << >>)

    1 需要在类前增加 类的前置声明 函数的前置声明

    template<typename T>
    
    class Complex;  
    
    template<typename T>
    
    Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
    

    2 类的内部声明 必须写成:

    friend Complex<T> mySub <T> (Complex<T> &c1, Complex<T> &c2);
    

    3 友元函数实现 必须写成:

    template<typename T>
    
      Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
    
    {
    
    Complex<T> tmp(c1.a - c2.a, c1.b-c2.b);
    
    return tmp;
    
    }
    

    4 友元函数调用 必须写成

    Complex<int> c4 = mySub<int>(c1, c2);
    
    cout<<c4;
    

    结论:友元函数只用来进行 左移 友移操作符重载。

    复数类:

    代码:

     1 #include<iostream>
     2 using namespace std;
     3 
     4 template<typename T>
     5 class Complex;
     6 template<typename T>
     7 Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
     8 
     9 template <typename T>
    10 class Complex
    11 {
    12 public:
    13     friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
    14 
    15     friend ostream & operator<< <T>(ostream &out, Complex &c3);
    16     Complex(T a, T b);
    17     void printCom();
    18     Complex operator+(Complex &c2);
    19     Complex operator-(Complex &c2);
    20     
    21 protected:
    22 private:
    23     T a;
    24     T b;
    25 };
    26 
    27 //构造函数的实现,写在了外部
    28 template <typename T>
    29 Complex<T>::Complex(T a, T b)
    30 {
    31     this->a = a;
    32     this->b = b;
    33 }
    34 
    35 template <typename T>
    36 void Complex<T>::printCom()
    37 {
    38     cout << "a:" << a << " b:" << b << endl;
    39 }
    40 //成员函数实现加号运算符重载
    41 template <typename T>
    42 Complex<T> Complex<T>::operator+(Complex<T> &c2)
    43 {
    44     Complex tmp(a + c2.a, b + c2.b);
    45     return tmp;
    46 }
    47 template <typename T>
    48 Complex<T> Complex<T>::operator-(Complex<T> &c2)
    49 {
    50     Complex(a-c2.a,a-c2.b);
    51     return tmp;
    52 }
    53 //友元函数实现<<左移运算符重载
    54 
    55 /*
    56 严重性    代码    说明    项目    文件    行    禁止显示状态
    57 错误    C2768    “operator <<”: 非法使用显式模板参数    泛型编程课堂操练    
    58 
    59 错误的本质:两次编译的函数头,第一次编译的函数头,和第二次编译的函数有不一样
    60 */
    61 template <typename T>
    62 ostream & operator<< (ostream &out, Complex<T> &c3)//不加T
    63 {
    64     out << c3.a << "+" << c3.b << "i" << endl;
    65     return out;
    66 }
    67 
    68 
    69 //
    70 template <typename T>
    71 Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
    72 {
    73     Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
    74     return tmp;
    75 }
    76 
    77 void main()
    78 {
    79     Complex<int>     c1(1, 2);
    80     Complex<int>     c2(3, 4);
    81 
    82     Complex<int> c3 = c1 + c2;//重载加号运算符
    83 
    84     c3.printCom();
    85 
    86     //重载左移运算符
    87     cout << c3 << endl;
    88 
    89     {
    90         Complex<int> c4 = mySub<int>(c1, c2);
    91 
    92         cout << c4 << endl;
    93     }
    94     system("pause");
    95 }
    

    所有的类模板函数写在类的外部,在不同的.h和.cpp中
    也就是类模板函数说明和类模板实现分开

    //类模板函数

    构造函数

    普通成员函数

    友元函数

    用友元函数重载<<>>;

    用友元函数重载非<< >>

    demo_complex.cpp

     1 #include"demo_09complex.h"
     2 #include<iostream>
     3 using namespace std;
     4 
     5 template <typename T>
     6 Complex<T>::Complex(T a, T b)
     7 {
     8     this->a = a;
     9     this->b = b;
    10 }
    11 
    12 template <typename T>
    13 void Complex<T>::printCom()
    14 {
    15     cout << "a:" << a << " b:" << b << endl;
    16 }
    17 //成员函数实现加号运算符重载
    18 template <typename T>
    19 Complex<T> Complex<T>::operator+(Complex<T> &c2)
    20 {
    21     Complex tmp(a + c2.a, b + c2.b);
    22     return tmp;
    23 }
    24 //template <typename T>
    25 //Complex<T> Complex<T>::operator-(Complex<T> &c2)
    26 //{
    27 //    Complex(a - c2.a, a - c2.b);
    28 //    return tmp;
    29 //}
    30 template <typename T>
    31 ostream & operator<< (ostream &out, Complex<T> &c3)//不加T
    32 {
    33     out << c3.a << "+" << c3.b << "i" << endl;
    34     return out;
    35 }
    36 
    37 
    38 //
    39 //template <typename T>
    40 //Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
    41 //{
    42 //    Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
    43 //    return tmp;
    44 //}
    

    demo_09complex.h

     1 #pragma once
     2 #include<iostream>
     3 using namespace std;
     4 template <typename T>
     5 class Complex
     6 {
     7 public:
     8     //friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
     9 
    10     friend ostream & operator<< <T>(ostream &out, Complex &c3);
    11     Complex(T a, T b);
    12     void printCom();
    13     Complex operator+(Complex &c2);
    14     //Complex operator-(Complex &c2);
    15 
    16 protected:
    17 private:
    18     T a;
    19     T b;
    20 };
    

    demo_09complex_text.cpp

     1 #include"demo_09complex.h"
     2 #include"demo_09complex.cpp"
     3 
     4 #include<iostream>
     5 using namespace std;
     6 
     7 void main()
     8 {
     9     Complex<int>     c1(1, 2);
    10     Complex<int>     c2(3, 4);
    11 
    12     Complex<int> c3 = c1 + c2;//重载加号运算符
    13 
    14     c3.printCom();
    15 
    16     //重载左移运算符
    17     cout << c3 << endl;
    18 
    19     /*{
    20         Complex<int> c4 = mySub<int>(c1, c2);
    21 
    22         cout << c4 << endl;
    23     }*/
    24     system("pause");
    25 }
    

    2.5总结

    归纳以上的介绍,可以这样声明和使用类模板:

    1. 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
    2. 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
    3. 在类声明前面加入一行,格式为:

      template <class 虚拟类型参数>
    
      如:
    
          template <class numtype> //注意本行末尾无分号
    
          class Compare
    
            {…}; //类体
    
    1. 用类模板定义对象时用以下形式:

     类模板名<实际类型名> 对象名;
    
          类模板名<实际类型名> 对象名(实参表列);
    
      如:
    
          Compare<int> cmp;
    
          Compare<int> cmp(3,7);
    
    1. 如果在类模板外定义成员函数,应写成类模板形式:

     template <class 虚拟类型参数>
    
         函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
    

    关于类模板的几点说明:

    1. 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:

      template <class T1,class T2>
      
       class someclass
      
       {…};
      

      在定义对象时分别代入实际的类型名,如:

      someclass<int,double> obj;

    2. 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。

    3. 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。

    2.6类模板中的static关键字

    从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
    每个模板类有自己的类模板的static数据成员副本

     1 #include<iostream>
     2 using namespace std;
     3 template <typename T>
     4 class AA
     5 {
     6 public:
     7     static T m_a;
     8 protected:
     9 private:
    10 };
    11 template <typename T>
    12 T AA<T>::m_a =0;
    13 void main()
    14 {
    15     AA<int> a1, a2, a3;
    16     a1.m_a = 10;
    17     a2.m_a++;
    18     a3.m_a++;
    19     cout << AA<int>::m_a << endl;
    20 
    21     AA<char> b1, b2, b3;
    22     b1.m_a = 'a';
    23     b2.m_a++;
    24     b3.m_a++;
    25     cout << AA<char>::m_a << endl;
    26 
    27     //m_a是每个类型的类,去使用,手工写两个类 int  char
    28     system("pause");
    29 }
    

    案例2:以下来自:C++类模板遇上static关键字

     1 #include <iostream>
     2 using namespace std;
     3 
     4 template<typename T>
     5 class Obj{
     6 public:
     7     static T m_t;
     8 };
     9 
    10 template<typename T>
    11 T Obj<T>::m_t = 0;
    12 
    13 int main04(){
    14     Obj<int> i1,i2,i3;
    15     i1.m_t = 10;
    16     i2.m_t++;
    17     i3.m_t++;
    18     cout << Obj<int>::m_t<<endl;
    19 
    20     Obj<float> f1,f2,f3;
    21     f1.m_t = 10;
    22     f2.m_t++;
    23     f3.m_t++;
    24     cout << Obj<float>::m_t<<endl;
    25 
    26     Obj<char> c1,c2,c3;
    27     c1.m_t = 'a';
    28     c2.m_t++;
    29     c3.m_t++;
    30     cout << Obj<char>::m_t<<endl;
    31 }
    

    当类模板中出现static修饰的静态类成员的时候,我们只要按照正常理解就可以了。static的作用是将类的成员修饰成静态的,所谓的静态类成员就是指类的成员为类级别的,不需要实例化对象就可以使用,而且类的所有对象都共享同一个静态类成员,因为类静态成员是属于类而不是对象。那么,类模板的实现机制是通过二次编译原理实现的。c++编译器并不是在第一个编译类模板的时候就把所有可能出现的类型都分别编译出对应的类(太多组合了),而是在第一个编译的时候编译一部分,遇到泛型不会替换成具体的类型(这个时候编译器还不知道具体的类型),而是在第二次编译的时候再将泛型替换成具体的类型(这个时候编译器知道了具体的类型了)。由于类模板的二次编译原理再加上static关键字修饰的成员,当它们在一起的时候实际上一个类模板会被编译成多个具体类型的类,所以,不同类型的类模板对应的static成员也是不同的(不同的类),但相同类型的类模板的static成员是共享的(同一个类)。

    2.7类模板在项目开发中的应用

    小结

    • 模板是C++类型参数化的多态工具。C++提供函数模板和类模板。

    • 模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。

    • 同一个类属参数可以用于多个模板。

    • 类属参数可用于函数的参数类型、返回类型和声明函数中的变量。

    • 模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。

    • 模板称为模板函数;实例化的类模板称为模板类。

    • 函数模板可以用多种方式重载。

    • 类模板可以在类层次中使用 。

    训练题

    1) 请设计一个数组模板类( MyVector ),完成对int、char、Teacher类型元素的管理。

    设计:

    • 类模板 构造函数 拷贝构造函数 << [] 重载=操作符

    • a2=a1

    • 实现

    2) 请仔细思考:

    a) 如果数组模板类中的元素是Teacher元素时,需要Teacher类做什么工作

    b) 如果数组模板类中的元素是Teacher元素时,Teacher类含有指针属性哪?

     1 class Teacher
     2 {
     3     friend ostream & operator<<(ostream &out, const Teacher &obj);
     4 public:
     5     Teacher(char *name, int age)
     6     {
     7         this->age = age;
     8         strcpy(this->name, name);
     9     }
    10 
    11     Teacher()
    12     {
    13         this->age = 0;
    14         strcpy(this->name, "");
    15     }
    16     
    17 private:
    18     int age;
    19     char name[32];
    20 };
    21 
    22 
    23 class Teacher
    24 {
    25     friend ostream & operator<<(ostream &out, const Teacher &obj);
    26 public:
    27     Teacher(char *name, int age)
    28     {
    29         this->age = age;
    30         strcpy(this->name, name);
    31     }
    32 
    33     Teacher()
    34     {
    35         this->age = 0;
    36         strcpy(this->name, "");
    37     }
    38     
    39 private:
    40     int age;
    41     char *pname;
    42 };
    

    结论1: 如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现深拷贝和浅拷贝的问题。

    结论2:需要Teacher封装的函数有:

    1. 重写拷贝构造函数

    2. 重载等号操作符

    3. 重载左移操作符。

    理论提高:

    所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。

    3) 请从数组模板中进行派生

     1 //演示从模板类 派生 一般类
     2 #include "MyVector.cpp"
     3 
     4 class MyArray01 : public MyVector<double>
     5 {
     6 public:
     7     MyArray01(int len) : MyVector<double>(len)
     8     {
     9         ;
    10     }
    11 protected:
    12 private:
    13 };
    14 
    15 
    16 //演示从模板类 派生 模板类 //BoundArray 
    17 template <typename T>
    18 class MyArray02 : public MyVector<T>
    19 {
    20 public:
    21     MyArray02(int len) : MyVector<double>(len)
    22     {
    23         ;
    24     }
    25 protected:
    26 private:
    27 };
    28 测试案例:
    29 
    30 //演示 从模板类 继承 模板类
    31 void main()
    32 {
    33     MyArray02<double> dArray2(10);
    34     dArray2[1] = 3.15;
    35 
    36 }
    37 
    38 
    39 //演示 从模板类 继承 一般类
    40 void main11()
    41 {
    42     MyArray01 d_array(10);
    43 
    44     for (int i=0; i<d_array.getLen(); i++)
    45     {
    46         d_array[i] = 3.15;
    47     }
    48 
    49     for (int i=0; i<d_array.getLen(); i++)
    50     {
    51         cout << d_array[i] << " ";
    52     }
    53 
    54     cout<<"hello..."<<endl;
    55     system("pause");
    56     return ;
    57 }
    

    作业:

    封装你自己的数组类;设计被存储的元素为类对象;

    思考:类对象的类,应该实现的功能。

    1. 优化Teacher类, 属性变成 char *panme, 构造函数里面 分配内存

    2. 优化Teacher类,析构函数 释放panme指向的内存空间

    3. 优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数

    4. 优化Teacher类,在Teacher增加 <<

    5. 在模板数组类中,存int char Teacher Teacher*(指针类型)

    zuoye.h

     1 #pragma once
     2 
     3 template <typename T>
     4 class MyVector
     5 {
     6 
     7     //friend ostream & operator<< <T>(ostream &out, const MyVector &obj);
     8 public:
     9     MyVector(int size = 0);//构造函数
    10     MyVector(const MyVector &obj);//copy构造函数
    11     ~MyVector();
    12 public:
    13     T& operator [](int index);
    14     MyVector &operator=(const MyVector &obj);
    15 
    16 
    17     int getLen()
    18     {
    19         return m_len;
    20     }
    21 protected:
    22 private:
    23     T *m_space;
    24     int m_len;
    25 
    26 };
    

    zuoye_test12.cpp

      1 #include"zuoye.h"
      2 #include"zuoye12.cpp"
      3 #include<iostream>
      4 using namespace std;
      5 class Teacher
      6 {
      7 public:
      8     Teacher()
      9     {
     10         age = 33;
     11         m_p = new char[1];
     12         strcpy(m_p, " ");
     13     }
     14 
     15     Teacher(char *name, int age)
     16     {
     17         this->age = age;
     18         m_p = new char[strlen(name)+1];
     19         strcpy(this->m_p, name);
     20 
     21     }
     22     Teacher(const Teacher &obj)
     23     {
     24         m_p = new char[strlen(obj.m_p) + 1];
     25         strcpy(this->m_p, obj.m_p);
     26         age = obj.age;
     27     }
     28     ~Teacher()
     29     {
     30         if (m_p!=NULL)
     31         {
     32             delete[] m_p;
     33             m_p = NULL;
     34         }
     35     }
     36     void printT()
     37     {
     38         cout << m_p << ", " << age;
     39     }
     40 public:
     41     //重载<< ==
     42     friend ostream & operator<<(ostream &out,Teacher &t);
     43     Teacher & operator=(const Teacher &obj) 
     44     {
     45         //1
     46         if (m_p!=NULL)
     47         {
     48             delete[] m_p;
     49             m_p = NULL;
     50             age = 33;
     51         }
     52         //2
     53         m_p = new char[strlen(obj.m_p) + 1];
     54         age = obj.age;
     55         //3
     56         strcpy(this->m_p, obj.m_p);
     57         return *this;
     58     }
     59 protected:
     60 private:
     61     int age;
     62     //char name[32];
     63     char *m_p;
     64 };
     65 ostream & operator<<(ostream &out, Teacher &t)
     66 {
     67     out << t.m_p << ", " << t.age << endl;
     68     return out;
     69 }
     70 
     71 void main()
     72 {
     73     Teacher t1("t1", 31), t2("t2", 32);
     74 
     75     MyVector<Teacher *> Tarray(2);
     76 
     77     Tarray[0] = &t1;
     78     Tarray[1] = &t2;
     79     for (int i = 0; i < 2; i++)
     80     {
     81         Teacher *tmp = Tarray[i];
     82         tmp->printT();
     83     }
     84     system("pause");
     85 }
     86 void main123()
     87 {
     88     Teacher t1("t1", 31), t2("t2", 32);
     89     MyVector<Teacher> Tarray(2);
     90     Tarray[0] = t1;
     91     Tarray[1] = t2;
     92     for (int i = 0; i < 2; i++)
     93     {
     94         Teacher tmp = Tarray[i];
     95         tmp.printT();
     96     }
     97     system("pause");
     98 }
     99 void main112()
    100 {
    101     MyVector<int>  myv1(10);
    102     myv1[0] = 'a';
    103     myv1[1] = 'b';
    104     myv1[2] = 'c';
    105     myv1[3] = 'd';
    106     myv1[4] = 'e';
    107     //cout << myv1;
    108     MyVector<int>  myv2 = myv1;
    109 }
    110 
    111 
    112 void main111()
    113 {
    114     MyVector<int>  myv1(10);
    115     for (int i = 0; i < myv1.getLen(); i++)
    116     {
    117         myv1[i] = i + 1;
    118         cout << myv1[i] << " ";
    119     }
    120 
    121     MyVector<int>  myv2 = myv1;
    122     for (int i = 0; i < myv2.getLen(); i++)
    123     {
    124         myv2[i] = i + 1;
    125         cout << myv2[i] << " ";
    126     }
    127 
    128     //cout << myv2 << endl;//重载左移运算符
    129 
    130     system("pause");
    131 }
    

    zuoye12.cpp

     1 #include"zuoye.h"
     2 #include<iostream>
     3 using namespace std;
     4 
     5 template <typename T>
     6 ostream & operator<<(ostream &out, const MyVector<T> &obj)
     7 {
     8     for (int i = 0; i<obj.m_len; i++)
     9     {
    10         out << obj.m_space[i] << " ";
    11     }
    12     out << endl;
    13     return out;
    14 }
    15 //构造函数
    16 template <typename T>
    17 MyVector<T>::MyVector(int size = 0)
    18 {
    19     m_space = new T[size];
    20     m_len = size;
    21 }
    22 //MyVector<int>  myv2 = myv1;
    23 template <typename T>
    24 MyVector<T>::MyVector(const MyVector &obj)
    25 {
    26     //根据大小分配内存
    27     m_len = obj.m_len;
    28     m_space = new T[m_len];
    29     //copy数据
    30     for (int i = 0; i<m_len; i++)
    31     {
    32         m_space[i] = obj.m_space[i];
    33     }
    34 }
    35 template <typename T>
    36 MyVector<T>::~MyVector()
    37 {
    38     if (m_space != NULL)
    39     {
    40         delete[] m_space;
    41         m_space = NULL;
    42         m_len = 0;
    43 
    44     }
    45 }
    46 template <typename T>
    47 T& MyVector<T>::operator [](int index)
    48 {
    49     return m_space[index];
    50 }
    51 template <typename T>
    52 MyVector<T> & MyVector<T>::operator=(const MyVector<T> &obj)
    53 {
    54     //先把a2的内存释放掉
    55     if (m_space != NULL)
    56     {
    57         delete[] m_space;
    58         m_space = NULL;
    59         m_len = 0;
    60 
    61     }
    62 
    63     //根据a1分配内存
    64     m_len = obj.m_len;
    65     m_space = new T[m_len];
    66 
    67     //copy数据
    68     for (i = 0; i<m_len; i += )
    69     {
    70         m_space[i] = obj.m_space[i];
    71     }
    72     return *this;//a2= a1 返回a2的自身
    73 }
    

    参考一些资料,加上一些见解,如果有雷同,纯属巧合。
    更多C++相关知识体系,请移步C++知识目录

    展开全文
  • C++之函数模板

    千次阅读 2021-11-28 11:47:49
    函数模板有类型自推的能力,使用函数模板可以不用传模板类型参数; 对于上边的程序: template<typename T> void Rectangle(T&x,T&y) { T s; s=x*y cout; } 这是函数模板 void Rectangle(int a,int b) { int s; s=a...
  • C++函数模板详解

    千次阅读 2022-04-26 19:08:04
    C++函数模板 在我们对不同数据类型的程序逻辑和操作相同时,使用函数模板会非常的方便,只需要定义单个函数模板函数模板实际上就是对数据类型进行了参数化。
  • C++模板(函数模板/类模板)

    千次阅读 多人点赞 2021-06-07 16:30:12
    函数模板 一.泛型编程 在引入泛型编程之前,我们先来看这样一个问题,怎么做到实现一个通用的交换函数呢? 在C语言阶段我们可能会像下面这样写,需要分别实现不同类型的交换函数,又由于C语言不允许出现同名函数,所以函数...
  • 函数模板参数

    千次阅读 2019-03-24 00:22:11
    1)从模板函数实参表获得的信息有矛盾之处。 template<typename T> void fun(T const& a,T const& b);但是你调用时却是fun(250,250.4);那你就必须写成fun<int>(250,250.4); 2)需要获得特定...
  • 函数模板---清晰的例子,通俗易懂(超详细)

    千次阅读 多人点赞 2021-04-15 16:39:09
    C++提供两种模板机制:函数模板和类模板 1.函数模板语法 函数模板作用: 建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表 语法: 1.template<typename T 2.函数的声明或...
  • 函数模板2.1 函数模板的概念2.2 函数模板的格式2.3 函数模板的原理2.4 函数模板的实例化2.4.1 隐式实例化:让编译器根据实参推演模板参数的实际类型2.4.2 显示实例化:在函数名后的<>中指定模板参数的实际...
  • 模板分为模板函数模板类。 如果是交换两个数据,我们会定义对应类型的函数,比如要交换int类型数据,我们会定义int类型swap函数,如果是交换double类型数据,会再定义double类型交换函数。 void Swap(int&...
  • C++中的函数模板

    万次阅读 多人点赞 2018-08-23 19:57:54
    之前我们知道的交换两个变量的方法有宏定义、函数,这两种方式都能实现两个变量的交换,但是各有各的优缺点 宏定义: - 优点:代码复用,适合所有的类型 - 缺点:缺少类型检查,宏在预处理阶段就被替换掉,编译器...
  • 函数模板的使用

    千次阅读 2021-08-29 21:41:42
    函数模板还是比较强大的,大家都知道函数模板可以增加代码的复用性,提高编程效率。但殊不知函数模板其实还分为三种情况,隐式实例化、显式实例化、显示具体化。下面逐一来说明。 (1)隐式实例化 最常使用的函数...
  • C++函数模板与类模板的区别

    万次阅读 多人点赞 2018-07-18 16:54:55
    C++ 除了支持函数模板,还支持类模板(Class Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。 声明...
  • 2、函数模板 2.1 语法 template<typename T> 函数的声明与定义 template --声明一个模板 typename --表明后面的符合是一种类型,可以使用class 代替 T -- 通用的数据类型 2.3 总结 函数模板利用关键字 ...
  • C++函数模板和函数重载

    千次阅读 2020-04-24 13:55:50
    所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的形参类型不具体指定,用一个虚拟的类型来代表。这种通用的方式称为模板模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码...
  • 定义模板——函数模板和类模板

    千次阅读 2021-03-15 18:21:19
    面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况。不同之处在于:OOP能处理类型在程序运行之前都未知的情况;而在泛型编程中,在编译时就能获知类型...一个模板就是一个创建类或函数的蓝图或者说...
  • C++ 函数模板(template)详解

    千次阅读 2021-10-27 19:13:34
    4.3如果函数模板会产生更好的匹配,使用函数模板 4.3函数模板和普通函数在一起,调用规则 5.函数模板调用机制 1.前言 C++提供了模板(template)编程的概念。所谓模板,实际上是建立一个通用函数或类,其类内部的...
  • C++ | 函数模板的重载(模板重载)

    千次阅读 2020-07-18 12:47:10
    所谓的函数模板的重载是指,普通函数的版本,函数模板的版本和函数模板特例化的版本可以共存,例如: //普通函数版本 bool Compare(char* a, char* b) { cout << "普通函数版本" << endl; return ...
  • C++函数模板和模板函数的区别

    千次阅读 2020-09-02 09:44:52
    函数模板:函数的重载能够实现一个函数名多用,将实现相同或者相似功能的函数用同一个函数名来定义,但是在程序中仍然要分别定义每一个函数。为进一步简化,C++提供了函数模板,实际上是建立一个通用函数,其函数...
  • C++模板-33-类模板和函数模板的区别

    千次阅读 2020-06-11 23:06:45
    这篇开始学习类模板相关知识,本篇主要学习什么是类模板,还有类模板和函数模板的区别 1.类模板语法 template <class T> 类 模板声明下面是函数就是函数模板,如果是类就叫类模板 2.一个类模板例子 ...
  • 实例004——使用函数模板实现不同数据类型的极值函数 实例004——使用函数模板实现不同数据类型的极值函数 实例004——使用函数模板实现不同数据类型的极值函数

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 677,208
精华内容 270,883
关键字:

函数模板