C++primer知识点(五)(终结C++primer)

  • Post author:
  • Post category:其他


二十七:

(1)异常使我们能将问题的检测和解决分离开来。

当匹配不到catch时,将调用标准库函数terminate(当异常没有被捕获)

异常对象:编译器使用异常抛出表达式来

对异常对象进行拷贝初始化

,因此throw表达式必须拥有完全类型(只是声明如class A;不是完全类型),如果是类类型的话,相应的类必须含有

一个可访问的析构函数和一个可访问的拷贝或移动构造函数

静态类型决定了异常对象的类型。如果throw表达式解引用一个

基类指针

,而该指针实际指向

派生类对象

,则抛出的对象将被切掉一部分,

只有基类部分被抛出

特殊的

catch

要出现在前面,如

派生类异常的处理要在基类异常的处理之前

。应该把继承链最底端的类放在前面。

如果不能完全处理某个异常,需

重新抛出

,仍是一条throw语句,但不包含任何表达式。

如:throw;

捕获所有异常:catch(…)

(2)要处理

构造函数“初始值列表”异常

,可以使用“

函数


try


语句块

try出现在初始值列表的冒号之前,函数体后直接跟catch(..){}

noexcept要么出现在该函数所有声明和定义中,要么一次也不要出现。


函数指针声明定义




可以有


noexcept


typedef/


类型别名




不能有


noexcept

需要在const/引用限定符之后,final,override和虚函数=0之前。

一旦noexcept函数抛出异常,程序就会调用terminate。

鉴于早期C++版本:等价的声明:

1)void fun(int) noexcept;

2)void fun(int) throw();

void fun(int)noexcept(true);//不会抛出异常

void fun(int)noexcept(false);//可能抛出异常。

noexcept()也是一个运算符,返回bool表示是否会抛出异常。

(3)只能严不能松:

函数指针如果声明了不抛出异常,则只可以指向不抛出异常的函数;如果显式或隐式指定可以抛出异常,则可以指向任何函数。

如果虚函数承诺不会抛出异常,则后续派生出来的虚函数也必须做出同样的承诺。

如果对所有成员和基类的操作都承诺了不抛出异常,则合成的成员是noexcept的。

(4)异常类层次图:

(5)命名空间

命名空间不可以定义在函数或者类内部。作用域后面无需分号。可以是不连续的。

1)c++11

内联命名空间

:可以被外层命名空间直接使用,inline namespace name{} 如:

namespacenameTemp{

#include “a.h”//a.h里面有内联的命名空间,那么对于nameTemp::的访问默认就是此命名空间

#include “b.h”

}

inline必须出现在命名空间第一次定义的地方。

2)

未命名的命名空间

拥有静态生命周期。第一次使用前创建,程序结束才销毁。

可以在给定的文件内不连续,但是

不能跨越多个文件

。多个文件的未命名空间没关系。

变量可以直接使用,但是与全局的同名变量会有二义性。

类的作于域中,using声明只能指向基类的成员。(如构造函数)


using


指示

,可以在全局/局部/命名空间中,但是

不能出现在类的作用域中

。using指示会将成员提升到包含命名空间本身和using指示最近作用域的能力。(一次注入某个命名空间的所有名字),二义性的名字只有在使用时才会被发现,而using声明引起的二义性在声明处就可以被发现。


命名空间名字的隐藏规则有一个例外:给函数传递类类型对象时,除了常规作用域,还会查找实参类型所属的命名空间。(如果是派生类,包括基类所在命名空间也会查找)

友元只是说明了对类的访问权限,在类外使用需要重新声明。如果包含类对象参数,那么不重新声明也可以直接使用。


继承关系中,对于基类的函数可以


using


声明引入,再选择性覆盖。




对于命名空间中,


using


声明会和同名同参的函数冲突。

using指示引入相同函数也不会有冲突,只要我们调用时指定好就行。

(6)

多重继承,如果使用using声明,从多个基类中继承了相同的构造函数(形参完全相同),会有错误。如:

struct D1:publicB1,public B2

{

using B1::B1;

using B2::B2;

//

要避免错误,需要为该构造函数定义自己的版本。

D1(const string &s):B1(s),B2(s){}

D1() = default;//

一旦定义了自己的构造函数,则必须出现

}

合成版本的拷贝/移动/赋值函数,会自动处理基类的部分。(派生类到派生类赋值,派生类到基类赋值会有截断。)

