C++ 类模板

文章概述

  1. 类模板出现的原因;
  2. 几个重要概念的区分;
  3. 单个类模板语法:
  4. 继承中的类模板语法;
  5. 类模板语法知识体系梳理

类模板出现的原因

两个或者多个类的功能是相同的,仅仅是数据类型不同。类模板将数据类型和算法实现了分离。


几个重要概念的区分

  1. 函数模板和模板函数?
函数模板是一个产生函数的蓝图(抽象);
模板函数是函数模板的实例。
  1. 类模板和模板类
类模板是一个产生类的蓝图(抽象);
模板类是类模板的实例。

单个类模板语法

类模板是由模板说明和类说明构成的。

template <模板参数列表>
类声明

注意: 模板参数至少在类声明中出现一次; 类模板用于实现成员变量的参数化。

template<typename T>
class Tclass
{
public:
    T a;
public:
    Tclass(T a)
    {
        this->a = a;
    }
    T getA()
    {
        return a;
    }
};

int main()
{
    //创建实例
    Tclass<int> a(10);  //类模板是抽象的,需要用类型具体化,C++编译器才会分配内存,使用模板参数列表        
    cout << a.getA() << endl;
    return 0;
}

总结: (单个类模板怎么产生对象)
类模板(抽象:类型参数化)——(通过模板参数列表)——–>模板类————>定义具体的对象


<单个类模板做函数参数>

//模板类
template<typename T>
class Tclass
{
public:
    T a;
public:
    Tclass(T a)
    {
        this->a = a;
    }
    T getA()
    {
        return a;
    }
};

void UseR(Tclass<int>& a)
{
    cout << a.getA()<<endl;
}

int main()
{
    //创建实例,需要显示的说明什么类型
    Tclass<int> a(10);
    UseR(a);
    return 0;
}

总结: 类模板实例化创建对象或者做函数形参时,都需要C++编译器分配内存,所以需要知道怎么分配内存,即函数参数列表不能忽略。


继承中的类模板语法

<一. 从模板类A中派生普通类B>

//从模板类A中派生普通类B
template<typename T>
class TA
{
public:
    T a;
public:
    TA(int a)
    {
        this->a = a;
    }
};

class B:public TA<int>        //继承的是具体的类,不是抽象类(模板类,不是类模板)
{
public:
    int b;
public:
    B(int a,int b):TA<int>(a)  //TA<int>(a)需要调用父类的构造函数
    {
        this->b = b;
    }
    int getB()
    {
        return b;
    }
};

int main()
{
    B b(8,10);
    b.getB();
    return 0;
}

注意: 普通类继承模板类时,需要实例化类模板。C++编译器需要知道父类是什么类型的,才能分配内存空间。总而言之,类模板派生时,需要实例化。


<二. 从模板类A中派生模板类B>

//从模板类A中派生普通类B
template<typename T>
class TA
{
public:
    T a;
public:
    TA(int a)
    {
        this->a = a;
    }
};

template<typename T>
class B:public TA<T>
{
public:
    int b;
public:
    B(int a,int b):TA<T>(a)
    {
        this->b = b;
    }
    int getB()
    {
        return b;
    }
};

int main()
{
    B<int> b(8,10);
    cout << b.getB() << endl;
    return 0;
}

类模板语法知识体系梳理

针对类的普通函数,友元函数,构造函数的说明:

<一. 所有的类模板函数写在类的内部>

扫描二维码关注公众号,回复: 2713119 查看本文章
//所有的类模板函数写在类的内部
template<typename T>
class Test
{
    //友元函数(普通的友元函数)
    friend Test<T> sumMy(Test<T>& a,Test<T>& b)
    {
        Test<T> temp;
        temp.x = a.x + b.x;
        return temp;
    }
    //友元函数实现<<运算符(返回引用主要是链式编译)
    friend ostream& operator<<(ostream& out,Test<T>& a)
    {
        out << a.x ;
        return out;
    }
private:
    T x;
public:
    //构造函数
    Test(T x)
    {
        this->x = x;
    }
    Test()
    {
        this->x = 2;
    }
    //普通函数
    T getX()
    {
        return x;
    }
};


int main()
{
    Test<int> a(10);
    Test<int> b(20);

    cout << a << endl;
    Test<int> c = sumMy(a,b);
    cout << c << endl;
    return 0;
}

通过实例发现,不管是构造函数,普通函数,普通的友元函数或者友元函数重载运算符,写在函数的内部都没有问题。


<二. 所有的类模板函数写在类的外部(同一个文件中)>

(1). 先完成构造函数和普通函数

//所有的类模板函数写在类的内部
template<typename T>
class Test
{
private:
    T x;
public:
    //构造函数
    Test(T x);
    Test();
    //普通函数
    T getX();
};
//构造函数
template<typename T>
Test<T>::Test()
{
    this->x = 2;
}
template<typename T>
Test<T>::Test(T x)
{
    this->x = x;
}
//普通函数
template<typename T>
T Test<T>::getX()
{
    return x;
}

int main()
{
    Test<int> a(10);
    Test<int> b(20);

    cout<<a.getX();
    return 0;
}

编译之后,正常运行。说明构造函数和普通函数写在类的外部没有问题
(2). 友元函数重载运算符

//所有的类模板函数写在类的内部
template<typename T>
class Test
{
    //友元函数实现<<运算符(返回引用主要链式编译)
    friend ostream& operator<<(ostream& out, Test<T>& a);
private:
    T x;
public:
    //构造函数
    Test(T x);
    Test();
    //普通函数
    T getX();
};
//构造函数
template<typename T>
Test<T>::Test()
{
    this->x = 2;
}
template<typename T>
Test<T>::Test(T x)
{
    this->x = x;
}
//普通函数
template<typename T>
T Test<T>::getX()
{
    return x;
}
//友元函数
template<typename T>
ostream& operator<<(ostream& out, Test<T>& a)
{
    out << a.x;
    return out;
}

