二十七:
(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);