C++复习笔记21
创始人
2025-05-30 22:34:27
0

1.自动类型推导

        在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎 么给,或者类型写起来特别复杂,C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方 便。将程序中c与it的类型换成auto,程序可以通过编译,而且更加简洁。

void test01()
{mapssmap;auto it = ssmap.begin();
}

2.列表初始化

       C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定 义的类型,使用初始化列表时,可添加等号(=),也可不添加。

void test02()
{int arr[] = { 1,2,3,4,5 };vectoriv={ 1,2,3,4,5 };//c11初始化方法vectoriv1{ 1,2,3,4,5 };
}void test03()
{int* p = new int[10]{ 1,2,3,4,5,6,7,8,9,10 };
} class Point
{
public:Point(int x = 0, int y = 0) : _x(x), _y(y){}
private:int _x;int _y;
};
void test04()
{Point p{ 1, 2 };//标准库支持单个对象的列表初始化
}
template
class SeqList
{
public:SeqList(const initializer_list& list):capacity(list.size()), size(0){base = new Type[capacity];for (const auto& e : list)base[size++] = e;}
private:Type* base;size_t capacity;size_t size;
};void test05()
{SeqListsq = { 1,2,3,4,5 };
}

3.decltype类型推导

       auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。但有 时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就 无能为力。

template
T1 Add(const T1& left, const T2& right)
{return left + right;
}
       如果能用加完之后结果的实际类型作为函数的返回值类型就不会出错,但这需要程序运行完才能知道结果的实际类型,即RTTI(Run-Time Type Identifification 运行时类型识别)       decltype是根据表达式的实际类型推演出定义变量时所用的类型,比如: (1). 推演表达式类型作为变量的定义类型
