IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++ 模板 -> 正文阅读

[C++知识库]C++ 模板

C++模版的诞生

程序本质是数据结构+算法,任何一门语言都可以这样理解,这个公式对计算机科学的影响程度足以类似物理学中爱因斯坦的“E=MC^2”——一个公式展示出了程序的本质。

最初C++是没有标准库的,任何一门语言的发展都需要标准库的支持,为了让C++更强大,更方便使用,Bjarne Stroustrup觉得需要给C++提供一个标准库,但标准库设计需要一套统一机制来定义各种通用的容器(数据结构)和算法,并且能很好在一起配合,这就需要它们既要相对的独立,又要操作接口保持统一,而且能够很容易被别人使用(用到实际类中),同时又要保证开销尽量小(性能要好)。Bjarne Stroustrup 提议C++需要一种机制来解决这个问题,所以就催生了模板的产生,最后经标准委员会各路专家讨论和发展,就发展成如今的模版, C++ 第一个正式的标准也加入了模板。

C++模版是一种解决方案,初心是提供参数化容器类和通用的算法(函数),目的就是为了减少重复代码,让通用性和高性能并存,提高C++程序员生产力。

C++模板的实现

C++标准委员会采用一套类似函数式语言的语法来设计C++模板,而且设计成图灵完备 (Turing-complete)(详见参考),我们可以把C++模板看成是一种新的语言,而且可以看成是函数式编程语言,只是设计依附在(借助于)C++其他基础语法上(类和函数)。
在这里插入图片描述
C++ 的泛型编程是基于模板实现的,而 C++ 的模板采用的是代码膨胀技术。例如 std::list 容器,如果你将 int 类型的数据存进去,C++ 编译器就为你生成一个专门用来存 int 类型数据的列表数据结构。也就是说,你向 std::list 容器中存放什么类型,C++ 编译器就为你生成相应的列表数据结构。理论上,数据的类型是无限的,因此 C++ 要生成的列表数据结构也是无限的。如果你的程序中有大量的数据类型要存到 std::list 容器,那么代码就会高度膨胀,这种膨胀是 C++ 编译器在目标文件连接阶段无法优化的。

C++实现类模板(class template)技术

  1. 定义模板类,让每个模板类拥有模板签名。
template<typename T>class X{...};

上面的模板签名可以理解成:X; 主要包括模板参数和模板名字X(类名)

模板参数在形式上主要包括四类,为什么会存在这些分类,主要是满足不同类对参数化的需求:

type template parameter: 类型模板参数,以class或typename 标记;此类主要是解决朴实的参数化类的问题(上面描述的问题),也是模板设计的初衷。

non-type template parameter: 非类型模板参数,比如整型,布尔,枚举,指针,引用等;此类主要是提供给大小,长度等整型标量参数的控制,其次还提供参数算术运算能力,这些能力结合模板特化为模板提供了初始化值,条件判断,递归循环等能力,这些能力促使模板拥有图灵完备的计算能力。

template template parameter,模板参数是模板,此类参数需要依赖其他模板参数(作为自己的入参),然后生成新的模板参数,可以用于策略类的设计policy-base class。

parameter pack,C++11的变长模板参数,此类参数是C++11新增的,主要的目的是支持模板参数个数的动态变化,类似函数的变参,但有自己独有语法用于定义和解析(unpack),模板变参主要用于支持参数个数变化的类和函数,比如std::bind,可以绑定不同函数和对应参数,惰性执行,模板变参结合std::tuple就可以实现。

  1. 在用模板类声明变量的地方,把模板实参(Arguments)(类型)带入模板类,然后按照匹配规则进行匹配,选择最佳匹配模板. 模板实参和形参类似于函数的形参和实参,模板实参只能是在编译时期确定的类型或者常量,C++17支持模板类实参推导。

  2. 选好模板类之后,编译器会进行模板类实例化–记带入实际参数的类型或者常量自动生成代码,然后再进行通常的编译。

C++实现模板函数(function template)技术

模板函数实现技术和模板类形式上差不多:

template<typename T>
retType function_name(T t)

