c++11特性
c++11
‘Effective Modern c++’ 英文版,c++新特性讲得不错,需要的戳文末。
初始化列表
数组之外,STL容器和自定义数据类型也支持初始化列表,而且格式统一
initializer_list
是轻量级的容器,只有begin(),end(),size(),只能被整体初始化,要求内部类型一致。
初始化列表可以阻止类型收窄,即从高精度到低精度的隐式转化
#include <initializer_list>
std::map<int, std::string> m = { {1, "a"}, {2, "b"} }; //STL容器
class A {
public:
B(const std::initializer_list<int>& items): m_items(items){}
private:
std::vector<int> m_items;
};
A a1{ 1, 2, 3 }; //自定义数据类型
类型推导
auto: 主要用于容器迭代器,也可以推导函数返回类型
- 当不声明为指针或引用时,推导结果会抛弃引用和volatile/const
- 声明为指针或引用类型时,会保留volatile/const
- 隐式类型定义的类型推导发生在编译器,而不像python发生在运行期
decltype: 推导表达式的类型
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) { //拖尾返回类型
return x+y;
}
template<typename T, typename U> //c++14中一般函数允许返回值推导
auto add(T x, U y) {
return x+y;
}
空指针
NULL实际上是一个为0的int值
nullptr类型为nullptr_t
//f(NULL)调用,引发歧义,f(nullptr)调用
void f(int i) {}
void f(int* arr) {}
for-each循环
1)对于 map 这种关联性容器而言,for 循环中 val 的类型是 std::pair需要使用 val.first
或 val.second
来提取键值。
2)auto 自动推导出的类型是容器中的 value_type,而不是迭代器。
3)基于范围的 for 循环倾向于在循环开始之前确定好迭代的范围,在循环中修改容器也许会导致异常
4)适合容器,区间迭代还是使用普通for数组
for (std::vector<int>::const_iterator itr = v.begin(); itr != v.end(); ++itr) {
std::cout << *itr << std::endl; //迭代器声明和解引用
}
for (int i : v) { //每次循环会拷贝一次i,如果开销大可考虑引用
std::cout << i << std::endl;
}
强枚举类型
enum class Direction { //关键字从enum变成enum class
Left, Right
};
enum class Answer {
Right, Wrong
};
auto a = Direction::Left; //引用时必须加上枚举名称,枚举值不再是全局的
auto b = Answer::Right;
if (a == b)
std::cout << "a == b" << std::endl;
else
std::cout << "a != b" << std::endl;
using关键字
取代typedef定义别名
using cbyte = char
定义模板别名,typedef实现比较麻烦
template<class T>
using Tlist = std::list<T>;
using Tlist = std::list<char>;
在子类引用父类成员
class Base
{
public:
void func() { // some code}
int func(int n) { // some code}
}
class Sub : public Base
{
public:
using Base::func;
void func() { // some code}
}
int main()
{
Sub s;
s.func();
s.func(1); // Success!
}
final禁止
禁止虚函数被重写
class A {
public:
virtual void f1() final {}
};
class B : public A {
virtual void f1() {} //报错
};
禁止类被继承
class A final {
};
class B : public A { //报错
};
override显式声明
class A {
public:
virtual void f1() const {}
};
class B : public A {
virtual void f1() override {} //编译器会报错,不加override会认为子类新增函数f1
};
default默认构造函数
当自定义构造函数时,编译器不会自动生成无参构造函数,可以使用default关键字强制要求生成。
class A {
public:
A(int i) {}
A() = default;
};
delete禁止函数调用
隐藏函数不必声明为private,可以使用delete关键字
Lambda匿名函数
auto add = [](int a, int b) { return a + b; };
捕获列表 | 作用 |
---|---|
[] |
不捕获变量 |
[a] |
a 为值传递 |
[a, &b] |
a 为值传递,b 为引用传递 |
[&] |
所有变量都用引用传递。当前对象(即this 指针)也用引用传递。 |
[=] |
所有变量都用值传递。当前对象用引用传递。 |
注意:lambda访问上下文的局部变量时不能超过其生命周期
随机数生成器
控制范围的均匀分布,不需要初始化种子
#include <random>
#include <iostream>
using namespace std;
int main()
{
mt19937 mt(random_device{}());
cout<<mt()%100;
}
字符串数值类型转换
字符串<<--数值
std::string to_string(int value); //支持long,double,unsigned等其他数据类型
字符串-->>数值
stoi、stol、stoul、stoll、stoull、stof、stod、stold函数
委托构造函数
避免有多个参数表不同但是逻辑相近(或者有公共部分)的构造函数的时候的代码重复。
class A
{
int i;
double d;
public:
A(int x1,double x2) :i(x1),d(x2){
//do something here
}
A():A(0,0){}
A(int i):A(i,0) {}
};
移动语义
std::move将一个左值强制转化为右值引用,对象的状态或者所有权从一个对象转移到另一个对象,没有内存搬迁或者内存拷贝
- 左值是指表达式结束后依然存在的持久化对象
- 右值是指表达式结束时就不再存在的临时对象
std::string str = "Hello";
std::vector<std::string> v;
//调用常规的拷贝构造函数,新建字符数组,拷贝数据
v.push_back(str);
//调用移动构造函数,无内存的额外创建,str在move之后为空
v.push_back(std::move(str));
智能指针
shared_ptr
多个指针指向相同对象,当最后一个shared_ptr离开作用域时,内存才自动释放。
shared_ptr<int> sptr1( new int ); //先申请数据内存,再申请内存块
shared_ptr<int> sptr1 = make_shared<int>(“Hello”); //一次申请,但weak_ptr会锁内存
接口:
get()
: 获取shared_ptr绑定的资源unique()
:判断是否唯一指向当前内存的shared_ptrreset()
:释放关联内存块的所有权,如果是最后一个指向该资源的指针,释放该内存operation bool
:判断当前的shared_ptr是否指向一个内存块swap
:交换指针use_count
:返回引用计数
注意:
-
避免从naked pointer创建shared_ptr(不同的shared_ptr指向一个资源)
-
循环引用问题
-
多线程访问同一个shared_ptr需要加锁,shared_ptr引用计数是安全的,对象读写操作不是。
weak_ptr
指向shared_ptr对象,赋值不会增加引用计数
接口:
expire()
:如果use_count()为零,返回true,否则返回falselock()
:如果expire为true,返回空的shared_ptr,否则返回一个指向该弱指针的shared_ptruse_count()
:共享的shared_ptr数量
unique_ptr
unique_ptr<int[ ]> uptr( new int[5] ); //创建指向数组类型的unique_ptr
注意:
- 不支持直接拷贝,但支持移动语义
release()
只释放所有权,reset()
释放所有权和资源
noexcept
c++的异常处理是在运行时而不是编译时检测,编译器会生成额外的代码。
noexcept告诉编译器函数不会发生异常,但如果发生异常,直接终止程序。
void swap(Type& x, Type& y) noexcept{ //整个函数都不会发生异常
x.swap(y);
}
void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y))){ //x.swap(y)不发生异常,那么函数就不会异常
x.swap(y);
}
鼓励使用noexcept的情形:
- 移动构造函数
- 移动分配函数
- 析构函数
引用Effective Modern c++的一句话描述"noexcept is particularly valuable for the move operations, swap, memory deallocation functions, and destructors."
模板默认参数
c++11支持函数模板默认参数,类模板默认参数指定默认值必须从右往左;
若能从实参推导类型,则放弃默认模板参数
书籍链接在这里Effective Modern c++,提取码是我的id