编译器不会在派生类向基类的几种转化中进行比较和选择,在他看来,转化成任何一种基类都是一样好,没有优先级。


如果名字在多个基类中被找到,会有二义性,使用时要指定它的版本。不使用也会避免二义性。(即使参数列表不同,或者私有公有都会产生二义性)

避免二义性,还可以定义新的版本。

派生类可以直接和间接继承一个类多次。

iostream要对同一个缓冲区进行读写,要用到虚继承。

虚继承的基类(共享)叫虚基类。

派生类中只有一份共享的虚基类子对象。virtual可以在继承说明符public等之前或之后。


1:B有x,D1,D2,都没有x 没有二义性

2:B有x,D1或D2有一个有x, 没有二义性,但派生类比B的x优先级高

3:B,D1,D2都有x,会有二义性

解决二义性:D重新定义x

虚基类是由最低层的派生类初始化的。

构造顺序:首先构造虚基类部分,然后按照派生列表构造直接基类


虚基类总是先于非虚基类构造,跟继承体系中的次序和位置无关。

二十八:

(1)


new


表达式:


1)


申请空间


new

(malloc/allocator)

operator new


函数

2)构造函数 /construct (定位new 在指定地点构造对象)


delete


表达式:

3)析构函数(destroy执行析构函数)


4)


释放空间


delete

(free/deallocate)

operator delete


函数

我们可以重载1)4) operator new 和 operatordelete ,全局作用域或类作用域,其中类作用域中是隐式静态的。(默认是static,不用显示指定static),以为是在对象构造之前和销毁之后,所以是静态的。

//

返回类型和第一个参数(不能有默认实参)必须是这样的:(可以加参数)

//这些版本可能抛出异常

void *operator new(size_t);

void *operator new[](size_t);

void *operator delete(void *) noexcept;

void *operator delete[](void *) noexcept;

//这些版本承诺不会抛出异常

void *operator new(size_t,

nothrow_t&

)noexcept;

void *operator new[](size_t,nothrow_t&) noexcept;

void *operator delete(void*,nothrow_t&) noexcept;

void *operator delete[](void *,nothrow_t&)noexcept;


其中,


void * operator new(size_t,void*);//


这种形式只供标准库使用,不能被重新定义


定位


new

:在指定地址构造对象:但不分配内存,指针地址不一定是动态内存。如:new (&str) string(“nihao”)

new(place_address) type

(args)

;//红色可省

new(place_address) type[size]

{initializer list}

//红色可省

调用自己的new,delete程序:

#include<iostream>

using namespacestd;

class MyClass

{

public:

int i;

};

void *operatornew(size_t size)

{

cout << “调用自己的new” << endl;

if (void *mem = malloc(size))

{

return mem;

}

else

{

throw bad_alloc();

}

}

void operatordelete(void *mem) noexcept

{

cout << “调用自己的delete” << endl;

free(mem);

}

int main()

{

cout << “main开始” << endl;

MyClass *pmy = new MyClass;

pmy->i = 20;

cout << “pmy->i = “<< pmy->i << endl;

delete pmy;

cout << “main结束” << endl;

return 0;

}

结果:

(2)运行时类型识别

1)dynamic_cast:可以将基类的指针或引用安全的转成派生类的指针或引用

dynamic_cast<type*>(e);

dynamic_cast<type&>(e);

dynamic_cast<type&&>(e);

对于指针的转换,如果失败,

返回所需类型的空指针

,而对于引用的转换,如果失败,抛出bad_cast异常(typeinfo头文件中)

2)typeid返回表达式的类型,(

只有有虚函数的时候才能到运行时确定实际类型

,否则就是静态类型)


数组就是返回数组类型,而不是指针类型。对于指针就是指针的静态编译类型,而不是指向对象的类型。

如果p是空指针,则typeid(*p)抛出bad_typeid异常

typeid操作的结果是一个常量对象的引用,对象类型是type_info(没有默认构造函数,且拷贝/移动构造函数、赋值运算符都是删除的)或它的派生类型。

(3)枚举类型

限定枚举类:enum class/struct开头,成员在作用域外不可访问(c++11),不能进行隐式类型转换成int等

不限定枚举类:省略 class/struct,成员与本身的作用域相同,可以隐式的转换。

可以用作switch

默认的类型可以在enum名字后加:类型。

前置声明:

enum intValues:unsigned long long;//不限定作用域,必须指定成员类型