其中几个关键点:

函数模板的签名包括模板参数,返回值,函数名,函数参数, cv-qualifier;

函数模板编译顺序大致:名称查找(可能涉及参数依赖查找)->实参推导->模板实参替换(实例化,可能涉及 SFINAE)->函数重载决议->编译;

函数模板可以在实例化时候进行参数推导,必须知道每个模板的实参,但不必指定每个模板的实参。编译器会从函数实参推导缺失的模板实参。这发生在尝试调用函数、取函数模板地址时,和某些其他语境中;

函数模板在进行实例化后会进行函数重载解析, 此时的函数签名不包括返回值(template argument deduction/substitution);

函数模板实例化过程中,参数推导不匹配所有的模板或者同时存在多个模板实例满足,或者函数重载决议有歧义等,实例化失败;

为了编译函数模板调用,编译器必须在非模板重载、模板重载和模板重载的特化间决定一个无歧义最佳的模板;

C++模板的核心技术

1. SFINAE -Substitution failure is not an error

要理解这句话的关键点是failure和error在模板实例化中意义,模板实例化时候,编译器会用模板实参或者通过模板实参推导出参数类型带入可能的模板集(模板备选集合)中一个一个匹配,找到最优匹配的模板定义,

Failure:在模板集中,单个匹配失败;

Error:在模板集中,所有的匹配失败;

所以单个匹配失败,不能报错误,只有所有的匹配都失败了才报错误。

2. 模板特化

模板特化为了支持模板类或者模板函数在特定的情况(指明模板的部分参数(偏特化)或者全部参数(完全特化))下特殊实现和优化,而这个机制给与模板某些高阶功能提供了基础,比如模板的递归(提供递归终止条件实现),模板条件判断(提供true或者false 条件实现)等。

3. 模板实参推导

模板实参推导机制给与编译器可以通过实参去反推模板的形参,然后对模板进行实例化,具体推导规则见参考;

4. 模板计算

模板参数支持两大类计算:

一类是类型计算(通过不同的模板参数返回不同的类型),此类计算为构建类型系统提供了基础,也是泛型编程的基础;
一类是整型参数的算术运算, 此类计算提供了模板在实例化时候动态匹配模板的能力;实参通过计算后的结果作为新的实参去匹配特定模板(模板特化)。

5. 模板递归

模板递归是模板元编程的基础,也是C++11变参模板的基础。

函数模板

函数模版的定义

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

template 就是模板定义的关键词,T 代表的是任意变量的类型

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

那么定义好「函数模板」后,在编译的时候,编译器会根据传入 Swap 函数的参数变量类型,自动生成对应参数变量类型的 Swap 函数:

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

上面的实例化函数模板的例子,是让编译器自己来判断传入的变量类型,那么我们也可以自己指定函数模板的变量类型,具体代码如下:

int main()
{
    int n = 1,m = 2;
    Swap<int>(n,m);     // 指定模板函数的变量类型为int

    double f = 1.2,g = 2.3;
    Swap<double>(f,g); // 指定模板函数的变量类型为double

    return 0;
}

多个类型参数模板函数

函数模板中,可以不止一个类型的参数:

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

函数模板的重载

函数模板可以重载,只要它们的形参表或类型参数表不同即可。

// 模板函数 1
template<class T1, class T2>
void print(T1 arg1, T2 arg2) 
{
    cout<< arg1 << " "<< arg2<<endl;
}

// 模板函数 2
template<class T>
void print(T arg1, T arg2) 
{
    cout<< arg1 << " "<< arg2<<endl;
}

// 模板函数 3
template<class T,class T2>
void print(T arg1, T arg2) 
{
    cout<< arg1 << " "<< arg2<<endl;
}

上面都是 print(参数1, 参数2) 模板函数的重载,因为「形参表」或「类型参数表」名字不同。

函数模板和函数的次序

在有多个函数和函数模板名字相同的情况下,编译器如下规则处理一条函数调用语句: 1. 先找参数完全匹配的普通函数(非由模板实例化而得的函数); 2. 再找参数完全匹配的模板函数; 3. 再找实参数经过自动类型转换后能够匹配的普通函数; 4. 上面的都找不到,则报错。

