当前位置: 首页 > news >正文

网站建设模板成功案例虚拟主机wordpress不能用

网站建设模板成功案例,虚拟主机wordpress不能用,外贸是先做网站还是做阿里巴巴,网站建设的时候如何上传图片目录 1.列表初始化 1.1. C98中使用{}初始化的问题 1.2. 内置类型的列表初始化 1.3. 自定义类型的列表初始化 2. 变量类型推导 2.1. 为什么需要类型推导 2.2. decltype类型推导 2.2.1 为什么需要decltype 2.2.2. decltype 3. 对默认成员的控制(default、delete) 3.1. …目录 1.列表初始化 1.1. C98中使用{}初始化的问题 1.2. 内置类型的列表初始化 1.3. 自定义类型的列表初始化 2. 变量类型推导 2.1. 为什么需要类型推导 2.2. decltype类型推导 2.2.1 为什么需要decltype 2.2.2. decltype 3. 对默认成员的控制(default、delete) 3.1. 显式缺省函数 3.2. 删除默认函数 3.3. final和override 4. 右值引用 4.1. 概念 4.2. 右值与左值 4.3. 左值引用与右值引用 4.4. 左值引用的缺陷 4.5. 移动语义 4.6. 移动构造和移动赋值 4.7. 完美转发 5. lambda表达式 5.1. lambda表达式语法 6. 包装器 6.1. 为什么需要包装器 6.2. 包装器的使用 6.3. bind包装器 6.3.1. bind包装器改变参数位置 6.3.2. bind包装器绑定固定参数 7. thread线程库 7.1. thread线程库函数介绍 7.2. 线程函数参数 7.3. lock_guard与unique_lock 7.3.1. Mutex的种类 7.3.2. lock_guard 7.3.3. unique_lock 7.4. 原子性操作库(atomic) 8. 条件变量(condition_variable) 1.列表初始化 1.1. C98中使用{}初始化的问题 在C98中标准允许使用花括号{}对数组元素进行统一的列表初始值设定。比如 int array1[] {1,2,3,4,5}; int array2[5] {0}; 对于一些自定义的类型却无法使用这样的初始化。比如 vectorint v{1,2,3,4,5}; 就无法通过编译导致每次定义vector时都需要先把vector定义出来然后使用循环对其赋初始值非常不方便。C11为了兼容C语言的这种特性扩大了用大括号括起的列表(初始化列表)的使用范围使其可用于所有的内置类型和用户自定义的类型使用初始化列表时可添加等号()也可不添加。 1.2. 内置类型的列表初始化 int main() { // 内置类型变量 int x1 {10}; int x2{10}; int x3 12; int x4 {12}; int x5{12}; // 数组 int arr1[5] {1,2,3,4,5}; int arr2[]{1,2,3,4,5}; // 动态数组在C98中不支持 int* arr3 new int[5]{1,2,3,4,5}; // 标准容器 vectorint v{1,2,3,4,5}; mapint, int m{{1,1}, {2,2,},{3,3},{4,4},make_pair(5,5)}; return 0; } 注意列表初始化可以在{}之前使用等号其效果与不使用没有什么区别. 1.3. 自定义类型的列表初始化 1.标准库支持单个对象的列表初始化 class Point { public:Point(int x 0, int y 0): _x(x), _y(y){} private:int _x;int _y; }; int main() {Pointer p{ 1, 2 };return 0; } 2.多个对象的列表初始化 多个对象想要支持列表初始化需给该类(模板类)添加一个带有initializer_list类型参数的构造函数即可。注意initializer_list是系统自定义的类模板该类模板中主要有三个方法begin()、end()迭代器以及获取区间中元素个数的方法size()。 其底层可以看作是使用数组暂时将需要初始化的数据存储起来。 例如这里简单实现以下vector底层的初始化列表 #include initializer_list templateclass T class Vector { public:Vector(initializer_listT l): _capacity(l.size()), _size(0) {_array new T[_capacity];for(auto e : l)_array[_size] e; } VectorT operator(initializer_listT l) {_array new T[_capacity];size_t i 0;for (auto e : l)_array[i] e;return *this; }private:T* _array;size_t _capacity;size_t _size; }; 2. 变量类型推导 2.1. 为什么需要类型推导 在定义变量时必须先给出变量的实际类型编译器才允许定义但有些情况下可能不知道需要实际类型怎么给或者类型写起来特别复杂比如 #include map #include string int main() {short a 32670;short b 32670;// c如果给成short会造成数据丢失如果能够让编译器根据ab的结果推导c的实际类型就不会存在问题short c a b;std::mapstd::string, std::string m{{apple, 苹果}, {banana,香蕉}};// 使用迭代器遍历容器, 迭代器类型太繁琐std::mapstd::string, std::string::iterator it m.begin();while(it ! m.end()){coutit-first it-secondendl;it;}return 0; } C11中可以使用auto来根据变量初始化表达式类型推导变量的实际类型可以给程序的书写提供许多方便。将程序中c与it的类型换成auto程序可以通过编译而且更加简洁。 #include map #include string int main() {std::mapstd::string, std::string m{{apple, 苹果}, {banana,香蕉}};auto it m.begin();while(it ! m.end()){coutit-first it-secondendl;it;}return 0; } 2.2. decltype类型推导 2.2.1 为什么需要decltype auto使用的前提是必须要对auto声明的类型进行初始化否则编译器无法推导出auto的实际类型。但有时候可能需要根据表达式运行完成之后结果的类型进行推导因为编译期间代码不会运行此时auto也就无能为力。 templateclass T1, class T2 T1 Add(const T1 left, const T2 right) {return left right; }templateclass T1, class T2 auto Add(const T1 left, const T2 right) // 将返回值类型换成auto去自动推导这里就会出错 {return left right; } 如果能用加完之后结果的实际类型作为函数的返回值类型就不会出错但这需要程序运行完才能知道结果的实际类型即RTTI(Run-Time Type Identification 运行时类型识别)。 C98中确实已经支持RTTItypeid只能查看类型不能用其结果类定义类型dynamic_cast只能应用于含有虚函数的继承体系中运行时类型识别的缺陷是降低程序运行的效率。 2.2.2. decltype decltype是根据表达式的实际类型推演出定义变量时所用的类型比如 1.推演表达式类型作为变量的定义类型 int main() {int a 10;int b 20;// 用decltype推演ab的实际类型作为定义c的类型decltype(ab) c;couttypeid(c).name()endl; // typeid只能用作打印出对象的类型return 0; } 2. 推演函数返回值的类型 void* func(size_t size) {return malloc(size); } int main() {// 如果没有带参数推导函数的类型cout typeid(decltype(func)).name() endl;// 如果带参数列表推导的是函数返回值的类型,注意此处只是推演不会执行函数cout typeid(decltype(func(0))).name() endl;return 0; } 3. 对默认成员的控制(default、delete) 在C中对于空类编译器会生成一些默认的成员函数比如构造函数、拷贝构造函数、运算符重载、析构函数和和const的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了编译器将不会重新生成默认版本。有时候这样的规则可能被忘记最常见的是声明了带参数的构造函数必要时则需要定义不带参数的版本以实例化无参的对象。而且有时编译器会生成有时又不生成容易造成混乱于是C11让程序员可以控制是否需要编译器生成。3.1. 显式缺省函数 在C11中可以在默认函数定义或者声明时加上default从而显式的指示编译器生成该函数的默认版本用default修饰的函数称为显式缺省函数。 比如看以下代码 #includeiostream #includestring using namespace std;class person { public://person(){}//person() default;person(const person p) //这里由于显式的创建拷贝构造所以编译器不会默认生成构造函数因为拷贝构造也是特殊的构造函数{_age p._age;_name p._name;} private:int _age;string _name; };int main() {person p1; // 由于编译器没有生成默认构造函数所以这里在定义对象p1时会找不到默认的构造函数导致出错person p2 p1; return 0; } 所以这时如果不想显式的写出构造函数就可以使用default这样编译器就会认为并没有默认构造函数就会自动生成。 person() default; 3.2. 删除默认函数 如果能想要限制某些默认函数的生成在C98中是该函数设置成private并且不给定义这样只要其他人想要调用就会报错。在C11中更简单只需在该函数声明加上delete即可该语法指示编译器不生成对应函数的默认版本称delete修饰的函数为删除函数。 例如不想让一个类对象进行拷贝构造 class person { public:person(int age 10, string name edward):_age(age),_name(name){}person(const person p) delete; // C11做法使用delete关键字private:int _age;string _name;person(const person p); // C98做法将拷贝构造私有并且只声明不实现 };int main() {person p1;return 0; } 3.3. final和override 这两个关键字也是C11新增的但是其实我们在学习继承和多态的时候已经见过了这里不再过多描述。 final修饰类使该类不能被继承修饰虚函数该虚函数不能被重写。 override检查派生类虚函数是否重写了基类某个虚函数如果没有重写编译报错 4. 右值引用 4.1. 概念 C98中提出了引用的概念引用即别名引用变量与其引用实体公共同一块内存空间而引用的底层是通过指针来实现的因此使用引用可以提高程序的可读性。 为了提高程序运行效率C11中引入了右值引用右值引用也是别名但其只能对右值引用。 int fun(int n) {return n - 1; }int main() {int x 1, y 2;int a 10; // 引用常量int b x y; // 引用表达式int c fun(2); // 引用函数返回值return 0; } 4.2. 右值与左值 左值与右值是C语言中的概念但C标准并没有给出严格的区分方式一般认为可以放在左边的或者能够取地址的称为左值只能放在右边的或者不能取地址的称为右值但是也不一定完全正确。 关于左值与右值的区分不是很好区分一般认为 普通类型的变量因为有名字可以取地址都认为是左值。const修饰的常量不可修改只读类型的理论应该按照右值对待但因为其可以取地址(如果只是const类型常量的定义编译器不给其开辟空间如果对该常量取地址时编译器才为其开辟空间)C11认为其是左值。如果表达式的运行结果是一个临时变量或者对象认为是右值。如果表达式运行结果或单个变量是一个引用则认为是左值。总结 不能简单地通过能否放在左侧右侧或者取地址来判断左值或者右值要根据表达式结果或变量的性质判断比如上述c常量能得到引用的表达式一定能够作为引用否则就用常引用。C11对右值进行了严格的区分C语言中的纯右值比如ab, 100将亡值。比如表达式的中间结果、函数按照值的方式进行返回。4.3. 左值引用与右值引用 那么这里有一个问题左值引用能否引用右值右值引用能否引用左值 int main() {int x 1, y 2;int ra 10; // 左值引用引用右值int rb x y;int rra x; // 右值引用引用左指 int rrb y;return 0; } 这里通过编译器可以看到是会报错的 注意 普通引用只能引用左值不能引用右值const引用既可引用左值也可引用右值。C11中右值引用只能引用右值一般情况不能直接引用左值可以通过move将左值变成右值然后引用。 int main() {int x 1, y 2;const int ra x y;int rra std::move(x); // move将x变成右值cout ra endl;cout rra endl;return 0; } 4.4. 左值引用的缺陷 当我们在函数中使用引用传传参时几乎是没有任何问题的但是当函数中的返回值使用引用返回时可能就会出现问题。 比如我们使用string的实现作为例子 namespace wt {class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;}string(const char* str ):_size(strlen(str)), _capacity(_size){cout string(char* str) endl;_str new char[_capacity 1];strcpy(_str, str);}// s1.swap(s2)void swap(string s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string s):_str(nullptr), _size(0), _capacity(0){cout string(const string s) -- 深拷贝 endl;string tmp(s._str);swap(tmp);}string operator(const string s){cout string operator(string s) -- 深拷贝 endl;string tmp(s);swap(tmp);return *this;}~string(){//cout ~string() endl;delete[] _str;_str nullptr;}char operator[](size_t pos){assert(pos _size);return _str[pos];}void reserve(size_t n){if (n _capacity){char* tmp new char[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}}void push_back(char ch){if (_size _capacity){size_t newcapacity _capacity 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] ch;_size;_str[_size] \0;}//string operator(char ch)string operator(char ch){push_back(ch);return *this;}string operator(char ch){string tmp(*this);push_back(ch);return tmp;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};wt::string to_string(int value){wt::string str;while (value){int val value % 10;str (0 val);value / 10;}reverse(str.begin(), str.end());return str;} }// 场景1 // 左值引用做参数基本完美的解决所有问题 void func1(wt::string s) {}void func2(const wt::string s) {}// 场景2 // 左值引用做返回值只能解决部分问题 // wt::string operator(char ch) //解决了 // wt::string operator(char ch) // 没有解决不能使用引用返回 以前在学习拷贝构造时我们学习过当上面的operator这种情况如果返回值是一个自定义类型由于返回的是一个右值所以在处理该函数的作用域之后该右值会被立即销毁。所以在返回之前会调用一次拷贝构造将返回值临时保存在调用该函数的栈帧中然后再将临时值拷贝构造给接收该函数的对象。这里编译器会优化为一次拷贝构造 这里会发现返回值、拷贝构造的临时对象、ans每个对象创建后都有自己的独立的空间而且每个空间中的内容也完全相同相当于创建了三个内容完全相同的对象对于空间是一种浪费程序的效率也会降低而且临时对象确实作用不是很大 。 4.5. 移动语义 C11提出了移动语义概念即将一个对象中资源移动到另一个对象中的方式可以有效缓解该问题。 // 移动构造string(string s) //创建一个空string对象:_str(nullptr), _size(0), _capacity(0){cout string(string s) -- 资源转移 endl;this-swap(s); // 将s内部的资源转移给空对象由于s是右值后面会被释放所以这里不会对它造成影响}// 移动赋值string operator(string s){cout string operator(string s) -- 转移资源 endl;swap(s);return *this;} int main() {wt::string s1;wt::string s2 wt::to_string(1234);cout endl;s1 wt::to_string(1234);return 0; } 这里我们使用to_string(1234)函数的返回值去初始化s2和赋值给s1如果没有移动构造和移动赋值那么肯定是会去深拷贝的 如果有移动构造和移动赋值则不会 有了移动语义应该慎用move因为如果将一个左值给move了那么他内部的资源就可能被转移走了这时再去使用这个左值对象就可能出现问题。 4.6. 移动构造和移动赋值 原来C类中有6个默认成员函数 构造函数析构函数拷贝构造函数拷贝赋值重载取地址重载const 取地址重载 最后重要的是前4个后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。 C11新增了两个:移动构造函数和移动赋值运算符重载。 针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下: 如果自己没有实现移动构造函数且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数对于内置类型成员会执行逐成员按字节拷贝自定义类型成员则需要看这个成员是否实现移动构造如果实现了就调用移动构造没有实现就调用拷贝构造。如果自己没有实现移动赋值重载函数且没有实现析构函数、拷贝构造、考贝赋值重载中的任意一个那么编译器会白动生成一个默认移动赋值。默认生成的移动构造函数对于内置类型成员会执行逐成员按字节拷贝白定义类型成员则需要看这个成员是否实现移动赋值如果实现了就调用移动赋值没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)如果你提供了移动构造或者移动赋值编译器不会自动提供拷贝构造和拷贝赋值4.7. 完美转发 完美转发是指在函数模板中完全依照模板的参数的类型将参数传递给函数模板中调用的另外一个函数。 假如有以下场景 void Fun(int x) { cout 左值引用 endl; } void Fun(const int x) { cout const 左值引用 endl; }void Fun(int x) { cout 右值引用 endl; } void Fun(const int x) { cout const 右值引用 endl; } templatetypename T void PerfectForward(T t) {Fun(t); }int main() {PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0; } 根据上面的代码产生的结果我们会发现为什么给函数中传入的右值再传入Fun函数后全部匹配到了左值引用的函数 模板中的不代表右值引用而是万能引用其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力但是引用类型的唯一作用就是限制了接收的类型后续使用中都退化成了左值由于传入的是右值所以PerfectForward函数在接收他时会创建一块临时的空间保存它这时这个右值就可以被取地址了所以它的属性就变成了右值 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发 Fun(std::forwardT(t)); //forwardT 完美转发将参数按照传递给转发函数的实际类型转给目标函数而不产生额外的开销 所谓完美函数模板在向其他函数传递自身形参时如果相应实参是左值它就应该被转发为左值如果相应实参是右值它就应该被转发为右值。 5. lambda表达式 从C语言的函数指针到C98的仿函数在有些时候使用其实很不方便特别是函数指针所以C11中添加了lambda表达式。 5.1. lambda表达式语法 lambda表达式书写格式[capture-list] (parameters) mutable - return-type { statement } lambda表达式各部分说明 [capture-list] : 捕捉列表该列表总是出现在lambda函数的开始位置编译器根据[]来判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用。 (parameters)参数列表。与普通函数的参数列表一致如果不需要参数传递则可以连同()一起省略 mutable默认情况下lambda函数总是一个const函数mutable可以取消其常量性。使用该修饰符时参数列表不可省略(即使参数为空)。 -returntype返回值类型。用追踪返回类型形式声明函数的返回值类型没有返回值时此部分可省略。返回值类型明确情况下也可省略由编译器对返回类型进行推导。 {statement}函数体。在该函数体内除了可以使用其参数外还可以使用所有捕获到的变量。 注意 在lambda函数定义中参数列表和返回值类型都是可选部分而捕捉列表和函数体可以为空。 因此C11中最简单的lambda函数为[]{}; 该lambda函数不能做任何事情。 int main() {// 最简单的lambda表达式, 该lambda表达式没有任何意义[]{};// 省略参数列表和返回值类型返回值类型由编译器推导为intint a 3, b 4;[]{return a 3; };// 省略了返回值类型无返回值类型auto fun1 [](int c){b a c; };fun1(10)cout a bendl;// 各部分都很完善的lambda函数auto fun2 [, b](int c)-int{return b a c; };coutfun2(10)endl;// 赋值捕捉xint x 10;auto add_x [x](int a) mutable { x * 2; return a x; };cout add_x(10) endl;return 0; } 通过上述例子可以看出lambda表达式实际上可以理解为无名函数该函数无法直接调用如果想要直接调用可借助auto将其赋值给一个变量。 捕获列表说明 捕捉列表描述了上下文中那些数据可以被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()。 6. 包装器 6.1. 为什么需要包装器 function包装器 也叫作适配器。C中的function本质是一个类模板也是一个包装器。 func可能是什么呢那么func可能是函数名函数指针函数对象(仿函数对象)也有可能是lamber表达式对象所以这些都是可调用的类型如此丰富的类型可能会导致模板的效率低下! 为什么呢 templateclass F, class T T useF(F f, T x) {static int count 0;cout count: count endl;cout count: count endl;return f(x); }double f(double i) {return i / 2; }struct Functor {double operator()(double d){return d / 3;} };int main() {// 函数名cout useF(f, 22.22) endl;// 函数对象cout useF(Functor(), 33.33) endl; //匿名对象// lamber表达式cout useF([](double d)-double { return d / 4; }, 44.44) endl;return 0; } 由于函数指针、仿函数、lambda表达式是不同的类型因此useF函数会被实例化出三份三次调用useF函数所打印count的地址也是不同的。但实际这里根本没有必要实例化出三份useF函数因为三次调用useF函数时传入的可调用对象虽然是不同类型的但这三个可调用对象的返回值和形参类型都是相同的。 使用包装器可以解决这里的问题 template class T function; template class Ret, class... Args class functionRet(Args...); 模板参数说明 Ret 被包装的可调用对象的返回值类型。Args... 被包装的可调用对象的形参类型。6.2. 包装器的使用 function包装器可以对可调用对象进行包装包括函数指针函数名、仿函数函数对象、lambda表达式、类的成员函数 templateclass F, class T T useF(F f, T x) {static int count 0;cout count: count endl;cout count: count endl;return f(x); }double f(double i) {return i / 2; }struct Functor {double operator()(double d){return d / 3;} };int main() {//函数名functiondouble(double) func1 f;cout useF(func1, 22.22) endl;//函数对象functiondouble(double) func2 Functor();cout useF(func2, 33.33) endl;//lambda表达式functiondouble(double) func3 [](double d)-double {return d / 4; };cout useF(func3, 44.44) endl;return 0; } 用包装器分别对着三个可调用对象进行包装然后再用这三个包装后的可调用对象来调用useF函数这时就只会实例化出一份useF函数。 根本原因就是因为包装后这三个可调用对象都是相同的function类型因此最终只会实例化出一份useF函数该函数的第一个模板参数的类型就是function类型的。 当包装器包装类的非静态成员函数时需要额外注意 class Plus { public:static int plusi(int a, int b){return a b;}double plusd(double a, double b){return a b;} }; int main() {functiondouble(Plus, double, double) func5 Plus::plusd; //不可省略cout func5(Plus(), 1.1, 2.2) endl; // 需要传入类对象去调用类中的非静态成员函数return 0; } 6.3. bind包装器 bind也是一种函数包装器也叫做适配器。它可以接受一个可调用对象生成一个新的可调用对象来“适应”原对象的参数列表。 bind函数模板的原型 template class Fn, class... Args bind(Fn fn, Args... args);template class Ret, class Fn, class... Args bind(Fn fn, Args... args); fn : 可调用对象。args... 要绑定的参数列表值或占位符。6.3.1. bind包装器改变参数位置 int Plus(int a, int b) {return a - b; }int main() {functionint(int, int) func bind(Plus, placeholders::_2, placeholders::_1);// 将参数1与参数2交换位置cout func(1, 2) endl; //1return 0; } 绑定时第一个参数传入函数指针这个可调用对象但后续传入的要绑定的参数列表依次是placeholders::2和placeholders::1表示后续调用新生成的可调用对象时传入的第一个参数传给placeholders::2传入的第二个参数传给placeholders::1。 6.3.2. bind包装器绑定固定参数 int Plus(int a, int b) {return a b; }int main() {//绑定固定参数functionint(int) func bind(Plus, placeholders::_1, 10);cout func(2) endl; //12return 0; } 想把Plus函数的第二个参数固定绑定为10可以在绑定时将参数列表的placeholders::_2设置为10此时调用绑定后新生成的可调用对象时就只需要传入一个参数它会将该值与10相加后的结果进行返回bind包装器的意义 将一个函数的某些参数绑定为固定的值让我们在调用时可以不用传递某些参数。可以对函数参数的顺序进行灵活调整。7. thread线程库 7.1. thread线程库函数介绍 在C11之前涉及到多线程问题都是和平台相关的比如windows和linux下各有自己的接口这使得代码的可移植性比较差。 C11中最重要的特性就是对线程进行支持了使得C在并行编程时不需要依赖第三方库而且在原子操作中还引入了原子类的概念。 要使用标准库中的线程必须包含 thread 头文件。 函数名 功能 thread() 构造一个线程对象没有关联任何线程函数即没有启动任何线程 thread(fn, args1, args2, ...) 构造一个线程对象并关联线程函数fnargs1args2...为线程函数的参数 get_id() 获取线程id jionable() 线程是否还在执行joinable代表的是一个正在执行中的线程。 jion() 该函数调用后会阻塞住线程当该线程结束后主线程继续执行 detach() 在创建线程对象后马上调用用于把被创建线程与线程对象分离开分离的线程 变为后台线程创建的线程的死活就与主线程无关 注意 线程是操作系统中的一个概念线程对象可以关联一个线程用来控制线程以及获取线程的状态。当创建一个线程对象后没有提供线程函数该对象实际没有对应任何线程 。 例如 #includeiostream #includethread using namespace std;void func(int n) {cout this_thread::get_id() endl; //打印该线程的idfor (int i 0; i n; i){cout i endl;} }int main() {thread t1(func, 10); //创建线程//thread(func, 10).detach(); //创建匿名线程 注意匿名线程必须在创建时将线程分离因为后面会找不到t1.join(); //线程等待// this_thread::sleep_for(std::chrono::seconds(3)); //使当前线程休眠return 0; } 其实这里线程的创建的方法与前面linux中学习的类似只是C11中用对象封装了使用起来更加方便了。 get_id()的返回值类型为id类型id类型实际为std::thread命名空间下封装的一个类(因为Windows和Linux下对线程id处理的方式不同C中为了方便跨平台的使用所以这样处理)该类中包含了一个结构体 // vs下查看 typedef struct { /* thread identifier for Win32 */void *_Hnd; /* Win32 HANDLE */unsigned int _Id; } _Thrd_imp_t; 当创建一个线程对象后并且给线程关联线程函数该线程就被启动与主线程一起运行。线程函数一般情况下可按照以下三种方式提供 函数指针 lambda表达式 函数对象例如 class add { public:int operator()(int x, int y){return x y;} };int func(int x, int y) {return x y; }int main() {int a 10, b 20;thread t1(func, a, b); // 函数指针thread t2([](int, int)-int {return a b; }, a, b); // lambda表达式functionintint,int A add();thread t3(A,a,b); // 函数对象t1.join();t2.join();t3.join();return 0; } thread类是防拷贝的不允许拷贝构造以及赋值但是可以移动构造和移动赋值即将一个线程对象关联线程的状态转移给其他线程对象转移期间不意向线程的执行。 可以通过jionable()函数判断线程是否是有效的如果是以下任意情况则线程无效 1.采用无参构造函数构造的线程对象 2.线程对象的状态已经转移给其他线程对象 3.线程已经调用jion或者detach结束 面试题并发与并行的区别 并发 当存在多个线程时若系统仅有一个CPU则根本不可能真正地同时进行一个以上的线程系统只能把CPU的运行时间划分为若干个时间段再将时间段分配给各个线程。在一个线程在其时间段执行时其余线程处于挂起状。这种方式我们称之为并发。并行 若系统拥有一个以上CPU时则存在多个线程时可并行执行。当一个CPU执行一个线程时另一个CPU可以执行另一个线程两个线程互不抢占CPU资源可以同时进行。这种方式我们称之为并行。区别 并发和并行是即相似又有区别的两个概念并行是指两个或者多个事件在同一时刻发生而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下并发性是指在一段时间内宏观上有多个程序在同时运行但在单处理机系统中每一时刻却仅能有一道程序执行故微观上这些程序只能是分时地交替执行。7.2. 线程函数参数 线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的因此即使线程参数为引用类型在线程中修改后也不能修改外部实参因为其实际引用的是线程栈中的拷贝而不是外部实参。 当然还有一种方法能够改变参数的值那就是指针 注意如果是类成员函数作为线程参数时必须将this作为线程函数参数。 class A { public:int add(int x, int y){cout x y endl;return x y;} };int main() {A a;// 传入顺序 线程函数实例化类指针函数参数thread t1(A::add, a, 10, 20);t1.join();return 0; } 7.3. lock_guard与unique_lock 在多线程环境下如果想要保证某个变量的安全性只要将其设置成对应的原子类型即可即高效又不容易出现死锁问题。但是有些情况下我们可能需要保证一段代码的安全性那么就只能通过锁的方式来进行控制。 比如两个线程同时对一个变量进行操作 int x 0;void add(int n) {for (int i 0; i n; i){x;cout this_thread::get_id() x endl;} }int main() {thread t1(add, 10000);thread t2(add, 10000);t1.join();t2.join();return 0; } 上面的代码两个线程各自对x加了10000次但是从结果中可以发现x最后的值只有1992所以这其中一定出现了线程安全问题。 所以这里需要进行加锁操作C11中也新增了加锁的库 那么这里问题来了这里的加锁应该加载for循环的里面还是外面 答案是外面。虽然加载外面看起来两个线程就是串行运行了但是针对这里的实际问题而言的操作是非常快的如果锁加在循环里面会导致两个线程频繁的去竞争锁和释放锁频繁的去切换上下文导致对资源的消耗非常大。 int x 0; mutex mtx; void add(int n) {mtx.lock();for (int i 0; i n; i){//mtx.lock();x;cout this_thread::get_id() x endl;//mtx.unlock();}mtx.unlock(); }int main() {thread t1(add, 10000);thread t2(add, 10000);t1.join();t2.join();return 0; } 7.3.1. Mutex的种类 上述代码的缺陷锁控制不好时可能会造成死锁最常见的比如在锁中间代码返回或者在锁的范围内抛异常。因此C11采用RAII的方式对锁进行了封装即lock_guard和unique_lock。 在C11中Mutex总共包了四个互斥量的种类 mutex: C11提供的最基本的互斥量该类的对象之间不能拷贝也不能进行移动。mutex最常用的三个函数: 函数名 函数功能 lock() 上锁锁住互斥量 unlock() 解锁释放对互斥量的所有权 try_lock() 尝试锁住互斥量如果互斥量被其他线程占有则当前线程也不会被阻塞 注意线程函数调用lock()时可能会发生以下三种情况 如果该互斥量当前没有被锁住则调用线程将该互斥量锁住直到调用 unlock之前该线程一直拥有该锁如果当前互斥量被其他线程锁住则当前的调用线程被阻塞住如果当前互斥量被当前调用线程锁住则会产生死锁(deadlock)线程函数调用try_lock()时可能会发生以下三种情况 如果当前互斥量没有被其他线程占有则该线程锁住互斥量直到该线程调用 unlock 释放互斥量如果当前互斥量被其他线程锁住则当前调用线程返回 false而并不会被阻塞掉如果当前互斥量被当前调用线程锁住则会产生死锁(deadlock)recursive_mutex : 其允许同一个线程对互斥量多次上锁即递归上锁来获得对互斥量对象的多层所有权释放互斥量时需要调用与该锁层次深度相同次数的 unlock()除此之外std::recursive_mutex 的特性和std::mutex 大致相同。 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。 7.3.2. lock_guard std::lock_gurad 是 C11 中定义的模板类。定义如下 : templateclass _Mutex 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; }; lock_guard类模板主要是通过RAII的方式对其管理的互斥量进行了封装在需要加锁的地方只需要用上述介绍的任意互斥体实例化一个lock_guard调用构造函数成功上锁出作用域前lock_guard对象要被销毁调用析构函数自动解锁可以有效避免死锁问题。 lock_guard的缺陷太单一用户没有办法对该锁进行控制因此C11又提供了unique_lock。 7.3.3. unique_lock 与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所管理的互斥量的指针)。 7.4. 原子性操作库(atomic) 多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的那么没问题因为只读操作不会影响到数据更不会涉及对数据的修改所以所有线程都会获得同样的数据。但是当一个或多个线程要修改共享数据时就会产生很多潜在的麻烦 。 所谓原子操作即不可被中断的一个或一系列操作C11引入的原子操作类型使得线程间数据的同步变得非常高效 。 对于上面的问题两个线程同时对一个变量进行操作这里就可以用到原子性操作 #includeatomic atomicint x 0; void add(int n) {for (int i 0; i n; i){x; // 原子操作cout this_thread::get_id() x endl;} }int main() {thread t1(add, 10000);thread t2(add, 10000);t1.join();t2.join();return 0; } 在C11中程序员不需要对原子类型变量进行加锁解锁操作线程能够对原子类型变量互斥的访问。 更为普遍的程序员可以使用atomic类模板定义出需要的任意原子类型。 atmoicT t; // 声明一个类型为T的原子类型变量t 注意原子类型通常属于资源型数据多个线程只能访问单个原子类型的拷贝因此在C11中原子类型只能从其模板参数中进行构造不允许原子类型进行拷贝构造、移动构造以及operator等为了防止意外标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。 #include atomic int main() {atomicint a1(0);//atomicint a2(a1); // 编译失败atomicint a2(0);//a2 a1; // 编译失败return 0; } 8. 条件变量(condition_variable) 在学习Linux时我们学到过条件变量在C11中也增加了条件变量。 问题如果实现一个线程打印偶数一个线程打印奇数且两个线程交替打印 如果我们使用前面学过的加锁来试一试 int main() {int end 100;int i 0;mutex mtx;thread t1([end, i, mtx] {while (i end){unique_lockmutex lock(mtx);this_thread::sleep_for(std::chrono::milliseconds(100));cout this_thread::get_id() - i endl;i;}});thread t2([end, i, mtx] {while (i end){unique_lockmutex lock(mtx);this_thread::sleep_for(std::chrono::milliseconds(100));cout this_thread::get_id() - i endl;i;}});t1.join();t2.join();return 0; } 通过结果我们可以看到上面的方法根本不能实现交替打印奇偶数甚至一个线程打印了多次。 所以这里可以使用互斥锁条件变量解决条件变量可以因为一个条件的不满足而使线程陷入休眠状态并且释放已经申请到的锁直到条件满足才会唤醒线程。 注意Predicate是一个可调用的对象或函数他的结果会决定线程是否进入等待具体实现为下图所示若while条件成立会去调用wait函数。 这里条件变量停止等待的条件时pred为真。 #includecondition_variable int main() {int end 100;int i 0;mutex mtx;condition_variable cv;bool flag false;//打印偶数thread t1([end, i, mtx,cv,flag] {while (i end){unique_lockmutex lock(mtx);// flagfalse,返回true,t1不会等待cv.wait(lock, [flag] {return !flag; });this_thread::sleep_for(std::chrono::milliseconds(100));cout this_thread::get_id() - i endl;i;flag true;cv.notify_one();}});//打印奇数thread t2([end, i, mtx, cv, flag] {while (i end){unique_lockmutex lock(mtx);// flag false返回falset2会等待如果申请到了锁也会释放cv.wait(lock, [flag] {return flag; });this_thread::sleep_for(std::chrono::milliseconds(100));cout this_thread::get_id() - i endl;i;flag false;cv.notify_one();}});t1.join();t2.join();return 0; }
文章转载自:
http://www.morning.gmmyn.cn.gov.cn.gmmyn.cn
http://www.morning.nngq.cn.gov.cn.nngq.cn
http://www.morning.wgcng.cn.gov.cn.wgcng.cn
http://www.morning.yqlrq.cn.gov.cn.yqlrq.cn
http://www.morning.xfxqj.cn.gov.cn.xfxqj.cn
http://www.morning.ktblf.cn.gov.cn.ktblf.cn
http://www.morning.kztts.cn.gov.cn.kztts.cn
http://www.morning.nkjjp.cn.gov.cn.nkjjp.cn
http://www.morning.qkdcb.cn.gov.cn.qkdcb.cn
http://www.morning.lmxzw.cn.gov.cn.lmxzw.cn
http://www.morning.xlxmy.cn.gov.cn.xlxmy.cn
http://www.morning.dybth.cn.gov.cn.dybth.cn
http://www.morning.zrbpx.cn.gov.cn.zrbpx.cn
http://www.morning.kmqlf.cn.gov.cn.kmqlf.cn
http://www.morning.fdsbs.cn.gov.cn.fdsbs.cn
http://www.morning.cyysq.cn.gov.cn.cyysq.cn
http://www.morning.ffksr.cn.gov.cn.ffksr.cn
http://www.morning.yrjkp.cn.gov.cn.yrjkp.cn
http://www.morning.fxpyt.cn.gov.cn.fxpyt.cn
http://www.morning.glkhx.cn.gov.cn.glkhx.cn
http://www.morning.sogou66.cn.gov.cn.sogou66.cn
http://www.morning.bryyb.cn.gov.cn.bryyb.cn
http://www.morning.lxqyf.cn.gov.cn.lxqyf.cn
http://www.morning.hwljx.cn.gov.cn.hwljx.cn
http://www.morning.xhklb.cn.gov.cn.xhklb.cn
http://www.morning.mpbgy.cn.gov.cn.mpbgy.cn
http://www.morning.pjwfs.cn.gov.cn.pjwfs.cn
http://www.morning.dmtbs.cn.gov.cn.dmtbs.cn
http://www.morning.tgbx.cn.gov.cn.tgbx.cn
http://www.morning.xnlj.cn.gov.cn.xnlj.cn
http://www.morning.qrmry.cn.gov.cn.qrmry.cn
http://www.morning.nhgkm.cn.gov.cn.nhgkm.cn
http://www.morning.jhzct.cn.gov.cn.jhzct.cn
http://www.morning.pgkpt.cn.gov.cn.pgkpt.cn
http://www.morning.easiuse.com.gov.cn.easiuse.com
http://www.morning.dlgjdg.cn.gov.cn.dlgjdg.cn
http://www.morning.lmxzw.cn.gov.cn.lmxzw.cn
http://www.morning.qzzmc.cn.gov.cn.qzzmc.cn
http://www.morning.gskzy.cn.gov.cn.gskzy.cn
http://www.morning.gjzwj.cn.gov.cn.gjzwj.cn
http://www.morning.qxdrw.cn.gov.cn.qxdrw.cn
http://www.morning.fjtnh.cn.gov.cn.fjtnh.cn
http://www.morning.zycll.cn.gov.cn.zycll.cn
http://www.morning.gtdf.cn.gov.cn.gtdf.cn
http://www.morning.kwnbd.cn.gov.cn.kwnbd.cn
http://www.morning.haibuli.com.gov.cn.haibuli.com
http://www.morning.qshxh.cn.gov.cn.qshxh.cn
http://www.morning.0dirty.cn.gov.cn.0dirty.cn
http://www.morning.bfycr.cn.gov.cn.bfycr.cn
http://www.morning.dbxss.cn.gov.cn.dbxss.cn
http://www.morning.rywr.cn.gov.cn.rywr.cn
http://www.morning.kkwgg.cn.gov.cn.kkwgg.cn
http://www.morning.jzykq.cn.gov.cn.jzykq.cn
http://www.morning.dgsr.cn.gov.cn.dgsr.cn
http://www.morning.ctxt.cn.gov.cn.ctxt.cn
http://www.morning.gcqs.cn.gov.cn.gcqs.cn
http://www.morning.czgfn.cn.gov.cn.czgfn.cn
http://www.morning.rkzk.cn.gov.cn.rkzk.cn
http://www.morning.pbtrx.cn.gov.cn.pbtrx.cn
http://www.morning.rtbx.cn.gov.cn.rtbx.cn
http://www.morning.bpmdg.cn.gov.cn.bpmdg.cn
http://www.morning.rgrz.cn.gov.cn.rgrz.cn
http://www.morning.tsnq.cn.gov.cn.tsnq.cn
http://www.morning.bgbnc.cn.gov.cn.bgbnc.cn
http://www.morning.cwzzr.cn.gov.cn.cwzzr.cn
http://www.morning.nwclg.cn.gov.cn.nwclg.cn
http://www.morning.rdnpg.cn.gov.cn.rdnpg.cn
http://www.morning.qtbnm.cn.gov.cn.qtbnm.cn
http://www.morning.mbrbg.cn.gov.cn.mbrbg.cn
http://www.morning.zxrtt.cn.gov.cn.zxrtt.cn
http://www.morning.zrgx.cn.gov.cn.zrgx.cn
http://www.morning.xnltz.cn.gov.cn.xnltz.cn
http://www.morning.pwmm.cn.gov.cn.pwmm.cn
http://www.morning.mmxnb.cn.gov.cn.mmxnb.cn
http://www.morning.jklns.cn.gov.cn.jklns.cn
http://www.morning.bdqpl.cn.gov.cn.bdqpl.cn
http://www.morning.lqytk.cn.gov.cn.lqytk.cn
http://www.morning.rnwt.cn.gov.cn.rnwt.cn
http://www.morning.qbpqw.cn.gov.cn.qbpqw.cn
http://www.morning.qddtd.cn.gov.cn.qddtd.cn
http://www.tj-hxxt.cn/news/237485.html

相关文章:

  • wordpress设置多域名多站点手机网站底部导航菜单
  • 湖北网站建设营销qqwordpress 多站 列表
  • 国外平面设计师常看的网站黄骅招聘信息最新2022
  • 在线免费源码资源源码站网页制作总结心得
  • 郑州网站建设及托管百度seo优化是做什么的
  • 怎么登陆公司网站的后台营销的网站
  • 北京正规网站建设调整阜阳网站是
  • asp做网站技术怎样海口建设网站建设
  • 重庆黔江做防溺水的网站东莞网络营销班
  • 自己想做网站怎么做网站开发语音
  • gta5办公室网站正在建设黑龙江省高速公路建设局网站
  • 合肥网站建设市场分析企业微信app下载安装官网
  • dedecms 网站名称做微信网站
  • 工信部网站备案号查询网络安装公司
  • 做网站多少钱西宁君博相约泰安58同城招聘网找工作招聘信息
  • 网站开发代码规范百度怎么搜索关键词
  • 做网站的公司现在还赚钱吗企业在线查询系统
  • 做一个推广网站大概多少钱成功网站运营案例
  • 北京网站建设推荐安徽秒搜科技阿里巴巴网站建设过程
  • 安康网站制作网站建设上海网站建设
  • 网站建设需要哪种人才高新区建设局网站
  • 在技校计算机网站建设dw网页设计官网
  • 佛山新网站建设如何网站搭建工作室加盟
  • 郑州网站建设市场重庆网站制作福州
  • 网站建设最好的公司排名河北网站备案 多长时间通过
  • 电子商务网站计划书建设网站网站多少钱
  • 成都网站海口网站建设wordpress 自动上传插件
  • 网站恶意镜像网站优秀设计方案
  • 淘客怎么建网站做推广微信分销系统软件开发
  • 微网站的好处网站建设与管理(第2版)