C++11&14.md 14 KB

  • C++的一些版本
    • C++98(1.0)
    • C++03
    • C++11(2.0)
    • C++14

Variadic Templates 可变参数模板

void printX()
{}

template <typename T, typename... Types>
void printX(const T& firstArg, const Types&... args)
{
    cout << firstArg << endl;
    printX(args...);
}

通过sizeof...(args)可以知道args中包括多少个参数

通过可变参数模板,可以帮助我们使用递归做一些操作

// 可变参数模板的用例

#include <functional>
template<typename T>
inline void hash_combine(size_t& seed. const T& val)// 函数_4
{
    seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed<<6) + (seed >> 2);
}

template<typename T>
inline void hash_val(size_t& seed, const T& val){// 函数_3
    hash_combine(seed, val);
}

template<typename T, typename... Types>
inline void hash_val(size_t& seed, const T& val, const Types&... args)// 函数_2
{
    hash_combine(seed, val);
    // 根据args...的参数个数来执行 函数_2 或者 函数_3
    hash_val(seed, args... );
}

template<typename... Types>
inline size_t hash_val(const Types&... args)// 函数_1
{
    size_t seed = 0;
    // 虽然 函数_1 和 函数_2 都符合执行条件,但是 函数_2 相比而言更特化,所以执行 函数_2
    hash_val(seed, args);
    return seed;
}

class CustomerHash{
    public:
    std::size_t operator()(const Customer& c) const{
        // 因为第一个fname不是size_t类型,所以执行的是 函数_1 hash_val
        return hash_val(c.fname, c.lname, c.no);    
    }
}

这里用args配合递归不停的切分参数

template<typename... Values> class tuple;
template<> class tuple<> {}; // 作为中止点

template<typename Head, typename... Tail>
class tuple<Head, Tail...> : private tuple<Tail>
{
    typedef tuple<Tail...> inherited;
public:
    tuple() {}
    tuple(Head v, Tail... vtail) : m_head(v), inherited(vtail) {}
    typename Head::type head() {return m_head;}
    inherited& tail() {return *this;}

protected:
    Head m_head;
}

// tuple<int, float, string> t(41, 6.3, "Niko");
// t.head() -> 41
// t.tail() -> tuple<float, string>(6.3, "Niko")
// t.tail()->head() -> 6.3

这里最上面有个空的 tuple<>,是作为递归继承的中止点
这里用递归继承的方式做出了tuple

模板中的空格

vector<list<int> > // 全版本支持
vector<list<int>> // C++11之后没有空格也行

因为老版本一些编译器会将>>识别为操作符,所以需要加空格
C++11之后不加空格也行

nullptr ans std::nullptr_t

nullptr 指的是 空指针,C++11允许用nullptr代替0或者NULL

void f(int);
void f(void*);
f(0);               // 运行  f(int)
f(NULL);            // 运行  f(int),如果是其他数,就不清楚具体调用哪个了
f(nullptr);         // 云心  f(void*)
#if defined(__cplusplus) && __cplusplus >= 201103L
#ifndef __GXX_NULLPTR_T
#define __GXX_NULLPTR_T
    typedef decltype(nullptr) nullptr_t;

auto

auto i = 20;    // i 的 类型是 int

double f();
auto d = f();   // d 的 类型是 double

vector<string> v;
auto pos = v.begin();   // pos 的 类型是 vector<string>::iterator

auto l = [](int x) -> bool {
    // ...
};              // l 的类型是 lambda表达式

使用auto主要是用在数据类型 太长 或者 太复杂 的情况 (比如迭代器、lambda的类型)
倒也不是什么情况都用auto

// 标准库中对auto的使用
#if __cplusplus >= 201103L
    inline auto operator- (const reverse_iterator<_IteratorL> &__x, 
        const reverse_iterator<_IteratorR> &__y)
        -> decltype(__y.base() - __x.base())
#else
    inline typename reverse_iterator<_IteratorL>::difference_type 
        operator- (const reverse_iterator<_IteratorL>& __x, 
        const reverse_iteratror<_IteratorR>& __y)
#endif

unifor initialization 一致性初始化

初始化可能发生在小括号、中括号、赋值中

Rect r1 = {3, 4, 5};        // 大括号
Rect r1(3, 4, 5);           // 小括号
int values[5] = {1, 3, 4, 5};   // 赋值

C++11引入一致性初始化——大括号(虽然用以前的也没错)

int values[]{1, 3, 4, 5}; 
vector<int> v{1, 3 ,4 ,5};
complex<duoble> c{3.0, 4.0};

变量之后直接加上大括号即表示初始化