enum class open_modes;//现代作用域,可以使用默认的int

(4)类成员指针

必须在*之前加上:classname::

1)数据成员指针


声明


:

const stringScreen::*pdata;

auto pdata =&Screen::contents;


使用

auto s =myScreen.*pdata;//




.*





->*


运算符,先解引用然后再取值


访问控制规则对成员指针同样有效

,对于pdata的使用必须位于成员或友元内部才行。

2)成员函数指针


声明:

auto pmf =&Screen::get_cursor;


这样声明的前提是该函数不接受任何实参,并且返回一个


char

要声明具体指向的类型,需要自己先声明好:

char(Screen::*pmf2)(Screen::pos,Screen::pos) const;

pmf2 = &Screen::get;

//


取地址符不能丢

或者使用类型别名:

using Action =char (Screen::*)(Screen::pos,Screen::pos) const;

Action get =&Screen::get;


使用:

(myScreen.*pmf2)(0,0);

也可以作为函数参数,可以传递默认实参。

3)将成员函数作为可调用对象

因为成员函数指针在调用时,必须绑定到特定的对象上,所以这样的指针不支持函数调用运算符。所以不能将它传递给算法。

如:

auto fp = &string::empty;

find_if(svec.begin(),svec.end(),fp);

实际是:if(fp(*it))//错误,成员函数指针调用必须通过->*

解决办法:


<1>


使用


function


生成可调用对象

我们需要告诉function一个事实:即empty是一个接受string参数并返回bool值的函数,通常,执行成员函数的对象被传给隐式的this形参,所以我们必须翻译该代码,使隐式的形参变成显式的。(

第一个形参表示该成员是哪个对象上执行的)

function<bool (

const string&

)> fcn = &string::empty;

或者:

function<bool (

const string*

)> fcn = &string::empty;

调用时:

find_if(svec.begin(),svec.end(),fcn);


<2>


使用


mem_fn


生成可调用对象

mem_fn让编译器推断成员的类型。

find_if(svec.begin(),svec.end(),mem_fn(&string::empty));

mem_fn生成的可调用对象,既可通过对象调用又可指针调用。

auto f =mem_fn(&string::empty);

f(*svec.begin());//传入对象,使用.*调用empty

f(&svec[0]);//传入指针,使用->*调用empty

<3>使用bind生成可调用对象

auto it =find_if(svec.begin(),svec.end(),

bind(&string::empty,_1)

);

与function类似的地方:必须将函数中用于表示执行对象的隐式形参转换成显式的。

与mem_fn类似的是:生成的可调用对象的第一个实参即可以是string指针,也可以是string的引用。

(5)嵌套类

外层类与嵌套类的对象互相独立。

嵌套类相当于外层类的一个类型成员。可以直接使用外层类的成员,不必加类名说明符。


嵌套类里面定义的静态成员需要在外层类的作用域之外进行定义。

(6)union(默认公有)

任意时刻只有一个数据成员可以有值。某个成员赋值,其他成员是未定义的状态。不能继承或当基类,不能含有虚函数。


匿名


union

定义所在作用域内,它的成员可以被直接访问。不能包含受保护和私有成员,也不能定义成员函数。

如果union含有类类型成员,且该类定义了默认构造/拷贝控制成员,编译器将为union合成对应的版本并将其定义为删除的。含有union的类对应的拷贝控制成员也会使删除的。

我们可以定义一个enum或其他东西作为

union


判别式

。(判断union此时表示的是什么类型)

(7)局部类

不能声明静态数据成员。

只能访问

外层作用域的

类型名、静态变量、枚举成员

。外面函数的普通变量不能被访问,(包括全局变量必须用::)

(8)extern “C” 连接指示

指出任意非C++函数所用的语言。

连接指示不能出现在类和函数定义的内部。


编写函数所用的语言,是函数类型的一部分,指向


C


函数的指针,与之相


C++


函数的指针是不一样的类型。

连接指示对返回类型和参数都有效。因为连接指示作用域声明语句的所有函数,所以如果我们希望给C++传入一个指向C函数的指针,必须使用类型别名。

//FC是指向C函数的指针

extern “C” typedefvoid FC(int);

//f2是一个C++函数,形参是C函数的指针。

void f2(FC *);

对连接到C的预处理器的支持:

#ifdef __cplusplus

extern “C”

#endif

int com(inti,inti);



版权声明:本文为ndzjx原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。