函数模板和类模板

一 函数模板的语法

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

// 函数模板定义形式
template < 类型形式参数表 >  // typename T 和 class T 都可以
类型 函数名( 形式参数表 )
{
    // 语句
}

// 例如
template <typename T, typename T>
void swap(T& num1, T& num2)
{
    T temp = num1;
    num1 = num2;
    num2 = temp;
}

// 函数模板的调用
int main()
{
    int a = 1;
    int b = 2;

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

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

二 函数模板和函数重载

  • 第一种情况,函数模板和普通函数共存,参数类型和普通重载函数都符合调用时,调用普通函数。
template <typename T>
void swap(T& a, T& b)
{
    cout << "void swap(T& a, T& b)" << endl;
    T temp = a;
    a = b;
    b = temp;
}

void swap(int& a, int& b)
{
    cout << "void swap(int& a, int& b)" << endl;
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    int a = 1;
    int b = 2;

    // 函数模板和普通函数并存,参数类型和普通重载函数都符合调用时,调用普通函数。
    swap(a, b);
    //swap<>(a, b);  // 如果显示使用函数模板,会报错:有多个重载函数示例与列表匹配

    return 0;
}

image

  • 第二种情况,不存在普通函数,函数模板不提供隐式的数据类型转换,必须是严格的匹配
template <typename T>
void swap(T& a, T& b)
{
    T temp = a;
    a = b;
    b = temp;
}

int main()
{
    int a = 1;
    char b = 'a';

    // 不存在普通重载函数,函数模板不提供隐式类型转换,必须是严格的匹配
    //swap(a, b);    // 报错:没有与参数列表匹配的实例

    return 0;
}
  • 第三种情况,如果函数模板会产生更好的匹配,使用函数模板
template <typename T>
void Swap(T& a, T& b)
{
    cout << "void Swap(T& a, T& b)" << endl;
    T temp = a;
    a = b;
    b = temp;
}

void Swap(int& a, int& b)
{
    cout << "void swap(int a, int b)" << endl;
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    char a = 'A';
    char b = 'a';

    // 如果函数模板会产生更好的匹配,使用函数模板
    Swap(a, b);   

    return 0;
}

image

函数模板和普通函数在一起,调用规则:
c++编译器优先考虑普通函数
如果函数模板可以产生一个更好的匹配,那么选择函数模板

三 函数模板调用机制

编译器并不是把函数模板处理成能够处理任意类型的函数,而是通过具体类型产生不同的函数

#include <iostream>

using namespace std;

template <typename T>
void Swap(T& a, T& b)
{
    cout << "void Swap(T& a, T& b)" << endl;
    T temp = a;
    a = b;
    b = temp;
}

int main()
{
    char a = 'A';
    char b = 'a';
    Swap(a, b);   

    int x = 1;
    int y = 2;
    Swap<int>(x, y);

    float f1 = 0.1f;
    float f2 = 0.2f;
    Swap(f1, f2);

    return 0;
}
//--------------------  汇编  ------------------
    14: int main()
    15: {
002F61F0 55                   push        ebp  
002F61F1 8B EC                mov         ebp,esp  
002F61F3 81 EC 0C 01 00 00    sub         esp,10Ch  
002F61F9 53                   push        ebx  
002F61FA 56                   push        esi  
002F61FB 57                   push        edi  
002F61FC 8D 7D B4             lea         edi,[ebp-4Ch]  
002F61FF B9 13 00 00 00       mov         ecx,13h  
002F6204 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
002F6209 F3 AB                rep stos    dword ptr es:[edi]  
002F620B A1 04 C0 2F 00       mov         eax,dword ptr [__security_cookie (02FC004h)]  
002F6210 33 C5                xor         eax,ebp  
002F6212 89 45 FC             mov         dword ptr [ebp-4],eax  
002F6215 B9 29 F0 2F 00       mov         ecx,offset _A62F0345_...\...\main@cpp (02FF029h)  
002F621A E8 6A B1 FF FF       call        @__CheckForDebuggerJustMyCode@4 (02F1389h)  
    16:     char a = 'A';
002F621F C6 45 F7 41          mov         byte ptr [a],41h  
    17:     char b = 'a';
002F6223 C6 45 EB 61          mov         byte ptr [b],61h  
    18:     Swap(a, b);   
002F6227 8D 45 EB             lea         eax,[b]  
002F622A 50                   push        eax  
002F622B 8D 4D F7             lea         ecx,[a]  
002F622E 51                   push        ecx  
002F622F E8 0E B2 FF FF       call        Swap<char> (02F1442h)    // Swap(a, b);
002F6234 83 C4 08             add         esp,8  
    19: 
    20:     int x = 1;
002F6237 C7 45 DC 01 00 00 00 mov         dword ptr [x],1  
    21:     int y = 2;
002F623E C7 45 D0 02 00 00 00 mov         dword ptr [y],2  
    22:     Swap<int>(x, y);
002F6245 8D 45 D0             lea         eax,[y]  
002F6248 50                   push        eax  
002F6249 8D 4D DC             lea         ecx,[x]  
002F624C 51                   push        ecx  
002F624D E8 FA B1 FF FF       call        Swap<int> (02F144Ch)  // Swap<int>(x, y);
002F6252 83 C4 08             add         esp,8  
    23: 
    24:     float f1 = 0.1f;
002F6255 F3 0F 10 05 4C 9B 2F 00 movss       xmm0,dword ptr [string "T1 Max(T1 a, T2 b)" (02F9B4Ch)]  
002F625D F3 0F 11 45 C4       movss       dword ptr [f1],xmm0  
    25:     float f2 = 0.2f;
002F6262 F3 0F 10 05 00 9C 2F 00 movss       xmm0,dword ptr [string "void Swap(T& a, T& b)" (02F9C00h)]  
002F626A F3 0F 11 45 B8       movss       dword ptr [f2],xmm0  
    26:     Swap(f1, f2);
002F626F 8D 45 B8             lea         eax,[f2]  
002F6272 50                   push        eax  
002F6273 8D 4D C4             lea         ecx,[f1]  
002F6276 51                   push        ecx  
002F6277 E8 D5 B1 FF FF       call        Swap<float> (02F1451h)  // Swap(f1, f2);
002F627C 83 C4 08             add         esp,8  
    27: 
    28:     return 0;
002F627F 33 C0                xor         eax,eax  
    29: }
002F6281 52                   push        edx  
002F6282 8B CD                mov         ecx,ebp  
002F6284 50                   push        eax  
002F6285 8D 15 B0 62 2F 00    lea         edx,ds:[2F62B0h]  
002F628B E8 A0 AF FF FF       call        @_RTC_CheckStackVars@8 (02F1230h)  
002F6290 58                   pop         eax  
002F6291 5A                   pop         edx  
002F6292 5F                   pop         edi  
002F6293 5E                   pop         esi  
002F6294 5B                   pop         ebx  
002F6295 8B 4D FC             mov         ecx,dword ptr [ebp-4]  
002F6298 33 CD                xor         ecx,ebp  
002F629A E8 E2 AE FF FF       call        @__security_check_cookie@4 (02F1181h)  
002F629F 81 C4 0C 01 00 00    add         esp,10Ch  
002F62A5 3B EC                cmp         ebp,esp  
002F62A7 E8 E8 AF FF FF       call        __RTC_CheckEsp (02F1294h)  
002F62AC 8B E5                mov         esp,ebp  
002F62AE 5D                   pop         ebp  
002F62AF C3                   ret  
002F62B0 06                   push        es  
002F62B1 00 00                add         byte ptr [eax],al  
002F62B3 00 B8 62 2F 00 F7    add         byte ptr [eax-8FFD09Eh],bh  
002F62B9 FF                   ?? ?????? 
002F62BA FF                   ?? ?????? 

四 类模板的定义

类模板由模板说明和类说明构成 模板说明同函数模板,如下:
template <类型形式参数表>
类声明

template <typename Type>
class Test
{
public:
    // 函数的参数列表使用虚拟类型
    Test(Type& data = 0)
    {
        this->data = data;
    }

    // 成员函数的返回值使用虚拟类型
    Type& getData()
    {
        return data;
    }

private:
    Type data;   // 数据成员使用虚拟类型
};

// 类模板对象作为函数参数,必须显示指定类型
void print(Test<int> t)
{
    cout << "print:" << t.getData() << endl;
}

int main()
{
    // 类模板定义对象,必须显示指定类型
    Test<int> t1(1);

    cout << "main:" << t1.getData() << endl;
    print(t1);

    return 0;
}

image

五 类模板在继承中的使用

继承中父子类和模板类的结合情况:
1.父类一般类,子类是模板类, 和普通继承的玩法类似
2.子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数(子类从模板类继承的时候,需要让编译器知道父类的数据类型具体是什么)
3.父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中

5.1 父类一般类,子类是模板类, 和普通继承的用法相似

#include <iostream>

using namespace std;

// 1.父类是一般类,子类是模板类,和普通继承的用法相似
class Father
{
public:
    Father(const char* name = "未知")
    {
        this->name = name;
    }

protected:
    string name;
};

template <typename Type>
class Son : public Father
{
public:
    Son(Type data = 0, const char* name = "未知") :Father(name)
    {
        this->data = data;
    }
    void print()
    {
        cout << "name:" << this->name << " data:" << this->data << endl;
    }

private:
    Type data;
};

int main()
{
    Son<int> s1(12, "小明");
    s1.print();
    return 0;
}

image

5.2 子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数

#include <iostream>

using namespace std;

// 2.子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数
template <typename Type>
class Father
{
public:
    Father(Type data = 0)
    {
        this->data = data;
    }

protected:
    Type data;
};

class Son : public Father<int>   // 要实例化父类的参数类型
{
public:
    Son(int data = 0, const char* name = "未知") :Father(data)
    {
        this->name = name;
    }

    void print()
    {
        cout << "name:" << this->name << " data:" << this->data << endl;
    }
private:
    string name;
};

int main()
{
    Son s1(12, "小明");
    s1.print();
    return 0;
}

image

5.3 父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中

#include <iostream>

using namespace std;

// 3.父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中
template <typename TypeFather>
class Father
{
public:
    Father(TypeFather data = 0)
    {
        this->fatherData = data;
    }

protected:
    TypeFather fatherData;
};

template <typename TypeSon>
class Son : public Father<TypeSon>  //子类的虚拟的类型可以传递到父类中
{
public:
    Son(TypeSon fatherData = 0, TypeSon sonData = 0) :Father<TypeSon>(fatherData)  //子类的虚拟的类型可以传递到父类中
    {
        this->sonData = sonData;
    }

    void print()
    {
        cout << "FatherData:" << this->fatherData << " SonData:" << this->sonData << endl;
    }
private:
    TypeSon sonData;
};

int main()
{
    Son<int> s1(10,12);
    s1.print();
    return 0;
}

image

六 类模板函数的三种表达描述方式

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

template <typename Type>
class Test
{
public:
    // 所有类模板函数写在类的内部
    Test(Type& data = 0)
    {
        this->data = data;
    }

    Type& getData()
    {
        return data;
    }

private:
    Type data; 
};

6.2 所有类模板函数写在类的外部,但和类声明在一个cpp中

#include <iostream>

using namespace std;

//所有类模板函数写在类的外部,但和类声明在一个cpp中
template <typename Type>
class Demo
{
public:
    Demo(Type data = 0);
    Type& getData() const;
    Demo& operator+(const Demo& other);
    void print() const;
private:
    Type data;
};

// 1.成员函数的实现前要声明: template <typename Type>
// 2.类的成员函数前的类限定域说明必须要带上虚拟参数列表 Demo<Type>::
template <typename Type>
Demo<Type>::Demo(Type data)
{
    this->data = data;
}

template <typename Type>
Type& Demo<Type>::getData() const
{
    return data;
}

// 3.返回的变量是模板类的对象时必须带上虚拟参数列表 Demo<Type>&
// 4.成员函数参数中出现模板类的对象时必须带上虚拟参数列表
template <typename Type>
Demo<Type>& Demo<Type>::operator+(const Demo<Type>& other)
{
    // 5.成员函数内部没有限定
    Demo temp;   //类的内部类型可以显示声明也可以不显示  
    //Demo<Type> temp;  // 也可以
    temp.data = this->data + other.data;
    return temp;
}

template <typename Type>
void Demo<Type>::print() const
{
    cout << "data:" << data << endl;
}

int main()
{
    Demo<int> d1(1);
    Demo<int> d2(2);
    d1 = d1 + d2;
    d1.print();
    return 0;
}

image

总结:
所有类模板函数写在类的外部,但和类声明在一个cpp中时,需要注意以下几点:
1.成员函数的实现前要声明: template <typename Type>
2.类的成员函数前的类限定域说明必须要带上虚拟参数列表 Demo<Type>::
3.返回的变量是模板类的对象时必须带上虚拟参数列表 Demo<Type>&
4.成员函数参数中出现模板类的对象时必须带上虚拟参数列表
5.成员函数内部没有限定

6.3 所有的类模板函数写在类的外部,在.h文件和对应的.cpp文件中

当类模板的声明(.h 文件)和实现(.cpp 或.hpp 文件)完全分离,因为类模板的特殊实现,我们应在使用类模板时使用#include 包含 实现部分的 .cpp文件 或 hpp 文件。(不成文的规定:类模板的实现部分命名为 .hpp 文件)

//--------------------- Demo.h -------------------
#pragma once

template <typename Type>
class Demo
{
public:
    Demo(Type data = 0);
    Type& getData() const;
    Demo& operator+(const Demo& other);
    void print() const;
private:
    Type data;
};

//--------------------- Demo.hpp -------------------
#include "Demo.h"
#include <iostream>
using namespace std;

template <typename Type>
Demo<Type>::Demo(Type data)
{
    this->data = data;
}

template <typename Type>
Type& Demo<Type>::getData() const
{
    return data;
}

template <typename Type>
Demo<Type>& Demo<Type>::operator+(const Demo<Type>& other)
{
    Demo temp;  
    //Demo<Type> temp;  // 也可以
    temp.data = this->data + other.data;
    return temp;
}

template <typename Type>
void Demo<Type>::print() const
{
    cout << "data:" << data << endl;
}

//--------------------- main.cpp -------------------
#include "Demo.hpp"  // 注意这里要包含 hpp 文件 

int main()
{
    Demo<int> d1(1);
    Demo<int> d2(2);
    Demo<int> d3 = d1 + d2;
    d3.print();

    return 0;
}

image

七 当类模板遇上友元函数

建议:尽量不用友元

//----------------  Demo.h  ------------------
#pragma once

template <typename Type>
class Demo
{
public:
    Demo(Type data = 0);
    Demo operator+(const Demo& other);
    void print() const;

    // 注意:1.类内声明友元函数,必须写成以下形式
    template <typename Type> // 友元函数,实现两个Demo类对象相加
    friend Demo<Type> Add(const Demo<Type>& demo, const Demo<Type>& other); 
private:
    Type data;
};

//----------------  Demo.hpp  ------------------
#include "Demo.h"
#include <iostream>
using namespace std;

template <typename Type>
Demo<Type>::Demo(Type data)
{
    this->data = data;
}

template <typename Type>
Demo<Type> Demo<Type>::operator+(const Demo<Type>& other)
{
    return Demo(this->data + other.data);
}

template <typename Type>
void Demo<Type>::print() const
{
    cout << "data:" << data << endl;
}

// 注意:2.友元函数的实现必须写成以下形式
template <typename Type>
Demo<Type> Add(const Demo<Type>& demo, const Demo<Type>& other)
{
    //Demo<Type> temp; // 友元函数实现中如果要用,必须显示声明
    return Demo<Type>(demo.data + other.data);
}

//----------------  main.cpp  ------------------
#include "Demo.hpp"

int main()
{
    Demo<int> d1(1);
    Demo<int> d2(2);

    Demo<int> d3 = d1 + d2;
    d3.print();
    
    // 注意:3.友元函数的调用,必须写成以下形式
    Demo<int> d4 = Add<int>(d1, d2);
    d4.print();

    return 0;
}

image

八 类模板和静态成员

从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享 一个 static 数据成员
static 数据成员也可以使用虚拟类型参数 T

//--------------  Demo.h  ---------------
#pragma once

template <typename Type>
class Demo
{
public:
    Demo(Type data = 0);
public:
    static int count;
private:
    Type data;
};

//--------------  Demo.hpp  ---------------
#include "Demo.h"
#include <iostream>
using namespace std;

// 初始化静态成员
template <typename Type> 
int Demo<Type>::count = 0;

template <typename Type>
Demo<Type>::Demo(Type data)
{
    this->data = data;
}

//--------------  main.cpp  ---------------
#include "Demo.hpp"

int main()
{
    Demo<int> d1(1);
    Demo<int> d2(2);
    Demo<float> d3(3.0);
    Demo<float> d4(4.0);

    cout << "d1的count:" << d1.count << endl;
    cout << "d2的count:" << d2.count << endl;
    cout << "d3的count:" << d3.count << endl;
    cout << "d4的count:" << d4.count << endl;

    cout << "------- d1.count = 100 后 ----------" << endl;
    d1.count = 100;
    cout << "d1的count:" << d1.count << endl;
    cout << "d2的count:" << d2.count << endl;
    cout << "d3的count:" << d3.count << endl;
    cout << "d4的count:" << d4.count << endl;

    cout << "------- d3.count = 200 后 ----------" << endl;
    d3.count = 200;
    cout << "d1的count:" << d1.count << endl;
    cout << "d2的count:" << d2.count << endl;
    cout << "d3的count:" << d3.count << endl;
    cout << "d4的count:" << d4.count << endl;
    return 0;
}

image

九 类模板和友元函数

// 类模板
template <typename T>
class myVector
{
public:
    // 友元函数, 这里注意要加上 template <typename T>
    template <typename T>
    friend ostream& operator<<(ostream& os, const myVector<T>& other);
	
private:
    int m_len;
    T* m_base;
};

template <typename T>
ostream& operator<</*<T>*/(ostream& os, const myVector<T>& other)
{
    for (int i = 0; i < other.m_len; i++)
    {
        os << other.m_base[i] << " ";
    }
    return os;
}

// 普通类
class Student
{
public:
    // 友元函数
    friend ostream& operator<<(ostream& os, const Student& student);
private:
    int m_age;
    char m_name[64];
};

// 这里要加上inline
inline ostream& operator<<(ostream& os, const Student& student)  
{
    os << "姓名:" << student.m_name << " 年龄:" << student.m_age << endl;
    return os;
}

因为我们在模板类中用友元函数重载了运算符<<,在普通函数中再重载运算符<<的时候要注意加上inline,否则会报以下错误:
image

posted @ 2022-05-13 22:33  荒年、  阅读(412)  评论(0编辑  收藏  举报