C++ 学习笔记

c5fcbbb589743ba3d56a2dc4628e18a2.gif

作者:readywang(王玉龙)

template 是 c++ 相当重要的组成部分,堪称 c++语言的一大利器。在大大小小的 c++ 程序中,模板无处不在。c++ templates 作为模板学习的经典书籍,历来被无数 c++学习者所推崇。第二版书籍覆盖了 c++ 11 14 和 17 标准,值得程序猿们精读学习,特此整理学习笔记,将每一部分自认为较为重要的部分逐条陈列,并对少数错误代码进行修改

一、函数模板

1.1 函数模板初探

1.模板实例化时,模板实参必须支持模板中类型对应的所有运算符操作

template <typename T>
T max(const T &amp;a, const T &amp;b) {
    return a > b? a : b;
}

class NdGreater {
};

int main() {
    NdGreater n1, n2;
    ::max(n1, n2); // 不支持 > 编译报错
}

2.模板编译时会进行两阶段检查

a.模板定义时,进行和类型参数无关的检查,如未定义的符号等。

b.模板实例化时,进行类型参数相关的检查。

template<typename T>
void foo(T t) {
    undeclared(); // 如果 undeclared()未定义,第一阶段就会报错,因为与模板参数无关
    static_assert(sizeof(T) > 10, "T too small"); //与模板参数有关,只会在第二阶段报错
}

3.根据两阶段检查,模板在实例化时要看到完整定义,最简单的方法是将实现放在头文件中

1.2 模板参数推断

1.函数模板的模板参数可以通过传递的函数参数进行推断。

2.函数推断时会用到参数类型转换,规则如下:

a.如果函数参数是按引用传递的,任何类型转换都不被允许(此处有疑问,const 转换还是可以的)

b.如果函数参数是按值传递的,可以进行退化(decay)转换:const(指针或者引用只有顶层 const 可以被忽略) 和 volatile 被忽略;引用变为非引用;数组和函数变为对应指针类型

template <typename T>
void RefFunc(const T &amp;a, const T &amp;b){};

template <typename T>
void NoRefFunc(T a, T b){};

int main() {
    int *const ic = nullptr;
    const int *ci = nullptr;
    int *p = nullptr;
    RefFunc(p, ic);  // ok 顶层const可以被忽略 T 为 int *
    RefFunc(p, ci);  // error 底层const不可以忽略
    NoRefFunc(p, ci); // error 底层const不可以忽略

    int i = 0;
    int &amp;ri = i;
    NoRefFunc(i, ri); // ok ri从int &amp;转换为int

    int arr[4];
    NoRefFunc(p, arr);  // ok arr 被推断为int *

    NoRefFunc(4, 5.0);  // error T 可以推断为int或double
}

3.上文的最后一句调用,类型推断具有二义性,无法正确实例化。可以通过以下方式解决

a.类型转换:

b.显式指定模板实参:

NoRefFunc(static_cast<double>(4), 5.0);  // ok 类型转换
    NoRefFunc<int>(4, 5.0);  // 显式指定

4.函数模板无法通过默认参数推断模板参数。如果函数模板只有一个函数参数,且函数参数提供了默认值的情况,应该为模板类型参数 T 也提供和函数参数默认值匹配的默认类型

template <typename T>
void Default(T t = 0){};

Default(); // error 无法推断为int

template <typename T = int>
void Default(T t = 0){};

Default(); // ok 默认类型为int
1.3 多模板参数

1.当函数返回类型不能或不便由函数参数类型直接推断时,可以在函数模版中新增模板参赛指定返回类型。

2.c++11 之后,可以通过 auto + decltype +尾后返回类型 推断函数模板返回类型。当函数参数为引用类型时,返回类型应该为非引用。而decltype 会保留引用,因此还需通过 decay 进行类型退化

3.c++14 之后,可以通过 auto 直接推断函数模板返回类型,前提是函数内部的多个返回语句推断出的返回类型要一致auto 会自动对类型进行 decay。

4.c++11 之后,可以通过 common_type 返回多个模版类型参赛的公共类型,common_type 返回的类型也是 decay 的

#include<type_traits>
// 单独通过RT指定返回类型
template <typename RT, typename T1, typename T2>
RT max1(const T1&amp; a, const T2&amp; b) { return a > b ? a : b; }

