C++进阶 | 模版

模版分成函数模版和类模版
模板的精神其实很简单:参数化类型。 换句话说, 把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽出来 做成模板参数 T。

函数模版

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

语法格式:
template<typename T>
template<class T>
这两个哪个都行

多类型参数的话:
template<typename 类型参数表>
返回类型 函数模板名(函数参数列表) {
函数模板定义体
}

如:template<class t1,class t2...>

template 是语义是模板的意思,尖括号中先写关键字 typename 或是 class ,后 面跟一个类型 T,此类即是虚拟的类型。

#include <iostream>
using namespace std; 
template<typename T>     //一个template下只能有一个函数
void myswap(T &a, T &b)
{
    T t = a;
    a = b;
    b = t;
}
int main(void)
{
    int x=1;
    int y=2;
    myswap(x, y);
    cout <<"x: " << x <<", y:" << y <<endl;
    myswap<int>(x, y);
    cout <<"x: " << x <<", y:" << y <<endl;
    char a = 'a';
    char b = 'b';
    myswap(a, b);     //自动类型推导
    cout <<"a: " << a <<", b:" << b <<endl;
    myswap<char>(a, b);   //显式的指定类型
    cout <<"a: " << a <<", b:" << b <<endl;
    return 0;
}

函数模板,只适用于函数的参数个数相同而类型不同,且函数体相同的情况。 如果个数不同,则不能用函数模板。

函数模版和普通函数的区别__函数模版调用规则

函数模版和普通函数的区别:
函数模版不允许自动类型转化
普通函数能够进行自动类型转化

#include <iostream>
using namespace std;
template <typename T>
void myswap(T &a, T &b)
{
    T t;
    t = a;
    a = b;
    b = t;
    cout<<"myswap 模板函数do"<<endl;
}

void myswap(char  a, int  b)
{
    int t;
    t = a;
    a = b;
    b = t;
cout<<"myswap 普通函数do"<<endl;
}

int main() {
    char cData = 'a'; 
    int iData= 2;
    myswap(cData, iData);
    myswap(iData, cData);
    //普通函数会进行隐式的数据类型转换,但这时函数参数列表不能引用
    myswap<int>(cData, iData);
    // 函数模板不提供隐式的数据类型转换 必须是严格的匹配 
    return 0;
}

调用优先顺序:

  1. 当函数模板和普通函数都符合调用时,优先选择普通函数
  2. 若显示使用函数模板,则使用<> 类型列表。
    如:myswap<>(a,b);
  3. 如果 函数模板产生更好的匹配 使用函数模板
编译器对模板机制剖析

编译器编译过程:


20988794-e25babf3818722f5.png
屏幕快照 2020-02-06 上午1.57.40.png

g++ -E hello.c -o hello.i(预处理)
g++ -S hello.i -o hello.s(编译)
g++ -c hello.s -o hello.o(汇编)
g++ hello.o -o hello(链接)
以上四个步骤,可合成一个步骤 g++ hello.c -o hello(直接编译链接成可执行目标文件)

20988794-27651e1d580fd284.png
屏幕快照 2020-02-06 上午2.18.02.png
  1. 编译器并不是把函数模板处理成能够处理任意类的函数

  2. 编译器从函数模板通过具体类型产生不同的函数
    比如上图:三次调用Myadd,实际上只会从函数模版产生两种模版函数

  3. 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译; 在调用的地方对参数替换后的代码进行编译。
    先对函数模版编译一次,然后对产生的模版函数再编译一次。

类模版

类模版基本语法:
template<typename T>
class A
{
}

与函数模版不同的是:
函数模版在调用时可以自动类型推导
而类模版必须显式指定类型
如:A<int> a();

类模版派生普通类_类模版派生类模版
template<class T>
class A
{
public:
    A(T a) {
        this->a = a;
} 
protected:
  T a; 
 };

class B : public A<int>{...}  //派生普通类必须要显式指定类型
class B : public A<T>{...}  //派生类模版尖括号内填T

类模版的类内实现和类外实现

类内实现是函数体写在类中:

template <class T>
class Complex
{
    friend ostream & operator<<(ostream &os, Complex &c)
    {
        os << "( " << c.a << " + " << c.b << "i" << " )";
        return os; 
    }
public:
    Complex()
    { }
    Complex(T a, T b)
    {
        this->a = a;
        this->b = b; }
    void printComplex()
    {
        cout << "( " << a << " + " << b << "i" << " )" << endl;
    }
    Complex operator+(Complex &another)
    {
        Complex temp(a +another.a, b +another.b);
        return temp;
    }
private:
    T a;
    T b;
};

