C++之模板

模板

泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的公式。

函数模板

函数模板定义的一般形式

template <typename type> ret-type func-name(parameter list)
{
   // 函数的主体
}
type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。

具体用法见下面案例

#include <iostream>
#include <string>

using namespace std;

template <typename T>
inline T const& Max (T const& a, T const& b)
{
    return a < b ? b:a;
}
int main ()
{

    int i = 39;
    int j = 20;
    cout << "Max(i, j): " << Max(i, j) << endl;

    double f1 = 13.5;
    double f2 = 20.7;
    cout << "Max(f1, f2): " << Max(f1, f2) << endl;

    string s1 = "Hello";
    string s2 = "World";
    cout << "Max(s1, s2): " << Max(s1, s2) << endl;

    return 0;
}

在这里插入图片描述

上诉代码中的const&去掉,输出不变

类模板

类模板定义的一般形式

template <class type> class class-name {//这里改成template <typename type>也可
	//
}
type 是占位符类型名称,可以在类被实例化的时候进行指定。可以使用一个逗号分隔的列表来定义多个泛型数据类型

具体用法见下面案例

#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>

using namespace std;

template <class T>
class Stack {
private:
    vector<T> elems;     // 元素

public:
    void push(T const&);  // 入栈
    void pop();               // 出栈
    T top() const;            // 返回栈顶元素
    bool empty() const{       // 如果为空则返回真。
        return elems.empty();
    }
};

template <class T>
void Stack<T>::push (T const& elem)
{
    // 追加传入元素的副本
    elems.push_back(elem);
}

template <class T>
void Stack<T>::pop ()
{
    if (elems.empty()) {
        throw out_of_range("Stack<>::pop(): empty stack");
    }
    // 删除最后一个元素
    elems.pop_back();
}

template <class T>
T Stack<T>::top () const
{
    if (elems.empty()) {
        throw out_of_range("Stack<>::top(): empty stack");
    }
    // 返回最后一个元素的副本
    return elems.back();
}

int main()
{
    try {
        Stack<int>         intStack;  // int 类型的栈
        Stack<string> stringStack;    // string 类型的栈

        // 操作 int 类型的栈
        intStack.push(7);
        cout << intStack.top() <<endl;

        // 操作 string 类型的栈
        stringStack.push("hello");
        cout << stringStack.top() << std::endl;
        stringStack.pop();
        stringStack.pop();
    }
    catch (exception const& ex) {
        cerr << "Exception: " << ex.what() <<endl;
        return -1;
    }
}

在这里插入图片描述

定义一个类模板

template<class 模板参数表>
 
class 类名{
 
 // 类定义......
 
};
template 是声明类模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是类型参数 ,也可以是非类型参数。
类型参数由关键字classtypename及其后面的标识符构成。非类型参数由一个普通参数构成,代表模板定义中的一个常量。
【用法见下面】
template<class type,int width>

 //type为类型参数,width为非类型参数
 
class Graphics;

七点注意

(1)如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏掉。

(2)模板参数名不能被当作类模板定义中类成员的名字。

(3)同一个模板参数名在模板参数表中只能出现一次。

(4)在不同的类模板或声明中,模板参数名可以被重复使用。
typedef string type;

template<class type,int width>

class Graphics
{
    type node;//node不是string类型(对应1)

    typedef double type;//错误:成员名不能与模板参数type同名(对应2)
};

template<class type,class type>//错误:重复使用名为type的参数(对应3)

class Rect;

template<class type> //参数名”type”在不同模板间可以重复使用(对应4)

class Round;
5
6
7
类模板实例化

在这里插入图片描述

类模板什么时候会被实例化??
1、当使用了类模板实例的名字,并且上下文环境要求存在类的定义时。

2、对象类型是一个类模板实例,当对象被定义时。此点被称作类的实例化点。

3、一个指针或引用指向一个类模板实例,当检查这个指针或引用所指的对象时。

模板类的继承

在模板类的继承中,需要注意以下几点:
1、若父类定义了构造函数,记得子类要使用构造函数列表来初始化
2、继承的时候,如果子类不是模板类,则必须指明当前的父类的类型,【因为要分配内存空间】
3、继承的时候,如果子类是模板类,要么指定父类的类型,要么用子类的泛型来指定父类

详情见下面代码

template <typename T>
class Parent{
public:
    Parent(T p)
    {
        this->p = p;
    }
    
private:
    T p;
};

//如果子类不是模板类,需要指明父类的具体类型
class ChildOne:public Parent<int>{
    
public:
    ChildOne(int a,int b):Parent(b)
    {
        this->cone = a;
    }
    
private:
    int cone;
};


