首页 > 编程语言 >C++11新标准

C++11新标准

时间:2023-09-23 11:58:53浏览次数:36  
标签:11 const 函数 int C++ 标准 线程 string cout

c++11标准(1)

一、long long类型

  • 新增了类型long long和unsigned long long,以支持64位(或更宽)的整型。

  • 在VS中,int和long都是4字节,long long是8字节。

  • 在Linux中,int是4字节,long和long long是8字节。

二、char16_t和char32_t类型

新增了类型char16_t和char32_t,以支持16位和32位的字符。意义不大,好像没什么人用,连demo程序都找不到。

三、原始字面量

略。

四、统一的初始化(列表)

C++11丰富了大括号的使用范围,用大括号括起来的列表(统一的初始化列表)可以用于所有内置类型和用户自定义类型。

  • 使用统一的初始化列表时,可以添加等号(=),也可以不添加:
int x={5};

double y{2.75};

short quar[5]{4,5,2,76,1};
  • 统一的初始化列表也可以用于new表达式中:

int *ar=new int[4]{2,4,6,7};

  • 创建对象时,也可以使用大括号(而不是圆括号)来调用构造函数:
class Girl

{

private:

int m_bh;

string m_name;

public:

Girl(int bh,string name) : m_bh(bh),m_name(name) {}

};

Girl g1(3, "西施"); // C++98的风格。

Girl g2={5, "冰冰"}; // C++11的风格。

Girl g3{8, "幂幂"}; // C++11的风格。
STL容器提供了将initializer_list模板类作为参数的构造函数:

vector<int> v1(10); // 把v1初始化为10个元素。

vector<int> v2{10}; // 把v2初始化为1个元素,这个元素的值是10。

vector<int> v2{3,5,8}; // 把v3初始化为3个元素,值分别是3、5、8。
  • 头文件<initializer_list>提供了对模板类initializer_list的支持,这个类包含成员函数begin()和end()。除了用于构造函数外,还可以将initializer_list用于常规函数的参数:
#include <iostream>

#include <initializer_list>

double sum(std::initializer_list<double> il)

{

double total = 0;

for (auto it = il.begin(); it != il.end(); it++)

total = total + *it;

return total;

}

int main()

{

// double total = sum( 3.14, 5.20, 8 ); // 错误,如果没有大括号,这是三个参数。

double total = sum({ 3.14, 5.20, 8 }); // 正确,有大括号,这是一个参数。

std::cout << "total=" << total << std::endl;

}

五、自动推导类型auto

略。

六、decltype关键字

略。

七、函数后置返回类型

八、模板的别名

九、空指针nullptr

  • 空指针是不会指向有效数据的指针。以前,C/C++用0表示空指针,这带来了一些问题,这样的话0既可以表示指针常量,又可以表示整型常量。

  • C++11新增了关键字nullptr,用于表示空指针;它是指针类型,不是整型类型。

  • 为了向后兼容,C++11仍允许用0来表示空指针,因此表达式nullptr==0为true。

  • 使用nullptr提供了更高的类型安全。例如,可以将0传递给形参为int的函数,但是,如果将nullptr传递给这样的函数,编译器将视为错误。

  • 因此,出于清晰和安全考虑,请使用nullptr。

十、智能指针

略。

十一、异常规范方面的修改

略。

十二、强类型枚举(枚举类)

  • 传统的C++枚举提供了一种创建常量的方式,但类型检查比较低级。还有,如果在同一作用域内定义的两个枚举,它们的成员不能同名。

  • 针对枚举的缺陷,C++11 标准引入了枚举类,又称强类型枚举。

  • 声明强类型枚举非常简单,只需要在enum后加上关键字 class。

例如

enum e1{ red, green };

enum class e2 { red, green, blue };

enum class e3 { red, green, blue, yellow };
  • 使用强类型枚举时,要在枚举成员名前面加枚举名和::,以免发生名称冲突,如:e2::red,e3::blue

  • 强类型枚举默认的类型为int,也可以显式地指定类型,具体做法是在枚举名后面加上:type,type可以是除wchar_t以外的任何整型。

例如:

enum class e2:char { red, green, blue };

十三、explicit关键字

  • C++支持对象自动转换,但是,自动类型转换可能导致意外。为了解决这种问题,C++11引入了explicit关键字,用于关闭自动转换的特性。

十四、类内成员初始化

在类的定义中初始化成员变量。

class Girl

{

private:

int m_bh=20; // 年龄。

string m_name="美女"; // 姓名。

char m_xb = 'X'; // 性别。

public:

Girl(int bh, string name) : m_bh(bh), m_name(name) {}

};

十五、基于范围的for循环

十六、新的STL容器

  1. array(静态数组)

  2. array的大小是固定的,不像其它的模板类,但array有begin()和end()成员函数,程序员可以array对象使用STL算法。

  3. forward_list(单向链表)

  4. unordered_map、unordered_multimap、unordered_set、unordered_multiset(哈希表)

十七、新的STL方法(成员函数)

  1. C++11新增了的方法cbegin()、cend()、crbegin()、crend(),这些方法将元素视为const。

  2. iterator emplace (iterator pos, …); // 在指定位置插入一个元素,…用于构造元素,返回指向插入元素的迭代器。

  3. 更重要的是,除了传统的拷贝构造函数和赋值函数,C++11新增了移动构造函数和移动赋值函数。

十八、摒弃export

  • C++98新增了export关键字,C++11不再使用,但仍保留它作为关键字,供以后使用。

十九、嵌套模板的尖括号

  • 为了避免与运算符>>混淆,C++要求在声明嵌套模板时使用空格将尖括号分开:

  • vector<list > v1; // 两个>之间必须加空格。

C++11不再这样要求:

vector<list<int>> v2; // 两个>之间不必加空格。

二十、final关键字

  • final关键字用于限制某个类不能被继承,或者某个虚函数不能被重写。

  • final关键字放在类名或虚函数名的后面。

示例

class AA

{

public:

virtual void test()

{

cout << "AA class...";

}

};

class BB : public AA

{

public:

void test() final // 如果有其它类继承BB,test()方法将不允许重写。

{

cout << "BB class...";

}

};

class CC : public BB

{

public:

void test() // 错误,BB类中的test()后面有final,不允许重写。

{

cout << "CC class...";

}

};

二十一、override关键字

  • 在派生类中,把override放在成员函数的后面,表示重写基类的虚函数,提高代码的可读性。

  • 在派生类中,如果某成员函数不是重写基类的虚函数,随意的加上override关键字,编译器会报错。

示例:

class AA

{

public:

virtual void test()

{

cout << "AA class...";

}

};

class BB : public AA

{

public:

void test() override

{

cout << "BB class...";

}

};

二十二、数值类型和字符串之间的转换
传统方法用sprintf()和snprintf()函数把数值转换为char字符串;用atoi()、atol()、atof()把char字符串转换为数值。

C++11提供了新的方法,在数值类型和string字符串之间转换。

1、数值转换为字符串

使用to_string()函数可以将各种数值类型转换为string字符串类型,这是一个重载函数,在头文件 中声明,函数原型如下:

string to_string (int val);

string to_string (long val);

string to_string (long long val);

string to_string (unsigned val);

string to_string (unsigned long val);

string to_string (unsigned long long val);

string to_string (float val);

string to_string (double val);

string to_string (long double val);

2、字符转换为串数值

在C++中,数值类型包括整型和浮点型,针对于不同的数值类型提供了不同的函数在头文件 中声明,函数原型如下:

int stoi( const string& str, size_t* pos = nullptr, int base = 10 );

long stol( const string& str, size_t* pos = nullptr, int base = 10 );

long long stoll( const string& str, size_t* pos = nullptr, int base = 10 );

unsigned long stoul( const string& str, size_t* pos = nullptr, int base = 10 );

unsigned long long stoull( const string& str, size_t* pos = nullptr, int base = 10 );

float stof( const string& str, size_t* pos = nullptr );

double stod( const string& str, size_t* pos = nullptr );

long double stold( const string& str, size_t* pos = nullptr );

形参说明:

str:需要要转换的string字符串。

pos:传出参数,存放从哪个字符开始无法继续解析的位置,例如:123a45, 传出的位置将为3。