// auto c++11支持 通过decay 进行类型退化 typename 用于声明嵌套从属名称 type 为类型而不是成员
template <typename T1, typename T2>
auto max2(const T1&amp; a, const T2&amp; b) -> typename std::decay<decltype(a > b ? a : b)>::type { return a > b ? a : b; }

// auto c++14支持
template <typename T1, typename T2>
auto max3(const T1&amp; a, const T2&amp; b) { return a > b ? a : b; }

// common_type c++11支持 max4(5, 7.3) max4(7.4, 5) 的返回类型均被推断为double
template <typename T1, typename T2>
typename std::common_type<T1, T2>::type max4(const T1&amp; a, const T2&amp; b) { return a > b ? a : b; }
1.4 默认模板参数

1.可以给模板参数指定默认值。

// 默认模板参赛 因为RT需要T1 T2推断,所以放在最后
template <typename T1, typename T2, typename RT = typename std::common_type<T1, T2>::type>
RT max5(const T1&amp; a, const T2&amp; b) { return a > b ? a : b; }
1.5 函数模板重载

1.一个非模板函数可以和同名的函数模板共存,并且函数模板可实例化为和非模板函数具有相同类型参数的函数。函数调用时,若匹配度相同,将优先调用非模板函数但若显式指定模板列表,则优先调用函数模板

2.函数模板不可以进行类型自动转换,非模板函数可以。

#pragma once

template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

template <typename RT, typename T1, typename T2>
RT max(T1 a, T2 b) {
    return a > b ? a : b;
}
int max(int a, int b) {
    return a > b ? a : b;
}

int main() {
    ::max(6, 8);  // 调用非模板函数
    ::max<>(6, 8); // 调用函数模板 max<int>
    ::max('a', 'b'); // 调用函数模板 max<char>
    ::max(4, 4.0); // 通过类型转换调用非模板函数
    ::max<double>(4, 4.0); //指定了返回类型 调用max<double,int,double>
}

3.调用函数模板时,必须保证函数模板已经定义。

int max(int a, int b) {
    return a > b ? a : b;
}

template <typename T>
T max(T a, T b, T c) {
    return max(max(a,b),c);  //T为int时,并不会调用max<int> 而是调用非模板函数
}

template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

max(1, 2, 3);  // 最终调用非模板函数比较
max("sjx", "wyl", "shh"); // error 找不到二元的max<const char *>

二、类模板

2.1 stack 类模板实现

1.类模板不可以定义在函数作用域或者块作用域内部,通常定义在 global/namespace/类作用域。

#include<vector>
#include<iostream>

template <typename T>
class Stack
{
public:
    void push(const T&amp; value);
    void pop();
    T top();
    int size() const { elem_.size(); };
    bool empty() const { return elem_.empty(); };

    void print(std::ostream &amp; out) const;
protected:
    std::vector<T> elem_;
};

template <typename T>
void Stack<T>::push(const T &amp;value)
{
    elem_.push_back(value);
}

template <typename T>
void Stack<T>::pop()
{
    elem_.pop_back();
}

template <typename T>
T Stack<T>::top()
{
    return elem_.back();
}

template <typename T>
void Stack<T>::print(std::ostream &amp;out) const
{
    for (auto e : elem_)
    {
        out << e << std::endl;
    }
}
2.2 stack 类模板使用

1.直到 c++17,使用类模板都需要显式指定模板参数。

2.类模板的成员函数只有在调用的时候才会实例化

2.3 部分使用类模板

1.类模板实例化时,模板实参只需要支持被实例化部分所有用到的操作

int main()
{
    // 只会实例化类模板中的push 和 print函数
    Stack<int> s;
    s.push(3);
    s.print(std::cout);

    // Stack<int>未重载<<运算符,实例化print函数时失败
    Stack<Stack<int>> ss;
    ss.push(s);
    ss.print(std::cout);
    return 0;
}

2.c++11 开始,可以通过 static_assert 和 type_traits 做一些简单的类型检查

template <typename T>
class C
{
    static_assert(std::is_default_constructible<T>::value, "class C requires default contructible");
};
2.4 友元
2.5 模板特化

1.可以对类模板的一个参数进行特化,类模板特化的同时需要特化所有的成员函数,非特化的函数在特化后的模板中属于未定义函数,无法使用。

// stringle类型特化
template <>
class Stack<std::string>
{
public:
    void push(const std::string&amp; value);
    /* 特化其他成员函数*/
};
2.6 模板偏特化

