C++11

目录

  • std::array
  • std::move
  • delete
  • 类型推断
  • for_each
  • 范围for循环
  • 非静态成员的sizeof
  • final
  • override
  • 就地初始化
  • 列表初始化
  • initialize_list
  • constexpr
  • 智能指针
  • 提高类型安全
  • 数值极限
  • 属性
  • 引用限定符
  • C++14
    • 变量模板
    • std::exchange
    • std::shared_timed_mutex
    • 属性

连续两个右尖括号>中间空格隔开,C++98会优先解析为右移,C++11会优先解析为模板参数界定符

std::array

使用std::array替代C数组带来很多好处:总是知道自身大小;不会自动转化为指针;具有迭代器,可以方便的遍历元素

std::move

头文件#include
并不能移动任何东西,只是将左值强制转成右值,从实现上相当于static_cast(lvalue)
注意:被转换的左值,其生命周期并没有随着左右值的转化而改变,就是说,lvalue的析构不能依赖于move的实现,所以要析构掉就要靠自己
在返回语句中使用std::move(C++20P244):

  • 对于return object形式的语句,如果object是局部变量、函数的参数或临时值,则它们被视为右值表达式,并触发返回值优化(RVO);如果object是个局部变量,则会启动命名返回值优化(NRVO)。RVO和NRVO都是复制省略的形式,使得从函数返回对象非常有效。避免了复制和移动函数返回的对象,导致了零拷贝值传递语义
  • 使用std::move返回,编译器无法再使用RVO或NRVO,因为这只适用于形式为return object的语句。由于RVO和NRVO不再适用,编译器的下一个选在是在对象支持的情况下使用移动语义,如果不支持则使用复制语义。

所以当从函数中返回一个局部变量或参数时,只要return object即可,不要使用std:::move;(N)RVO仅适用于局部变量或函数参数。

delete

可以删除重载函数

class MyClass {
public:
  void set_value(double d) {
    std::cout << d << endl;
  }
  void set_value(int) = delete;
};
// 此时,如果调用c.set_value(2);不会发生隐式转换而是编译报错

类型推断

auto

  • auto声明变量的类型必须由编译器在编译时期推导而得;auto声明的变量必须被初始化;优势是可以匹配返回类型,因为有时候不确定返回类型,就会声明错误
  • 自动类型推导的一种新写法:auto func(T params) -> type;可以配合decltype关键字:templateauto add(U a, V b) -> decltype(a+b)
  • auto&语法:使用auto推断类型时去除了引用和限定符
  • auto*语法 Todo
  • 拷贝列表初始化和直接列表初始化:
// 拷贝列表初始化
T obj = {arg1, arg2, ... };
// 直接列表初始化
T obj{arg1, arg2, ... }

与自动类型推断相结合,拷贝列表初始化和C++17引入的直接列表初始化之间存在重要区别(以下部分未验证):

// C++17后:
auto a = { 11 };    // initializer_list
auto b = { 11, 12 } // initializer_list
auto c{11}; // int
auto d{11,12};  // error

在C++11/14中,拷贝列表和直接列表初始化都将推断出initializer_list<>

// C++11/c++14:
auto a = { 11 };    // initializer_list
auto b = { 11, 12 } // initializer_list
auto c { 11 }; // initializer_list
auto d { 11, 12 };  // initializer_list

delctype 主要用在模板中

for_each

  • C++11中新增long long类型(其实在之前就支持了),标准要求long long类型可以在不同的平台上有不同的长度,但至少64位

范围for循环

非静态成员的sizeof

在C++98中对非静态成员变量使用sizeof是不能通过编译的,需要对象的实例才能对其成员进行sizeof操作;C++11可以——有什么用?

struct People{ int hand; static People *all; }
People p;
cout << sizeof(p.hand) << endl;         // 都支持
cout << sizeof(People::all) << endl;    // 都支持
cout << sizeof(People::hand) << endl;   // C++11才支持

final

  • 禁止继承:将类标记为final,方法是直接在类名后使用关键字final,这样继承该类将导致编译出错 class MyClass final
  • 禁止重写:在成员函数的参数列表后使用final关键字,使派生类不可覆盖它所修饰的虚函数(防止重载和重写)

override

派生类中重写父类的虚函数,可以在重写的函数名后加override关键字。这样做的好处是如果该函数不是父类定义的虚函数会报错。如果不是虚函数不能使用override。
final和override说明符出现在形参列表以及尾置返回类型之后

就地初始化

只能使用"=“或”{}"进行初始化;作用先于列表初始化;如果既有列表初始化又有就地初始化,最终值取决于列表初始化

列表初始化

在C++11之前,class和struct的初始化是不同的。

MyClass myobj(10,20,30);
MyStruct myobj = {10, 20, 30};

C++11之后,允许一律使用{…}语法初始化类型。并且等号是可选的

MyClass myobj{102030};
MyStruct myobj{102030}

列表初始化可以用于初始化C++中的任何内容,不局限于结构和类,但是大括号中的值类型必须要一致;还可以对变量进行零初始化,只需要指定一堆空的大括号