base:若base为0,则自动检测数值进制:若前缀为0,则为八进制,若前缀为0x或0X,则为十六进制,否则为十进制。

注意:string字符串转换为数值的函数可能会抛出异常,在《209、C++异常》中有详细介绍。

示例:

string str="123a45";

size_t pos;

int val = stoi(str, &pos, 10);

cout << "val=" << val << endl; // 输出123

cout << "pos=" << pos << endl; // 输出3

二十三、静态断言static_assert
在《210、C++断言》中有详细介绍。

二十四、常量表达式constexpr关键字
const关键字从功能上来说有双重语义:只读变量和修饰常量。

示例:

void func(const int len1)

{

// len1是只读变量,不是常量。

int array1[len1]={0}; // VS会报错,Linux平台的数组长度支持变量,不会报错。

const int len2 = 8;

int array2[len2]={0}; // 正确,len2是常量。

}

C++11标准为了解决const关键字的双重语义问题,保留了const表示“只读”的语义,而将“常量”的语义划分给了新添加的constexpr关键字。

所以,C++11 标准中,建议将const和constexpr的功能区分开,表达“只读”语义的场景用const,表达“常量”语义的场景用constexpr。

二十五、默认函数控制=default与=delete
在C++中自定义的类,编译器会默认生成一些成员函数:

无参构造函数
拷贝构造函数
拷贝赋值函数
移动构造函数
移动赋值函数
析构函数
=default表示启用默认函数。

=delete表示禁用默认函数。

示例:

include

using namespace std;

class Girl

{

private:

int m_bh = 20; // 年龄。

string m_name = "美女"; // 姓名。

char m_xb = 'X'; // 性别。

public:

Girl() = default; // 启用默认构造函数。

Girl(int bh, string name) : m_bh(bh), m_name(name) {}

Girl(const Girl& g) = delete; // 删除拷贝构造函数。

void show() { cout << "bh=" << m_bh << ",m_name=" << m_name << endl; }

};

int main()

{

Girl g1;

g1.show();

// Girl g2 = g1; // 错误,拷贝构造函数已删除。

}

221、委托构造和继承构造
C++11标准新增了委托构造和继承构造两种方法,用于简化代码。

一、委托构造
在实际的开发中,为了满足不同的需求,一个类可能会重载多个构造函数。多个构造函数之间可能会有重复的代码。例如变量初始化,如果在每个构造函数中都写一遍,这样代码会显得臃肿。

委托构造就是在一个构造函数的初始化列表中调用另一个构造函数。

注意:

不要生成环状的构造过程。
一旦使用委托构造,就不能在初始化列表中初始化其它的成员变量。
示例:

include

using namespace std;

class AA

{

private:

int m_a;

int m_b;

double m_c;

public:

// 有一个参数的构造函数,初始化m_c

AA(double c) {

m_c = c + 3; // 初始化m_c

cout << " AA(double c)" << endl;

}

// 有两个参数的构造函数,初始化m_a和m_b

AA(int a, int b) {

m_a = a + 1; // 初始化m_a

m_b = b + 2; // 初始化m_b

cout << " AA(int a, int b)" << endl;

}

// 构造函数委托AA(int a, int b)初始化m_a和m_b

AA(int a, int b, const string& str) : AA(a, b) {

cout << "m_a=" << m_a << ",m_b=" << m_b << ",str=" << str << endl;

}

// 构造函数委托AA(double c)初始化m_c

AA(double c, const string& str) : AA(c) {

cout << "m_c=" << m_c << ",str=" << str << endl;

}

};

int main()

{

AA a1(10, 20, "我是一只傻傻鸟。");

AA a2(3.8, "我有一只小小鸟。");

}

二、继承构造
在C++11之前,派生类如果要使用基类的构造函数,可以在派生类构造函数的初始化列表中指定。在《126、如何构造基类》中有详细介绍。

C++11推出了继承构造(Inheriting Constructor),在派生类中使用using来声明继承基类的构造函数。

示例:

include

using namespace std;

class AA // 基类。

{

public:

int m_a;

int m_b;

// 有一个参数的构造函数,初始化m_a

AA(int a) : m_a(a) { cout << " AA(int a)" << endl; }

// 有两个参数的构造函数,初始化m_a和m_b

AA(int a, int b) : m_a(a), m_b(b) { cout << " AA(int a, int b)" << endl; }

};

class BB :public AA // 派生类。

{

public:

double m_c;

using AA::AA; // 使用基类的构造函数。

// 有三个参数的构造函数,调用A(a,b)初始化m_a和m_b,同时初始化m_c

BB(int a, int b, double c) : AA(a, b), m_c(c) {

cout << " BB(int a, int b, double c)" << endl;

}

void show() { cout << "m_a=" << m_a << ",m_b=" << m_b << ",m_c=" << m_c << endl; }

};

int main()

{

// 将使用基类有一个参数的构造函数,初始化m_a

BB b1(10);

b1.show();

// 将使用基类有两个参数的构造函数,初始化m_a和m_b

BB b2(10,20);

b2.show();

// 将使用派生类自己有三个参数的构造函数,调用A(a,b)初始化m_a和m_b,同时初始化m_c

BB b3(10,20,10.58);

b3.show();

}

222、lambda函数
lambda函数是C++11标准新增的语法糖,也称为lambda表达式或匿名函数。

lambda函数的特点是:距离近、简洁、高效和功能强大。

示例:[](const int& no) -> void { cout << "亲爱的" << no << "号:我是一只傻傻鸟。\n"; };

语法:

示例:

include

include

include

using namespace std;

// 表白函数。

void zsshow(const int & no) {

cout << "亲爱的" << no << "号:我是一只傻傻鸟。\n";

}

// 表白仿函数。

class czs

{

public:

void operator()(const int & no) {

cout << "亲爱的" << no << "号:我是一只傻傻鸟。\n";

}

};

int main()

{

vector vv = { 5,8,3 }; // 存放超女编号的容器。

// 第三个参数是普通函数。

for_each(vv.begin(), vv.end(), zsshow);

// 第三个参数是仿函数。

for_each(vv.begin(), vv.end(), czs());

// 第三个参数是lambda表达式。

for_each(vv.begin(), vv.end(),

[](const int& no) {

cout << "亲爱的" << no << "号:我是一只傻傻鸟。\n";

}

);

}

一、参数列表
参数列表是可选的,类似普通函数的参数列表,如果没有参数列表,()可以省略不写。

与普通函数的不同:

lambda函数不能有默认参数。
所有参数必须有参数名。
不支持可变参数。
二、返回类型
用后置的方法书写返回类型,类似于普通函数的返回类型,如果不写返回类型,编译器会根据函数体中的代码推断出来。

如果有返回类型,建议显式的指定,自动推断可能与预期不一致。

三、函数体
类似于普通函数的函数体。

四、捕获列表
通过捕获列表,lambda函数可以访问父作用域中的非静态局部变量(静态局部变量可以直接访问,不能访问全局变量)。

捕获列表书写在[]中,与函数参数的传递类似,捕获方式可以是值和引用。

以下列出了不同的捕获列表的方式。

1)值捕获

与传递参数类似,采用值捕获的前提是变量可以拷贝。

与传递参数不同,变量的值是在lambda函数创建时拷贝,而不是调用时拷贝。

例如:

size_t v1 = 42;

auto f = [ v1 ] { return v1; }; // 使用了值捕获,将v1拷贝到名为f的可调用对象。

v1 = 0;

auto j = f(); // j为42,f保存了我们创建它是v1的拷贝。

由于被捕获的值是在lambda函数创建时拷贝,因此在随后对其修改不会影响到lambda内部的值。

默认情况下,如果以传值方式捕获变量,则在lambda函数中不能修改变量的值。

2)引用捕获

和函数引用参数一样,引用变量的值在lambda函数体中改变时,将影响被引用的对象。

size_t v1 = 42;

auto f = [ &v1 ] { return v1; }; // 引用捕获,将v1拷贝到名为f的可调用对象。

v1 = 0;

auto j = f(); // j为0。

如果采用引用方式捕获变量,就必须保证被引用的对象在lambda执行的时候是存在的。

3)隐式捕获

除了显式列出我们希望使用的父作域的变量之外,还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。

隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。

int a = 123;

auto f = [ = ] { cout << a << endl; }; //值捕获

f(); // 输出:123

auto f1 = [ & ] { cout << a++ << endl; }; //引用捕获

f1(); //输出:123(采用了后++)

cout << a << endl; //输出 124

4)混合方式捕获

lambda函数还支持混合方式捕获,即同时使用显式捕获和隐式捕获。

混合捕获时,捕获列表中的第一个元素必须是 = 或 &,此符号指定了默认捕获的方式是值捕获或引用捕获。

需要注意的是:显式捕获的变量必须使用和默认捕获不同的方式捕获。例如:

int i = 10;

int j = 20;

auto f1 = [ =, &i] () { return j + i; }; // 正确,默认值捕获,显式是引用捕获

auto f2 = [ =, i] () { return i + j; }; // 编译出错,默认值捕获,显式值捕获,冲突了

auto f3 = [ &, &i] () { return i +j; }; // 编译出错,默认引用捕获,显式引用捕获,冲突了

5)修改值捕获变量的值

在lambda函数中,如果以传值方式捕获变量,则函数体中不能修改该变量,否则会引发编译错误。

在lambda函数中,如果希望修改值捕获变量的值,可以加mutable选项,但是,在lambda函数的外部,变量的值不会被修改。

int a = 123;

auto f = amutable { cout << ++a << endl; }; // 不会报错

cout << a << endl; // 输出:123

f(); // 输出:124

cout << a << endl; // 输出:123

6)异常说明

lambda可以抛出异常,用throw(…)指示异常的类型,用noexcept指示不抛出任何异常。

五、lambda函数的本质
当我们编写了一个lambda函数之后,编译器将它翻译成一个类,该类中有一个重载了()的函数。

1)采用值捕获

采用值捕获时,lambda函数生成的类用捕获变量的值初始化自己的成员变量。

例如:

int a =10;

int b = 20;

auto addfun = [=] (const int c ) -> int { return a+c; };

int c = addfun(b);

cout << c << endl;

等同于:

class Myclass

{

int m_a; // 该成员变量对应通过值捕获的变量。

public:

Myclass( int a ) : m_a(a){}; // 该形参对应捕获的变量。

// 重载了()运算符的函数,返回类型、形参和函数体都与lambda函数一致。

int operator()(const int c) const

{

return a + c;

}

};

默认情况下,由lambda函数生成的类是const成员函数,所以变量的值不能修改。如果加上mutable,相当于去掉const。这样上面的限制就能讲通了。

2)采用引用捕获

如果lambda函数采用引用捕获的方式,编译器直接引用就行了。

唯一需要注意的是,lambda函数执行时,程序必须保证引用的对象有效。

225、右值引用
一、左值、右值
在C++中,所有的值不是左值,就是右值。左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束后就不再存在的临时对象。有名字的对象都是左值,右值没有名字。

还有一个可以区分左值和右值的便捷方法:看能不能对表达式取地址,如果能,则为左值,否则为右值。

C++11扩展了右值的概念,将右值分为了纯右值和将亡值。

纯右值:a)非引用返回的临时变量;b)运算表达式产生的结果;c)字面常量(C风格字符串除外,它是地址)。
将亡值:与右值引用相关的表达式,例如:将要被移动的对象、T&&函数返回的值、std::move()的返回值、转换成T&&的类型的转换函数的返回值。
不懂纯右值和将亡值的区别其实没关系,统一看作右值即可,不影响使用。

示例:

class AA {

int m_a;

};

AA getTemp()

{

return AA();

}

int ii = 3; // ii是左值,3是右值。

int jj = ii+8; // jj是左值,ii+8是右值。

AA aa = getTemp(); // aa是左值 ,getTemp()的返回值是右值(临时变量)。

二、左值引用、右值引用
C++98中的引用很常见,就是给变量取个别名,在C++11中,因为增加了右值引用(rvalue reference)的概念,所以C++98中的引用都称为了左值引用(lvalue reference)。

右值引用就是给右值取个名字。

语法:数据类型&& 变量名=右值;

示例:

include

using namespace std;

class AA {

public:

int m_a=9;

};

AA getTemp()

{

return AA();

}

int main()

{

int&& a = 3; // 3是右值。

int b = 8; // b是左值。

int&& c = b + 5; // b+5是右值。

AA&& aa = getTemp(); // getTemp()的返回值是右值(临时变量)。

cout << "a=" << a << endl;

cout << "c=" << c << endl;

cout << "aa.m_a=" << aa.m_a << endl;

}

getTemp()的返回值本来在表达式语句结束后其生命也就该终结了(因为是临时变量),而通过右值引用重获了新生,其生命周期将与右值引用类型变量aa的生命周期一样,只要aa还活着,该右值临时变量将会一直存活下去。

引入右值引用的主要目的是实现移动语义。

左值引用只能绑定(关联、指向)左值,右值引用只能绑定右值,如果绑定的不对,编译就会失败。

但是,常量左值引用却是个奇葩,它可以算是一个万能的引用类型,它可以绑定非常量左值、常量左值、右值,而且在绑定右值的时候,常量左值引用还可以像右值引用一样将右值的生命期延长,缺点是,只能读不能改。

int a = 1;

const int& ra = a; // a是非常量左值。

const int b = 1;

const int& rb = b; // b是常量左值。

const int& rc = 1; // 1是右值。

总结一下,其中T是一个具体类型:

1)左值引用, 使用 T&, 只能绑定左值。

2)右值引用, 使用 T&&, 只能绑定右值。

3)已命名的右值引用是左值。

4)常量左值,使用 const T&, 既可以绑定左值又可以绑定右值。

226、移动语义
如果一个对象中有堆区资源,需要编写拷贝构造函数和赋值函数,实现深拷贝。

深拷贝把对象中的堆区资源复制了一份,如果源对象(被拷贝的对象)是临时对象,拷贝完就没什么用了,这样会造成没有意义的资源申请和释放操作。如果能够直接使用源对象拥有的资源,可以节省资源申请和释放的时间。C++11新增加的移动语义就能够做到这一点。

实现移动语义要增加两个函数:移动构造函数和移动赋值函数。

移动构造函数的语法:

类名(类名&& 源对象){......}

移动赋值函数的语法:

类名& operator=(类名&& 源对象){……}

注意:

1)对于一个左值,会调用拷贝构造函数,但是有些左值是局部变量,生命周期也很短,能不能也移动而不是拷贝呢?C++11为了解决这个问题,提供了std::move()方法来将左值转义为右值,从而方便使用移动语义。它其实就是告诉编译器,虽然我是一个左值,但不要对我用拷贝构造函数,用移动构造函数吧。左值对象被转移资源后,不会立刻析构,只有在离开自己的作用域的时候才会析构,如果继续使用左值中的资源,可能会发生意想不到的错误。

2)如果没有提供移动构造/赋值函数,只提供了拷贝构造/赋值函数,编译器找不到移动构造/赋值函数就去寻找拷贝构造/赋值函数。

3)C++11中的所有容器都实现了移动语义,避免对含有资源的对象发生无谓的拷贝。

4)移动语义对于拥有资源(如内存、文件句柄)的对象有效,如果是基本类型,使用移动语义没有意义。

示例:

include

using namespace std;

class AA

