最近工作需要,学习C++ 11的知识,学习中总结了一些知识点,分享给大家。 

函数模板

简介

建立一个通用函数,这个通用函数的形参类型不具体指定,用一个虚拟类型来代表,这个通用函数就被称为函数模板

区别

模板区别在于其函数声明,前面加了个template<typename T>,这句话告诉编译器,函数体出现类型T时,不要报错,T是一个通用类型。

格式

template<parameter-list> function-declaration

函数模板在形式上分为两部分:模板、函数。在函数前面加上template<...>就成为函数模板,因此对函数的各种修饰(inline、constexpr等)需要加在function-declaration上,而不是template前。如

template<typename T>

inline T min(const T &, const T &);

 注意

  1. 函数模板不是函数;
  2. 函数模板用来定义一族函数,而不是一个函数;
  3. 当用具体类型去替换T时,才会生成具体函数,该过程叫做函数模板的实例化
  4. 如果我们只是编写了函数模板,但不在任何地方使用它(也不显式实例化),则编译器不会为该函数模板生成任何代码。

详细解说

1)显式实例化

简介

显式实例化的方式告诉编译器生成指定实参的函数,显式实例化声明会阻止隐式实例化

例子

template<typename R, typename T1, typename T2>

R add(T1 a, T2 b) {

    return static_cast<R>(a + b);

}

// 显式实例化

template double add<double, int, double>(int, double);

// 显式实例化, 推导出第三个模板实参

template int add<int, int>(int, int);

// 全部由编译器推导

template double add(double, double);

  在显式实例化时,只指定部分模板实参,则指定顺序必须s自左至右依次指定,不能越过前参模板形参,直接指定后面的。 

2)隐式实例化

简介

当函数模板被调用,且在之前没有显式实例化时,即发生函数模板的隐式实例化。 

例子

#include <iostream>

template<typename T>

void print(const T &r) {

    std::cout << r << std::endl;

}

int main() {

    // 隐式实例化print<int>(int)

    print(1);

    // 实例化print<char>(char)

    print<>('c');

    // 仍然是隐式实例化,我们希望编译器生成print<double>(double)

    print<double>(1);

}

3)返回值为auto

简介

函数的返回值类型取决于函数参数某种运算后的类型。对于这种情况可以采用auto关键字作为返回值占位符

 例子

template<typename T1, typename T2>

auto multi(T a, T b) -> decltype(a * b) {

    return a * b;

}

decltype操作符用于查询表达式的数据类型,也是C++11标准引入的新的运算符,其目的是解决泛型编程中有些类型由模板参数决定,而难以表示的问题。 

4)类成员函数模板

简介

类成员中定义函数模板 

例子

#include <iostream>



class object {

public:

    template<typename T>

    void print(const char *name, const T &v) {

        std::cout << name << ": " << v << std::endl;

    }

};



int main() {

    object o;

    o.print("name", "Crystal");

    o.print("age", 18);

}

注意:函数模板不能用作虚函数。这是因为C++编译器在解析类的时候就要确定虚函数表(vtable)的大小。

 5)函数模板重载

简介

函数模板之间、普通函数和模板函数之间可以重载 

调用顺序

顺序

行为

1

最符合函数名和参数类型的普通函数

2

特殊模板(具有非类型形参的模板,即对T有类型限制)

3

普通模板(对T没有任何限制的)

4

通过类型转换进行参数匹配的重载函数

 例子

#include <iostream>

template<typename T>

const T &max(const T &a, const T &b) {

    std::cout << "max(&, &) = ";

    return a > b ? a : b;

}



// 函数模板重载

template<typename T>

const T *max(T *a, T *b) {

    std::cout << "max(*, *) = ";

    return *a > *b ? a : b;

}



// 函数模板重载

template<typename T>

const T &max(const T &a, const T &b, const T &c) {

    std::cout << "max(&, &, &) = ";

    const T &t = (a > b ? a : b);

    return t > c ? t : c;

}

// 普通函数

const char *max(const char *a, const char *b) {

    std::cout << "max(const char *, const char *) = ";

    return strcmp(a, b) > 0 ? a : b;

}



int main() {

    int a = 1, b = 2;

    std::cout << max(a, b) << std::endl;

    std::cout << *max(&a, &b) << std::endl;

    std::cout << max(a, b, 3) << std::endl;

    std::cout << max("en", "ch") << std::endl;

    // 可以通过空模板实参列表来限定编译器只匹配函数模板

    std::cout << max<>("en", "ch") << std::endl;

}

可以通过空模板实参列表来限定编译器只匹配函数模板。

 6)普通函数模板

#include <iostream>

// N必须是编译时的常量表达式

template<typename T, int N>

void printArray(const T (&a)[N]) {

    std::cout << "[";

    const char *sep = "";

    for (int i = 0; i < N; i++, (sep = ", ")) {

        std::cout << sep << a[i];

    }

    std::cout << "]" << std::endl;

}



int main() {

    // T: int, N: 3

    printArray({1, 2, 3});

}
//输出:[1, 2, 3]

 

函数模板 .vs. 模板函数

函数模板重点在模板。表示这是一个模板,用来生成函数。
模板函数重点在函数。表示的是由一个模板生成而来的函数。 

类模板

简介

C++中类模板通常是容器(如std::vector)或行为的封装,类模板只是定义了一组通用的操作,在具体实例化前是不占用程序空间的。

语法格式

template < parameter-list > class-declaration

parameter-list  形参

class-declaration 类声明

说明:构成类模板的形参(parameter-list)约束与函数模板相同,但是函数模板的类型自动推演不同,类模板实例化时,需要显式指定,例如:

std::vector<int> intArr;

 详细解说

  1. 类模板--成员函数

成员函数(含成员函数模板)只有在使用时才生成

template<typename T>

class A {

    T a_;

public:

    void add(int n) {

        a_ += n;

    }

}

class M{};

int main() {

    A<M> s;      // s未用到add函数,因此整个程序得以成功编译

   // s.add(1);  // 如果有这句话,则会编译失败

}

A<T>::add在变量s中未使用到,因此虽然a_ += n不合法,但整个程序仍然通过了编译。 

2)类模板--虚函数

类模板中可以有虚函数,虚函数在类模板实例化为模板类时由编译器生成。

类模板的虚函数可以访问模板类中的泛型成员(变量、成员函数模板都可以访问)。 例子:

#include <iostream>

template<typename T>

class A {

    T a_;

public:

    virtual void say() {

        std::cout << "a -> " << a_ << std::endl;

}  };

class M{};

int main() {

    // 尽管say函数未被使用,此处会编译仍会失败,因为std::cout << m.a_操作是非法的

    A<M> m;

}

 3)成员函数模板

类模板和函数模板结合就是成员函数模板。

#include <iostream>

template<typename T>

class Printer {

T prefix_;

public:

explicit Printer(const T &prefix) :prefix_(prefix) {

}

// 成员函数模板

template<typename U, typename ...Args> void print(const U &u, Args... args);

void print() {

std::cout << std::endl;

}

};



template<typename T> template<typename U, typename ...Args>

void Printer<T>::print(const U &u, Args... args) {

std::cout << this->prefix_ << u << std::endl;

print(args...);

}