19- C++ 泛型编程-7 (C++)

第九章 泛型编程

在C++里,不考虑具体数据类型 的编程模式叫做泛型编程,泛型也是一种数据类型,只不过它是一种用来代替所有类型的“通用类型”。

泛型编程通过函数模板和类模板来实现泛型编程。

9.1 函数模板

9.1.1 函数模板的基本使用

当我们想写个Swap()交换函数时,通常这样写:

void Swap(int& a, int& b)
{
    int c = a;
    a = b;
    b = c;
}

但是这个函数仅仅只能支持int类型,如果我们想实现交换double,float,string等等时,就还需要从新去构造Swap()重载函数,这样不但重复劳动,容易出错,而且还带来很大的维护和调试工作量。更糟的是,还会增加可执行文件的大小。

我们可以通过函数模板解决这个问题:

void Swap(T& a, T& b)
{
    T c = a;
    a = b;
    b = c;
}

Swap 泛型写法中的 T 不是一个具体的数据类型,而是泛指任意的数据类型

函数模板其实是一个具有相同行为的函数家族

函数模板的语法规则如下

  • template   关键字用于声明开始进行泛型编程

  • typename  关键字用于声明泛指类型

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

函数模板的应用

  • 自动类型推导调用

  • 具体类型显示调用

9.1.2 深入理解函数模板

1、为什么函数模板能够执行不同的类型参数?

其实编译器对函数模板进行了两次编译:

第一次编译时,首先去检查函数模板本身有没有语法错误

第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正的函数。

所以函数模板,其实只是一个模具,当我们调用它时,编译器就会给我们生成真正的函数.

2、如何 验证 呢?

编译程序生成.o文件,然后通过nm命令查看符号表

g++ -c 函数模板.cpp nm 函数模板.o

从符号表中可以非常直观的看到生成了两个不同的符号。

需要注意的是:函数模板是不允许隐式类型转换的,调用时类型必须严格匹配

也就说如下代码是非法的:

int a;
float b;
Swap(a, b);

3、函数模板的特点

数模板还可以定义任意多个不同的类型参数,但是对于多参数函数模板:

  • 编译器是无法自动推导返回值类型的

  • 可以从左向右部分指定类型参数

#include <iostream>

using namespace std;
template <typename T1, typename T2, typename T3>
T1 add(T2 a, T3 b)
{
    T1 ret;
    ret = static_cast<T1>(a + b);
    return ret;
}

int main() {
    int c = 12;
    float d = 23.4;
    cout << add<float>(c, d) << endl;
    cout << add<int, int, float>(c, d) << endl;
}

4、函数模板的重载

函数模板跟普通函数一样,也可以被重载

  • C++编译器优先考虑普通函数
  • 如果函数模板可以产生一个更好的匹配,那么就选择函数模板
  • 也可以通过空模板实参列表<>限定编译器只匹配函数模板
#include <iostream>

using namespace std;

template <typename T>
void fun(T a)
{cout << "void fun(T a)" << endl;}

template <typename T1, typename T2>
void fun(T1 a, T2 B)
{cout << "void fun(T1 a, T2 b)" << endl;}

int main() {
    int a = 0;
    float b = 0.0;
    fun(a);
    fun(a, b);
    fun(b, a);
    fun<>(a, b);
}

5、总结

  • 函数模板 是泛型编程在C++中的应用方式之一
  • 函数模板能够根据 实参对参数类型进行推导
  • 函数模板支持 显示的指定参数类型
  • 函数模板是 C++中重要的代码复用方式
  • 函数模板通过 具体类型产生不同的函数
  • 函数模板 可以定义 任意多个不同的类型参数
  • 函数模板中的返回值类型必须显示指定
  • 函数模板可以像 普通函数一样重载
  • 在任何一个函数模板前都必须 使用template <typename 泛型,...> 声明开始 泛型编程

9.2 类模板

9.2.1 类模板的定义

还记得我们上次实现的Array类吗?在Array类中我们只能操作 int类型的数据,如果需要操作char,float类型的数据我们该如何处理呢?难道我们再重新实现一个类吗?当然不用,我们可以使用 类模板 来实现。

在实际工作中,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,我们可以使用类模板来实现。

template<class 模板参数表>
class 类名{
    //类定义.......
};

这样我们就定义了一个 简单的类模板,其中的T代表任意的类型,可以出现在类模板中的任意地方,与函数模板不同的是,使用类模板构造对象时必须显示的指定数据类型,编译器无法自动推导,例如test<int>t;需要显示的指定数据类型。

编译器对类模板的处理方式和函数模板相同

  • 编译器 从类模板通过具体类型产生不同的类

  • 编译器在声明的地方对类模板代码本身进行编译

  • 编译器在使用的地方对参数替换后的代码进行编译