{

public:

int* m_data = nullptr; // 数据成员,指向堆区资源的指针。

AA() = default; // 启用默认构造函数。

void alloc() { // 给数据成员m_data分配内存。

m_data = new int; // 分配内存。

memset(m_data, 0, sizeof(int)); // 初始化已分配的内存。

}

AA(const AA& a) { // 拷贝构造函数。

cout << "调用了拷贝构造函数。\n"; // 显示自己被调用的日志。

if (m_data == nullptr) alloc(); // 如果没有分配内存,就分配。

memcpy(m_data, a.m_data, sizeof(int)); // 把数据从源对象中拷贝过来。

}

AA(AA&& a) { // 移动构造函数。

cout << "调用了移动构造函数。\n"; // 显示自己被调用的日志。

if (m_data != nullptr) delete m_data; // 如果已分配内存,先释放掉。

m_data = a.m_data; // 把资源从源对象中转移过来。

a.m_data = nullptr; // 把源对象中的指针置空。

}

AA& operator=(const AA& a) { // 赋值函数。

cout << "调用了赋值函数。\n"; // 显示自己被调用的日志。

if (this == &a) return *this; // 避免自我赋值。

if (m_data == nullptr) alloc(); // 如果没有分配内存,就分配。

memcpy(m_data, a.m_data, sizeof(int)); // 把数据从源对象中拷贝过来。

return *this;

}

AA& operator=(AA&& a) { // 移动赋值函数。

cout << "调用了移动赋值函数。\n"; // 显示自己被调用的日志。

if (this == &a) return *this; // 避免自我赋值。

if (m_data != nullptr) delete m_data; // 如果已分配内存,先释放掉。

m_data = a.m_data; // 把资源从源对象中转移过来。

a.m_data = nullptr; // 把源对象中的指针置空。

return *this;

}

~AA() { // 析构函数。

if (m_data != nullptr) {

delete m_data; m_data = nullptr;

}

}

};

int main()

{

AA a1; // 创建对象a1。

a1.alloc(); // 分配堆区资源。

*a1.m_data = 3; // 给堆区内存赋值。

cout << "a1.m_data=" << *a1.m_data << endl;

AA a2 = a1; // 将调用拷贝构造函数。

cout << "a2.m_data=" << *a2.m_data << endl;

AA a3;

a3 = a1; // 将调用赋值函数。

cout << "a3.m_data=" << *a3.m_data << endl;

auto f = [] { AA aa; aa.alloc(); *aa.m_data = 8; return aa; }; // 返回AA类对象的lambda函数。

AA a4 = f(); // lambda函数返回临时对象,是右值,将调用移动构造函数。

cout << "a4.m_data=" << *a4.m_data << endl;

AA a6;

a6 = f(); // lambda函数返回临时对象,是右值,将调用移动赋值函数。

cout << "a6.m_data=" << *a6.m_data << endl;

}

227、完美转发
在函数模板中,可以将参数“完美”的转发给其它函数。所谓完美,即不仅能准确的转发参数的值,还能保证被转发参数的左、右值属性不变。

C++11标准引入了右值引用和移动语义,所以,能否实现完美转发,决定了该参数在传递过程使用的是拷贝语义还是移动语义。

为了支持完美转发,C++11提供了以下方案:

1)如果模板中(包括类模板和函数模板)函数的参数书写成为T&& 参数名,那么,函数既可以接受左值引用,又可以接受右值引用。

2)提供了模板函数std::forward(参数) ,用于转发参数,如果 参数是一个右值,转发之后仍是右值引用;如果参数是一个左值,转发之后仍是左值引用。

示例:

include

using namespace std;

void func1(int& ii) { // 如果参数是左值,调用此函数。

cout << "参数是左值=" << ii << endl;

}

void func1(int&& ii) { // 如果参数是右值,调用此函数。

cout << "参数是右值=" << ii << endl;

}

// 1)如果模板中(包括类模板和函数模板)函数的参数书写成为T&& 参数名,

// 那么,函数既可以接受左值引用,又可以接受右值引用。

// 2)提供了模板函数std::forward(参数) ,用于转发参数,

// 如果参数是一个右值,转发之后仍是右值引用;如果 参数是一个左值,转发之后仍是左值引用。

template

void func(TT&& ii)

{

func1(forward(ii));

}

int main()

{

int ii = 3;

func(ii); // 实参是左值。

func(8); // 实参是右值。

}

228、可变参数模板
可变参数模版是C++11新增的最强大的特性之一,它对参数进行了泛化,能支持任意个数、任意数据类型的参数。

示例:

include

include

using namespace std;

template

void show(T girl) // 向超女表白的函数,参数可能是超女编号,也可能是姓名,所以用T。

{

cout << "亲爱的" << girl << ",我是一只傻傻鸟。\n";

}

// 递归终止时调用的非模板函数,函数名要与展开参数包的递归函数模板相同。

void print()

{

cout << "递归终止。\n";

}

// 展开参数包的递归函数模板。

template <typename T, typename ...Args>

void print(T arg, Args... args)

{

//cout << "参数: " << arg << endl; // 显示本次展开的参数。

show(arg); // 把参数用于表白。

//cout << "还有" << sizeof...(args) << "个参数未展开。" << endl; // 显示未展开变参的个数。

print(args...); // 继续展开参数。

}

template <typename...Args>

void func(const string& str, Args...args) // 除了可变参数,还可以有其它常规参数。

{

cout << str << endl; // 表白之前,喊句口号。

print(args...); // 展开可变参数包。

cout << "表白完成。\n";

}

int main(void)

{

//print("金莲", 4, "西施");

//print("冰冰", 8, "西施", 3);

func("我是绝世帅歌。", "冰冰", 8, "西施", 3); // "我是绝世帅歌。"不是可变参数,其它的都是。

}

229、时间操作chrono库
C++11提供了chrono模版库,实现了一系列时间相关的操作(时间长度、系统时间和计时器)。

头文件:#include

命名空间:std::chrono

一、时间长度
duration模板类用于表示一段时间(时间长度、时钟周期),如:1小时、8分钟、5秒。

duration的定义如下:

template<class Rep, class Period = std::ratio<1, 1>>

class duration

{

……

};

为了方便使用,定义了一些常用的时间长度,比如:时、分、秒、毫秒、微秒、纳秒,它们都位于std::chrono命名空间下,定义如下:

using hours = duration<Rep, std::ratio<3600>> // 小时

using minutes = duration<Rep, std::ratio<60>> // 分钟

using seconds = duration // 秒

using milliseconds = duration<Rep, std::milli> // 毫秒

using microseconds = duration<Rep, std::micro> // 微秒

using nanoseconds = duration<Rep, std::nano> // 纳秒

注意:

duration模板类重载了各种算术运算符,用于操作duration对象。
duration模板类提供了count()方法,获取duration对象的值。
示例:

include

include // chrono库的头文件。

using namespace std;

int main()

{

chrono::hours t1(1); // 1小时

chrono::minutes t2(60); // 60分钟

chrono::seconds t3(60 * 60); // 60*60秒

chrono::milliseconds t4(60 * 60 * 1000); // 60601000毫秒

chrono::microseconds t5(60 * 60 * 1000 * 1000); // 警告:整数溢出。

chrono::nanoseconds t6(60 * 60 * 1000 * 1000*1000); // 警告:整数溢出。

if (t1 == t2) cout << "t1==t2\n";

if (t1 == t3) cout << "t1==t3\n";

if (t1 == t4) cout << "t1==t4\n";

// 获取时钟周期的值,返回的是int整数。

cout << "t1=" << t1.count() << endl;

cout << "t2=" << t2.count() << endl;

cout << "t3=" << t3.count() << endl;

cout << "t4=" << t4.count() << endl;

chrono::seconds t7(1); // 1秒

chrono::milliseconds t8(1000); // 1000毫秒

chrono::microseconds t9(1000 * 1000); // 1000*1000微秒

chrono::nanoseconds t10(1000 * 1000 * 1000); // 100010001000纳秒

if (t7 == t8) cout << "t7==t8\n";

if (t7 == t9) cout << "t7==t9\n";

if (t7 == t10) cout << "t7==t10\n";

// 获取时钟周期的值。

cout << "t7=" << t7.count() << endl;

cout << "t8=" << t8.count() << endl;

cout << "t9=" << t9.count() << endl;

cout << "t10=" << t10.count() << endl;

}

二、系统时间
system_clock类支持了对系统时钟的访问,提供了三个静态成员函数:

// 返回当前时间的时间点。

static std::chrono::time_pointstd::chrono::system_clock now() noexcept;

// 将时间点time_point类型转换为std::time_t 类型。

static std::time_t to_time_t( const time_point& t ) noexcept;

// 将std::time_t类型转换为时间点time_point类型。

static std::chrono::system_clock::time_point from_time_t( std::time_t t ) noexcept;

示例:

define _CRT_SECURE_NO_WARNINGS // localtime()需要这个宏。

include

include

include // put_time()函数需要包含的头文件。

include

using namespace std;

int main()

