目录
连续两个右尖括号>中间空格隔开,C++98会优先解析为右移,C++11会优先解析为模板参数界定符
std::array
使用std::array替代C数组带来很多好处:总是知道自身大小;不会自动转化为指针;具有迭代器,可以方便的遍历元素
std::move
头文件#include <utility>
并不能移动任何东西,只是将左值强制转成右值,从实现上相当于static_cast<T&&>(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);不会发生隐式转换而是编译报错
std::addressof
头文件定义:#include<memory>
std::reference_wrapper
头文件定义:#include<functional>
类型推断
auto
- auto声明变量的类型必须由编译器在编译时期推导而得;auto声明的变量必须被初始化;优势是可以匹配返回类型,因为有时候不确定返回类型,就会声明错误
- 自动类型推导的一种新写法:
auto func(T params) -> type
;可以配合decltype关键字:template<typename U, typename V>auto 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<int>
auto b = { 11, 12 } // initializer_list<int>
auto c{11}; // int
auto d{11,12}; // error
在C++11/14中,拷贝列表和直接列表初始化都将推断出initializer_list<>
// C++11/c++14:
auto a = { 11 }; // initializer_list<int>
auto b = { 11, 12 } // initializer_list<int>
auto c { 11 }; // initializer_list<int>
auto d { 11, 12 }; // initializer_list<int>
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{10,20,30};
MyStruct myobj{10,20,30};
列表初始化可以用于初始化C++中的任何内容,不局限于结构和类,但是大括号中的值类型必须要一致;还可以对变量进行零初始化,只需要指定一堆空的大括号
int a{3};
int b = {3};
int c {};
使用列表初始化的一个优点是,可以阻止窄化,旧风格会隐式执行窄化。
int x = 3.14; // 隐式窄化,会截断为3
int y { 3.14 }; // 编译报错
initialize_list
标准库中容器对初始化列表的支持源自 #include<initializer_list>
这个头文件中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可能什么都做不了
使用举例:
enum class Color { Red, Green, Blue };
Color color = Color::Red;
switch (color) {
case Color::Red:
// handle Red
break;
case Color::Green:
// handle Green
break;
case Color::Blue:
// handle Blue
break;
default:
// handle other cases
break;
}
- C++20开始可以使用using enum声明来避免使用枚举值的全名(但这种做法不推荐),如:
enum class PieceType : unsigned long {
King = 1,
Queue,
Rook = 10 }
using enum PieceType;
PieceType piece { King };
nullptr
nullptr是一个“指针空值类型”的常量,指针空值类型被命名为 nullptr_t,所以可以通过nullptr_t来声明一个指针空值类型的变量(使用nullptr_t的头文件 #include<cstddef>
)
- 所有定义为nullptr_t类型的数据都是等价的,行为也是完全一致
- nullptr_t类型数据可以隐式转换为任意一个指针类型
- nullptr_t类型数据不能转换为非指针类型,即使是使用reinterpret_cast<nullptr_t>()的方式
- nullptr_t类型数据不适用于算术运算表达式
数值极限
数值极限是指当前平台上能取得的最大值,在C中,可以使用各种宏定义,如INT_MAX,这些方法在C++中仍然可以使用,但推荐的做法是使用定义在<limits>
中的类模板std::numeric_limits
。
属性
属性是一种将可选的和/或特定于编译器厂商的信息添加到源代码中的机制。在C++对属性进行标准化之前,编译器厂商决定了如何指定此类信息,例如 __attribute__
, __declspec
。从C++11开始通过使用 [[ attribute ]]
对属性进行标准化支持。
[[noreturn]]
这个属性仅适用于函数声明。向函数添加[[noreturn]] 意味着它永远不会将控制权返回给调用点。使用此属性可以避免编译器发出没有返回值的告警。
声明了[[noreturn]]属性的函数如果有返回值将被认为是未定义的行为
[[noreturn]] void forceProgram() {
std::exit(1);
}
__has_include
参考 C++之预编译
类
- 支持显式构造函数(默认构造,拷贝构造,拷贝运算符)(=default) 和显式删除的构造函数(默认构造,拷贝构造,拷贝运算符)(=delete)
- 支持初始化列表构造函数,使用
std::initializer_list<T>
作为第一个参数 - 支持委托构造函数,即在构造函数中调用同一个类的其他构造函数。注意这个调用不能放在构造函数体内,必须放在构造函数初始化列表器中,且必须是列表中唯一的成员初始化器。
- 在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;
}
bitset
bitset操作定义了多种检测和设置一个或多个二进制位的方法,支持以下操作:(置位即为1)
b.any()
b中是否存在置位的二进制位b.all()
b中所有位都置位了吗b.none()
b中不存在置位的二进制位吗b.count()
b中置位的位数b.size()
一个constexpr函数,返回b中的位数b.test(pos)
若pos位置的位是置位的,则返回true,否则返回flaseb.set(pos, v) / b.set()
将位置pos处的位设置为bool值v,v默认为true。如果未传递实参,则将b中的所有位置位b.reset(pos) / b.reset()
将位置pos处的位复位或将b中所有位复位b.flip(pos)/b.flip()
: 改变位置pos处的位的状态或改变b中所有位复位b[pos]
: 访问b中位置pos处的位,如果b是const的,则当该位置位时b[pos]返回一个bool值true,否则返回falseb.to_ulong() / b.to_ullong()
返回一个unsigned log或一个unsigned long long值,其位模式与b相同,如果b中位模式不能放入指定的结果类型,则抛出一个overflow_error异常b.to_string(zero, one)
返回一个string,表示b中的位模式,zero和one的默认值分别为0和1,用来表示b中的0和1os<<b
:将b中二进制位打印位字符1或0,打印到流osis>>b
:将is读取字符存入b,当下一个字符不是1或0时或是已经读入b.size()个位时,读取过程停止。
C++14
变量模板
template < parameter-list > variable-declaration
一个变量模板定义了一个族系的变量,或者静态数据成员
使用前缀0b允许定义二进制字面量,如:int b = 0b101010;
;单引号"'"可以作为分隔符插入数字当中,在编译时会被编译器忽略。如unsigned long long var=1844'6744'0737'0955'0592uLL;
(分隔符的插入可以是随意的)
数字分隔符可以增强数字字面量的可读性
std::exchange
头文件:#include<utility>
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<void(*)()>(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; }
如果使用了这个已经弃用的函数将会收到编译错误或警告: