《C++程序设计教程(第2版)》教学ppt课件—10-C++11新特性

上传人:风*** 文档编号:240570583 上传时间:2024-04-17 格式:PPTX 页数:177 大小:7.58MB
收藏 版权申诉 举报 下载
《C++程序设计教程(第2版)》教学ppt课件—10-C++11新特性_第1页
第1页 / 共177页
《C++程序设计教程(第2版)》教学ppt课件—10-C++11新特性_第2页
第2页 / 共177页
《C++程序设计教程(第2版)》教学ppt课件—10-C++11新特性_第3页
第3页 / 共177页
资源描述:

《《C++程序设计教程(第2版)》教学ppt课件—10-C++11新特性》由会员分享,可在线阅读,更多相关《《C++程序设计教程(第2版)》教学ppt课件—10-C++11新特性(177页珍藏版)》请在装配图网上搜索。

1、10.1.1关键字C+11新增了很多关键字,这些关键字都各有不同的用途,有的关键字功能很复杂,可以用在编程的各个方面。通过使用有特殊功能的关键字,可以极大的缩减代码量。10.1.1关键字1.auto在C+11标准之前,auto关键字已经存在,其作用是限定变量的作用域。在C+11标准中,auto被赋予了新的功能,使用它可以让编译器自动推导出变量的类型。autox=10;/变量x为int类型10.1.1关键字在上述代码中,使用auto定义了变量x,并为其赋值为10,则变量x的类型由它的初始化值决定。由于编译器根据初始化值推导并确定变量的类型,因此auto修饰的变量必须初始化。10.1.1关键字除了

2、修饰变量,auto还可以作为函数的返回值。autofunc()/.功能代码return1;10.1.1关键字虽然auto可以作为函数的返回值,但是auto不能修饰函数参数。除了修饰变量、函数返回值等,auto最大的用途就是用于模板编程中,简化代码。mapstring,vectorm;for(autovalue=m.begin();value!=m.end();value+)/10.1.1关键字如果不使用auto,则代码如下所示:mapstring,vectorm;mapstring,vector:iteratorvalue;for(value=m.begin();value!=m.end();

3、value+)/10.1.1关键字此外,在模板编程中,变量的类型依赖于模板参数,有时很难确定变量的类型。当不确定变量类型时,可以使用auto解决。templatevoidmultiply(T1x,T2y)autoresult=x*y;/使用auto修饰变量result10.1.1关键字2.decltypedecltype关键字是C+11标准新增的关键字,其功能与auto关键字类似,也是在编译时期进行类型推导,但其用法与auto不同,decltype关键字的使用格式如下所示:decltype(表达式)在上述格式中,decltype关键字会根据表达式的结果推导出数据类型,但它并不会真正计算出表达式

4、的值。需要注意的是,decltype关键字的参数表达式不能是具体的数据类型。10.1.1关键字inta;intb;floatf;couttypeid(decltype(a+b).name()endl;/推导结果:intcouttypeid(decltype(a+f).name()endl;/推导结果:floatcouttypeid(decltype(int).name()decltype()放在函数后面用于推导函数返回值类型。示例代码如下:templateautomultiply(T1x,T2y)-decltype(x*y)在泛型编程中,这种方式称为追踪返回类型,也称尾推导。有了-declty

5、pe(),程序设计者在编写代码时就无需关心任何时段的类型选择,编译器会进行合理的推导。10.1.1关键字3.nullptr在C语言中,为避免野指针的出现,通常使用NULL为指针赋值。C语言中NULL的定义如下:#defineNULL(void*)0)由上述定义可知,NULL是一个void*类型的指针,其值为0。在使用NULL给其他指针赋值时,发生了隐式类型转换,即将void*类型指针转换为了要赋值的指针类型。10.1.1关键字NULL的值被定义为字面常量0,这样会导致指针在使用过程中产生一些不可避免的错误。例如,有两个函数,函数声明如下:voidfunc(inta,int*p);voidfun

6、c(inta,intb);在上述代码中,函数func()是重载函数,如果在调用func()函数时,传入第二个参数为0或NULL,则编译器总会调用第二个func()函数,即两个参数都是int类型的函数。这就与实际想要调用的函数相违背了。10.1.1关键字如果想要根据传入的参数,成功调用相应的func()函数,则需要使用static_cast转换运算符将0强制转换,示例代码如下:func(1,0);/调用func(inta,intb)func(1,static_cast(0);/调用func(inta,int*p)虽然使用static_cast转换运算符解决了问题,但是这种方式极易出错,而且也会增

7、加代码的复杂程度。10.1.1关键字为了修复上述缺陷,C+11标准引入了一个新的关键字nullptr,nullptr也可以为指针赋值,避免出现野指针。但是,nullptr是一个有类型的空指针常量,当使用nullptr给指针赋值时,它可以隐式转换为等号左侧的指针类型。10.1.1关键字nullptr只能被转换为其他指针类型,不能转换为非指针类型。示例代码如下:int*p=nullptr;/正确intx=nullptr;/错误,nullptr不能转换为int类型注意10.1.1关键字由于nullptr只能转换为其他指针类型,因此它能够消除字面常量0带来的二义性。在调用func()函数时,如果传入n

8、ullptr作为第二个参数,则func()函数能够被正确调用。func(1,0);/调用func(inta,intb)func(1,nullptr);/调用func(inta,int*p)10.1.1关键字4.=default与=delete构造函数、析构函数、拷贝构造函数等是类的特殊成员函数,如果在类中没有显式定义这些成员函数,编译器会提供默认的构造函数、析构函数、拷贝构造函数等。但是,如果在类中显式定义了这些函数,编译器将不会再提供默认的版本。10.1.1关键字如果在程序中需要调用无参的构造函数,就需要程序设计者自己定义一个无参的构造函数,即使这个无参的构造函数体为空,并没有实现任何功能。

9、在实际开发中,一个项目工程中的类非常多,这样做势必会增加代码量。10.1.1关键字为了使代码更简洁高效,C+11标准引入了一个新特性,在默认函数声明后面添加“=default”,显式的指示编译器生成该函数的默认版本。classAnimalpublic:Animal()=default;Animal(stringname);private:string_name;10.1.1关键字有时,不希望类的某些成员函数在类外被调用,例如,在类外禁止调用类的拷贝构造函数。在C+98标准中,通常的做法是显式声明类的拷贝构造函数,并将其声明为类的私有成员。而C+11标准提供了一种更简便的方法,在函数的声明后面加

10、上“=delete”,编译器就会禁止函数在类外调用,这样的函数称为已删除函数。10.1.1关键字classAnimalpublic:/./在类外禁止调用拷贝构造函数Animal(constAnimal&)=delete;10.1.1关键字除了修饰类的成员函数,“=delete”还可以修饰普通函数,被“=delete”修饰的普通函数,在程序中也被禁止调用。示例代码如下:voidfunc(charch)=delete;func(a);/错误在上述代码中,func()函数被“=delete”修饰,当传入字符a调用func()函数时,编译器就会报错,提示“func()函数是已删除函数”。10.1.2基

11、于范围的for循环在传统C+中,使用for循环遍历一组数据时,必须要明确指定for循环的遍历范围,但是,很多时候,对于一个有范围的集合,明确指定遍历范围是多余的,而且容易出现错误。针对这个问题,C+11标准提出了基于范围的for循环,该for循环语句可以自动确定遍历范围。10.1.2基于范围的for循环基于范围的for循环格式如下:for(变量:对象)/在上述语法格式中,for循环语句在遍历时,会遍历对象,将取到的值赋给变量,执行完循环体中的操作之后,再自动取对象中的下一个值赋给变量,直到对象中的数据被迭代完毕。10.1.2基于范围的for循环vectorv=1,2,3,4,5,6;for(a

12、utoi:v)couti返回值类型函数体在上述格式中,参数列表、返回值类型、函数体的含义都与普通函数相同。如果lambda表达式不需要参数,并且函数没有返回值,则可以将()、-和返回值类型一起省略。捕获列表是lambda表达式的标识,编译器根据判断接下来的代码是否是lambda表达式。捕获列表能够捕获lambda表达式上下文中的变量以供lambda表达式使用。10.1.3lambda表达式根据捕获规则,捕获列表有以下几种常用的捕获形式:(1):空捕获,表示lambda表达式不捕获任何变量。(2)var:变量捕获,表示捕获局部变量var。如果捕获多个变量,变量之间用“,”分隔。(3)&var:引

13、用捕获,表示以引用方式捕获局部变量var。(4)=:隐式捕获,表示捕获所有的局部变量。(5)&:隐式引用捕获,表示以引用方式捕获所有的局部变量。10.1.3lambda表达式/例10-1lambda.cppintmain()intnum=100;autof=num(intx)-intreturnx+num;/lambda表达式coutf(10)endl;vectorv=54,148,3,848,2,89;/创建vector对象v/调用for_each()函数遍历输出v容器中的元素for_each(v.begin(),v.end(),(auton)coutn;);return0;10.1.3la

14、mbda表达式上述代码首先定义了一个lambda表达式,该lambda表达式有一个int类型的参数x,返回值为int类型,并以值传递的方式捕获局部变量num;在函数体内部返回x与num的相加之和。将lambda表达式赋值给变量f,通过f调用lambda表达式,调用方式与普通函数类似。然后创建了vector对象v,并调用for_each()函数遍历对象v,输出其中的元素。for_each()函数的第三个参数是一个lambda表达式,该lambda表达式不捕获任何变量,它有一个int类型的参数n(对象v中的元素),在函数体中输出n的值。10.2智能指针在C+编程中,如果使用new手动申请了内存,则

15、必须要使用delete手动释放,否则会造成内存泄露。对内存管理来说,手动释放内存并不是一个好的解决方案,C+98中提出了智能指针auto_ptr解决了内存的自动释放,但是auto_ptr有诸多缺点,例如,不能调用delete,并且,如果auto_ptr出现错误,只能在运行时检测而无法在编译时检测。10.2智能指针为此,C+11标准提出了三个新的智能指针:unique_ptr、shared_ptr和weak_ptr。unique_ptr、shared_ptr和weak_ptr是C+11标准提供的模板类,用于管理new申请的堆内存空间。这些模板类定义了一个以堆内存空间(new申请的)指针为参数的构

16、造函数,在创建智能指针对象时,将new返回的指针作为参数。同样,这些模板类也定义了析构函数,在析构函数中调用delete释放new申请的内存。当智能指针对象生命周期结束时,系统调用析构函数释放new申请的内存空间。10.2.1unique_ptrunique_ptr主要用来代替C+98标准中的auto_ptr,它的使用方法与auto_ptr相同,其语法格式如下:unique_ptr智能指针对象名称(指针);在上述格式中,unique_ptr是模板类型,后面是智能指针对象名称,其命名遵守标识符命名规范。智能指针对象后面的小括号中的参数是一个指针,该指针是new运算符申请堆内存空间返回的指针。10

17、.2.1unique_ptrunique_ptrpi(newint(10);classA;unique_ptrpA(newA);10.2.1unique_ptr在上述代码中,第一行代码创建了一个unique_ptr智能指针对象pi,用于管理一个int类型堆内存空间指针。后两行代码创建了一个unique_ptr智能指针对象pA,用于管理一个A类型的堆内存空间指针。当程序运行结束时,即使没有delete,编译器也会调用unique_ptr模板类的析构函数释放new申请的堆内存空间。需要注意的是,使用智能指针需要包含memory头文件。10.2.1unique_ptr对于unique_ptr智能指针

18、,同类型的智能指针对象之间不可以赋值,错误示例代码如下:unique_ptrps(newstring(C+);unique_ptrpt;pt=ps;/错误,不能对unique_ptr智能指针赋值10.2.1unique_ptr在上述代码中,直接将智能指针ps赋值给智能指针pt,编译器会报错。这是因为在unique_ptr模板类中,使用“=delete”修饰了“=”运算符的重载函数。之所以这样做,是因为unique_ptr在实现时是通过所有权的方式管理new对象指针的,一个new对象指针只能被一个unique_ptr智能指针对象管理,即unique_ptr智能指针拥有对new对象指针的所有权。当

19、发生赋值操作时,智能指针会转让所有权,例如,上述代码中的pt=ps语句,如果赋值成功,pt将拥有对new对象指针的所有权,而ps则失去所有权,指向无效的数据,成了危险的悬挂指针,如果后面程序中使用到ps,会造成程序崩溃。10.2.1unique_ptrC+98标准中的auto_ptr就是这种实现方式,因此auto_ptr使用起来比较危险,C+11标准为了修复这种缺陷,就将unique_ptr实现为不能直接使用“=”进行赋值。如果需要实现unique_ptr智能指针对象之间的赋值,可以调用C+标准库提供的move()函数,示例代码如下:unique_ptrps(newstring(C+);uni

20、que_ptrpt;pt=move(ps);/正确,可以通过编译调用move()函数完成赋值之后,pt拥有new对象指针的所有权,而ps则被赋值为nullptr。10.2.2shared_ptrshared_ptr与unique_ptr的用法相同,创建shared_ptr智能指针对象的格式如下所示:shared_ptr智能指针对象名称(指针);10.2.2shared_ptrshared_ptr是一种智能级别更高的指针,它在实现时采用了引用计数的方式,多个shared_ptr智能指针对象可以同时管理一个new对象指针。每增加一个shared_ptr智能指针对象,则new对象指针的引用计数就加1

21、;当shared_ptr智能指针对象失效时,new对象指针的引用计数就减1,而其他shared_ptr智能指针对象的使用并不会受到影响。只有在引用计数归为0时,shared_ptr才会真正释放所管理的堆内存空间。10.2.2shared_ptrshared_ptr提供的成员函数:(1)get()函数get()函数用于获取shared_ptr管理的new对象指针,其声明如下所示:T*get()const;在上述声明中,get()函数返回一个T*类型的指针。当使用cout输出get()函数的返回结果时,会得到new对象的地址。10.2.2shared_ptr(2)use_count()函数use_

22、count()函数用于获取new对象的引用计数,其声明如下所示:longuse_count()const;在上述声明中,use_count()函数返回一个long类型的数据,表示new对象的引用计数。10.2.2shared_ptr(3)reset()函数reset()函数用于取消shared_ptr智能指针对象对new对象的引用,其声明如下所示:voidreset();在上述声明中,reset()的声明比较简单,既没有参数也没有返回值。当调用该函数之后,new对象的引用计数就会减1。取消引用之后,当前智能指针对象被赋值为nullptr。10.2.2shared_ptr/例10-2shared

23、_ptr.cppintmain()/创建shared_ptr智能指针对象language1、language2、language3shared_ptrlanguage1(newstring(C+);shared_ptrlanguage2=language1;shared_ptrlanguage3=language1;/通过智能指针对象language1、language2、language3调用get()函数coutlanguage1:language1.get()endl;coutlanguage2:language2.get()endl;coutlanguage3:language3.ge

24、t()endl;cout引用计数:;coutlanguage1.use_count();10.2.2shared_ptrcoutlanguage2.use_count();coutlanguage3.use_count()endl;language1.reset();cout引用计数:;coutlanguage1.use_count();coutlanguage2.use_count();coutlanguage3.use_count()endl;coutlanguage1:language1.get()endl;coutlanguage2:language2.get()endl;coutla

25、nguage3:language3.get()endl;return0;10.2.3weak_ptrweak_ptr可以指向shared_ptr管理的new对象,但却没有该对象的所有权,即无法通过weak_ptr对象管理new对象。10.2.3weak_ptrshared_ptr、weak_ptr和new对象的关系示意图10.2.3weak_ptrweak_ptr模板类没有提供与unique_ptr、shared_ptr相同的构造函数,因此,不能通过传递new对象指针的方式创建weak_ptr对象。weak_ptr最常见的用法是验证shared_ptr对象的有效性。weak_ptr提供了一个成

26、员函数lock(),该函数用于返回一个shared_ptr对象,如果weak_ptr指向的new对象没有shared_ptr引用,则lock()函数返回nullptr。10.2.3weak_ptr/例10-3weak_ptr.cppvoidfunc(weak_ptr&pw)/通过pw.lock()获取一个shared_ptr对象shared_ptrps=pw.lock();if(ps!=nullptr)cout编程语言是*psendl;elsecoutshared_ptr智能指针失效!endl;10.2.3weak_ptrintmain()shared_ptrpt1(newstring(C+)

27、;shared_ptrpt2=pt1;weak_ptrpw=pt1;func(pw);/调用func()函数*pt1=Java;pt1.reset();/取消pt1的引用func(pw);/调用func()函数pt2.reset();/取消pt2的引用func(pw);/调用func()函数return0;10.2.3weak_ptr上述代码定义了func()函数,该函数参数为weak_ptr引用pw,在函数内部,通过pw调用lock()函数,然后判断返回值是否是有效的shared_ptr对象,如果是有效的shared_ptr对象,就输出shared_ptr对象指向的堆内存空间中的数据;如果不

28、是有效的shared_ptr对象,就输出相应的提示信息。在main()函数中,创建多个shared_ptr指针管理一块内存空间,通过weak_ptr获取该内存空间的shared_ptr指针引用计数,通过func()函数判断该内存空间的shared_ptr指针引用计数是否为0。10.3.1右值引用在C+11标准之前,程序中只有左值与右值的概念,简单来说,左值就是“=”符号左边的值,右值就是“=”符号右边的值。区分左值与右值的一个简单有效方法为,可以取地址的是左值,不可以取地址的是右值。10.3.1右值引用C+11标准对右值进行了更详细的划分,将右值分为纯右值与将亡值。纯右值是指字面常量、运算表达

29、式、lambda表达式等,将亡值是那些即将被销毁却可以移动的值,如函数返回值。随着对右值的详细划分,C+11标准提出了右值引用,右值引用就是定义一个标识符引用右值,右值引用通过“&”符号定义,其格式如下所示:类型&引用名称=右值;10.3.1右值引用intx=10,y=20;int&r1=100;/字面常量100是一个右值int&r2=x+y;/表达式x+y是一个右值int&r3=sqrt(9.0);/函数返回值是一个右值10.3.1右值引用与左值引用相同,右值引用在定义时也必须初始化。右值引用只能引用右值,不能引用左值,错误示例代码如下:intx=10,y=20;int&a=x;/错误int

30、&b=y;/错误在上述代码中,变量x、y都是左值,因此不能将它们绑定到右值引用。10.3.1右值引用一个已经定义的右值引用是一个左值,即它是可以被赋值的变量,因此不能使用右值引用来引用另一个右值引用,示例代码如下:int&m=100;int&n=m;/错误,m是变量,是左值,不能被绑定到右值引用n上注意10.3.2移动构造C+11标准提出右值引用最主要的目的就是在函数调用中,解决将亡值(临时对象)带来的效率问题。传统C+程序对函数返回值的处理,经过两次拷贝将函数的返回值返回出来。10.3.2移动构造/例10-4func.cppclassA/定义类Apublic:A()cout构造函数endl;

31、A(constA&a)cout拷贝构造函数endl;A()cout析构函数endl;Afunc()Aa;returna;/*返回对象a*/intmain()Ab=func();/调用func()函数return0;10.3.2移动构造在上述代码中,func()函数的调用过程中,func()函数并不会直接将对象a返回出去,而是创建一个临时对象,将对象a的值赋给临时对象,在返回时,将临时对象返回给对象b。func()函数的返回过程如下图。10.3.2移动构造如果函数返回的数据是非常大的堆内存数据,那么频繁的拷贝、析构过程会严重影响程序的运行效率。针对这个问题,C+11标准提出了用右值引用来解决,在

32、构造对象b时,直接通过右值引用的方式,让对象b引用临时对象,即对象b指向临时对象的内存空间。返回右值引用的func()函数返回过程如下图。10.3.2移动构造返回右值引用,就需要定义相应的构造函数,这样的构造函数称为移动构造函数。移动构造函数也是特殊的成员函数,函数名称与类名相同,有一个右值引用作为参数。class类名public:移动构造函数名称(类名&对象名)函数体;10.3.2移动构造在定义移动构造函数时,由于需要在函数内部修改参数对象,因此不使用const修饰引用的对象。注意10.3.2移动构造/例10-5moveConstructor.cppclassApublic:A(intn);

33、/构造函数A(constA&a);/拷贝构造函数A(A&a);/移动构造函数A();/析构函数private:int*p;/成员变量;10.3.2移动构造A:A(intn):p(newint(n)cout构造函数endl;A:A(constA&a)p=newint(*(a.p);cout拷贝构造函数endl;A:A(A&a)/类外实现移动构造函数p=a.p;/将当前对象指针指向a.p指向的空间a.p=nullptr;/将a.p赋值为nullptrcout移动构造函数endl;10.3.2移动构造A:A()cout析构函数endl;Afunc()Aa(10);returna;intmain()A

34、m=func();return0;10.3.2移动构造在类A中增加了一个成员变量int*p;并且定义了移动构造函数。首先使用a.p给当前对象的指针p赋值,即将当前对象的指针p指向参数对象a的指针指向的内存空间。然后将a.p赋值为nullptr。这样一块内存空间只有一个指针是有效的,避免了同一块内存空间被析构两次。在main()函数中调用func()函数返回对象时,调用的是移动构造函数。10.3.3move()函数C+11在标准库utility中提供了move()函数,该函数的功能就是将一个左值强制转换为右值,以便可以通过右值引用使用该值。move()函数的用法示例代码如下:intx=10;in

35、t&r=move(x);/将左值x强制转换为右值10.3.3move()函数C+11在标准库utility中提如果类中有指针或者动态数组成员,在对象的拷贝或赋值时,可以直接调用move()函数将对象转换为右值,去初始化另一个对象。使用右值进行初始化,调用的是移动构造函数,而不是拷贝构造函数,这样就可以避免大量数据的拷贝,能够极大的提高程序的运行效率。10.3.4完美转发一个已经定义的右值引用其实是一个左值,这样在参数转发(传递)时就会造成一些问题。例如,在函数的嵌套调用时,外层函数接收一个右值作为参数,但外层函数将参数转发给内层函数时,参数就变成了一个左值,并不是它原来的类型了。10.3.4完

36、美转发/例10-6transmit.cpptemplatevoidtransimit(T&t)cout左值endl;templatevoidtransimit(T&t)cout右值endl;templatevoidtest(U&u)transimit(u);transimit(move(u);intmain()test(1);/调用test()函数return0;10.3.4完美转发上述代码定义了两个重载的模板函数transimit(),第一个重载函数接受一个左值引用作为参数,第二个重载函数接受一个右值引用作为参数。又定义了一个模板函数test(),在test()函数内部以不同的参数调用tra

37、nsimit()函数。在main()函数中,调用test()函数,传入右值1作为参数,则参数由test()函数转发给两次transmit()函数,分别输出左值、右值。10.3.4完美转发调用test()函数时,传递的是右值,但在test()函数内部,第一次调用transimit()函数时,右值变为了左值,这显然不符合程序设计者的期望。针对这种情况,C+11标准提供了一个函数forward(),它能够完全依照模板的参数类型,将参数传递给函数模板中调用的函数,即参数在转发过程中,参数类型一直保持不变,这种转发方式称为完美转发。10.3.4完美转发例如,将上述代码中test()函数中第一次函数调用修

38、改如下:transimit(forward(u);/调用forward()函数实现完美转发此时,再调用test(1)函数时,其输出结果均为右值。forward()函数在实现完美转发时遵循引用折叠规则,该规则通过形参和实参的类型,推导出内层函数接收到的参数的实际类型。10.3.4完美转发引用折叠规则形参类型实参类型实际接收类型T&TT&T&T&T&T&T&T&T&TT&T&T&T&T&T&T&10.3.4完美转发引用折叠规则:内层函数最终接受到的参数是左值引用还是右值引用。在引用折叠规则中,所有的右值引用都可以叠加,最后变成一个右值引用;所有的左值引用也都可以叠加,最后变成一个左值引用。10.3

39、.5委托构造如果一个类定义了多个构造函数,这些构造函数就可能会有大量的重复代码。每一个构造函数都需要给成员变量赋值,这些赋值语句都很重复。为了简化构造函数的编写,C+11标准提出了委托构造函数。委托构造函数就是在构造函数定义时,调用另一个已经定义好的构造函数完成对象的初始化。被委托的构造函数称为目标构造函数。10.3.5委托构造classStudentpublic:Student():Student(lili,1003,99)/*.其他代码*/委托构造函数Student(stringname):Student(name,1001,97.6)/*其他代码*/委托构造函数Student(strin

40、gname,intid):Student(name,id,98.5)/*其他代码*/委托构造函数private:string_name;int_id;double_score;Student(conststringname,intid,doublescore)/目标构造函数_name=name;_id=id;_score=score;/.其他代码;10.3.5委托构造上述代码中,无参构造函数、一个参数的构造函数、两个参数的构造函数都是委托构造函数,它们都委托有三个参数的目标构造函数来完成对象的初始化工作。10.3.5委托构造委托构造函数体中的语句在目标构造函数完全执行后才被执行。目标构造函数体

41、中的局部变量不在委托构造函数体中起作用。在定义委托构造函数时,目标构造函数还可以再委托给另一个构造函数。但是,需要注意的是,委托构造函数不能递归定义(如构造函数C1委托给另一个构造函数C2,而C2又委托给C1)。10.3.6继承构造在传统C+编程中,派生类不能继承基类的构造函数,无法通过继承直接调用基类构造函数来完成基类成员变量的初始化。如果想要在派生类中完成基类成员变量的初始化,只能在派生类中定义若干构造函数,通过参数传递的方式,调用基类构造函数完成基类成员变量来实现。10.3.6继承构造为了简化代码的编写,C+11标准提出了继承构造函数的概念,使用using关键字在派生类中引入基类的构造函

42、数,格式如下所示:using基类名:构造函数名;在派生类中使用using关键字引入基类构造函数之后,派生类就不需要再定义用于参数传递的构造函数了。C+11标准将继承构造函数设计为派生类的隐匿声明函数,如果某个继承构造函数不被调用,编译器不会为其生成真正的函数代码。10.3.6继承构造继承构造函数可以简化派生类的代码编写,但是它只能初始化基类的成员变量,无法初始化派生类的成员变量。如果要初始化派生类的成员变量,还需要定义相应的派生类构造函数。注意10.3.6继承构造/例10-7heritages.cppclassBasepublic:Base();/无参构造函数Base(intnum);/一个i

43、nt类型参数的构造函数Base(doubled);/一个double类型参数的构造函数Base(intnum,doubled);/两个参数的构造函数private:int_num;double_d;Base:Base():_num(0),_d(0)coutBase无参构造函数endl;Base:Base(intnum):_num(num),_d(1.2)coutBase构造函数,初始化intnumendl;Base:Base(doubled):_num(100),_d(d)coutBase构造函数,初始化doubledendl;Base:Base(intnum,doubled):_num(nu

44、m),_d(d)coutBase两个参数构造函数endl;10.3.6继承构造classDerive:publicBase/定义派生类Derivepublic:usingBase:Base;/继承基类构造函数Derive();/派生类无参构造函数Derive(stringname);/派生类有参构造函数private:string_name;/派生类成员变量_name;Derive:Derive():_name(xixi)coutDerive无参构造函数endl;Derive:Derive(stringname):_name(name)coutDerive有参构造函数endl;10.3.6继承

45、构造intmain()Derived1;/调用Derive类的无参构造函数Derived2(qiqi);/调用Derive类的有参构造函数Derived3(6);/调用Base类的有参构造函数,初始化intnumDerived4(12.8);/调用Base类的有参构造函数,初始化doubledDerived5(100,2.9);/调用Base类两个参数构造函数return0;10.3.6继承构造上述代码定义了两个类:基类Base:该类中定义了两个成员变量和四个重载构造函数。派生类Derive:Derive类公有继承Base类,在Derive类中,定义了一个成员变量和两个构造函数,并且使用usi

46、ng关键字继承了基类Base的构造函数。在main()函数中创建了对象d1、d2、d3、d4、d5。创建对象d1,由于没有参数,它会调用Derive类的无参构造函数。调用Derive类无参构造函数之前,编译器会先调用Base类的无参构造函数,即先调用基类构造函数再调用派生类构造函数。10.3.6继承构造创建对象d2,程序首先会调用Base类的无参构造函数,然后调用Derive类的有参构造函数。这是因为创建对象d2时没有传入基类成员的初始化数据。创建对象d3、对象d4和对象d5,对象d3传入一个int类型参数,对象d4传入一个double类型参数,对象d5传入一个int类型参数和一个double

47、类型参数。由于Derive类没有相应的构造函数,因此编译器会调用继承构造函数。10.3.6继承构造在使用继承构造函数时,如果基类构造函数有默认值,则每个默认值使用与否的不同组合都会创建出新的构造函数。classBasepublic:Base(intn=3,doubled=3.14)/带有默认值的构造函数;classDerive:publicBase/Derive类公有继承Base类usingBase:Base;/继承构造函数;10.3.6继承构造在上述代码中,基类Base中定义了一个构造函数,该构造函数的两个参数均有默认值,则基类的构造函数在调用时会存在多种情况:Base():默认的构造函数,

48、两个参数均使用默认值。Base(intn):double类型的参数使用默认值。Base(intn,doubled):两个参数都不使用默认值。Base(constBase&):默认的拷贝构造函数。10.3.6继承构造由于基类构造函数的版本有多个,则派生类中的继承构造函数也会有多个版本:Derive():默认的继承构造函数,两个参数都使用默认值。Derive(int):带有一个参数的继承构造函数,double类型的参数使用默认值。Derive(int,double):带有两个参数的继承构造函数,两个参数都不使用默认值。Derive(constDerive&):默认的拷贝构造函数。10.3.6继承构

49、造若派生类继承自多个基类,多个基类中的构造函数可能会导致派生类中的继承构造函数的函数名、参数相同,从而引发冲突。注意10.3.6继承构造classBase1public:Base1(intx);classBase2public:Base2(intx);classDerive:publicBase1,publicBase2public:usingBase1:Base1;usingBase2:Base2;10.3.6继承构造在上述代码中,两个基类构造函数都拥有int类型参数,这会导致派生类中重复定义相同类型的继承构造函数。例如,通过Derived(100)创建对象时,编译器会提示Derive()构

50、造函数调用不明确。10.3.6继承构造这种冲突可以通过显式定义派生类的构造函数,示例代码如下:classDerive:publicBase1,publicBase2public:Derive(intx):Base1(x),Base2(x);10.3.7函数包装C+11标准提供了一个函数包装器function,function是一个类模板,它能够为多种类似的函数提供统一的调用接口,即对函数进行包装。function可以包装除类成员函数之外的所有函数对象,包括普通函数、函数指针、lambda表达式和仿函数。10.3.7函数包装在模板编程中,function能够用统一的方式处理函数,减少函数模板的实

51、例化,因此可以提高程序的运行效率。在学习function之前,先来看一个案例。10.3.7函数包装/例10-8call.cpptemplateTfunc(Tt,Uu)staticintcount=0;count+;coutcount=count,&count=&countendl;returnu(t);/定义普通函数square(),用于求算参数的平方intsquare(inta)returna*a;10.3.7函数包装classStudentprivate:int_id;public:Student(intid=1001):_id(id)intoperator()(intnum)return

52、_id+num;intmain()intx=10;coutsquare()函数:func(x,square)endl;coutStudent类:func(x,Student(1002)endl;coutlambda表达式:func(x,(intb)returnb/2;)endl;return0;10.3.7函数包装上述代码首先定义了函数模板func(),该函数模板有两个类型参数,并返回T类型的返回值。在函数模板内部,定义了静态变量count用于标识函数模板的调用情况。然后定义square()函数、学生类Student,并重载()运算符。在main()函数中三次调用func()函数,分别传入sq

53、uare()函数、Student()仿函数和lambda表达式。10.3.7函数包装这三次func()函数调用,静态变量count的地址都不相同。10.3.7函数包装这表明func()函数模板被实例化了3次。但是,分析square()函数、Student()仿函数、lambda表达式,它们都有一个int类型的参数,并且返回值都为int类型,它们的调用特征标相同。调用特征标由函数的返回值类型和参数列表的类型决定,上述代码中,square()函数、Student()仿函数、lambda表达式的调用特征标为int(int),即函数有一个int类型的参数,返回值类型为int。10.3.7函数包装对于调

54、用特征标相同的函数,作为参数去调用函数模板时,只实例化一个对应的函数即可。为此,C+11标准提供了function函数包装器,function可以从调用特征标的角度定义一个对象,用于包装调用特征标相同的函数指针、函数对象或lambda表达式。例如,定义一个调用特征标为int(int)的function对象,示例代码如下:functionfi;10.3.7函数包装使用function包装调用特征标相同的函数对象,当使用这些函数对象调用函数模板时,function可以保证函数模板只实例化一次。下面修改上述案例,使用function完成square()函数、Student()仿函数和lambda表达

55、式的调用。10.3.7函数包装/例10-9function.cpp/前述代码不变intmain()intx=10;functionfi1=square;functionfi2=Student(1002);functionfi3=(intb)returnb/2;coutsquare()函数:func(x,fi1)endl;coutStudent类:func(x,fi2)endl;coutlambda表达式:func(x,fi3)endl;return0;10.3.7函数包装上述代码分别定义了三个function函数对象fi1、fi2、fi3;第3335行代码,在调用func()函数时,分别传入f

56、i1、fi2、fi3作为参数。调用结果如下:10.3.7函数包装三次调用时,静态变量count的数值分别为1、2、3,且三次调用的地址都相同,表明func()函数只实例化了一次。函数模板的实例化次数减少,程序的运行效率就会提高。对于调用特征标相同的函数,可以只定义一个function对象,例如上述function对象,可以只定义一个:typedeffunctionfi;/func(x,fi(square);func(x,fi(Student(1002);func(x,fi(intb)returnb/2;);10.4并行编程在C+11标准之前,C+语言并没有对并行编程提供语言级别的支持,C+使用

57、的多线程都由第三方库提供,如POSIX标准(pthread)、OpenMG库或windows线程库,它们都是基于过程的多线程,这使得C+并行编程在移植性方面存在诸多不足。为此,C+11标准增加了线程及线程相关的类,用于支持并行编程,使C+并行编程在可移植性方面得到了极大提高。10.4.1多线程C+11标准提供了thread类模板用于创建线程,该类模板定义在thread标准库中,因此在创建线程时,需要包含thread头文件。thread类模板定义了一个无参构造函数和一个变参构造函数,因此在创建线程对象时,可以为线程传入参数,也可以不传入参数。10.4.1多线程thread类模板不提供拷贝构造函数

58、、赋值运算符重载等函数,因此线程对象之间不可以进行拷贝、赋值等操作。注意10.4.1多线程thread类模板定义了两个常用的成员函数:join()函数和detach()函数:(1)join()函数:将线程和线程对象连接起来,即将子线程加入程序执行。join()函数是阻塞的,它可以阻塞主线程(当前线程),等待子线程工作结束之后,再启动主线程继续执行任务。(2)detach()函数:分离线程与线程对象,即主线程和子线程可同时进行工作,主线程不必再等待子线程结束。但是,detach()函数分离的线程对象不能再调用join()函数将它与线程连接起来。10.4.1多线程/例10-10thread.cpp

59、voidfunc()/定义函数func()cout子线程工作endl;cout子线程工作结束endl;intmain()cout主线程工作endl;threadt(func);/创建线程对象tt.join();/将子线程加入程序执行cout主线程工作结束endl;return0;10.4.1多线程上述代码定义了函数func()执行任务。在main()函数中创建线程对象t,传入func()函数名作为参数,即创建一个子线程去执行func()函数的功能,并调用join()函数阻塞主线程。当子线程工作结束之后,再回到主线程继续执行任务。10.4.1多线程在C+多线程中,线程对象与线程是相互关联的,线程

60、对象出了作用域之后,就会被析构,如果此时线程函数还未执行完,程序就会发生错误,因此需要保证线程函数的生命周期在线程对象生命周期之内。一般通过调用thread中定义的join()函数阻塞主线程等待子线程结束,或者调用thread中的detach()函数将线程与线程对象进行分离,让线程在后台执行,这样即使线程对象生命周期结束,线程也不会受到影响。10.4.1多线程例如,在上述代码中,将join()函数替换为detach()函数,将线程对象与线程分离,让线程在后台运行,再次运行程序,运行结果就可能发生变化。即使main()函数(主线程)结束,子线程对象t生命周期结束,子线程依然会在后台将func()

61、函数执行完毕。10.4.1多线程小提示:this_thread命名空间C+11标准定义了this_thread命名空间,该空间提供了一组获取当前线程信息的函数,分别如下所示:(1)get_id()函数:获取当前线程id。10.4.1多线程(2)yeild()函数:放弃当前线程的执行权。操作系统会调度其他线程执行未用完的时间片,当时间片用完之后,当前线程再与其他线程一起竞争CPU资源。(3)sleep_until()函数:让当前线程休眠到某个时间点。(4)sleep_for()函数:让当前线程休眠一段时间。10.4.2互斥锁在并行编程中,为避免多线程对共享资源的竞争导致程序错误,线程会对共享资源

62、进行保护。通常的做法是对共享资源加锁,当线程修改共享资源时,会获取锁将共享资源锁上,在操作完成之后再进行解锁。加锁之后,共享资源只能被当前线程操作,其他线程只能等待当前线程解锁退出之后,再获取资源。10.4.2互斥锁为此,C+11标准提供了互斥锁mutex,用于为共享资源加锁,让多个线程互斥访问共享资源。mutex是一个类模板,定义在mutex标准库中,使用时要包含mutex头文件。10.4.2互斥锁mutex类模板定义了三个常用的成员函数:(1)lock()函数:lock()函数用于给共享资源上锁。如果共享资源已经被其他线程上锁,则当前线程被阻塞;如果共享资源已经被当前线程上锁,则产生死锁。

63、(2)unlock()函数:unlock()函数用于给共享资源解锁,释放当前线程对共享资源的所有权。10.4.2互斥锁(3)try_lock()函数:try_lock()函数也用于给共享资源上锁,但它是尝试上锁,如果共享资源已经被其他线程上锁,try_lock()函数返回false,当前线程并不会被阻塞,而是继续执行其他任务。如果共享资源已经被当前线程上锁,则产生死锁。10.4.2互斥锁/例10-11mutex.cppintnum=0;/定义全局变量nummutexmtx;/定义互斥锁mtxvoidfunc()mtx.lock();/上锁cout线程id:this_thread:get_id(

64、)endl;/获取当前线程idnum+;coutcounter:numendl;mtx.unlock();/解锁10.4.2互斥锁intmain()threadths3;/定义线程数组for(inti=0;i3;i+)thsi=thread(func);/分配线程任务for(auto&th:ths)th.join();/将线程加入程序cout主线程工作结束endl;return0;10.4.2互斥锁上述代码,在main()函数中创建了3个线程,分别执行func()函数,在func()内部,先调用互斥锁的lock()函数锁住要执行的代码,线程执行结束之后,再调用unlock()函数释放代码的所有

65、权,其他线程再次竞争,获取互斥锁、执行代码、释放互斥锁。当3个线程都执行完毕之后,主线程接着往下执行。10.4.2互斥锁如果func()函数中没有互斥锁,则3个线程会同时执行func()函数,输出结果就会超出预期。除了lock()函数,互斥锁也可以调用try_lock()函数锁住共享资源。voidfunc()if(mtx.try_lock()/调用try_lock()函数加锁cout线程id:this_thread:get_id()endl;num+;coutcounter:numendl;mtx.unlock();10.4.2互斥锁调用try_lock()函数时,当某个线程获取了互斥锁mtx

66、,就会为func()函数上锁,获得func()函数的执行权。另外两个线程调用try_lock()函数尝试上锁时,发现func()函数已经被其他线程上锁,这两个线程并没有被阻塞,而是继续执行其他任务,因此,最终只有一个线程执行func()函数。10.4.3lock_guard和unique_lock通过mutex的成员函数为共享资源上锁解锁,能够保证共享资源的安全性。但是,通过mutex上锁之后必须要手动解锁,如果忘记解锁,当前线程会一直拥有共享资源的所有权,其他线程不得访问共享资源,造成程序错误。此外,如果程序抛出了异常,mutex对象无法正确的析构,导致已经被上锁的共享资源无法解锁。10.4.3lock_guard和unique_lock为此,C+11标准提供了RAII技术的类模板:lock_guard和unique_lock。lock_guard和unique_lock可以管理mutex对象,自动为共享资源上锁解锁,不需要程序设计者手动的调用mutex的lock()函数和unlock()函数。即使程序抛出异常,lock_guard和unique_lock也能保证mutex对象正确解

展开阅读全文
温馨提示:
1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
2: 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
3.本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
关于我们 - 网站声明 - 网站地图 - 资源地图 - 友情链接 - 网站客服 - 联系我们

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!