自定义字面量
    字面量(literal)指在源代码中写出的固定常量,在c++98中只能是原生类型如
    “hello”字符串字面量 类型是cosnt char[6], 1整型字面量类型是int, 0.0 双精度浮点字面量double, 3.14f单精度浮点数字面量 float,123456789ul 无符号长整型字面量 usigned long

    c++11引用自定义字面量,使用operator”” 后缀来将用户提供的字面量转换成实际的类型对象,在c++14的标准库中就用这种方法定义了许多标准字面量

    1. #include <chrono>
    2. #include <complex>
    3. #include <iostream>
    4. #include <string>
    5. #include <thread>
    6. using namespace std;
    7. int main()
    8. {
    9. cout << "i * i = " << 1i * 1i // complex 虚数字面量 operator""i 后缀i
    10. << endl;
    11. cout << "Waiting for 500ms"
    12. << endl;
    13. this_thread::sleep_for(500ms); //chrono 时间字面量 operator""ms 后缀ms
    14. cout << "Hello world"s.substr(0, 5)// basic_string字面量operator""s 后缀s
    15. << endl;
    16. }

    输出是:i i = (-1,0)
    Waiting for 500ms
    Hello

    上面使用了using namespace std; ,这同时会引入std名空间和里面的内联名空间(inline namespace),包括了上面的字面量运算符所在的三个命名空间
    std::literals::complex_literals
    std::literals::chrono_literals
    std::literals::string_literals
    在实际项目中,不会也不应该全局使用using namespace std,这种情况下应当在 *我们使用这些字面量的作用域里 导入需要的名空间
    ,以免发生冲突
    在上面这个例子中应该

    1. int main()
    2. {
    3. //我们使用这些字面量的作用域里 导入需要的名空间
    4. using namespace std::literals::complex_literals
    5. using namespace std::literals::chrono_literals
    6. using namespace std::literals::string_literals
    7. cout << "i * i = " << 1i * 1i // complex 虚数字面量 operator""i 后缀i
    8. << endl;
    9. cout << "Waiting for 500ms"
    10. << endl;
    11. this_thread::sleep_for(500ms); //chrono 时间字面量 operator""ms 后缀ms
    12. cout << "Hello world"s.substr(0, 5)// basic_string字面量operator""s 后缀s
    13. << endl;
    14. }


    要自己的类支持字面量也很容易,唯一的限制是定义 字面量op时必须以下划线_打头
    比如
    length operator”” _m(long double v){}
    //为一个长度类定义字面量op

    1. struct length {
    2. double value;
    3. enum unit {
    4. metre,
    5. kilometre,
    6. millimetre,
    7. centimetre,
    8. inch,
    9. foot,
    10. yard,
    11. mile,
    12. };
    13. static constexpr double factors[] =
    14. {1.0, 1000.0, 1e-3,
    15. 1e-2, 0.0254, 0.3048,
    16. 0.9144, 1609.344};
    17. explicit length(double v,
    18. unit u = metre)
    19. {
    20. value = v * factors[u];
    21. }
    22. };
    23. length operator+(length lhs,
    24. length rhs)
    25. {
    26. return length(lhs.value +
    27. rhs.value);
    28. }
    29. // 可能有其他运算符
    30. length operator"" _m(long double v)//米单位字面量op
    31. {
    32. return length(v, length::metre);
    33. }
    34. length operator"" _cm(long double v)//厘米单位字面量op
    35. {
    36. return length(v, length::centimetre);
    37. }

    1.0_m + 10.0_cm 有了上面的三个op函数,这个表达式就能正常运行


    二进制字面量

    c中就有0x前缀,可以让程序员直接写出0xff这样的十六进制字面量,还有就是0后面直接跟0-7数字来表示八进制字面量。
    从c++14开始对于二进制也有了直接的字面量,以0b开头
    unsigned mask = 0b111000000;
    这在需要比特级操作的场合非常有用
    不过,遗憾的是, I/O streams 里只有 dec、hex、oct 三个操纵器(manipulator),而没有 bin,因而输出一个二进制数不能像十进制、十六进制、八进制那么直接。一个间接方式是使用 bitset,但调用者需要手工指定二进制位数:

    1. #include <bitset>
    2. cout << bitset<9>(mask) << endl;//手动指定二进制位数


    数字分隔符

    数字长了之后,看清位数就变得很麻烦特别是二进制字面量,c++14开始允许在数字型字面量中任意添加’来分隔 使得可读性更好

    1. unsigned mask = 0b111'000'000;//3位 二进制 分隔 提高可读性
    2. long r_earth_equatorial = 6'378'137;
    3. double pi = 3.14159'26535'89793;
    4. const unsigned magic = 0x44'42'47'4E;


    静态断言

    c++98的assert允许在运行时检查一个函数的前置条件是否成立,但是没有一种方法允许在编译时检查假设是否成立。
    例如有个模板参数alignment,表示对齐,我们需要再编译时就检查alignment是不是二的整数幂,c++98使用下面的方法来实现

    1. #define STATIC_ASSERT(_Expr,_Msg) static_assert(_Expr,#_Msg)

    通过alignment&(alignment-1) =? 0来判断alignment是否为二的整数幂

    1. STATIC_ASSERT( alignment&(alignment-1) ) == 0; //是用static_assert返回是否为0来判断

    9 易用性改进2:字面量、静态断言和成员函数说明符 - 图1

    输出的信息并不是那么直观

    在c++11中直接从语言层面提供了静态断言机制,能输出更好的信息,而且适用性也更好,可以直接放在类定义中,而不像之前的技巧只能放在函数体里
    9 易用性改进2:字面量、静态断言和成员函数说明符 - 图2
    语法上就是下面这样
    static_assert(编译期条件表达式,可选输出信息);
    c++11中就可以像下面这样写

    1. static_assert((alignment & (alignment - 1)) == 0,"Alignment must be power of two");



    default和delete成员函数

    一个类定义时,c++编译器可能自动生成一些默认的特殊成员函数,这些特殊成员函数包括:
    无参默认构造函数
    析构函数
    拷贝构造(默认数据成员拷贝赋值)
    拷贝赋值函数(op=)
    移动构造函数
    移动赋值函数

    是否生成或不生成这些函数的规则比较复杂可以参考 中的特殊成员函数
    https://en.cppreference.com/w/cpp/language/member_functions

    每个特殊成员函数可以有3种不同状态, 不同状态可以组合,虽然并不是所有的组合都有效
    隐式声明还是用户声明(显式定义) —-不是explict,explict是是否可以隐式地调用
    默认提供还是用户提供 default
    正常状态还是删除状态 delete

    一般在成员和父类没有特殊原因导致 此类对象 不可拷贝或移动 的情况下,用户不显式定义这些成员函数的情况下,编译器会自动产生这些成员函数—(隐式声明,默认提供,正常状态)
    下面是一些编译器在一些情况下会做的选择
    如果类内有未初始化的 非静态const数据成员 或 引用类型数据成员 将导致默认构造函数被删除
    类内有 非静态const数据成员 或 引用类型数据成员 将导致默认的拷贝构造、拷贝赋值、移动构造、移动赋值被删除

    只要用户提供了任一构造函数,编译器就不会提供默认无参构造

    用户没有提供形如Obj(Obj&)或Obj(const Obj&) (不含模板 就是自己类型的拷贝构造) 编译器会隐式声明一个

    用户没有提供形如Obj& opeartor=(Obj&)或Obj& opeartor=(const Obj&) (不含模板 就是自己类型的拷贝赋值) 编译器会隐式声明一个

    用户如果声明了一个移动构造或移动赋值, 则默认拷贝构造和拷贝赋值 将被删除

    用户没有声明自己的拷贝构造和拷贝赋值和移动赋值和析构函数,编译器隐式地声明一个移动构造

    用户没有声明自己的拷贝构造和拷贝赋值和移动构造和析构函数,编译器隐式地声明一个移动赋值
    对于移动构造和移动赋值而言 只要声明了所有特殊成员函数中的任一个(除了无参构造),编译器就不会默认提供移动函数,为了安全。

    这样的规则还用很多,并且比较复杂,还是在项目中体会其中缘由。

    我们这儿主要要说的是,我们可以改变缺省行为,在编译器能默认提供特殊成员函数时将其删除,或在编译器不默认提供特殊成员函数时明确声明其需要默认提供(不过,要注意,即使用户要求默认提供,编译器也可能根据其他规则将特殊成员函数标为删除)。

    1. template <typename T>
    2. class my_array {
    3. public:
    4. my_array(size_t size);
    5. private:
    6. T* data_{nullptr};
    7. size_t size_{0};
    8. };
    9. //用户提供了带参构造函数,编译器不会提供默认无参构造
    10. //如果我们需要默认构造函数,就需要我们手工提供这个构造
    11. template <typename T>
    12. class my_array {
    13. public:
    14. my_array(size_t size);
    15. my_array()
    16. : data_(nullptr)
    17. , size_(0) {}
    18. private:
    19. T* data_{nullptr};
    20. size_t size_{0};
    21. };
    22. //在c++11中可以
    23. template <typename T>
    24. class my_array {
    25. public:
    26. my_array(size_t size);
    27. my_array() = default; // … 强制编译器提供默认构造
    28. private:
    29. T* data_{nullptr};
    30. size_t size_{0};
    31. };

    我们在前面很多讲内讲过shape_wrapper的拷贝行为是不安全的(因为定义了移动行为,想一个资源只能被一个对象拥有),所以我们需要禁止编译器提供的拷贝构造和拷贝赋值,可以用=delete

    1. class shape_wrapper {
    2. shape_wrapper(
    3. const shape_wrapper&) = delete;
    4. shape_wrapper& operator=(
    5. const shape_wrapper&) = delete;
    6. };

    注意一下,用户声明成删除也是一种声明,因此根据上面讲的一大串规则,编译器不会提供默认版本的移动构造和移动赋值函数。


    override和final成员函数
    c++11新引入的说明符,不是关键词仅在写到函数声明的尾部起作用。这两个说明符可以组合使用,都是加在类成员函数声明的尾部。

    override 显式地声明了成员函数是虚函数且覆盖了父类中的该函数。如果一个函数被声明为override但是类中的虚函数表找不到这个函数(这个函数本身不是虚函数,或父类中不存在这个虚函数),编译器报错
    override主要有两个作用:1 给开发人员明确的提示,这个函数覆盖了父类成员的虚函数
    2 让编译器进行额外的检查,防止程序员由于拼写错误 导致子类的函数和父类的虚函数名称不一样

    1. class A {
    2. public:
    3. virtual void foo();
    4. virtual void bar();
    5. void foobar();
    6. };
    7. class B : public A {
    8. public:
    9. void foo() override; // OK
    10. //void foobar() override;// 非虚函数不能 override
    11. };
    12. class C : public B {
    13. public:
    14. void foo() override; // OK 不override的话,foo就是B中的foo
    15. };


    final声明成员函数是一个虚函数,且该虚函数不可被子类的同名函数覆盖。 两点中的任一点不满足编译器报错
    fianl写在类名后 声明某个类或结构体 不可被继承。

    1. class A {
    2. public:
    3. virtual void foo();
    4. virtual void bar();
    5. void foobar();
    6. };
    7. class B : public A {
    8. public:
    9. void foo() override; // OK
    10. void bar() override final; // OK
    11. //final override这种声明 合法但不必要。
    12. //void foobar() override;
    13. // 非虚函数不能 override
    14. };
    15. class C final : public B {
    16. public:
    17. void foo() override; // OK
    18. //void bar() override;
    19. // bar在B父类中被声明为 final final函数不可 override
    20. };
    21. class D : public C {
    22. // C类定义时被声明为final
    23. // 错误:final 类不可派生
    24. };


    补充一下 over load(重载) over write(隐藏) override(覆盖) 的区别
    ///////////////////////////////////////////////////////////////////
    Overload(重载):在C++程序中,可以将语义、功能相似的几个函数用同一个名字表示,但参数或返回值不同(包括类型、顺序不同),即函数重载。
    (1)相同的范围(在同一个类中);
    (2)函数名字相同;
    (3)参数不同;参数的个数,类型,顺序不同都符合重载标准
    (4)virtual 关键字可有可无。

    Override(覆盖):是指子类函数覆盖父类函数—多态行为,特征是:
    (1)不同的范围(分别位于子生类与父类);
    (2)函数名、参数列表、返回值类型都与父类中函数相同,或者函数是否被const声明也算
    (3)父类函数必须有virtual 关键字。

    Overwrite(隐藏):是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
    (1)如果子类的函数与父类的函数同名,但是参数不同。此时,不论有无virtual关键字,父类的函数将被隐藏(注意别与重载混淆)。
    (2)如果子生类的函数与父类的函数同名,并且参数也相同,但是父类函数没有virtual关键字。此时,父类的函数被隐藏(注意别与覆盖混淆)。
    只要同名函数,不管参数列表是否相同,基类函数都会被隐藏(除了覆盖的那种情况)
    可以用对象.父类名::同名隐藏函数()这种形式调用父类中被隐藏的函数
    ///////////////////////////////////////////////////////////////////