int main()
{int a = 10;int b = 20;// 用decltype推演a+b的实际类型,作为定义c的类型decltype(a+b) c;cout<
void test06()
{map::iterator pos;cout << typeid(pos).name() << endl;auto pos1 = pos;decltype(pos) it;int a = 10;int b = 20;decltype(a) c;//省去了初始化步骤,萃取a的类型
}
(2). 推演函数返回值的类型
void* GetMemory(size_t size)
{return malloc(size);
}
int main()
{// 如果没有带参数,推导函数的类型cout << typeid(decltype(GetMemory)).name() << endl;// 如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数cout << typeid(decltype(GetMemory(0))).name() <

4.默认成员函数控制

        在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构 函数和&const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生 成默认版本。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带 参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程 序员可以控制是否需要编译器生成

        在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。        如果能想要限制某些默认函数的生成,C++98中,是该函数设置成private,并且不给定义,这样只要其他人想要调用就会报错。C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对 应函数的默认版本,称=delete修饰的函数为删除函数
#include
using namespace std;//单例模式不是
class Test
{
public:Test(int d):m_a(d) {}Test(const Test&) = delete;Test& operator=(const Test&) = delete;
private://Test(const Test&);int m_a;
};void test01()
{Test t(10);//Test t1(t);Test t2(20);Test te(30);
}void main()
{test01();system("pause");
}
class A
{
public:A(int a) : _a(a){}// 显式缺省构造函数,由编译器生成//A() = default;//相当于A(){}A() = delete;A(const A&) = default;//占位符?// 在类中声明,在类外定义时让编译器生 成默认赋值运算符重载A& operator=(const A& a);
private:int _a;
};
A& A::operator=(const A& a) = default;
void test07()
{A a1(10);//A a2;A a3(a1);//a2 = a1;
}
class B
{
public:B(int a) :_a(a) {}B(const B& a) = delete;//一个类不能拷贝构造
private://B(const A& a) { this->_a = a._a; }//一个类不能拷贝构造int _a;
};void test08()
{B b(1);//B b1(b);//不能拷贝构造
}
5.右值引用         C++98中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通 过指针来实现的,因此使用引用,可以提高程序的可读性。为了提高程序运行效率C++11中引入了右值引用,右值引用也是别名,但其只能对右值引用。
void test09()
{int x = 10;int& b = x;const int& a = 10;//常量用常引用int&& a = 10;//对于右值的引用
}
#include
using namespace std;void test01()
{int a = 10;const int& b = a;a = 100;//int&& c = a;//右值引用只能引用右值const int& d = 10;int&& e = 10;
}int fun(int a, int b)
{int value = a + b;return value;//无名临时空间具有常性
}void test02()
{int&& ret = fun(10,20); const int& ret1 = fun(10, 20);cout << ret << endl;/* cout << ret << endl;cout << ret << endl;*/
}void main()
{test02();system("pause");
}

左值与右值是C语言中的概念,但C标准并没有给出严格的区分方式,一般认为:可以放在=左边的,或者能够取地址的称为左值,只能放在=右边的,或者不能取地址的称为右值,但是也不一定完全正确。        因此关于左值与右值的区分不是很好区分,一般认为: 1. 普通类型的变量,因为有名字,可以取地址,都认为是左值。 2. const修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址(如果只是const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间), C++11认为其是左值。 3. 如果表达式的运行结果是一个临时变量或者对象,认为是右值。 4. 如果表达式运行结果或单个变量是一个引用则认为是左值。        总结: 1. 不能简单地通过能否放在=左侧右侧或者取地址来判断左值或者右值,要根据表达式结果或变量的性质判断,比如上述:c常量 2. 能得到引用的表达式一定能够作为引用,否则就用常引用。        C++11对右值进行了严格的区分: C语言中的纯右值,比如:a+b, 100 将亡值。比如:表达式的中间结果、函数按照值的方式进行返回。
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引用既可引用左值,也可引用右值。 C++11中右值引用:只能引用右值,一般情况不能直接引用左值
int fun(int a, int b)
{static int value = a + b;return value;
}void test10()
{const int& ret = fun(10, 20);int&& ret = fun(10, 20);
}void test11()
{int a = 10;//int&& b = a;//右值引用只能引用右值
}

6.移动语义

为了解决:浅拷贝+对象按值返回会造成空间浪费

#define _CRT_SECURE_NO_WARNINGS
#include
using namespace std;
class String
{
public:String(const char* str = ""){if (nullptr == str)str = "";_str = new char[strlen(str) + 1];strcpy(_str, str);}String(const String& s): _str(new char[strlen(s._str) + 1]){strcpy(_str, s._str);}//右值引用 的拷贝构造函数 实现移动语义String(String&& s): _str(s._str){s._str = nullptr;}String& operator=(const String& s){if (this != &s){//异常安全char* pTemp = new char[strlen(s._str) + 1];strcpy(pTemp, s._str);delete[] _str;_str = pTemp;}return *this;}String operator+(const String& s){char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];strcpy(pTemp, _str);strcpy(pTemp + strlen(_str), s._str);String strRet(pTemp);return strRet;}~String(){if (_str) delete[] _str;}
private:char* _str;
};void test01()
{String s1("hello");//String s2 = s1;//不能使用移动语义,s1不是右值String s2 = move(s1);//move将s1强转为右值,可以调用移动语义拷贝构造 s1被搬空
}void test02()
{String s1("hello");String s2("world");String s3(s1 + s2);
}void test03()
{String s1("hello world");String s2(move(s1));//s1被搬空String s3(s2);
}int main()
{test03();return 0;
}
       在operator+中:strRet在按照值返回时,必须创建一个临时对象,临时对象创建好之后,strRet就被销毁了,最后使用返回的临时对象构造s3s3构造好之后,临时对象就被销毁了。仔细观察会发现:strRet、临 时对象、s3每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完 全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大。         C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题。         因为strRet对象的生命周期在创建好临时对象后就结束了,即将亡值,C++11认为其为右值,在用strRet构造临时对象时,就会采用移动构造,即将strRet中资源转移到临时对象中。而临时对象也是右值,因此在用临时对象构造s3时,也采用移动构造,将临时对象中资源转移到s3中,整个过程,只需要创建一块堆内存即可,既省了空间,又大大提高程序运行的效率。 注意: 1. 移动构造函数的参数千万不能设置成const类型的右值引用,因为资源无法转移而导致移动语义失效。 2. 在C++11中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造。 8.右值引用左值: 按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右 。C++11中,std::move()函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功 能就是将一个左值强制转化为右值引用,然后实现移动语义

 移动语义例子:

1. 被转化的左值,其生命周期并没有随着左值的转化而改变,即std::move转化的左值变量lvalue不会被销毁。 2. STL中也有另一个move函数,就是将一个范围中的元素搬移到另一个位置。
#define _CRT_SECURE_NO_WARNINGS
#include
using namespace std;
class String
{
public:String(const char* str = ""){if (nullptr == str)str = "";_str = new char[strlen(str) + 1];strcpy(_str, str);}String(String&& s): _str(s._str){s._str = nullptr;}String(const String& s): _str(new char[strlen(s._str) + 1]){strcpy(_str, s._str);}
String& operator=(const String& s)
{if (this != &s){char* pTemp = new char[strlen(s._str) + 1];strcpy(pTemp, s._str);delete[] _str;_str = pTemp;}return *this;
}String operator+(const String& s)
{char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];strcpy(pTemp, _str);strcpy(pTemp + strlen(_str), s._str);String strRet(pTemp);return strRet;
}~String()
{if (_str) delete[] _str;
}
private:char* _str;
};class Person
{
public:Person(const char* name, const char* sex, int age): _name(name), _sex(sex), _age(age){}Person(const Person& p): _name(p._name) , _sex(p._sex), _age(p._age){}#if 0Person(Person&& p): _name(p._name), _sex(p._sex), _age(p._age){}#else//调用String 类的移动语义Person(Person&& p): _name(move(p._name)), _sex(move(p._sex)), _age(p._age){}#endifprivate:String _name;String _sex;int _age;
};
Person GetTempPerson()
{Person p("prety", "male", 18);return p;
}
int main()
{Person p(GetTempPerson());return 0;
}
       注意:以下代码是move函数的经典的误用,因为move将s1转化为右值后,在实现s2的拷贝时就会使用移动 构造,此时s1的资源就被转移到s2中,s1就成为了无效的字符。
int main()
{String s1("hello world");String s2(move(s1));String s3(s2);return 0;
}

9.完美转发

       完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数完美转发是目标函数总希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外的开销,就好像转发者不存在 一样。 所谓完美:函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相 应实参是右值,它就应该被转发为右值。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进 行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)。