1.类模板特化时,可以只特化部分参数,或者对参数进行部分特化。

// 指针类型特化
template <typename T>
class Stack<T *>
{
    public:
    void push(T *value);
    void pop();
    T* top();
    int size() const { elem_.size(); };
    bool empty() const { return elem_.empty(); };
protected:
    std::vector<T *> elem_;
};

template <typename T>
void Stack<T*>::push(T *value)
{
    elem_.push_back(value);
}

template <typename T>
void Stack<T*>::pop()
{
    elem_.pop_back();
}

template <typename T>
T* Stack<T*>::top()
{
    return elem_.back();
}
2.7 默认类模板参数

1.类模板也可以指定默认模板参数。

template <typename T, typename COND = std::vector<T> >
class Stack
{
public:
    void push(const T&amp; value);
    void pop();
    T top();
    int size() const { elem_.size(); };
    bool empty() const { return elem_.empty(); };
protected:
    COND elem_;
};

template <typename T, typename COND>
void Stack<T, COND>::push(const T &amp;value)
{
    printf("template 1\n");
    elem_.push_back(value);
}

template <typename T, typename COND>
void Stack<T, COND>::pop()
{
    elem_.pop_back();
}

template <typename T, typename COND>
T Stack<T, COND>::top()
{
    return elem_.back();
}
2.8 类型别名

1.为了便于使用,可以给类模板定义别名。

typedef Stack<int> IntStack;
using DoubleStack = Stack<double>;

2.c++11 开始可以定义别名模板,为一组类型取一个方便的名字。

template <typename T>
using DequeStack = Stack<T, std::deque<T>>;

3.c++14 开始,标准库使用别名模板技术,为所有返回一个类型的 type_trait 定义了快捷的使用方式。

// stl库定义
namespace std
{
    template <typename T>
    using add_const_t = typename add_const<T>::type;
}

typename add_const<T>::type;  //c++ 11 使用
std::add_const_t<T>; //c++14使用
2.9 类模板类型推导

1.c++17 开始,如果构造函数能够推断出所有模板参数的类型,那么不需要指定参数类型了。

template <typename T>
class Stack
{
public:
    Stack() = default;
    Stack(T e): elem_({e}){};
protected:
    std::vector<T> elem_;
};

Stack intStack = 0; //通过构造函数推断为int

2.类型推导时,构造函数参数应该按照值传递,而非按引用。引用传递会导致类型推断时无法进行 decay 转化

Stack strStack = "sjx";
//若构造函数参数为值传递,则T为const char *,引用传递时则为const char[4]

3.c++ 17 支持提供推断指引来提供额外的推断规则,推断指引一般紧跟类模板定义之后。

// 推断指引,传递字符串常量时会被推断为string
Stack<const char *> -> Stack<std::string>
2.10 聚合类的模板化

1.聚合类:没有显式定义或继承来的构造函数,没有非 public 的非静态成员,没有虚函数,没有 virtual,private ,protected 继承。聚合类也可以是模板。

template <typename T>
struct ValueWithComment
{
    T val;
    std::string comment;
};

ValueWithComment<int> vc;
vc.val = 42;
vc.comment = "sjx";

三、非类型模板参数

3.1 类模板的非类型模板参数

1.模板参数不一定是类型,可以是数值,如可以给 Stack 指定最大容量,避免使用过程元素增删时的内存调整。

template <typename T, int MAXSIZE>
class Stack
{
public:
    Stack():num_(0){};
    void push(const T&amp; value);
    void pop();
    T top();
    int size() const { return num_; };
    bool empty() const { return num_ == 0; };
protected:
    T elem_[MAXSIZE];
    int num_;
};

template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T &amp;value)
{
    printf("template 1\n");
    assert(num_ < MAXSIZE);
    elem_[num_++] = value;
}

template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
    assert(num_ > 0);
    --num_;
}

template <typename T, int MAXSIZE>
T Stack<T, MAXSIZE>::top()
{
    assert(num_ > 0);
    return elem_[0];
}
3.2 函数模板的非类型模板参数

1.函数模板也可以指定非类型模板参数。

template<typename T, int VAL>
T addval(const T &amp;num)
{
    return num + VAL;
}