{

// 1)静态成员函数chrono::system_clock::now()用于获取系统时间。(C++时间)

auto now = chrono::system_clock::now();

// 2)静态成员函数chrono::system_clock::to_time_t()把系统时间转换为time_t。(UTC时间)

auto t_now = chrono::system_clock::to_time_t(now);

// t_now = t_now + 246060; // 把当前时间加1天。

// t_now = t_now + -16060; // 把当前时间减1小时。

// t_now = t_now + 120; // 把当前时间加120秒。

// 3)std::localtime()函数把time_t转换成本地时间。(北京时)

// localtime()不是线程安全的,VS用localtime_s()代替,Linux用localtime_r()代替。

auto tm_now = std::localtime(&t_now);

// 4)格式化输出tm结构体中的成员。

std::cout << std::put_time(tm_now, "%Y-%m-%d %H:%M:%S") << std::endl;

std::cout << std::put_time(tm_now, "%Y-%m-%d") << std::endl;

std::cout << std::put_time(tm_now, "%H:%M:%S") << std::endl;

std::cout << std::put_time(tm_now, "%Y%m%d%H%M%S") << std::endl;

stringstream ss; // 创建stringstream对象ss,需要包含头文件。

ss << std::put_time(tm_now, "%Y-%m-%d %H:%M:%S"); // 把时间输出到对象ss中。

string timestr = ss.str(); // 把ss转换成string的对象。

cout << timestr << endl;

}

三、计时器
steady_clock类相当于秒表,操作系统只要启动就会进行时间的累加,常用于耗时的统计(精确到纳秒)。

include

include

using namespace std;

int main()

{

// 静态成员函数chrono::steady_clock::now()获取开始的时间点。

auto start = chrono::steady_clock::now();

// 执行一些代码,让它消耗一些时间。

cout << "计时开始 ...... \n";

for (int ii = 0; ii < 1000000; ii++) {

// cout << "我是一只傻傻鸟。\n";

}

cout << "计时完成 ...... \n";

// 静态成员函数chrono::steady_clock::now()获取结束的时间点。

auto end = chrono::steady_clock::now();

// 计算消耗的时间,单位是纳秒。

auto dt = end - start;

cout << "耗时: " << dt.count() << "纳秒("<<(double)dt.count()/(100010001000)<<"秒)";

}

240、C++11线程
在C++11之前,C++没有对线程提供语言级别的支持,各种操作系统和编译器实现线程的方法不一样。

C++11增加了线程以及线程相关的类,统一编程风格、简单易用、跨平台。

一、创建线程
头文件:#include

线程类:std::thread

构造函数:

1)thread() noexcept;

默认构造函,构造一个线程对象,不执行任何任务(不会创建/启动子线程)。

2)template< class Function, class... Args >

explicit thread(Function&& fx, Args&&... args );

创建线程对象,在线程中执行任务函数fx中的代码,args是要传递给任务函数fx的参数。

任务函数fx可以是普通函数、类的非静态成员函数、类的静态成员函数、lambda函数、仿函数。

3)thread(const thread& ) = delete;

删除拷贝构造函数,不允许线程对象之间的拷贝。

4)thread(thread&& other ) noexcept;

移动构造函数,将线程other的资源所有权转移给新创建的线程对象。

赋值函数:

thread& operator= (thread&& other) noexcept;

thread& operator= (const other&) = delete;

线程中的资源不能被复制,如果other是右值,会进行资源所有权的转移,如果other是左值,禁止拷贝。

注意:

先创建的子线程不一定跑得最快(程序运行的速度有很大的偶然性)。
线程的任务函数返回后,子线程将终止。
如果主程序(主线程)退出(不论是正常退出还是意外终止),全部的子线程将强行被终止。
示例:

include

include // 线程类头文件。

include <windows.h> // Sleep()函数需要这个头文件。

using namespace std;

// 普通函数。

void func(int bh, const string& str) {

for (int ii = 1; ii <= 10; ii++)

{

cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;

Sleep(1000); // 休眠1秒。

}

}

// 仿函数。

class mythread1

{

public:

void operator()(int bh, const string& str) {

for (int ii = 1; ii <= 10; ii++)

{

cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;

Sleep(1000); // 休眠1秒。

}

}

};

// 类中有静态成员函数。

class mythread2

{

public:

static void func(int bh, const string& str) {

for (int ii = 1; ii <= 10; ii++)

{

cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;

Sleep(1000); // 休眠1秒。

}

}

};

// 类中有普通成员函数。

class mythread3

{

public:

void func(int bh, const string& str) {

for (int ii = 1; ii <= 10; ii++)

{

cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;

Sleep(1000); // 休眠1秒。

}

}

};

int main()

{

// 用普通函数创建线程。

//thread t1(func, 3, "我是一只傻傻鸟。");

//thread t2(func, 8, "我有一只小小鸟。");

// 用lambda函数创建线程。

auto f = [](int bh, const string& str) {

for (int ii = 1; ii <= 10; ii++)

{

cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;

Sleep(1000); // 休眠1秒。

}

};

//thread t3(f, 3, "我是一只傻傻鸟。");

// 用仿函数创建线程。

//thread t4(mythread1(), 3, "我是一只傻傻鸟。");

// 用类的静态成员函数创建线程。

//thread t5(mythread2::func, 3, "我是一只傻傻鸟。");

// 用类的普通成员函数创建线程。

mythread3 myth; // 必须先创建类的对象,必须保证对象的生命周期比子线程要长。

thread t6(&mythread3::func, &myth, 3, "我是一只傻傻鸟。"); // 第二个参数必须填对象的this指针,否则会拷贝对象。

cout << "任务开始。\n";

for (int ii = 0; ii < 10; ii++) {

cout << "执行任务中......\n";

Sleep(1000); // 假设执行任务需要时间。

}

cout << "任务完成。\n";

//t1.join(); // 回收线程t1的资源。

//t2.join(); // 回收线程t2的资源。

//t3.join(); // 回收线程t3的资源。

//t4.join(); // 回收线程t4的资源。

//t5.join(); // 回收线程t5的资源。

t6.join(); // 回收线程t6的资源。

}

二、线程资源的回收
虽然同一个进程的多个线程共享进程的栈空间,但是,每个子线程在这个栈中拥有自己私有的栈空间。所以,线程结束时需要回收资源。

回收子线程的资源有两种方法:

1)在主程序中,调用join()成员函数等待子线程退出,回收它的资源。如果子线程已退出,join()函数立即返回,否则会阻塞等待,直到子线程退出。

2)在主程序中,调用detach()成员函数分离子线程,子线程退出时,系统将自动回收资源。分离后的子线程不可join()。

用joinable()成员函数可以判断子线程的分离状态,函数返回布尔类型。

示例:

include

include // 线程类头文件。

include <windows.h> // Sleep()函数需要这个头文件。

using namespace std;

// 普通函数。

void func(int bh, const string& str) {

for (int ii = 1; ii <= 10; ii++)

{

cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;

Sleep(1000); // 休眠1秒。

}

}

int main()

{

// 用普通函数创建线程。

thread t1(func, 3, "我是一只傻傻鸟。");

thread t2(func, 8, "我有一只小小鸟。");

t1.detach(); t2.detach(); // 分离子线程。

//cout << "任务开始。\n";

//for (int ii = 0; ii < 12; ii++) {

// cout << "执行任务中......\n";

// Sleep(1000); // 假设执行任务需要时间。

//}

//cout << "任务完成。\n";

//t1.join(); // 回收线程t1的资源。

//t2.join(); // 回收线程t2的资源。

Sleep(12000);

}

三、this_thread的全局函数
C++11提供了命名空间this_thread来表示当前线程,该命名空间中有四个函数:get_id()、sleep_for()、sleep_until()、yield()。

1)get_id()

thread::id get_id() noexcept;

该函数用于获取线程ID,thread类也有同名的成员函数。

2)sleep_for() VS Sleep(1000) Linux sleep(1)

template <class Rep, class Period>

void sleep_for (const chrono::duration<Rep,Period>& rel_time);

该函数让线程休眠一段时间。

3)sleep_until() 2022-01-01 12:30:35

template <class Clock, class Duration>

void sleep_until (const chrono::time_point<Clock,Duration>& abs_time);

该函数让线程休眠至指定时间点。(可实现定时任务)

