C++11
零:序
相比较而言,C++11能够更好的用域系统开发和库的开发,语法更加泛化和简单化,更加稳定和安全,不仅功能更加强大,而且能提升程序员的开发效率
一:列表初始化(小重点)
C++11扩大了用大括号括起的初始化列表的适用范围,使其课用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=) 也可以不添加。
//C++11: 支持内置类型的列表初始化
int a = 1;
int b = { 1 };
int c{ 1 };
float d = { 1.2f };
//C++11: 支持自定义类型的列表初始化
vector<int> array3{1, 2, 3, 4, 5};
vector<int> array2 = { 1, 2 };
pair<int, int> p = { 1, 1 };
map<int, int> m = { { 1, 1 }, { 2, 2 }, { 3, 3 } };
//自定义类型:创建单个对象
A a3 = { 1, 2};
A a2(3, 4);
//单参构造函数的隐式类型转换
A a4 = 5;
//多个对象的列表初始化
//自定义类型:初始化多个元素 ----> 不是天然支持列表初始化
// 需要显示定义参数类型为initializer_list的构造函数
Vector(const initializer_list<T>& lst)
:_array(new T[lst.size()])
, _size(0)
, _capacity(lst.size())
{
for (auto& e : lst)
_array[_size++] = e;
}
二:变量类型推导
auto:编译时根据初始化表达式进行类型推导(编译时类型识别)
decltype:运行时类型识别(用结果的实际类型作为函数的返回值类型就不会出错)
map<string, string> m;
m["123"] = "456";
m["789"] = "012";
std::map<std::string, std::string>::iterator it = m.begin();
//auto: 编译时根据初始化表达式进行类型推导
// auto: 编译时类型识别
auto it2 = m.begin();
//decltype: 运行时类型识别
decltype(1 + 2) a; 推演表达式作为变量的定义类型
decltype(func(10)) b;
//decltype: 如果有参数列表,推导返回值类型
cout << "b: type: " << typeid(b).name() << endl;
//decltype: 如果没有参数列表,只有函数名,推导为函数的接口类型
cout << typeid(decltype(func)).name() << endl; 推演函数返回值的类型
三:默认成员函数
class C
{
public:
//default: 让编译器显式生成一个默认函数
C() = default;
//C(){}
//delete: 把一个函数声明成已删除函数,不能再被使用
// 拷贝构造声明为delete: 防拷贝
C(const C& c) = delete;
C& operator=(const C& c) = delete;
private:
int _c;
};
四:右值引用(重中之重)
1. 左值右值的区别
左值:可以出现在=的两边,或者可以取地址的
其中const类型的常量,因为其可以取地址,则被认为是左值
int a = 10;
int b = a;
int* p = &a;
int* p2 = &b;
右值:只能够出现在=右边,或不可以取地址的,不是右值的都为左值。
右值又被分为:
纯右值: 常量, 临时变量/匿名变量
将亡值: 声明周期即将结束
临时变量/匿名变量:函数以值返回的变量, 调用类的构造函数创建的变量
2. 引用
引用:左值引用,右值引用,在语法意义上都是变量的别名
int a = 10;
//左值引用:引用的实体既可以为左值,也可以为右值
//ra : 实体为左值
int& ra = a;
// : ri实体为右值
const int& ri = 10;
//右值引用:引用的实体只能是右值
//右值引用: 实体为常量
int&& lr = 10;
//右值引用: 实体为临时变量
int&& lr2 = getA(a);
const int& r3 = getA(a);
//右值引用: 不能引用左值
//int&& r4 = a;
3. 移动语义
移动构造:
//移动构造: 提高拷贝效率
String(String&& str)
:_str(str._str)
{
str._str = nullptr;
_size = _capacity = str._size;
cout << "String(String&&)" << endl;
}
移动赋值:
//移动赋值
String& operator=(String&& str)
{
if (this != &str)
{
swap(_str, str._str);
_size = _capacity = str._size;
cout << "String operator=(String&&)" << endl;
}
return *this;
}
浅拷贝: 移动构造, 直接获取将亡值(右值)的资源String ret = getString();
深拷贝: 拷贝构造, ret为左值String copy(ret);
String ret2 = String("456");
构造 + 拷贝构造 , 优化: 构造
move: 当需要右值引用引用一个左值时,可以通过move函数将左值转换为右值!(被转换的左值,其生命周期并没有随着左值的转换而改变,即转换的佐治变量不会被销毁)
int a = 10;
//std::move: 移动语义--> 把变量的属性变成右值
int&& rr = move(a);
String str("123");
//移动语义错误示例:把一个后面会用到的左值变成了一个右值
String copy(move(str));
//移动语义正确场景:需要保证属性被修改的左值后面不会再用到
Person(Person&& person)
:_name(move(person._name))
{
cout << "Person(Person&&)" << endl;
}
4. 完美转发
是指在函数模板中,完全依照模板的参数类型,将参数传递给函数模板中调用的另外一个函数
void Fun(int& x) { cout << "lvalue ref" << endl; }
void Fun(int&& x) { cout << "rvalue ref" << endl; }
void Fun(const int& x) { cout << "const lvalue ref" << endl; }
void Fun(const int&& x) { cout << "const rvalue ref" << endl; }
//特殊: 模板参数: T&& 未定引用类型---> 主要看模板参数接收的实际类型,和实际类型匹配
template<typename T>
//void PerfectForward(const T& t){ Fun(t); }
void PerfectForward(T&& t) { Fun(std::forward<T>(t)); }
void testForward()
{
PerfectForward(10); // rvalue ref
int a = 0;
PerfectForward(a); // lvalue ref
PerfectForward(std::move(a)); // rvalue ref
const int b = 8;
PerfectForward(b); // const lvalue ref
PerfectForward(std::move(b)); // const rvalue ref
}
五:lambda表达式
C++98中的元素排序:
int array[] = { 4, 1, 8, 5, 3, 7, 0, 9, 2, 6 };
// 默认按照小于比较,排出来结果是升序
std::sort(array, array + sizeof(array) / sizeof(array[0]));
// 如果需要降序,需要改变元素的比较规则
std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
如果待排序元素为自定义类型,则需要自定义排序比较规则
struct LessA
{
bool operator()(const A& a1, const A& a2)
{
return a1 < a2;
}
};
struct GreaterA
{
bool operator()(const A& a1, const A& a2)
{
return a1 > a2;
}
};
1. C++11中的lambda表达式
std::sort(array, array + sizeof(array) / sizeof(array[0]), [](const A& a1, const A& a2)->bool
{
return a1 < a2;
});
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
- [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
- (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
- mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)
- ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导
- {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量
int a = 10;
int b = 5;
//最简单的lambda表达式
[] {};
//mutable: 把捕捉列表中的变量属性改为非const(默认是const属性)
[a, b]()mutable {a = 100; b = 200; return a + b; };
auto func = [](int a, int b)->int {a = 1; b = 2; return a + b; };
//lambda表达式使用
func(a, b);
int a = 1;
int b = 2;
cout << a << " " << b << endl;
//[=]:以传值形式捕捉父类作用域的所有变量
auto func1 = [=](int num)mutable->int {
a = 5;
b = 10;
//c不能捕捉, c还没有定义
//return a + b +c + num;
return a + b + num;
};
func1(300);
cout << a << " " << b << endl;
//如果是传引用的形式,不需要mutable也可以修改捕捉列表中的变量
auto func2 = [&](int num)->int {
a = 5;
b = 10;
//c不能捕捉, c还没有定义
//return a + b +c + num;
return a + b + num;
};
func2(300);
cout << a << " " << b << endl;
int c = 3;
//[=,&a]除过a之外, 其它变量以值的形式捕捉,a以引用的形式捕捉
//[=,a]: 错误写法
auto func3 = [=, &a](int num)->int {
return a + b + num;
};
//[&,a]: 除过a之外, 其它变量以引用的形式捕捉,a以值的形式捕捉
auto func4 = [&, a](int num)->int {
return a + b + num;
};
- 父类作用域不一定就是直接父类作用域,也可以是嵌套父域
class C
{
public:
void printC()
{
int a = 1;
int b = 2;
//父类作用域不一定就是直接父类作用域,也可以是嵌套父域
auto func1 = [=](int num)->void {
//a,b属于C::printC函数的局部域
cout << a << " " << b << " " << this->_c << endl;
//_c属于类域C
cout << _c << endl;
};
func1(30);
}
public:
int _c = 5;
};
- 如果不是局部域,捕捉列表中不能指定具体的变量,但是可以写=或&
auto funcG = [=](int a, int b)->void {
cout << global << endl;
};
- lambda表达式之间不能赋值,但是可以拷贝。
auto fun3(fun2);
auto fun4 = fun2;
fptr ptr;
//可以把lambda表达式赋给一个函数指针
ptr = fun1;
- C++实现lambda表达式: 创建一个仿函数类
六:线程库(重要)
1. 线程
C++11中最重要的特性就是对线程的支持,使得C++在并行编程时不需要依赖第三方库,且引入了原子操作的原子类概念。
头文件:<thread>
thread t1(tfunc1);
thread t2(tfunc2, 1);
thread t3(tfunc3, 1, 2, 3);
cout << "线程等待" << endl;
t1.join();
t2.join();
t3.join();
return 0;
采用RAII的方式对线程进行封装,是防止join无法进行销毁的
//RAII: 资源获取立即初始化
// 在构造函数中初始化资源
// 在析构函数中销毁资源
class ThreadManage
{
public:
ThreadManage(thread& t)
:_thread(t)
{}
~ThreadManage()
{
if (_thread.joinable())
_thread.join();
}
private:
thread& _thread;
};
void testThread2()
{
thread t1(tfunc1);
thread t2(tfunc2, 1);
thread t3(tfunc3, 1, 2, 3);
ThreadManage tm1(t1);
ThreadManage tm2(t2);
ThreadManage tm3(t3);
return;
}
如果函数为成员函数
void testClassFunc()
{
ThreadClass tc;
//如果函数为成员函数,则需要写完整作用域,并且需要显示取地址,参数需要加上this指向的对象
thread t1(&ThreadClass::funcT, &tc, 10);
t1.join();
}
void testThreadRef()
{
int a = 0;
cout << a << endl;
//如果函数参数类型为引用,在线程中需要修改原始的变量,则需要通过ref转换
thread t1(func2, ref(a));
t1.join();
cout << a << endl;
return 0;
}
join和detach的区别:
join:主线程被阻塞,当新线程终止时,join()会清理相关的线程资源,然后返回,主线程再继续向下执行,然后销毁线程对象。由于join()清理了线程的相关资源,thread对象与已销毁的线程就没有关系了,因此一个线程对象只能使用一次join(),否则程序会崩溃。
detach():该函数被调用后,新线程与线程对象分离,不再被线程对象所表达,就不能通过线程对象控制线程了,新线程会在后台运行,其所有权和控制权将会交给c++运行库。同时,C++运行库保证,当线程退出时,其相关资源的能够正确的回收
2. 原子操作
#include <atomic>
sum(0);
atomic<int> sum(0);
number n;
atomic<number> atomic_number(n);
加锁:
mutex mtx;
int global = 0;
void fun3(int num)
{
for (int i = 0; i < num; ++i)
{
//mtx.try_lock: 非阻塞加锁操作:如果其它线程没有释放当前的锁,则直接返回加锁失败的结果
//mtx.lock: 阻塞加锁操作:如果其它线程没有释放当前的锁,阻塞等待,直到其它线程释放当前锁
mtx.lock();
//如果当前线程拥有该锁,执行第二次的加锁操作,会导致死锁
//mtx.lock();
++global;
mtx.unlock();
}
}
守卫锁:
//守卫锁
template <class Mutex>
class lockGuard
{
public:
//构造函数加锁
lockGuard(Mutex& mtx)
:_mtx(mtx)
{
//cout << "lockGuard(Mutex&)" << endl;
_mtx.lock();
}
//析构函数解锁
~lockGuard()
{
//cout << "~lockGuard" << endl;
_mtx.unlock();
}
//防拷贝
lockGuard(const lockGuard<Mutex>&) = delete;
lockGuard& operator=(const lockGuard<Mutex>&) = delete;
private:
Mutex& _mtx;
};
void fun4(int num)
{
for (int i = 0; i < num; ++i)
{
//创建守卫锁
lockGuard<mutex> lg(mtx);
//防拷贝
//lockGuard<mutex> copy(lg);
++global;
}
}