int main()
{
    Test<int> a(10);
    //使用,会发现报错
    cout << a << endl;
    return 0;
}

错误:

无法解析的外部符号 "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Test<int> &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@AAV?$Test@H@@@Z),该符号在函数 _main 中被引用    

这个错误表示,C++编译器无法根据friend ostream& operator<<(ostream& out, Test<T>& a);(函数头),找到函数的定义。

为什么会产生这个错误了???
归根结底是与函数模板的二次编译有关, 第一次编译看到这个函数,检查语法错误而已;

friend ostream& operator<<(ostream& out, Test<T>& a);

但是使用的时候(第二次编译)

cout << a << endl;

产生的函数的函数头与第一次编译看到的函数头不一样,所以C++编译器根据函数头找不到函数定义。
解决办法:

friend ostream& operator<<<T>(ostream& out, Test<T>& a);

(3).普通的友元函数

template<typename T>
class Test
{
    //友元函数(普通的友元函数)
    friend Test<T> sumMy(Test<T>& a, Test<T>& b);
    //友元函数实现<<运算符(返回引用主要是链式编译)
    friend ostream& operator<< <T> (ostream& out, Test<T>& a);
private:
    T x;
public:
    //构造函数
    Test(T x);
    Test();
    //普通函数
    T getX();
};
//构造函数
template<typename T>
Test<T>::Test()
{
    this->x = 2;
}
template<typename T>
Test<T>::Test(T x)
{
    this->x = x;
}
//普通函数
template<typename T>
T Test<T>::getX()
{
    return x;
}
//友元函数重载运算符
template<typename T>
ostream& operator<<(ostream& out, Test<T>& a)
{
    out << a.x;
    return out;
}
//普通的友元函数
//友元函数(普通的友元函数)
template<typename T>
Test<T> sumMy(Test<T>& a, Test<T>& b)
{
    Test<T> temp;
    temp.x = a.x + b.x;
    return temp;
}

int main()
{
    Test<int> a(10);
    Test<int> b(10);
    //这里会出现问题
    Test<int> c=sumMy(a,b);
    return 0;
}

错误的原因和友元函数重载运算符的一样,函数头找不到函数体。
解决办法:
a. 类前面添加前置声明:

//需要在类前增加 类的前置声明 函数的前置声明 
template<typename T>
class Test;
template<typename T>
Test<T> sumMy(Test<T>& a, Test<T>& b);

b. 类的内部普通的友元函数:

//友元函数(普通的友元函数)
friend Test<T> sumMy<T>(Test<T>& a, Test<T>& b);

c. 调用的时候:

Test<int> c=sumMy<int>(a,b);

改正后的代码:

//1)需要在类前增加 类的前置声明 函数的前置声明 
template<typename T>
class Test;
template<typename T>
Test<T> sumMy(Test<T>& a, Test<T>& b);

template<typename T>
class Test
{
    //友元函数(普通的友元函数)
    friend Test<T> sumMy<T>(Test<T>& a, Test<T>& b);
    //友元函数实现<<运算符(返回引用主要是链式编译)
    friend ostream& operator<< <T> (ostream& out, Test<T>& a);
private:
    T x;
public:
    //构造函数
    Test(T x);
    Test();
    //普通函数
    T getX();
};
//构造函数
template<typename T>
Test<T>::Test()
{
    this->x = 2;
}
template<typename T>
Test<T>::Test(T x)
{
    this->x = x;
}
//普通函数
template<typename T>
T Test<T>::getX()
{
    return x;
}
//友元函数重载运算符
template<typename T>
ostream& operator<<(ostream& out, Test<T>& a)
{
    out << a.x;
    return out;
}
//普通的友元函数
//友元函数(普通的友元函数)
template<typename T>
Test<T> sumMy(Test<T>& a, Test<T>& b)
{
    Test<T> temp;
    temp.x = a.x + b.x;
    return temp;
}

int main()
{
    Test<int> a(10);
    Test<int> b(10);
    Test<int> c=sumMy<int>(a,b);
    cout << c << endl;
    return 0;
}

<三. 所有的类模板函数写在类的外部(不在同一个文件中)>

//----------Testl.h文件
#include<iostream>
using namespace std;

//所有的类模板函数写在类的内部
template<typename T>
class Test
{
    //友元函数实现<<运算符(返回引用主要是链式编译)
    friend ostream& operator<< <T> (ostream& out, Test<T>& a);
private:
    T x;
public:
    //构造函数
    Test(T x);
    Test();
    //普通函数
    T getX();
};

//----------Testl.cpp文件
#include "stdafx.h"
#include "TestL.h"

template<typename T>
Test<T>::Test()
{
    this->x = 2;
}
template<typename T>
Test<T>::Test(T x)
{
    this->x = x;
}
//普通函数
template<typename T>
T Test<T>::getX()
{
    return x;
}
//友元函数重载运算符
template<typename T>
ostream& operator<<(ostream& out, Test<T>& a)
{
    out << a.x;
    return out;
}

//main函数文件
//注意: 这时候头文件应该改为: #include "TestL.cpp"(类模板的函数声明和定义
//应该放在一起)
#include "TestL.cpp"
int main()
{
    Test<int> a(10);
    return 0;
}

对于这个普通的友元函数未能找到解决办法。


总结:
a. 重载<<,>>运算符,我们应该使用友元函数,别的使用成员函数。(友元函数慎用)
b. 类模板中,最好将函数的定义和声明写在类的内部,其次是写在不同的文件。

猜你喜欢

转载自blog.csdn.net/wue1206/article/details/81485943