// 模板函数 - 1个参数类型
template <class T>
T Max(T a, T b) 
{
    cout << "TemplateMax" <<endl; return 0;
}

// 模板函数 - 2个参数类型
template <class T, class T2>
T Max(T a, T2 b) 
{
    cout << "TemplateMax2" <<endl; return 0;
}

// 普通函数
double Max(double a, double b)
{
    cout << "MyMax" << endl;
    return 0;
}

int main() 
{
    int i=4, j=5;

    // 输出MyMax - 匹配普通函数
    Max( 1.2, 3.4 ); 

    //输出TemplateMax - 匹配参数一样的模板函
    Max( i, j );

    //输出TemplateMax2 - 匹配参数类型不同的模板函数
    Max( 1.2, 3 );   

    return 0;
}

匹配模板函数时,当模板函数只有一个参数类型时,传入了不同的参数类型,是不进行类型自动转换,具体例子如下:

// 模板函数 - 1个参数类型
template<class T>
T myFunction( T arg1, T arg2)
{ 
    cout<<arg1<<" "<<arg2<<"\n"; 
    return arg1;
}

...

// OK :替换 T 为 int 类型
myFunction( 5, 7); 

// OK :替换 T 为 double 类型  
myFunction(5.8, 8.4);

// error :没有匹配到myFunction(int, double)函数
myFunction(5, 8.4);

类模板

类模板的定义
为了多快好省地定义出一批相似的类,可以定义「类模板」,然后由类模板生成不同的类。

类模板的定义形式如下:

template <class 类型参数1class 类型参数2...> //类型参数表
class 类模板名
{
   成员函数和成员变量
};

用类模板定义对象的写法:

类模板名<真实类型参数表> 对象名(构造函数实参表);

Pair类模板例子

接下来,用 Pair 类用类模板的方式的实现,Pair 是一对的意思,也就是实现一个键值对(key-value)的关系的类。

// 类模板
template <class T1, class T2>
class Pair
{
public:
    Pair(T1 k, T2 v):m_key(k),m_value(v) {};
    bool operator < (const Pair<T1,T2> & p) const;
private:
    T1 m_key;
    T2 m_value;
};

// 类模板里成员函数的写法
template <class T1, class T2>
bool Pair<T1,T2>::operator < (const Pair<T1,T2> &p) const
{
    return m_value < p.m_value;
}

int main()
{
    Pair<string,int> Astudent("Jay",20); 
    Pair<string,int> Bstudent("Tom",21);

    cout << (Astudent < Bstudent) << endl;

    return 0;
}

需要注意的是,同一个类模板的两个模板类是不兼容的:

Pair<string,int> *p;
Pair<string,double> a;
p = & a; //错误!!

函数模板作为类模板成员

当函数模板作为类模板的成员函数时,是可以单独写成函数模板的形式,成员函数模板在使用的时候,编译器才会把函数模板根据传入的函数参数进行实例化,例子如下:

// 类模板
template <class T>
class A
{
public:
    template<class T2>
    void Func(T2 t) { cout << t; } // 成员函数模板
};

int main()
{
    A<int> a;
    a.Func('K');     //成员函数模板 Func被实例化
    a.Func("hello"); //成员函数模板 Func再次被实例化

    return 0;
}

类模板与非类型参数

类模板的“<类型参数表>”中可以出现非类型参数:

template <class T, int size>
class CArray
{
public:
    void Print( )
    {
        for( int i = 0;i < size; ++i)
        cout << array[i] << endl;
    }
private:
    T array[size];
};

CArray<double,40> a2;
CArray<int,50> a3; //a2和a3属于不同的类

类模板与派生

  1. 类模板从类模板派生
    在这里插入图片描述
// 基类 - 类模板
template <class T1,class T2>
class A 
{
    T1 v1; T2 v2;
};

// 派生类 - 类模板
template <class T1,class T2>
class B:public A<T2,T1> 
{
    T1 v3; T2 v4;
};

