目录
简介,规定了C++的语法,相比于C++98新增了新语法使开发更高效,因此我们要学习它新增的规定。
一.列表初始化
在C++98中,允许用花括号{}对数组元素进行初始值设定,
int array1[] = {1,2,3,4,5};
int array2[5] = {0};
但是对于自定义类型或如下方式的数组初始化,却无法使用这样的初始化
vector<int>v{1,2,3,4,5};
int* array3 = new int[5]{1,2,3,4,5};
于是C++11便实现了这一功能。
二.变量类型推导
类型推导有什么用呢?1.解决无法给出实际类型的情况。2.类型写出来特复杂情况
使用auto编译器会自动推导类型,更加简洁。
但auto使用有一个前提,必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。
template<class T1, class T2>//模板函数
T1 Add(const T1& left, const T2& right)
{
return left + right;
}
使用加完结果的实际类型作为 函数的返回值类型就不会出错,这就需要RTTI(Run-Time Type Identification运行时类型识别)。
而decltype就是根据结果的实际类型作为 函数的返回值类型。
int main()
{
int a = 10;
int b = 20;
decltype(a+b)c;
return 0;
}
三.默认成员函数控制
C++中会生成一些默认的成员函数,构造函数,析构函数之类。如果显式定义了,编译器就不会重新生成默认函数,比如显式定义了带参的构造函数,无参的就不会生成。为了避免造成混乱,C++11让程序员可以控制是否需要编译器生成函数。
在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。
class A
{
public:
A(int a): _a(a)
{}
A() = default;//指定函数生成
// 在类中声明,在类外定义时让编译器生成默认赋值运算符重载
A& operator=(const A& a);
private:
int _a;
};
A& A::operator=(const A& a) = default;
int main()
{
A a1(10);
A a2;
a2 = a1;
return 0;
}
在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对
应函数的默认版本,称=delete修饰的函数为删除函数。
class A
{
public:
A(int a): _a(a)
{}
// 禁止编译器生成默认的拷贝构造函数以及赋值运算符重载
A(const A&) = delete;
A& operator(const A&) = delete;
private:
int _a;
};
四.右值引用
C++提出了引用,为了提高程序运行效率,C++11引入了右值引用,右值引用也是别名,但是只能对右值引用。
int Add(int a,int b)
{
return a + b;
}
int main()
{
const int&& ra = 10;
int&& rRet = Add(10,20);
return 0;
}
左值和右值并没有给出严格的区分方式,一般认为:
①可以放在=左边的,②或者能够取地址的称为左值,
①只能放在=右边的,②或者不能取地址的称为右值。
int main()
{
int a = 10;
int b = 20;
a = b;
b = a;//a和b都是左值,左值即可放在=的左侧,也可以放在=的右侧
b + 1 =20;//b+1的结果是一个临时变量,没有具体名称,也不能取地址,因此为右值
}
仅靠上边的概念很难区分是左值还是右值,所以一般认为:
1. 普通类型的变量,因为有名字,可以取地址,都认为是左值。
2. const修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址(如果只是const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间),C++11认为其是左值。
3. 如果表达式的运行结果是一个临时变量或者对象,认为是右值。
4. 如果表达式运行结果或单个变量是一个引用则认为是左值。
引用和右值引用比较
int main()
{
// 普通类型引用只能引用左值,不能引用右值
int a = 10;
int& ra1 = a; // ra为a的别名
//int& ra2 = 10; // 编译失败,因为10是右值
const int& ra3 = 10;
const int& ra4 = a;
return 0;
}
普通引用只能引用左值,不能引用右值,
const引用即可引用左值,也可引用右值。
右值引用只能引用右值。
int main()
{
// 10纯右值,本来只是一个符号,没有具体的空间,
// 右值引用变量r1在定义过程中,编译器产生了一个临时变量,r1实际引用的是临时变量
int&& r1 = 10;
r1 = 100;
int a = 10;
int&& r2 = a; // 编译失败:右值引用不能引用左值
return 0;
}
五.lambda表达式
在C++98中,对一个数据集合中的元素排序,可以使用std::sort。
int main()
{
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>());
return 0;
}
但随着C++语法的发展,人们开始觉得上边的写法太复杂了,因此出了lambda表达式。
lambda语法
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
[capture-list] 捕捉列表
(parameters) 参数列表
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。
->returntype:返回值类型。
{statement}:函数体。
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[]{};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=]{return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c){b = a + c; };
fun1(10)
cout<<a<<" "<<b<<endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int{return b += a+ c; };
cout<<fun2(10)<<endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
捕捉列表描述了上下文那些数据可以被lambda使用,以及使用的方式传值还是传引用。
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
函数对象与lambda表达式
函数对象,又称为仿函数。即可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象。
class Rate
{
public:
Rate(double rate): _rate(rate)
{}
double operator()(double money, int year)
private:
double _rate;
};
int main()
{
// 函数对象
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);
// lamber
auto r2 = [=](double monty, int year)->double{return monty*rate*year; };
r2(10000, 2);
return 0;
}
六.线程库
函数名 功能
thread() 构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
thread(fn,args1, args2,...) 构造一个线程对象,并关联线程函数fn,args1,args2,... 为线程函数的参数
get_id() 获取线程id
jionable() 线程是否还在执行,joinable代表的是一个正在执行中的线程。
jion() 该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
detach() 在创建线程对象后马上调用,用于把被创建线程与线程对象分离 开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关
线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
int main()
{
// 线程函数为函数指针
thread t1(ThreadFunc, 10);
// 线程函数为lambda表达式
thread t2([]{cout << "Thread2" << endl; });
// 线程函数为函数对象
TF tf;
thread t3(tf);
t1.join();
t2.join();
t3.join();
cout << "Main thread!" << endl;
return 0;
}
lock_guard与unique_lock
有些情况下,为了保证一段代码的安全性,就要通过锁的方式进行控制。
之前锁控制不好时,可能会造成死锁,C++11采用RAII的方式对锁进行了封装,即lock_guard与unique_lock。
C++11中,Mutex总共包了四个互斥量的种类:
1.std::mutex
mutex最常用的三个函数:
函数名 函数功能
lock() 上锁:锁住互斥量
unlock() 解锁:释放对互斥量的所有权
try_lock() 尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞
2. std::recursive_mutex
允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,释放互斥量时需要调用与该锁层次深度相同次数的 unlock()。
3. std::timed_mutex
比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until() 。
try_lock_for()
接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex
的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程
释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返
回 false。
try_lock_until()
接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期
间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得
锁),则返回 false。
4. std::recursive_timed_mutex