#include
using namespace std;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; }
template
void PerfectForward(T&& t) 
{Fun(forward(t));//对t进行完美转发//Fun(std::forward(t)); 
}void test01()
{int a = 10;PerfectForward(a);PerfectForward(10);const int b = 20;PerfectForward(b);
}int main()
{test01();return 0;
}

10lambda表达式的存在意义:提供一个匿名的函数对象(仿函数),不用整个给出类的定义,比较简便。

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement } 1. lambda表达式各部分说明 [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来 的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用。(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起 省略mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。 ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。 注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。 因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。 捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。 [var]:表示值传递方式捕捉变量var [=]:表示值传递方式捕获所有父作用域中的变量(包括this) [&var]:表示引用传递捕捉变量var [&]:表示引用传递捕捉所有父作用域中的变量(包括this) [this]:表示值传递方式捕捉当前的this指针 注意: a. 父作用域指包含lambda函数的语句块 b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。 比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量 c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复 d. 在块作用域以外的lambda函数捕捉列表必须为空。 e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。 f. lambda表达式之间不能相互赋值,即使看起来类型相同 实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
#include
#include
#include
using namespace std;void test01()
{int array[] = { 4,1,8,5,3,7,0,9,2,6 };int n = sizeof(array) / sizeof(array[0]);// 默认按照小于比较,排出来结果是升序sort(array, array + sizeof(array) / sizeof(array[0]));//qsort();for (int i = 0; i < n; ++i)cout << array[i] <<" ";cout << endl;// 如果需要降序,需要改变元素的比较规则sort(array, array + sizeof(array) / sizeof(array[0]), greater());for (int i = 0; i < n; ++i)cout << array[i] <<" ";
}struct Goods
{string _name;double _price;
};struct Goods_Less
{bool operator()(const struct Goods& g1, const struct Goods& g2){return g1._price < g2._price;}
};struct Goods_Greater
{bool operator()(const struct Goods& g1, const struct Goods& g2){return g1._price > g2._price;}
};int main()
{Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };int n = sizeof(gds) / sizeof(gds[0]);//sort(gds, gds + n, Goods_Greater());//谓词这里传函数指针或者仿函数都可以sort(gds, gds + n, [](const struct Goods& g1,const struct Goods& g2)->bool{return g1._price > g2._price; });//lambda表达式就是一个无名的仿函数对象,底层就是仿函数对象实现的return 0;
}

示例:

lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要 直接调用,可借助auto将其赋值给一个变量。
#include
using namespace std;void test01()//lambda表达式就是用在函数对象(谓词)那里使用的,省略了类的给出)
{[]()->void {};//最基本的lanbda //匿名函数对象//[]捕获块//()参数列表//->返回值//{}函数体
}void test02()
{auto f1 = [](int a, int b)->int {return a + b; };cout << f1(10, 20) << endl;cout << typeid(f1).name() << endl;
}void test03()
{auto f1 = []{cout << "hello lambda" << endl; };//没有参数可省略(),没有返回值->可省略f1();
}void main()
{test03();system("pause");
}
#include
using namespace std;int a = 1;
int b = 2;int fun()
{return a + b;
}void test01()
{cout << fun() << endl;int x = 10;int y = 20;auto f1 = [x,y/*,d*/] {cout << a + b+x+y << endl; };//局部变量需要捕获 lambda前面是父作用域,后面是子作用域,一般对父作用域进行捕获int d = 30;f1();
}void test02()
{int x = 10;int y = 20;auto f1 = [=](int p, int q)mutable->int//实参不会改变,仅在函数内部改变形参 mutable不可以省略auto f1 = [&](int p, int q)->int//实参会改变{p = 100;q = 200;x = 300;y = 500;return p + q + x + y;};cout << f1(10,20) << endl;cout << "x=" << x << " y=" << y << endl;
}class Test
{
public:int fun(int x, int y){auto f=[this](int x, int y)->int{return x + y + m_a + m_b;};return f(x,y);}
private:int m_a=1;int m_b = 2;
};void test03()
{Test t;cout << t.fun(10, 20) << endl;
}void main()
{test03();system("pause");
}

C11线程库操作:

1. 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。 2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。 3.当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。线程函数一 般情况下可按照以下三种方式提供: 函数指针 lambda表达式 函数对象 4. thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关 联线程的状态转移给其他线程对象,转移期间不影响线程的执行。 5. 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效 采用无参构造函数构造的线程对象 线程对象的状态已经转移给其他线程对象 线程已经调用jion或者detach结束        线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参.        注意:如果是类成员函数作为线程参数时,必须将this作为线程函数参数。

        启动了一个线程后,当这个线程结束的时候,如何去回收线程所使用的资源呢?thread库给我们两种选择:join()方式join():主线程被阻塞,当新线程终止时,join()会清理相关的线程资源,然后返回,主线程再继续向下 执行,然后销毁线程对象。由于join()清理了线程的相关资源,thread对象与已销毁的线程就没有关系 了,因此一个线程对象只能使用一次join(),否则程序会崩溃detach()方式         detach():该函数被调用后,新线程与线程对象分离,不再被线程对象所表达,就不能通过线程对象控 制线程了,新线程会在后台运行,其所有权和控制权将会交给c++运行库。同时,C++运行库保证,当 线程退出时,其相关资源的能够正确的回收。 因此:线程对象销毁前,要么以jion()的方式等待线程结束,要么以detach()的方式将线程与线程对分离。
#include
#include
using namespace std;struct Test
{int a;double b;char c;
};struct Student
{char name[10];int age;
};struct Data
{int data;Test test_data;Student student_data;
};//多线程程序
void thread_fun(Data* pdt)
{for (int i = 0; i < pdt->data; ++i){cout << "This is Child Thread." << endl;}cout << pdt->student_data.name << endl;cout << pdt->student_data.age << endl;
};void test01()
{int n = 5;Test t={ 10,12.34,'A' };Student s = { "hym",18 };Data dt = { n,t,s };thread td(thread_fun,&dt);//cout << td.get_id() << endl;for (int i = 0; i < 10; ++i){cout << "This is Main Thread." << endl;}//td.detach();td.join();
}void main()
{test01();system("pause");
}
#include
#include
using namespace std;class Test 
{
public:void fun(){cout << "class  Test.fun()" << endl;}int m_a;
};struct Threadobj
{void operator()(){cout << "Hello Threadobj" << endl;}
};void thread_fun()
{cout << "thread id" << this_thread::get_id() << endl;
}void thread_fun1(int& a)
{a += 10;cout << "int fun a="<fun();
}void test01()
{cout << "Main thread id" << this_thread::get_id() << endl;thread th(thread_fun);cout << "Child id" << th.get_id() << endl;th.join();
}void test02()
{thread th[10];/*for (int i = 0; i < 10; ++i)th[i](thread_fun1);*/
}void test03()
{cout << "Main thread id" << this_thread::get_id() << endl;thread th(thread_fun);cout << "thread id" << th.get_id() << endl;th.detach();cout << "thread id" << th.get_id() << endl;
}void test04()
{thread th;thread::id id = th.get_id();cout << "id=" << id << endl;
}void test05()
{thread th(thread_fun);//函数指针th.join();thread th1([] {cout << "hello lambda thread" << endl; });//lambda表达式th1.join();Threadobj thobj;thread th2(thobj);//thread th2(Threadobj());//不能创建线程 临时对象不行 必须先创建出对象再放进来th2.join();
}void test06()
{thread th(thread_fun);//thread th1 = th;//不能拷贝构造thread th1 = move(th);
}void test07()
{//thread th;thread th(thread_fun);th.detach();if (th.joinable()){cout << "th join able." << endl;}else{cout << "th join unable" << endl;}//th.join();
}//void test08()//传值不能改变实参
//{
//	int a = 10;
//	thread th(thread_fun1, a);
//	th.join();
//	cout << "int test08 a=" << a << endl;
//}void test09()
{int a = 10;thread th(thread_fun2, &a);//传地址th.join();cout << "int test08 a=" << a << endl;
}void test10()
{int a = 10;thread th(thread_fun1, ref(a));//传引用th.join();cout << "int test08 a=" << a << endl;
}void test11()//仅传成员变量不需要传this指针
{Test t;thread th(thread_fun4,t.m_a);th.join();
}void test12()//传对象的成员函数需要传this指针
{Test t;thread th(&Test::fun,&t);//使t对象调用fun函数 不加&也可以th.join();
}void main()
{test12();system("pause");
}
#include
#include
using namespace std;
// jion()的误用一
void ThreadFunc() { cout << "ThreadFunc()" << endl; }
bool DoSomething() { return false; }
int main()
{std::thread t(ThreadFunc);if (!DoSomething())return -1;t.join();return 0;
}
#include
#include
using namespace std;
void ThreadFunc() { cout << "ThreadFunc()" << endl; }
void Test1() { throw 1; }
void Test2()
{int* p = new int[10];std::thread t(ThreadFunc);try{Test1();}catch (...){delete[] p;throw;}//t.jion();//执行不到
}

以上两段代码中join的调用位置不对,导致线程资源无法正常回收。采用jion()方式结束线程时,jion()的调用位置非常关键。为了避免该问题,可以采用RAII的方式 对线程对象进行封装,比如:

#include
#include
using namespace std;
class mythread
{
public:explicit mythread(std::thread& t) :m_t(t) {}~mythread(){if (m_t.joinable())m_t.join();}mythread(mythread const&) = delete;mythread& operator=(const mythread&) = delete;
private:std::thread& m_t;
};
void ThreadFunc() { cout << "ThreadFunc()" << endl; }
bool DoSomething() { return false; }
int main()
{thread t(ThreadFunc);mythread q(t);if (DoSomething())return -1;//t.join();return 0;
}

detech例子:

#include
#include
using namespace std;void thread_fun()
{for(;;)cout << "thread_fun()" << endl;
}void test01()
{thread th(thread_fun);th.detach();//th.join();
}void main()
{test01();system("pause");
}

按数组创建线程:

#include
#include
using namespace std;void thread_fun()
{cout << "thread_fun" <join();
}void main()
{test01();system("pause");
}

线程安全:

        多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多 个线程要修改共享数据时,就会产生很多潜在的麻烦。比如:
#include 
#include
using namespace std;
#include 
//多线程 临界(共享资源) 创建线程的目的是并发性,效率高
unsigned long sum = 0L;
mutex t;
void fun(size_t num)
{for (size_t i = 0; i < num; ++i){t.lock();sum++;t.unlock();}
}
int main()
{cout << "Before joining,sum = " << sum << std::endl;thread t1(fun, 10000000);thread t2(fun, 10000000);t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;return 0;
}
        虽然加锁可以解决,但是加锁有一个缺陷就是:只要一个线程在对sum++时,其他线程就会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。 因此C++11中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作,C++11引入的原子操作类型,使得线程间数据的同步变得非常高效。

使用原子操作可以解决:

#include 
using namespace std;
#include 
#include 
atomic_long sum={0};//初始化列表
void fun(size_t num)
{for (size_t i = 0; i < num; ++i){sum++; // 原子操作//printf("sum=%d\n", sum); //cout << "sum=" << sum << endl;}
}
int main()
{cout << "Before joining, sum = " << sum << std::endl;thread t1(fun, 1000000);thread t2(fun, 1000000);t1.join();t2.join();cout << "After joining, sum = " << sum << std::endl;return 0;
}
       在C++11中,程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的访问。注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此C++11中,原子类 型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及operator=等,为了防止意 外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。 锁机制:std::mutex lock()锁已经被占用则当前线程阻塞,trylock()锁已经被占用则当前线程退出。 try_lock_for(),try——lock_until 是两种定时用法,不到时间会阻塞,如果锁被其他线程释放,这是当前线程占用并加锁,超时则返回false;        lock_guard类模板主要是通过RAII的方式,对其管理的互斥量进行了封装,在需要加锁的地方,只需要用上述介绍的任意互斥体实例化一个lock_guard,调用构造函数成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题。        与lock_gard类似,unique_lock类模板也是采用RAII的方式对锁进行了封装,并且也是以独占所有权的方式管理mutex对象的上锁和解锁操作,即其对象之间不能发生拷贝。在构造(或移动(move)赋值)时, unique_lock 对象需要传递一个 Mutex 对象作为它的参数,新创建的 unique_lock 对象负责传入的 Mutex 对象的上锁和解锁操作。使用以上类型互斥量实例化unique_lock的对象时,自动调用构造函数上锁, unique_lock对象销毁时自动调用析构函数解锁,可以很方便的防止死锁问题。         与lock_guard不同的是,unique_lock更加的灵活,提供了更多的成员函数:         上锁/解锁操作:lock、try_lock、try_lock_for、try_lock_until和unlock修改操作:移动赋值、交换(swap:与另一个unique_lock对象互换所管理的互斥量所有权)、释放 (release:返回它所管理的互斥量对象的指针,并释放所有权) 获取属性:owns_lock(返回当前对象是否上了锁)、operator bool()(与owns_lock()的功能相同)、 mutex(返回当前unique_lock所管理的互斥量的指针)。
#include
using namespace std;
#include 
#include int number = 0;
mutex g_lock;
int ThreadProc1()
{for (int i = 0; i < 1000000; i++){//g_lock.lock();//lock_guard lock(g_lock);unique_lock lock(g_lock);++number;//cout << "thread 1[] :" << number << endl;//printf("thread 1[%d] :%d\n", i, number);//g_lock.unlock();}return 0;
}
int ThreadProc2()
{for (int i = 0; i < 1000000; i++){//g_lock.lock();//lock_guard lock(g_lock);unique_lock lock(g_lock);--number;//cout << "thread 2 :" << number << endl;//printf("thread 2[%d] :%d\n", i, number);//g_lock.unlock();}return 0;
}
int main()
{thread t1(ThreadProc1);thread t2(ThreadProc2);t1.join();t2.join();cout << "number:" << number << endl;system("pause");return 0;
}
#include
using namespace std;
template
class lock_guard
{
public:// 在构造lock_gard时,_Mtx还没有被上锁explicit lock_guard(_Mutex& _Mtx): _MyMutex(_Mtx){_MyMutex.lock();}// 在构造lock_gard时,_Mtx已经被上锁,此处不需要再上锁lock_guard(_Mutex& _Mtx, adopt_lock_t): _MyMutex(_Mtx){}~lock_guard() _NOEXCEPT{_MyMutex.unlock();}lock_guard(const lock_guard&) = delete;lock_guard& operator=(const lock_guard&) = delete;
private:_Mutex& _MyMutex;
};

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...