由于类模板的编译机制不同 , 所以不能像普通类一样分开实现后在使用时只包含头文件,在工程实践上 , 一般会把类模板的定义直接放到头文件中!!

只有被调用的类模板成员函数才会被编译器生成可执行代码!!!

9.2.2 类模板的特化

上面的类模板好像已经实现了add加法运算。但是却不能支持指针类型。其实,类模板也可以像函数重载一样, 类模板通过特化的方式可以实现特殊情况.

类模板特化:

  • 表示可以存在多个相同的类名,但是模板类型都不一致(和函数重载的参数类似)

  • 特化类型有 完全特化 部分特化两种类型

  • 完全特化表示显示指定类型参数,模板声明只需写成template<>,并在类名右侧指定参数,比如:

#include <iostream>
using namespace std;

template <typename T1, typename T2>
class Operator
{
public:
    Operator()
    {cout << "Operator" << endl;}
    void add(T1 a, T2 b)
    {cout << a + b << endl;}
};

template <>
class Operator<int, int>
{
public:
    Operator()
    {cout << "Operator<int, int>" << endl;}
    void add(int a, int b)
    {cout << a + b << endl;}
};

int main() {
    //匹配完全特化类模板
    Operator<int, int> Op1;
    //匹配正常的类模板
    Operator<int, float> Op2;
    return 0;
}
  • 部分特化表示 通过特定规则约束类型参数,模板声明和类相似,并在类名右侧指定参数,比如:
#include <iostream>
using namespace std;

template <typename T1, typename T2>
class Operator
{
public:
    void add(T1 a, T2 b)
    {cout << a + b << endl;}
};

template <typename T>
class Operator<T*, T*>
{
public:
    void add(T* a, T* b)
    {cout << *a + *b << endl;}
};

int main() {
    Operator <int*, int*>Op1;
    Operator <int, float>Op2;
    return 0;
}
  • 编译时,会根据对象定义的类模板类型,首先去匹配完全特化,再来匹配部分特化,最后匹配正常的类模板
#include <iostream>
using namespace std;

template <typename T1, typename T2>
class Operator
{
public:
    void add(T1 a, T2 b)
    {
        cout << "add(T1 a, T2 b)" << endl;
        cout << a + b << endl;
    }
};

template <typename T>
class Operator<T, T>
{
public:
    void add(T a, T b)
    {
        cout << "add(T a, T b)" << endl;
        cout << a + b << endl;
    }
};

template <typename T1, typename T2>
class Operator <T1*, T2*>
{
public:
    void add(T1* a, T2* b)
    {
        cout << "add(T1* a, T2* b)" << endl;
        cout << *a + *b << endl;
    }
};

template <>
class Operator<void*, void*>
{
public:
    void add(void* a, void* b)
    {
        cout << "add(void* a, void* b)" << endl;
        cout << "add void* Error" << endl;
    }
};

int main() {
    int *p1 = new int(1);
    float *p2 = new float(1.25);

    Operator<int, float> Op1;
    Op1.add(1, 1.5);

    Operator<int, int> Op2;
    Op2.add(1, 5);

    Operator<int*, float*> Op3;
    Op3.add(p1, p2);

    Operator<void*, void*> Op4;
    Op4.add(NULL, NULL);

    delete p1;
    delete p2;
    return 0;
}

9.2.3 继承中类模板的使用

9.2.3.1 父类是一般类,子类是模板类

class A
{
public:
    A(int temp = 0)
    {this->temp = temp;}
    ~A(){}
private:
    int temp;
};

template <typename T>
class B: public A
{
public:
    B(T t = 0) : A(666)
    {this->t = t;};
    ~B(){}
private:
    T t;
};

9.2.3.2 子类是一般类,父类是模板类

template <typename T>
class A
{
public:
    A(T t = 0)
    {this->t = t;}
    ~A(){}
private:
    T t;
};

class B:public A<int>
{
public:
    B(int temp = 0): A<int>(666)
    {this->temp = temp;}
    ~B(){}
private:
    int temp;
};

9.2.3.3 子类和父类都是模板类

#include <iostream>
using namespace std;

template <typename T1, typename T2>
class A
{
    T1 x;
    T2 y;
};

template <typename T1, typename T2>
class B: public A<T2, T1>
{
    T1 x1;
    T2 x2;
};

template <typename T>
class C: public B<T, T>
{T x3;};

// 类模板继承模板类
template <typename T>
class D: public A<int, double>
{T x4;};

class E
{int x4;};

template <typename T>
class F: public E
{T x5;};

int main() {
    return 0;
}

 

猜你喜欢

转载自blog.csdn.net/March_A/article/details/132050104