int a{3};
int b = {3};
int c {};

使用列表初始化的一个优点是,可以阻止窄化,旧风格会隐式执行窄化。

int x = 3.14;  // 隐式窄化,会截断为3
int y { 3.14 };  // 编译报错

initialize_list

标准库中容器对初始化列表的支持源自 #include 这个头文件中initialize_list类模板的支持
初始化列表的使用场景:

  • 自定义类型使用列表初始化对象需要包含该头文件,并且声明一个initialize_list模板类为参数的构造函数
  • 函数的参数列表也可以使用初始化列表,即以initializer_list作为模板参数
int makeSum(initializer_list<int> values) {
    int total = 0;
    for(int value: values) {
        total += value;
    }
    return total;
}

int main() {
    int a { makeSum({1, 2, 3})};
    int b {makeSum({10, 20, 30, 40, 50})};
    std::cout << "a is: " << a << ", b is: " << b << std::endl;
}
  • 初始化列表可以用于函数返回的情况。返回一个初始化列表通常会导致构造一个临时变量,当然列表初始化构造成什么类型依据返回类
    使用初始化列表的一大优势防止类型收窄——类型收窄一般是一些使得数据变化或者精度丢失的隐式类型转换。初始化列表是类型安全的,列表中所有元素必须为同一类型。

constexpr

编译期常量

  • 常量表达式:值不会改变且在编译时就能得到结果的表达式
    • 一个对象是不是常量表达式由它的数据类型和初始值共同决定,数据类型是constexpr,初始值必须是常量
    • 用常量表达式初始化的const对象也是常量表达式
    • 声明constexpr类型的变量一定是一个常量且必须用常量表达式初始化
  • 常量表达式的类型要求:
    • 字面值类型:算术类型,引用,指针;string/IO/自定义等不是
    • 指针和引用的初始值受到严格限制:指针必须是nullptr或0,或存储于某个固定地址中的对象
    • 函数体内定义的变量并非存放在固定的地址中,因此不能指向这样的这样的变量;定义在所有函数体外的所有变量地址固定不变,可以用来初始化
    • 定义有效范围超出函数本身的变量,这类变量也有固定地址,可以用来初始化,如static变量?
    • constexpr定义的指针把定义的对象设置为了顶层const(仅对指针有效,对指针指向的对象无关)
    • constexpr修饰变量:const int i=1;和constexpr int j=1;两者在大多数情况下没有区别,但如果i在全局变量中编译期会为i产生数据,对于j,如果没有显式使用编译期不会为其产生数据
      const int *p = nullptr; p是一个指向整型常量的指针
      constexpr int *q = nullptr; q是一个指向整数的常量指针
  • constexpr函数:被隐式的指定为内联函数,
    • 函数的返回类型及所有形参都是字面值类型,且函数体中有且只有一条return语句(必须返回值)
    • 函数体中也可以包含其他语句,只要这些语句在运行时不执行任何操作就行(不执行任何操作有什么用?)如static_assert/using/typedef 等
    • 允许constexpr的返回值并非一个常量;所以constexpr函数不一定返回常量表达式
    • constexpr构造函数:声明了constexpr的构造函数的类就是字面值常量类了(不知道有啥用)。除了声明为=default或者=delete之外,constexpr构造函数的函数体一般为空,使用初始化列表或其他constexpr构造函数初始化成员

智能指针

参考:C++之内存管理

提高类型安全

  • 强类型枚举,在enum后加上关键字class,
    • 具有以下优势:
      • 强作用域:强类型枚举成员的名称不会被输出到其父作用域空间
      • 转换限制:不可与整型隐式的相互转换
      • 可以指定顶层类型:具体方法是在枚举名称后面加上“:type”,如 enum class Type: char { … }
      • 匿名的enum class可能什么都做不了
    • C++20开始可以使用using enum声明来避免使用枚举值的全名(但这种做法不推荐),如:
enum class PieceType : unsigned long {
  King = 1,
  Queue,
  Rook = 10 }