4)yield()

void yield() noexcept;

该函数让线程主动让出自己已经抢到的CPU时间片。

5)thread类其它的成员函数

void swap(std::thread& other); // 交换两个线程对象。

static unsigned hardware_concurrency() noexcept; // 返回硬件线程上下文的数量。

The interpretation of this value is system- andimplementation- specific, and may not be exact, but just an approximation.

Note that this does not need to match the actualnumber of processors or cores available in the system: A system can supportmultiple threads per processing unit, or restrict the access to its resourcesto the program.

If this value is not computable or well defined,the function returns 0.

示例:

include

include // 线程类头文件。

using namespace std;

// 普通函数。

void func(int bh, const string& str) {

cout << "子线程:" << this_thread::get_id() << endl;

for (int ii = 1; ii <= 3; ii++)

{

cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;

this_thread::sleep_for(chrono::seconds(1)); // 休眠1秒。

}

}

int main()

{

// 用普通函数创建线程。

thread t1(func, 3, "我是一只傻傻鸟。");

thread t2(func, 8, "我有一只小小鸟。");

cout << "主线程:" << this_thread::get_id() << endl;

cout << "线程t1:" << t1.get_id() << endl;

cout << "线程t2:" << t2.get_id() << endl;

t1.join(); // 回收线程t1的资源。

t2.join(); // 回收线程t2的资源。

}

四、call_once函数
在多线程环境中,某些函数只能被调用一次,例如:初始化某个对象,而这个对象只能被初始化一次。

在线程的任务函数中,可以用std::call_once()来保证某个函数只被调用一次。

头文件:#include

template< class callable, class... Args >

void call_once( std::once_flag& flag, Function&& fx, Args&&... args );

第一个参数是std::once_flag,用于标记函数fx是否已经被执行过。

第二个参数是需要执行的函数fx。

后面的可变参数是传递给函数fx的参数。

示例:

include

include // 线程类头文件。

include // std::once_flag和std::call_once()函数需要包含这个头文件。

using namespace std;

once_flag onceflag; // once_flag全局变量。本质是取值为0和1的锁。

// 在线程中,打算只调用一次的函数。

void once_func(const int bh, const string& str) {

cout << "once_func() bh= " << bh << ", str=" << str << endl;

}

// 普通函数。

void func(int bh, const string& str) {

call_once(onceflag,once_func,0, "各位观众,我要开始表白了。");

for (int ii = 1; ii <= 3; ii++)

{

cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;

this_thread::sleep_for(chrono::seconds(1)); // 休眠1秒。

}

}

int main()

{

// 用普通函数创建线程。

thread t1(func, 3, "我是一只傻傻鸟。");

thread t2(func, 8, "我有一只小小鸟。");

t1.join(); // 回收线程t1的资源。

t2.join(); // 回收线程t2的资源。

}

五、native_handle函数
C++11定义了线程标准,不同的平台和编译器在实现的时候,本质上都是对操作系统的线程库进行封装,会损失一部分功能。

为了弥补C++11线程库的不足,thread类提供了native_handle()成员函数,用于获得与操作系统相关的原生线程句柄,操作系统原生的线程库就可以用原生线程句柄操作线程。

示例:

include

include

include <pthread.h> // Linux的pthread线程库头文件。

using namespace std;

void func() // 线程任务函数。

{

for (int ii=1;ii<=10;ii++)

{

cout << "ii=" << ii << endl;

this_thread::sleep_for(chrono::seconds(1)); // 休眠1秒。

}

}

int main()

{

thread tt(func); // 创建线程。

this_thread::sleep_for(chrono::seconds(5)); // 休眠5秒。

pthread_t thid= tt.native_handle(); // 获取Linux操作系统原生的线程句柄。

pthread_cancel(thid); // 取消线程。

tt.join(); // 等待线程退出。

}

六、线程安全
示例:

include

include // 线程类头文件。

using namespace std;

int aa = 0; // 定义全局变量。

// 普通函数,把全局变量aa加1000000次。

void func() {

for (int ii = 1; ii <= 1000000; ii++)

aa++;

}

int main()

{

// 用普通函数创建线程。

thread t1(func); // 创建线程t1,把全局变量aa加1000000次。

thread t2(func); // 创建线程t2,把全局变量aa加1000000次。

t1.join(); // 回收线程t1的资源。

t2.join(); // 回收线程t2的资源。

cout << "aa=" << aa << endl; // 显示全局变量aa的值。

}

242、互斥锁
C++11提供了四种互斥锁:

mutex:互斥锁。
timed_mutex:带超时机制的互斥锁。
recursive_mutex:递归互斥锁。
recursive_timed_mutex:带超时机制的递归互斥锁。
包含头文件:#include

一、mutex类
1)加锁lock()

互斥锁有锁定和未锁定两种状态。

如果互斥锁是未锁定状态,调用lock()成员函数的线程会得到互斥锁的所有权,并将其上锁。

如果互斥锁是锁定状态,调用lock()成员函数的线程就会阻塞等待,直到互斥锁变成未锁定状态。

2)解锁unlock()

只有持有锁的线程才能解锁。

3)尝试加锁try_lock()

如果互斥锁是未锁定状态,则加锁成功,函数返回true。

如果互斥锁是锁定状态,则加锁失败,函数立即返回false。(线程不会阻塞等待)

示例:

include

include // 线程类头文件。

include // 互斥锁类的头文件。

using namespace std;

mutex mtx; // 创建互斥锁,保护共享资源cout对象。

// 普通函数。

void func(int bh, const string& str) {

for (int ii = 1; ii <= 10; ii++)

{

mtx.lock(); // 申请加锁。

cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;

mtx.unlock(); // 解锁。

this_thread::sleep_for(chrono::seconds(1)); // 休眠1秒。

}

}

int main()

{

// 用普通函数创建线程。

thread t1(func, 1, "我是一只傻傻鸟。");

thread t2(func, 2, "我是一只傻傻鸟。");

thread t3(func, 3, "我是一只傻傻鸟。");

thread t4(func, 4, "我是一只傻傻鸟。");

thread t5(func, 5, "我是一只傻傻鸟。");

t1.join(); // 回收线程t1的资源。

t2.join(); // 回收线程t2的资源。

t3.join(); // 回收线程t3的资源。

t4.join(); // 回收线程t4的资源。

t5.join(); // 回收线程t5的资源。

}

二、timed_mutex类
增加了两个成员函数:

bool try_lock_for(时间长度);

bool try_lock_until(时间点);

三、recursive_mutex类
递归互斥锁允许同一线程多次获得互斥锁,可以解决同一线程多次加锁造成的死锁问题。

示例:

include

include // 互斥锁类的头文件。

using namespace std;

class AA

{

recursive_mutex m_mutex;

public:

void func1() {

m_mutex.lock();

cout << "调用了func1()\n";

m_mutex.unlock();

}

void func2() {

m_mutex.lock();

cout << "调用了func2()\n";

func1();

m_mutex.unlock();

}

};

int main()

{

AA aa;

//aa.func1();

aa.func2();

}

四、lock_guard类
lock_guard是模板类,可以简化互斥锁的使用,也更安全。

lock_guard的定义如下:

template

class lock_guard

{

explicit lock_guard(Mutex& mtx);

}

lock_guard在构造函数中加锁,在析构函数中解锁。

lock_guard采用了RAII思想(在类构造函数中分配资源,在析构函数中释放资源,保证资源在离开作用域时自动释放)。

243、条件变量-生产消费者模型
条件变量是一种线程同步机制。当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。

C++11的条件变量提供了两个类:

condition_variable:只支持与普通mutex搭配,效率更高。

condition_variable_any:是一种通用的条件变量,可以与任意mutex搭配(包括用户自定义的锁类型)。

包含头文件:<condition_variable>

一、condition_variable类
主要成员函数:

1)condition_variable() 默认构造函数。

2)condition_variable(const condition_variable &)=delete 禁止拷贝。

3)condition_variable& condition_variable::operator=(const condition_variable &)=delete 禁止赋值。

4)notify_one() 通知一个等待的线程。

5)notify_all() 通知全部等待的线程。

6)wait(unique_lock lock) 阻塞当前线程,直到通知到达。