友元函数类内实现是默认全局的。

类模版的类外实现

普通函数的类外实现
包括友元函数的类外实现的注意事项

#include <iostream>
using namespace std;

template <class T>   //
class Complex;

template <class T>   //
ostream&  operator<<(ostream &os, Complex<T> &c); 



template <class T>
class Complex
{
public:
   friend ostream&  operator<< <T>(ostream &os, Complex<T> &c); 
    //下面的类外定义是模版函数,如果这里不加<>,则代表普通函数,在链接时会出错,因为找不到普通函数的定义
    //加了之后就是跟编译器说明这是模版函数
    //同时友元函数类外实现时,需要在这个类前面声明这个函数
    //滥用友元函数,本来可以当成员函数,却要用友元函数

Complex<T> mySub (Complex<T> &one, Complex<T> &another);
//最终的结论, 模板类 不要轻易写友元函数。但重载<<和>>必须要用到友元 
    Complex();
    Complex(T a, T b);  //普通函数,在类内不加<>的函数都是普通函数
    Complex operator+(Complex &another); 
    Complex operator-(Complex &another);
    void printComplex();
private:
    T a;
    T b;
};

template <class T>
Complex<T>::Complex()
{
}

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

template <class T>
void Complex<T>::printComplex()
{
    cout << "( " << a << " + " << b << "i" << " )" << endl;
}

template <class T>
Complex<T> Complex<T>::operator+(Complex<T> &another)
{
    Complex temp(a + another.a, b + another.b);
    return temp;
}

template <class T>
Complex<T> Complex<T>::operator-(Complex<T> &another)
{
    Complex temp(this->a - another.a, this->b = another.b);
    return temp;
}

//友元函数
template <class T>
ostream & operator<< (ostream &os, Complex<T> &c) {
    os << "( " << c.a << " + " << c.b << "i" << " )";
    return os; 
}

template <class T>
Complex<T> Complex<T>::mySub(Complex<T> &one, Complex<T> &another)
{
    Complex<T> temp(one.a - another.a, one.b - another.b);
    return temp;
}

int main(void)
{
    Complex<int> a(10, 20); //让模板类具体化是为了告诉编译具体的大小,分配内存 
    Complex<int> b(3, 4);
    a.printComplex();
    Complex<int> c;
    c = a + b;
    c.printComplex();
    cout << c << endl;
    c = c.mySub(a, b);
    cout << c << endl;
    return 0; 
}
类模版h和cpp分离编写分析

类模版声明写.h中,实现写.cpp中,在main.cpp中调用这个类模版,编译会错误,显示无法解析的外部命令,也就是发生链接错误。
原因:编译main.cpp中声明的.h编译时没有确定类的具体实现,在main函数内调用类模版的构造函数时编译器没有发现有这个函数的声明。于是就标记个符号,再让链接器去其它文件找。找到类模版的.cpp文件时,因为类模版要编译两次,第一次只是只是编译器的词法校验和分析,第二次并没有进行,所以没有生成具体的类。于是乎产生了错误。

解决方法:引入hpp文件。类模版.h声明,类模版.hpp写实现方法。
在main.cpp中。#include “模版.hpp”
之所以这样是为了让别人知道这是模版。增强可读性

类模版中的static成员
#include <iostream>
using namespace std;
template <class T>
class A{
    public:
        static T s_value;
};
template <class T>
T A<T>::s_value=60;
int main(){
    A<int> a,b;
    A<int> e;
    A<char> c,d;
    cout<<a.s_value<<" "<<b.s_value<<" "<<e.s_value<<endl;
    cout<<c.s_value<<" "<<d.s_value<<endl;
    a.s_value++;
    cout<<a.s_value<<" "<<b.s_value<<" "<<e.s_value<<endl;
    cout<<c.s_value<<" "<<d.s_value<<endl;
    return 0;
}
运行结果:
60 60 60
< <
61 61 61
< <

类模版实例化出A<int>和A<char> ,这两个类的static成员互不干涉。
但同属于类A<int> 的对象共享static成员

ps :void add(int &&a) 能对右值取引用,此时只能 add(3);
void add(int & a) 此时只能,a=3,add(a)
但是void add(const &a)可以解决两种情况,因为const &a 可以接受常量和变量

容器都是值寓意而非引用寓意,向容器中放入元素,都是放入元素的拷贝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值