//如果子类是模板类,可以用子类的泛型来表示父类
template <typename T>
class ChildTwo:public Parent<T>{
    
public:
    ChildTwo(T a, T b):Parent<T>(b)
    {
        this->ctwo = a;
    }
    
private:
    T ctwo;
};

补充

C++ 类构造函数初始化列表

见博客

这里还有一个二次编译的问题(用前置声明来解决)
见博客

推荐比较好的博客
推荐比较好的博客

成员模板

定义:成员定义前加上template及模板参数表。

要点:

1、在一个类模板中定义一个成员模板,意味着该类模板的一个实例包含了可能无限多个嵌套类和无限多个成员函数.

2、只有当成员模板被使用时,它才被实例化.

3、成员模板可以定义在其外围类或类模板定义之外.

模板泛化

模板特化

模板偏特化

补充

声明和定义

变量(函数)的声明有两种情况:
1、需要建立存储空间的。例如:int  a。在声明的时候就已经建立了存储空间。
这种声明是"定义性声明(defining declaration)",即我们平时所说的【“定义”】。
2、不需要建立存储空间的,只是告诉编译器某变量已经在别处定义过了。
例如:extern int a。其中,变量a是在别处定义的。
这种声明是"引用性声明(referncing declaration)",即我们平时所说的【“声明”】。
从广义的角度来讲,声明中包含着定义,但是并非所有的声明都是定义。
即,定义性声明既是定义又是声明,而引用性声明只是声明。
例如:int a。它既是声明,同时又是定义。然而对于 extern int a 来讲,它只是声明不是定义。
【一般的情况下我们常常这样叙述,把建立空间的声明称之为”定义”,而把不需要建立存储空间的称之为”声明”。】

参考博客

前向声明

前向声明:可以声明一个类而不定义它。这个声明被称为【前向声明(forward declaration)】
例如:class name。在声明之后,定义之前,类name是一个不完全类型(incompete type),
即已知name是一个类型,但不知道包含哪些成员。

参考

C++ 类构造函数初始化列表

构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式
class CExample {
public:
    int a;
    float b;
    //构造函数初始化列表
    CExample(): a(0),b(8.8)
    {}
    //构造函数内部赋值
    CExample()
    {
        a=0;
        b=8.8;
    }
};
上面的例子中两个构造函数的结果是一样的。
上面的构造函数(使用初始化列表的构造函数)【显式的初始化】类的成员;
而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。
存在必须用带有初始化列表的构造函数的情况
1.成员类型是没有默认构造函数的类。
若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,
若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
2.const 成员或引用类型的成员。
因为 const 对象或引用类型只能初始化,不能对他们赋值。

注意

初始化列表的成员初始化顺序:
C++ 初始化类成员时,是按照【声明的顺序】初始化的,而不是按照出现在初始化列表中的顺序。
class CMyClass {
    CMyClass(int x, int y);
    int m_x;//先声明,先初始化
    int m_y;//后声明,后初始化
};

CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y)
{
};
编译器先初始化 m_x,然后是 m_y,,因为它们是按这样的顺序声明的

参考

C++两种抽象方法

1、面向对象编程
封装、继承、多态

泛型编程概念

2、泛型编程
概念、模型化、强化
泛型编程:指在【多种数据类型】上皆可操作。和面向对象编程不同,它并不要求额外的间接层来调用函数
而是【使用完全一般化并可重复使用】的算法,效率与针对某特定数据类型而设计的算法相同
1、概念
类型必须满足的一组条件。基本的概念中有赋值、默认构造、相等比较、小于判断等
2、模型化
当类型满足这个条件,即为该概念的一个模型
若果能够复制类型X的值,或者赋给X对象一个新值的话,则类型X是Assignable的一个model
3、强化
如果concept  C2满足concept  C1的所有条件,再加上其他额外条件,则C2是C1的强化

泛型编程实现

1、模板
类模板
函数模板
2、STL

STL以【迭代器】和【容器】为基础,是一种泛型算法库,容器的存在使这些算法有东西可以操作。STL包含泛型算法、泛型指针泛型容器和函数对象等

迭代器是STL的核心,它是泛型指针,是一种指向其他对象的对象,迭代器能遍历由对象所形成的区间
五类迭代器

Input Iterator     只读,单向移动,如STL中的istream_iterator。
Output Iterator   只写,单向移动,如STL中的ostream_iterator。
Forward Iterator   具有读、写性,单向移动。
Bidirections Iterator   ​​​​​​​具有读、写性,双向移动。
​​​​​​​Random Access Iterator   具有读、写性,随机访问
泛型编程优点
1、通用性强
2、效率高
3、类型检查严
4、二进制复用性差

参考

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值