菜鸟学习历程【32】函数模板与类模板

函数模板

所谓函数模板,实际上是建立一个通用函数,其函数类型形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

模板:将 算法数据类型 相分离;

定义:

template <typename T>
void mySwap(T &a, T &b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

对于上面的函数体,我想大家很熟悉,就是一个交换两个数的值的过程,但有所不同的是 并没有明确指明 形参的类型 都将这些类型用 T 来代替。如果将 T 全都替换为 int ,这就是一个实现整型数据交换值的函数,如果将 T 全部替换为 char ,这就是一个实现字符型数据交换值的函数。

那么,像这样的函数,我们成为 函数模板


使用规则

1.普通函数调用时支持类型的隐式转换,函数模板调用时不支持类型的隐式转换;
例如:

#include <iostream>

using namespace std;

// 普通函数 在被调用时 如果 实参类型 与 形参类型 不一致时 会自动转换
void print(int a, int b)
{
    cout << "a = " << a << ", b = " << b << endl;
}

int main()
{
    int a = 10, b = 20;
    char c = 'A';
    print(a, b);  // a = 10, b = 20
    print(a, c);  // a = 10, b = 65

    return 0;
}
#include <iostream>

using namespace std;

// 函数模板 不支持 参数类型的隐式转换 ,调用时 没有明确 指明哪一种类型 函数模板 不会进行转换
// 如果调用时 明确指明使用 哪一种数据类型 那么函数模板 会进行数据类型转化
template<typename T>
void print(T a, T b)
{
    cout << "a = " << a << ", b = " << b << endl;
}

int main()
{
    int a = 10, b = 20;
    char c = 'A';
    print(a, b);  
    print(a, c); // 编译报错:没有找到与参数列表 匹配的函数模板“print”实例
    //print<int>(a, c); // 编译通过,通过<int>告知编译器 去构造一个 int 型的模板函数 ,那么在使用时 系统会自动转换
    return 0;
}

2.函数模板和普通函数可以共存

1)当 函数模板 和 普通函数 都可以使用时,调用 普通函数。如果想调用 函数模板 ,可以用空参数列表 <> 进行说明;例如: print<>(a, b);
2)当 函数模板 提供一个更优的选择,优先调用 函数模板;

// 普通函数
void print(char c, int a)
{
    cout<<"a = "<< a <<", c = " << c << endl;
}

// 函数模板
template<typename T>
void print(T c, T a)
{
    cout<<"a = "<< a <<", c = " << c << endl;
}

int main()
{
    int a = 10, b = 20;
    // 普通函数也可以打印 a 与 b 的值,只是会进行隐式的类型转换
    // 由于 函数模板 的存在,对于 a 与 b 两个相同类型的 数据,编译器更愿意去 构造一个 int 类型的模板函数 供使用
    print(a, b); 

    return 0;
}

3.函数模板 可以重载

template <typename T>
void print(T a, T b, T c)
{
    cout<<"a = "<< a <<", b = "<< b <<", c = "<< c <<endl;
}

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

函数模板机制

编译器会对函数模板进行两次编译
1.在声明的地方对模板代码本身进行编译;
2.在调用的地方对参数替换后的代码进行编译。

说明:通过 函数模板 产生的函数 被称为 模板函数


类模板

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

模板类的语法

#include <iostream>

using namespace std;

template <typename T>
class AA
{
public:
    AA()
    {
    }
    AA(T a)
    {
        this->a = a;
    }
    void print ()
    {
        cout << "a = " << a << endl;
    }
private:
    T a;
};

int main()
{
    // 注意:函数模板支持隐式调用,类模板不支持隐式调用,必须说明模板类型
    AA<int> a(10);
    return 0;
}

类模板对象做函数参数传递时
1)写一个具体的 模板类 对象

template <typename T>
void print(AA<int> &a)
{
    a.print();

template <typename T>
void print(AA<double> &a)
{
    a.print();
}

2)使用函数模板

template <typename T>
void print(AA<T> &a)
{
    a.print();
}

继承时:
1)派生一个具体的类,继承自一个具体的模板类

class BB:public AA<int>
{
public:
    BB(int a, int b):AA(a)
    {
        this->b = b;
    }
private:
    int b;
};

2)派生一个 类模板

template <typename T>
class C:public AA<T>
{
public:
    C(int a, T c):AA(a)
    {

    }
private:
    T c;
};

类的实现

以 复数类 (Complex)为例进行说明
1.类的实现放在类的内部

nclude <iostream>

using namespace std;

template <typename T>
class Complex
{