7)wait(unique_lock lock,Pred pred) 循环的阻塞当前线程,直到通知到达且谓词满足。

8)wait_for(unique_lock lock,时间长度)

9)wait_for(unique_lock lock,时间长度,Pred pred)

10)wait_until(unique_lock lock,时间点)

11)wait_until(unique_lock lock,时间点,Pred pred)

二、unique_lock类
template class unique_lock是模板类,模板参数为互斥锁类型。

unique_lock和lock_guard都是管理锁的辅助类,都是RAII风格(在构造时获得锁,在析构时释放锁)。它们的区别在于:为了配合condition_variable,unique_lock还有lock()和unlock()成员函数。

示例1:

include

include

include // 线程类头文件。

include // 互斥锁类的头文件。

include // deque容器的头文件。

include // queue容器的头文件。

include <condition_variable> // 条件变量的头文件。

using namespace std;

class AA

{

mutex m_mutex; // 互斥锁。

condition_variable m_cond; // 条件变量。

queue<string, deque> m_q; // 缓存队列,底层容器用deque。

public:

void incache(int num) // 生产数据,num指定数据的个数。

{

lock_guard lock(m_mutex); // 申请加锁。

for (int ii=0 ; ii<num ; ii++)

{

static int bh = 1; // 超女编号。

string message = to_string(bh++) + "号超女"; // 拼接出一个数据。

m_q.push(message); // 把生产出来的数据入队。

}

m_cond.notify_one(); // 唤醒一个被当前条件变量阻塞的线程。

}

void outcache() // 消费者线程任务函数。

{

while (true)

{

string message;

{

// 把互斥锁转换成unique_lock,并申请加锁。

unique_lock lock(m_mutex);

while (m_q.empty()) // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if

m_cond.wait(lock); // 等待生产者的唤醒信号。

// 数据元素出队。

message = m_q.front(); m_q.pop();

}

// 处理出队的数据(把数据消费掉)。

this_thread::sleep_for(chrono::milliseconds(1)); // 假设处理数据需要1毫秒。

cout << "线程:" << this_thread::get_id() << "," << message << endl;

}

}

};

int main()

{

AA aa;

thread t1(&AA::outcache, &aa); // 创建消费者线程t1。

thread t2(&AA::outcache, &aa); // 创建消费者线程t2。

thread t3(&AA::outcache, &aa); // 创建消费者线程t3。

this_thread::sleep_for(chrono::seconds(2)); // 休眠2秒。

aa.incache(3); // 生产3个数据。

this_thread::sleep_for(chrono::seconds(3)); // 休眠3秒。

aa.incache(5); // 生产5个数据。

t1.join(); // 回收子线程的资源。

t2.join();

t3.join();

}

示例2:

include

include

include // 线程类头文件。

include // 互斥锁类的头文件。

include // deque容器的头文件。

include // queue容器的头文件。

include <condition_variable> // 条件变量的头文件。

using namespace std;

class AA

{

mutex m_mutex; // 互斥锁。

condition_variable m_cond; // 条件变量。

queue<string, deque> m_q; // 缓存队列,底层容器用deque。

public:

void incache(int num) // 生产数据,num指定数据的个数。

{

lock_guard lock(m_mutex); // 申请加锁。

for (int ii=0 ; ii<num ; ii++)

{

static int bh = 1; // 超女编号。

string message = to_string(bh++) + "号超女"; // 拼接出一个数据。

m_q.push(message); // 把生产出来的数据入队。

}

//m_cond.notify_one(); // 唤醒一个被当前条件变量阻塞的线程。

m_cond.notify_all(); // 唤醒全部被当前条件变量阻塞的线程。

}

void outcache() { // 消费者线程任务函数。

while (true) {

// 把互斥锁转换成unique_lock,并申请加锁。

unique_lock lock(m_mutex);

// 条件变量虚假唤醒:消费者线程被唤醒后,缓存队列中没有数据。

//while (m_q.empty()) // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if

// m_cond.wait(lock); // 1)把互斥锁解开;2)阻塞,等待被唤醒;3)给互斥锁加锁。

m_cond.wait(lock, [this] { return !m_q.empty(); });

// 数据元素出队。

string message = m_q.front(); m_q.pop();

cout << "线程:" << this_thread::get_id() << "," << message << endl;

lock.unlock(); // 手工解锁。

// 处理出队的数据(把数据消费掉)。

this_thread::sleep_for(chrono::milliseconds(1)); // 假设处理数据需要1毫秒。

}

}

};

int main()

{

AA aa;

thread t1(&AA::outcache, &aa); // 创建消费者线程t1。

thread t2(&AA::outcache, &aa); // 创建消费者线程t2。

thread t3(&AA::outcache, &aa); // 创建消费者线程t3。

this_thread::sleep_for(chrono::seconds(2)); // 休眠2秒。

aa.incache(2); // 生产2个数据。

this_thread::sleep_for(chrono::seconds(3)); // 休眠3秒。

aa.incache(5); // 生产5个数据。

t1.join(); // 回收子线程的资源。

t2.join();

t3.join();

}

244、原子类型atomic
C++11提供了atomic模板类(结构体),用于支持原子类型,模板参数可以是bool、char、int、long、long long、指针类型(不支持浮点类型和自定义数据类型)。

原子操作由CPU指令提供支持,它的性能比锁和消息传递更高,并且,不需要程序员处理加锁和释放锁的问题,支持修改、读取、交换、比较并交换等操作。

头文件:#include

构造函数:

atomic() noexcept = default; // 默认构造函数。

atomic(T val) noexcept; // 转换函数。

atomic(const atomic&) = delete; // 禁用拷贝构造函数。

赋值函数:

atomic& operator=(const atomic&) = delete; // 禁用赋值函数。

常用函数:

void store(const T val) noexcept; // 把val的值存入原子变量。

T load() noexcept; // 读取原子变量的值。

T fetch_add(const T val) noexcept; // 把原子变量的值与val相加,返回原值。

T fetch_sub(const T val) noexcept; // 把原子变量的值减val,返回原值。

T exchange(const T val) noexcept; // 把val的值存入原子变量,返回原值。

T compare_exchange_strong(T &expect,const T val) noexcept; // 比较原子变量的值和预期值expect,如果当两个值相等,把val存储到原子变量中,函数返回true;如果当两个值不相等,用原子变量的值更新预期值,函数返回false。CAS指令。

bool is_lock_free(); // 查询某原子类型的操作是直接用CPU指令(返回true),还是编译器内部的锁(返回false)。

原子类型的别名:

注意:

atomic模板类重载了整数操作的各种运算符。
atomic模板类的模板参数支持指针,但不表示它所指向的对象是原子类型。
原子整型可以用作计数器,布尔型可以用作开关。
CAS指令是实现无锁队列基础。
示例:

include

include // 原子类型的头文件。

using namespace std;

int main()

{

atomic a = 3; // atomic(T val) noexcept; // 转换函数。

cout << "a=" << a.load() << endl; // 读取原子变量a的值。输出:a=3

a.store(8); // 把8存储到原子变量中。

cout << "a=" << a.load() << endl; // 读取原子变量a的值。 输出:a=8

int old; // 用于存放原值。

old = a.fetch_add(5); // 把原子变量a的值与5相加,返回原值。

cout << "old = " << old <<",a = " << a.load() << endl; // 输出:old=8,a=13

old = a.fetch_sub(2); // 把原子变量a的值减2,返回原值。

cout << "old = " << old << ",a = " << a.load() << endl; // 输出:old=13,a=11

atomic ii = 3; // 原子变量

int expect = 4; // 期待值

int val = 5; // 打算存入原子变量的值

// 比较原子变量的值和预期值expect,

// 如果当两个值相等,把val存储到原子变量中;

// 如果当两个值不相等,用原子变量的值更新预期值。

// 执行存储操作时返回true,否则返回false。

bool bret = ii.compare_exchange_strong(expect, val);

cout << "bret=" << bret << endl;

cout << "ii=" << ii << endl;

cout << "expect=" << expect << endl;

}

250、可调用对象
在C++中,可以像函数一样调用的有:普通函数、类的静态成员函数、仿函数、lambda函数、类的非静态成员函数、可被转换为函数的类的对象,统称可调用对象或函数对象。