当编译器看到{t1, t2, t3...}便做出一个initializer_list<T>,他关联至一个array<T, n>,调用函数(比如构造函数)时arry中的元素被逐个分解传给函数;如果调用函数的参数就是initializer_list<T>,就不会注意分解而是直接整个传给函数

比如:vector<int> v{1, 3 ,4 ,5}; 中的初始化参数 {1, 3 ,4 ,5}转换成 initlializer_list<int>,而这个关联array<int, 4>容器

initalizer_list 初始化列表

int i;          // 没有被初始化
int j{};        // j被初始化为0
int *p;         // p没有被初始化
int *q{};       // q被初始化为nullptr
int x1(4.3);            // ok
int x2 = 4.3;           // ok
int x3 {4.3};           // 部分平台Error,部分平台warning
int x4 = {5.3};         // 部分平台Error,部分平台warning

char c1{7};             // ok
char c2{9999};          // 部分平台Error,部分平台warning

std::vector<int> v1{1, 3, 4, 5};            // ok
std::vector<int> v2{1.0, 3.0, 4.0, 5.0};    // 部分平台Error,部分平台warning

初始化列表的作用,还可以帮助检查一些错误


void print(std::initlializer_list<int> vals)
{
    for(auto p = vals.begin(), p != vals.end(); ++p){
        std::cout << *p << std::endl;
    }
}

print({1, 3, 4, 5});

证明{t1, t2, t3...}被转换成initializer_list<T>

class P{
public:
    P(int a, int b){
        std::cout << "(int ,int)" << std::endl;
    }
    P(initializer_list<int> initlist){
        std::cout << "initializer_list" << std::endl;
    }
};

P p(7, 7);          //  输出 (int ,int)
P p{7, 7};          //  输出 initializer_list
P r{7, 5, 4};       //  输出 initializer_list
P p = {7, 7};       //  输出 initializer_list

如果不存在第二个initializer_list的构造函数,则第三个对象 r 将创建失败

explicit


/**
 * non-explicit-one-argument 只要一个实参就够了的构造函数
 * 下面例子中:没有写oprator * (Fraction, int)的操作符重载也不会报错,因为编译器将4通过一个参数的构造变成Fraction(4, 1)类型
 * 当构造函数前加上explicit关键字时,编译器不会进行隐式构造函数将4变成Fraction(4, 1)
 * 
 * ——————————————————————————————————————————————————————————————————————————————
 * 如果构造函数不存在explicit,且有operator double()转换函数,编译器报错,因为存在二义性即两种方式都可以行得通且没有优先级这么一说
 * 如果构造函数存在explicit,且有operator double()转换函数,编译器不报错,因为只有一条路可走,将Fraction转换成double计算
 * 如果构造函数不存在explicit,且没有operator double()转换函数,编译器不报错,因为只有一条路可走,将4转换成Fraction计算
 **/

class Fraction
{
  public:
    // 这里构造函数只需要一个参数其实也够
    Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}
    //explicit Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}

    Fraction operator*(const Fraction &f)
    {
        return Fraction(f.m_numerator * this->m_numerator, f.m_denominator * this->m_denominator);
    }
    std::string getString()
    {
        char numerator[10];
        char denominator[10];
        memset(numerator, 0, sizeof(numerator));
        memset(denominator, 0, sizeof(denominator));
        itoa(m_numerator, numerator, 10);
        itoa(m_denominator, denominator, 10);
        return std::string(std::string(numerator) + std::string("/") + std::string(denominator));
    }

  private:
    int m_denominator; // 分母
    int m_numerator;   // 分子
};

int main()
{
    Fraction f(3, 5);
    Fraction f2 = f * 4;
    std::cout << f2.getString() << std::endl;
    system("pause");
    return 0;
}

explicit告诉编译器,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换

range-based for statement

// for(decl : coll){
//     statement
// }

for(int i : {2, 3, 4, 5, 6}){
    cout << i << endl;
}

vector<double> vec;
//...
for(auto elem : vec){
    cout << elem << endl;
}
for(auto &elem : vec){
    elem *= 3;
}

// range-based for 的底层实现类似

for(auto _pos = coll.begin(), _end = coll.end(); _pos != _end; ++pos){
    decl = *_pos;
    // 对值的操作
}
// 或者
for(auto _pos = begin(coll), _end = end(coll); _pos != _end; ++pos){
    decl = *_pos;
    // 对值的操作
}

下面的写法会报错

class C{
public:
    explicit C(const string& s); // 禁止string隐式转换成C
    // ...
}

vector<string> vs;
for(const C& elem : vs){
    cout << elem << endl;   // 报错,因为字符串不能隐式的转换成C
}

=default, =delete

如果自行定义一个构造函数,那么编译器不会再给一个默认构造函数

如果强制加上 =default 就可以重新获得并使用默认构造函数