    // 友元函数写到类的内部
    // 虽然写到了类的内部,但是该函数还是友元函数,不是类的内部函数
    friend ostream &operator << (ostream &out, Complex &c)
    {
        out << c.a << " + " << c.b << "i";
        return out;
    }

    friend Complex mySub(Complex &c1, Complex &c2)
    {
        Complex tmp(c1.a - c2.a, c1.b - c2.b);
        return tmp;
    }
public:
    Complex(T a = 0, T b = 0)
    {
        this->a = a;
        this->b = b;
    }

    Complex operator +(Complex &c)
    {
        Complex tmp(a + c.a, b + c.b);
        return tmp;
    }
private:
    T a;
    T b;
};

int main()
{
    Complex<int> c1(1, 2), c2(3, 4), c;


    // mySub的实现 虽然是写到了类的内部,但是它还是一个友元函数
    // 如果是类的内部函数 调用如下:  c1.mySub(c2)
    // 但因为 mySub  是友元函数,实际是一个外部的全局函数,所以只能用如下调用:mySub(c1, c2);
    c = mySub(c1, c2);

    cout << c1 << endl;
    cout << c << endl;

    c = c1 + c2;
    cout << c << endl;

    return 0;
}

2.类的实现放在类的外部(同一文件中)

类的成员函数在类的外部实现时,需要将成员函数写成函数模板;

#include <iostream>

using namespace std;

// 声明类
template <typename T>
class Complex;

// 函数声明
template <typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);

template <typename T>
class Complex
{
    friend ostream &operator<< <T>(ostream &out, Complex<T> &c);

    friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
public:
    Complex(T a = 0, T b = 0);
    Complex operator +(Complex &c);

private:
    T a;
    T b;
};

template <typename T>
Complex<T>::Complex(T a = 0, T b = 0)
{
    this->a = a;
    this->b = b;
}

template <typename T>
Complex<T> Complex<T>::operator +(Complex &c)
{
    Complex tmp(a + c.a, b + c.b);
    return tmp;
}

template <typename T>
ostream & operator << (ostream &out, Complex<T> &c)
{
    out << c.a << " + " << c.b << "i";
    return out;
}


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;
}

int main()
{
    Complex<int> c1(1, 2), c2(3, 4), c;
    c = mySub(c1, c2);

    cout << c1 << endl;
    cout << c << endl;

    c = c1 + c2;
    cout << c << endl;

    return 0;
}

3.类的实现放在类的外部(不同文件中)

只需要将函数实现“cpp”文件,改名为“hpp”作为 main.cpp 的头文件即可。

总结:
1)对于在类的内部可 直接实现 的函数,放在类的外部实现时,类中的原型声明不变;
例如:构造函数、运算符重载(除了 外部 实现的 那 4 种)

template <typename T>
Complex<T>::Complex(T a = 0, T b = 0);        //外部原型
//前面的 Complex<T> 说明是 Complex 的成员函数

Complex(T a = 0, T b = 0);                    //类中声明

template<typename T>
Complex<T> Complex<T>::operator+(Complex &c); // 外部原型
// 第一个 Complex<T> 是返回值
// 第二个 Complex<T> 说明是Complex 的成员函数

Complex operator+(Complex &c);                // 类中声明

2.对于只能通过 友元函数 实现的运算符重载函数,在类中声明时 需要在函数名后,“()”前加上 < T >,以及在类名后加上 < T >;在外部实现时,函数原型 只需要在类名后加上< T >

friend ostream operator << <T>(ostream &out, Complex<T> &obj);
//类中声明

template <typename T>
ostream operator << (ostream &out, Complex<T> &obj);
// 类外部函数原型

3.对于其他的友元函数(无法在类的内部,改变左值的),在类中声明时,在函数名后,“()”前加上< T >,以及在类名后 加上 < T >;在类的外部声明时, 需要在类的上面,对 类 和 函数 进行声明;

friend Complex<T> mySub<T>(Complex<T> &c1, Complex<T> &c2);
// 类中声明

template <typename T>   // 声明类
class Complex;

template <typename T>   // 声明函数
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);

template <typename T>   // 外部实现 函数原型
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);

类模板中的 static 关键字

对属于 同一类型的 模板类的对象 共享 static
对不属于 同一类型的 模板类的对象 不共享 static

#include <iostream>

using namespace std;

template <typename T>
class A
{
public:
    T a;
    static T sa; 
};

template <typename T>
T A<T>::sa = 1; 

int main()
{
    A<int>  a;
    a.sa = 10;

    A<double> d;
    cout << "sa = " <<d.sa << endl;            //  sa = 1

    cout << "sq = " <<A<int>::sa << endl;      //  sa = 10
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值