可调用对象有类型,可以用指针存储它们的地址,可以被引用(类的成员函数除外)

一、普通函数
普通函数类型可以声明函数、定义函数指针和函数引用,但是,不能定义函数的实体。

示例:

include

using namespace std;

using Fun = void (int, const string&); // 普通函数类型的别名。

Fun show; // 声明普通函数。

int main()

{

show(1, "我是一只傻傻鸟。"); // 直接调用普通函数。

void(*fp1)(int, const string&) = show; // 声明函数指针,指向普通函数。

void(&fr1)(int, const string&) = show; // 声明函数引用,引用普通函数。

fp1(2, "我是一只傻傻鸟。"); // 用函数指针调用普通函数。

fr1(3, "我是一只傻傻鸟。"); // 用函数引用调用普通函数。

Fun* fp2 = show; // 声明函数指针,指向普通函数。

Fun& fr2 = show; // 声明函数引用,引用普通函数。

fp2(4, "我是一只傻傻鸟。"); // 用函数指针调用普通函数。

fr2(5, "我是一只傻傻鸟。"); // 用函数引用调用普通函数。

}

// 定义普通函数

void show(int bh, const string& message) {

cout << "亲爱的" << bh << "," << message << endl;

}

// 以下代码是错误的,不能用函数类型定义函数的实体。

//Func show1 {

// cout << "亲爱的" << bh << "," << message << endl;

//}

二、类的静态成员函数
类的静态成员函数和普通函数本质上是一样的,把普通函数放在类中而已。

示例:

include

using namespace std;

using Fun = void (int, const string&); // 普通函数类型的别名。

struct AA // 类中有静态成员函数。

{

static void show(int bh, const string& message) {

cout << "亲爱的" << bh << "," << message << endl;

}

};

int main()

{

AA::show(1, "我是一只傻傻鸟。"); // 直接调用静态成员函数。

void(*fp1)(int, const string&) = AA::show; // 用函数指针指向静态成员函数。

void(&fr1)(int, const string&) = AA::show; // 引用静态成员函数。

fp1(2, "我是一只傻傻鸟。"); // 用函数指针调用静态成员函数。

fr1(3, "我是一只傻傻鸟。"); // 用函数引用调用静态成员函数。

Fun* fp2 = AA::show; // 用函数指针指向静态成员函数。

Fun& fr2 = AA::show; // 引用静态成员函数。

fp2(4, "我是一只傻傻鸟。"); // 用函数指针调用静态成员函数。

fr2(5, "我是一只傻傻鸟。"); // 用函数引用调用静态成员函数。

}

三、仿函数
仿函数的本质是类,调用的代码像函数。

仿函数的类型就是类的类型。

示例:

include

using namespace std;

struct BB // 仿函数。

{

void operator()(int bh, const string& message) {

cout << "亲爱的" << bh << "," << message << endl;

}

};

int main()

{

BB bb;

bb(11, "我是一只傻傻鸟。"); // 用对象调用仿函数。

BB()(12, "我是一只傻傻鸟。"); // 用匿名对象调用仿函数。

BB& br = bb; // 引用函数

br(13, "我是一只傻傻鸟。"); // 用对象的引用调用仿函数。

}

四、lambda函数
lambda函数的本质是仿函数,仿函数的本质是类。

include

using namespace std;

int main()

{

// 创建lambda对象。

auto lb = [](int bh, const string& message) {

cout << "亲爱的" << bh << "," << message << endl;

};

auto& lr = lb; // 引用lambda对象。

lb(1, "我是一只傻傻鸟。"); // 用lambda对象调用仿函数。

lr(2, "我是一只傻傻鸟。"); // 用lambda对象的引用调用仿函数。

}

五、类的非静态成员函数
类的非静态成员函数有地址,但是,只能通过类的对象才能调用它,所以,C++对它做了特别处理。

类的非静态成员函数只有指针类型,没有引用类型,不能引用。

示例:

include

using namespace std;

struct CC // 类中有普通成员函数。

{

void show(int bh, const string& message) {

cout << "亲爱的" << bh << "," << message << endl;

}

};

int main()

{

CC cc;

cc.show(14, "我是一只傻傻鸟。");

void (CC:

标签:11,const,函数,int,C++,标准,线程,string,cout
From: https://www.cnblogs.com/Gal0721/p/17724097.html

相关文章

  • Win11用微软拼音输入法,打出繁体中文字
    Win11用微软拼音输入法,打出繁体中文字https://www.bilibili.com/video/BV1yS4y1i7C5/?vd_source=9bfc54d2ed901f1eab04708cc346c2f5......
  • 【2023潇湘夜雨】WIN11_Pro_22H2.22621.2359软件选装纯净版9.22
    【系统简介】=============================================================1.本次更新母盘来自WIN11_Pro_23H2.22621.2359。2.增加部分优化方案,手工精简部分较多。3.OS版本号为22621.2359。精简系统只是为部分用户安装,个别要求高的去MSDN下。4.集成《DrvCeo-2.13.0.8》网卡版、......
  • 代码随想录算法训练营day17 | ● 110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.
    110.平衡二叉树classSolution{public:intgetHeight(TreeNode*node){if(node==NULL)return0;intleftHeight=getHeight(node->left);if(leftHeight==-1)return-1;intrightHeigh......
  • 笔记 | C++ 命名空间
    命名空间(Namespace)是C++中用于组织和管理代码的重要机制。它允许开发者将一组相关的变量、函数、类等封装在一个独立的命名空间中,以避免命名冲突和提高代码的可维护性。本文将介绍命名空间的概念、如何应用命名空间,以及它的优点和缺点。命名空间的概念在C++中,命名空间是一个逻......
  • 南方cass软件下载-南方cass7.0/9.0/11.0中文版 各个版本下载
    南方CASS9.1是款非常专业的cad图形设计软件,它是在cad的基础上二次开发的,cass必须配合cad软件才能够使用,功能非常强大,界面简洁明晰、操作方便快捷。南方CASS9.0能完美的兼容AutoCAD2000、AutoCAD2004、AutoCAD2005、AutoCAD2006以及AutoCAD2010等等的加载,全新的属性面板,更精准的查......
  • 制作基于 apline 的 jdk11 环境
    制作基于apline的jdk11环境没有搞定的方法:https://blog.csdn.net/qq_43059674/article/details/103356158https://juejin.cn/post/7082235890180816904以上两种方法都试过了,没有成功。也不想深究原因了,主要是执行到RUN命令时报错:Step4/10:ADDopenjdk-11.0.2_linux-......
  • 11-JavaScript 逻辑条件 ,if判断 ,while循环,算数运算相关的案例演示
    1、案例:猜数字设置一个1-10之间的随机数,然后输入进行猜数字,猜的大了怎么样、猜的小了怎么样、猜对了怎么样知识点:设置随机数、if判断、while循环写题思路:1.设置弹框提出问题2.定义一个随机数0-10的数组3.if判断取值的范围,在其范围内反馈的结果4.while循环,直到猜对停止......
  • C++指针和地址偏移在HotSpot VM中的应用
     在前面我们介绍过new运算符,这个操作实际上上包含了如下3个步骤:调用operatornew的标准库函数。此函数会分配一块内存空间以便函存储相应类型的实例;调用相应类的构造函数;返回一个指向该对象的指针。在第一步中,其实我们可以自己写个operatornew函数对标准库函数进行重载,通......
  • c++ 数据类型及范围
    short:\(-2^{15}\sim2^{15}-1\)unsignedshort:\(0\sim2^{16}-1\)int:\(-2^{31}\sim2^{31}-1\)unsignedint:\(0\sim2^{32}-1\)longlong:\(-2^{63}\sim2^{63}-1\)unsignedlonglong:\(0\sim2^{64}-1\)参考资料:https://www.cnblogs.com......
  • C++笔记(细碎小知识点)1
    1.内联:写在类内或外部声明inline(编译器判断是否内联,不是满足上述条件就一定内联),优点更快2.protected:派生类可以直接调用基类的protected成员3.class类内默认private,struct内默认public4.构造函数最优写法,用初始化(只有构造函数有)效率比在函数中写更高(因编译器先进行初始化再执行......