class Zoo
{
public:
    Zoo(int i1, int i2) : d1(i1), d2(i2) {}
    Zoo(const Zoo&) =delete;    // 不允许拷贝构造
    Zoo(Zoo &&) =default;       // 使用编译器给的
    Zoo& operator=(const Zoo &) =default;
    Zoo& operator=(const Zoo&&) =delete;
    virtual ~Zoo(){}
private:
    int d1, d2;
};
class Foo{
public:
    Foo(int i) : _i(i) {}
    Foo() = default;    // 构造函数可以有多个,这里希望编译器提供默认构造

    Foo(const Foo& x) : _i(x._i) {}
    // Foo(const Foo&) = default;   // Error 拷贝构造函数不可被重载
    // Foo(const Foo&) = delete;    // Error 拷贝构造函数不可被重载

    Foo& operator=(const Foo& x) {_i = x._i; return *this;}
    // Foo& operator=(const Foo& x) = default; // Error 赋值不可被重载
    // Foo& operator=(const Foo& x) = delete;  // Error 赋值不可被重载

    void func1() = default; // Error 不可作为default
    void func2() = delete;  // ok 不过如果不需要可以不写这个函数…

    ~Foo() = default;
    // ~Foo() = delete // 可以new不可delete
};
  • =default 除了默认成员函数(构造、析构、拷贝、赋值等)之外,其他函数使用编译报错(很明显,其他函数也没有所谓的默认)
  • =delete可以用在任何函数上(=0只能用于virtual函数)

一般而言,如果成员对象中存在指针,建议考虑深拷贝、浅拷贝的问题,需要自定义拷贝、赋值函数

Alias Templat (template typedef)化名模板

template<typename T>
using Vec = std::vector<T, MyAlloc<T>>;

Vec<int> coll; // = std::vector<int, MyAlloc<int> coll;

decltype

使用该关键字可以找到表达式的参数类型

map<string, float> coll;
decltype(coll)::value_type elem;    // C++11可以通过对象使用
map<string,float>::value_type elem; // C++11之前必须指明类型使用

template<typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x+y);  // 通过decltype动态设置返回参数类型

decltype(x+y) add(T1 x, T2 y);      // 这样写报错,因为decltype先不知道x、y是什么,在后面的函数参数中才知道

auto cmp = [](const Person& p1, const Person& p2){
    // 比较大小
};
std::set<Person, decltype(cmp)> coll(cmp);

lambda表达式

[]{
    std::cout << "hello world" << std::endl;
}(); // 输出 hello world

auto l = []{
    std::cout << "hello world" << std::endl;
};
l();// 输出 hello world

[导入器](参数) mutable 异常 -> 返回类型 {
    函数体
}

导入器,取用的外部变量(传值、传引用)
参数,正常函数参数
mutable 是否可以修改值
异常 一些特殊操作抛出异常

{
    int id = 0;
    // 值传递
    auto f = [id]() mutable {
        std::cout << "id : " << id << std::endl;
        ++id;// 如果没有mutable,则id不可执行++操作
    };

    id = 42;
    f();                            // 输出 id : 0
    f();                            // 输出 id : 1
    f();                            // 输出 id : 2
    std::cout << id << std::endl;   // 输出 id : 42
}

{
    int id = 0;
    // 引用传递
    auto f = [&id](int param)  {
        std::cout << "id : " << id << std::endl;
        ++id; ++param;
    };

    id = 42;
    f(7);                            // 输出 id : 42
    f(7);                            // 输出 id : 43
    f(7);                            // 输出 id : 44
    std::cout << id << std::endl;   // 输出 id : 45
}

{
    int id = 0;
    auto f = [id]() {
        std::cout << "id : " << id << std::endl;
        ++id;
    };
    // 报错 没有mutable 不可更改
    id = 42;
    f();                           
    f();                           
    f();                           
    std::cout << id << std::endl;  
}

// 一个 = 表示 其他所有对象都是值传递, id为引用传递
auto f = [=, &id]{ 
    // ...
};

Vaiadic Templates

允许定义可变数量的模板参数。在传统的模板中,参数数量是固定的,而 Variadic Templates 允许我们在模板中接受任意数量的参数

void func() { /* ... */}

template<typenaem T, typename... Types>
void func(const T& firstArg, const Types... args) {
    // do something
    func(args);
}
template<typename T>
T sum(T t) {
    return t;
}

template<typename T, typename... Args>
T sum(T t, Args... args) {
    return t + sum(args...);
}

int main() {
    int result = sum(1, 2, 3, 4, 5);
    // result = 1 + 2 + 3 + 4 + 5 = 15
    return 0;
}

利用参数个数的逐一递减的特性实现递归函数调用