模板的一些总结

1.模板中嵌套依赖类型名在使用过程中遵循

·在声明 template parameters(模板参数)时,class 和 typename 是可互换的。

·用 typename 去标识 nested dependent type names(嵌套依赖类型名),在 base class lists(基类列表)中或在一个 member initialization list(成员初始化列表)中作为一个 base class identifier(基类标识符)时除外。

很明显当一个陌生的类中的函数被使用的时候 是会产生解析矛盾的,这个时候告诉编译器是什么类型很重要。

typename 关键字

提问一个问题,以下模板的声明中, class 和 typename 有什么不同?

template<class T> class Test;
template<typename T> class Test;

答案:没有不同。然而,C++ 并不总是把 class 和 typename 视为等价。有时候我们一定得使用 typename。
默认情况下,C++ 语言假定通过作用域运算符访问的名字不是类型。因此,如果我们希望使用一个模板类型参数的类型成员,就必须显式告诉编译器该名字是一个类型。我们通过使用关键字 typename 来实现这一点:

template<typename T>
typename T::value_type top(const T &c)
{
    if (!c.empty())
        return c.back();
    else
        return typename T::value_type();
}

在这里我们只需要记住一点,当我们希望通知编译器一个名字表示类型时,必须使用关键字 typename,而不能使用 class。

2.简单的用法总结

函数模板的声明形式如下:template <class identifier> function_declaration;template <typename identifier> function_declaration;

声明类的方法也是同理

//statck.h
template <class T> class Stack {
public:
Stack();
~Stack();
void push(T t);
T pop();
bool isEmpty();
private:
T *m_pT;
int m_maxSize;
int m_size;
};

3.模板可以有类型参数,也可以有常规的类型参数int,也可以有默认模板参数,例如

template<class T, T def_val> class Stack{...}

4.函数
模板专门化

 当我们要定义模板的不同实现,我们可以使用模板的专门化。例如我们定义的stack类模板,如果是char*类型的栈,我们希望可以复制char的所有数据到stack类中,因为只是保存char指针,char指针指向的内存有可能会失效,stack弹出的堆栈元素char指针,指向的内存可能已经无效了。还有我们定义的swap函数模板,在vector或者list等容器类型时,如果容器保存的对象很大,会占用大量内存,性能下降,因为要产生一个临时的大对象保存a,这些都需要模板的专门化才能解决。

函数模板专门化

  假设我们swap函数要处理一个情况,我们有两个很多元素的vector<int>,在使用原来的swap函数,执行tmpT = t1要拷贝t1的全部元素,占用大量内存,造成性能下降,于是我们系统通过vector.swap函数解决这个问题,代码如下:

#include <vector>
using namespace std;
template<class T> void swap(T& t1, T& t2) {
    T tmpT;
    tmpT = t1;
    t1 = t2;
    t2 = tmpT;
}

template<> void swap(std::vector<int>& t1, std::vector<int>& t2) {
    t1.swap(t2);
}
template<>前缀表示这是一个专门化,描述时不用模板参数

vector<int>的swap代码还是比较局限,如果要用模板专门化解决所有vector的swap,该如何做呢,只需要把下面代码

template<> void swap(std::vector<int>& t1, std::vector<int>& t2) {
    t1.swap(t2);
}
改为
template<class V> void swap(std::vector<V>& t1, std::vector<V>& t2) {
    t1.swap(t2);
}

就可以了,其他代码不变。

5.类模板专门化

//compare.h
#include <string.h>
template <class T>
class compare
{
public:
bool equal(T t1, T t2)
{
return t1 == t2;
}
};

template<>class compare<char *>
{
public:
bool equal(char* t1, char* t2)
{
return strcmp(t1, t2) == 0;
}
};

6.

一个类没有模板参数,但是成员函数有模板参数,是可行的,代码如下:

复制代码
class Util {
    public:
        template <class T> bool equal(T t1, T t2) {
            return t1 == t2;
        }
};

int main() {
    Util util;
    int a = 1, b = 2;
    util.equal<int>(1, 2);
    return 0;
}
复制代码

甚至可以把Util的equal声明为static,代码如下:

复制代码
 
class Util {
    public:
         template <class T> static bool equal(T t1, T t2) {
            return t1 == t2;
        }
};

int main() {
    int a = 1, b = 2;
    Util::equal<int>(1, 2);
    return 0;
}
复制代码

7.在 C++ 中,类型的名字(包括类的名字)本身也是一种运算符,即类型强制转换运算符。

类型强制转换运算符是单目运算符,也可以被重载,但只能重载为成员函数,不能重载为全局函数。经过适当重载后,(类型名)对象这个对对象进行强制类型转换的表达式就等价于对象.operator 类型名(),即变成对运算符函数的调用。

下面的程序对 double 类型强制转换运算符进行了重载。
  1. #include <iostream>
  2. using namespace std;
  3. class Complex
  4. {
  5. double real, imag;
  6. public:
  7. Complex(double r = 0, double i = 0) :real(r), imag(i) {};
  8. operator double() { return real; } //重载强制类型转换运算符 double
  9. };
  10. int main()
  11. {
  12. Complex c(1.2, 3.4);
  13. cout << (double)c << endl; //输出 1.2
  14. double n = 2 + c; //等价于 double n = 2 + c. operator double()
  15. cout << n; //输出 3.2
  16. }

程序的输出结果是:
1.2
3.2

第 8 行对 double 运算符进行了重载。重载强制类型转换运算符时,不需要指定返回值类型,因为返回值类型是确定的,就是运算符本身代表的类型,在这里就是 double。

重载后的效果是,第 13 行的(double)c等价于c.operator double()

有了对 double 运算符的重载,在本该出现 double 类型的变量或常量的地方,如果出现了一个 Complex 类型的对象,那么该对象的 operator double 成员函数就会被调用,然后取其返回值使用。

例如第 14 行,编译器认为本行中c这个位置如果出现的是 double 类型的数据,就能够解释得通,而 Complex 类正好重载了 double 运算符,因而本行就等价于:

double n = 2 + c.operator double();

posted @ 2020-11-04 10:14  Tonarinototoro  阅读(81)  评论(0编辑  收藏  举报