using enum PieceType;
PieceType piece { King };
  • nullptr: 是一个“指针空值类型”的常量,指针空值类型被命名为 nullptr_t,所以可以通过nullptr_t来声明一个指针空值类型的变量(使用nullptr_t的头文件 #include
    所有定义为nullptr_t类型的数据都是等价的,行为也是完全一致
    nullptr_t类型数据可以隐式转换为任意一个指针类型
    nullptr_t类型数据不能转换为非指针类型,即使是使用reinterpret_cast()的方式
    nullptr_t类型数据不适用于算术运算表达式

数值极限

数值极限是指当前平台上能取得的最大值,在C中,可以使用各种宏定义,如INT_MAX,这些方法在C++中仍然可以使用,但推荐的做法是使用定义在中的类模板std::numeric_limits

属性

属性是一种将可选的和/或特定于编译器厂商的信息添加到源代码中的机制。在C++对属性进行标准化之前,编译器厂商决定了如何指定此类信息,例如 __attribute__, __declspec。从C++11开始通过使用 [[ attribute ]] 对属性进行标准化支持。
[[noreturn]]
这个属性仅适用于函数声明。向函数添加[[noreturn]] 意味着它永远不会将控制权返回给调用点。使用此属性可以避免编译器发出没有返回值的告警。
声明了[[noreturn]]属性的函数如果有返回值将被认为是未定义的行为

[[noreturn]] void forceProgram() {
	std::exit(1);
}

  • 支持显式构造函数(默认构造,拷贝构造,拷贝运算符)(=default) 和显式删除的构造函数(默认构造,拷贝构造,拷贝运算符)(=delete)
  • 支持初始化列表构造函数,使用std::initializer_list作为第一个参数
  • 支持委托构造函数,即在构造函数中调用同一个类的其他构造函数。注意这个调用不能放在构造函数体内,必须放在构造函数初始化列表器中,且必须是列表中唯一的成员初始化器。
  • 在C++11之前,转换构造函数只能有一个参数,自C++11以来,由于支持列表初始化,转换构造函数可以有多个参数,例如:
class MyClass {
public:
  MyClass(int) {}
  MyClass(int, int) {}
};
int main() {
  MyClass s1({1});
  MyClass s2({1, 2});

这里都将执行默认转换,将1和{1, 2}转换成MyClass类型。为避免此类隐式转换,两个构造函数够标记成explicit

class MyClass {
public:
  explicit MyClass(int) {}
  explicit MyClass(int, int) {}
};
  • 移动语义,参考C++之对象模型中的移动语义

引用限定符

对类的非临时和临时实例上可以调用普通成员函数,C++11中增加引用限定符,可以显式指定能够调用某个方法的实例类型:

  • 如果在成员函数名之后添加一个&,则只能在非临时实例上调用,临时实例不允许调用
  • 在成员函数名之后添加&&,则只能在临时实例上调用
class MyClass {
public:
  int a { 10 };
  char b { 'a' };
  int set_value(int d) { return d; }
  int getVal() & { return a; }  // 只能由非临时实例调用,如果是const函数,&放在const之后, 如:int getVal() const & { ... }
  char getChar() && { return b; } // 只能由临时实例调用
};

调用:

int main() {
    MyClass c;
    cout << "c.set_value(1)" << c.set_value(1) << std::endl;                   // OK
    cout << "MyClass().set_value(2): " << MyClass().set_value(2) << std::endl; // OK
    cout << "c.getVal(): " << c.getVal() << std::endl;                         // OK
    cout << "c.getChar(): " << c.getChar() << std::endl;                       // error
    cout << "MyClass().getChar(): " << MyClass().getChar() << std::endl;       // OK
    cout << "MyClass().getChar(): " << MyClass().getVal() << std::endl;        // error
    return 0;
}

C++14

变量模板

template < parameter-list > variable-declaration 一个变量模板定义了一个族系的变量,或者静态数据成员
使用前缀0b允许定义二进制字面量,如:int b = 0b101010;;单引号"'"可以作为分隔符插入数字当中,在编译时会被编译器忽略。如unsigned long long var=1844'6744'0737'0955'0592uLL; (分隔符的插入可以是随意的)
数字分隔符可以增强数字字面量的可读性

std::exchange

头文件:#include

template< class T, class U = T >
T exchange( T& obj, U&& new_value );

将new_value 赋值给obj并返回obj原来的值。
操作类型可以是普通变量,STL容器,函数指针等。

class stream {
  public:
    flags_type flags() const { return flags_; }
    // Replaces flags_ by newf, and returns the old value.
    flags_type flags(flags_type newf)
    { return std::exchange(flags_, newf); }
private:
    int flags_ = 0;
};

void f() { std::cout << "f()"; }
void exchange_test () {
  stream s;
  std::cout << s.flags(12) << '\n'; // 修改flags_的值并返回原值
 
  std::vector<int> v;
  std::exchange(v, {1,2,3,4}); // 修改vector的值
  std::copy(begin(v),end(v), std::ostream_iterator<int>(std::cout,", "));
   void (*fun)();
 
   // the default value of template parameter also makes possible to use a
   // normal function as second argument. The expression below is equivalent to
   // std::exchange(fun, static_cast(f))
   std::exchange(fun,f); // 修改函数指针
   fun();
 
   std::cout << "\n\nFibonacci sequence: ";
   for (int a{0}, b{1}; a < 100; a = std::exchange(b, a + b))
       std::cout << a << ", ";
   std::cout << "...\n";
}

可以使用std::exchange很方便的实现移动语义:

MyClass::MyClass(MyClass&& s) {
  val = std::exchange(s.val, 0);
}

std::shared_timed_mutex

读写锁,可以同时多个读,做多一个写

属性

[[deprecated]] 将内容标记为已弃用,这意味着仍可以使用它,但不鼓励使用。此属性可以接收一个可选参数,该参数用于解释弃用的原因。
[[deprecated("Unsafe method, please use xyz")]] void func1() { std::cout << "hello" << std::endl; } 如果使用了这个已经弃用的函数将会收到编译错误或警告:
C++11_第1张图片

你可能感兴趣的:(C++编程,c++,开发语言,linux)