int main()
{
    std::vector<int> nums = {1, 2, 3, 4, 5};
    std::vector<int>nums2(nums.size(),0);
    std::transform(nums.begin(),nums.end(),nums2.begin(),addval<int,5>);
    for(auto num : nums2)
    {
        printf("%d\n",num);
    }
}
3.3 非类型模板参数限制

1.非类型模板参数只能是整形常量(包含枚举),指向 objects/functions/members 的指针,objects 或者 functions 的左值引用,或者是 std::nullptr_t(类型是 nullptr),浮点数和类对象不能作为非类型模板参数

2.当传递对象的指针或者引用作为模板参数时,对象不能是字符串常量,临时变量或者数据成员以及其它子对象

3.对于非类型模板参数是 const char*的情况,不同 c++版本有不同限制

a. C++11 中,字符串常量对象必须要有外部链接

b. C++14 中,字符串常量对象必须要有外部链接或内部链接

c. C++17 中, 无链接属性也可

4.内部链接:如果一个名称对编译单元(.cpp)来说是局部的,在链接的时候其他的编译单元无法链接到它且不会与其它编译单元(.cpp)中的同样的名称相冲突。例如 static 函数,inline 函数等。

  1. 如果一个名称对编译单元(.cpp)来说不是局部的,而在链接的时候其他的编译单元可以访问它,也就是说它可以和别的编译单元交互。

6.非类型模板参数的实参可以是任何编译器表达式,不过如果在表达式中使用了 operator >,就必须将相应表达式放在括号里面,否则>会被作为模板参数列表末尾的>,从而截断了参数列表。

#include<string>

template < double VAL > // error 浮点数不能作为非类型模板参数
double process(double v)
{
    return v * VAL;
}

template < std::string name > // error class对象不能作为非类型模板参数
class MyClass {
};

template < const char * name >
class Test{
};

extern const char s01[] = "sjx"; // 外部链接
const char s02[] = "sjx"; // 内部链接

template<int I, bool B>
class C{};
int main()
{
    Test<"sjx"> t0; // error before c++17
    Test<s01> t1; // ok
    Test<s02> t2; // since c++14
    static const char s03[] = "sjx"; // 无链接
    Test<s03> t3; // since c++17

    C<42, sizeof(int)> 4 > c; //error 第一个>被认为模板参数列表已经结束
    C<42, (sizeof(int)> 4) > c; // ok
}
3.4 用 auto 作为非模板类型参数的类

1.从 C++17 开始,可以不指定非类型模板参数的具体类型(代之以 auto),从而使其可以用于任意有效的非类型模板参数的类型。

template <typename T, auto MAXSIZE>
class Stack
{
public:
    using size_type = decltype(MAXSIZE); // 根据MAXSIZE推断类型
public:
    Stack():num_(0){};
    void push(const T&amp; value);
    void pop();
    T top();
    size_type size() const { num_; };
    bool empty() const { num_ == 0; };
protected:
    T elem_[MAXSIZE];
    size_type num_;
};

template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T &amp;value)
{
    printf("template 1\n");
    assert(num_ < MAXSIZE);
    elem_[num_++] = value;
}

template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
    assert(num_ > 0);
    --num_;
}

template <typename T, int MAXSIZE>
T Stack<T, MAXSIZE>::top()
{
    assert(num_ > 0);
    return elem_[0];
}

int main()
{
    Stack<int,20u> s1; // size_type为unsigned int
    Stack<double,40> s2; // size_type为int
}

四、可变参数模板

4.1 可变参数模板

1.c++11 开始,模板可以接收一组数量可变的参数。

#include <iostream>
void print(){};  // 递归基
template<typename T, typename ...Types>
void print(T firstArg, Types ... args)
{
    std::cout << firstArg << std::endl;
    print(args...);
}

int main(){
    print(7.5, "hello", 10); // 调用三次模板函数后再调用普通函数
}
  1. 当两个函数模板的区别只在于尾部的参数包的时候,会优先选择没有尾部参数包的函数模板

// 使用模板函数的递归基,最后只剩一个参数时会优先使用本模板
template<typename T>
void print(T arg)
{
    std::cout << arg << std::endl;
}

3.c++11 提供了sizeof...运算符来统计可变参数包中的参数数目

template<typename T, typename ...Types>
void print(T firstArg, Types ... args)
{
    std::cout << sizeof...(Types) << std::endl;
    std::cout << sizeof...(args) <
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值