C++11

目录

一.列表初始化

二.变量类型推导

三.默认成员函数控制

四.右值引用

五.lambda表达式

六.线程库


简介,规定了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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学c的长弓狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值