// 派生类 - 类模板
template <class T>
class C:public B<T,T> 
{
    T v5;
};

int main() 
{
    B<int,double> obj1; 
    C<int> obj2;
    return 0;
}
  1. 类模板从模板类派生
    在这里插入图片描述
template <class T1,class T2>
class A 
{
    T1 v1; T2 v2;
};

template <class T>
class B:public A<int,double>  // A<int,double> 模板类
{
    T v;
};

int main() 
{
    //自动生成两个模板类 :A<int,double> 和 B<char>
    B<char> obj1;
    return 0;
}
  1. 类模板从普通类派生
    在这里插入图片描述
// 基类 - 普通类
class A 
{
    int v1;
};

// 派生类 - 类模板
template <class T>
class B:public A  // 所有从B实例化得到的类 ,都以A为基类
{ 
    T v;
};

int main() 
{
    B<char> obj1;
    return 0;
}
  1. 普通类从模板类派生

在这里插入图片描述

template <class T>
class A 
{
    T v1;
};

class B:public A<int> 
{
    double v;
};

int main() 
{
    B obj1;
    return 0;
}

类模板与友元

  1. 函数、类、类的成员函数作为类模板的友元
// 普通函数
void Func1() { } 

// 普通类
class A { }; 

// 普通类
class B 
{
    public:
    void Func() { } // 成员函数
};

// 类模板
template <class T>
class Tmp
{
    friend void Func1();    // 友元函数
    friend class A;         // 友元类
    friend void B::Func();  // 友元类的成员函数
}; // 任何从 Tmp 实例化来的类 ,都有以上三个友元
  1. 函数模板作为类模板的友元
// 类模板
template <class T1,class T2>
class Pair
{
private:
    T1 key;   //关键字
    T2 value; //值
public:
    Pair(T1 k,T2 v):key(k),value(v) { };

    // 友元函数模板
    template <class T3,class T4>
    friend ostream & operator<< (ostream & o, const Pair<T3,T4> & p);
};

// 函数模板
template <class T3,class T4>
ostream & operator<< (ostream & o, const Pair<T3,T4> & p)
{
    o << "(" << p.key << "," << p.value << ")" ;
    return o;
}

int main()
{
    Pair<string,int> student("Tom",29);
    Pair<int,double> obj(12,3.14);

    cout << student << " " << obj;
    return 0;
}
  1. 函数模板作为类的友元
// 普通类
class A
{
private:
    int v;
public:
    A(int n):v(n) { }

    template <class T>
    friend void Print(const T & p); // 函数模板
};

// 函数模板
template <class T>
void Print(const T & p)
{
    cout << p.v;
}

int main() 
{
    A a(4);
    Print(a);
    return 0;
}
  1. 类模板作为类模板的友元
// 类模板
template <class T>
class B 
{
private:
    T v;
public:
    B(T n):v(n) { }

    template <class T2>
    friend class A; // 友元类模板
};

// 类模板
template <class T>
class A 
{
public:
    void Func( )  
    {
        B<int> o(10); // 实例化B模板类
        cout << o.v << endl;
    }
};

int main()
{
    A<double> a;
    a.Func ();
    return 0;
}

类模板与静态成员变量

类模板中可以定义静态成员,那么从该类模板实例化得到的所有类,都包含同样的静态成员。

template <class T>
class A
{
private:
    static int count; // 静态成员
public:
    A() { count ++; }
    ~A() { count -- ; };
    A( A & ) { count ++ ; }

    static void PrintCount() { cout << count << endl; } // 静态函数
};

template<> int A<int>::count = 0;    // 初始化
template<> int A<double>::count = 0; // 初始化

int main()
{
    A<int> ia;  
    A<double> da; // da和ia不是相同模板类
    ia.PrintCount();
    da.PrintCount();
    return 0;
}

上面的代码需要注意的点:

类模板里的静态成员初始化的时候,最前面要加template<>。
ia 和 da 对象是不同的模板类,因为类型参数是不一致,所以也就是不同的模板类。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-22 13:57:27  更:2021-07-22 13:58:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/2 4:00:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码