友元,异常和其他
可以将类作为友元,友元类的所有方法都可以访问原始类的私有成员和保护成员
例子:遥控器和电视机
class TV{
public:
friend class Remote;
}
一个遥控器可以控制多个电视机。类友元用于表示一些关系,否则必须要把tv类的私有部分设置为共有的,或者创建一个笨拙的大型类来包含电视机和遥控器。
友元成员函数:只允许某个类的成员函数是另一个类的友元
class Tv
{
friend void Remote::set_chan(Tv & t, int c);
}
这时出现了循环依赖,Remote定义必须在Tv之前,但是Remote的方法又会提到Tv对象,因此Tv类应该在前面?
避开循环的方法:前向声明
class Tv;//forward declaration
class Remote {
void onoff(Tv & t) {t.onoff();}//若这样写,此时还不知道Tv类有什么方法呢
void onoff (Tv & t);
};
class Tv {};
void Remote::onoff(Tv & t) {
}
//不能这样,因为编译器在Tv中看到Remote的一个方法被声明为友元方法之前,必须先看到Remote类的声明和set_chan()方法的声明
class Remote;
class Tv{};
class Remote {};
内联函数的链接性为内部,函数定义必须包含在使用函数的文件中,比如在头文件中
成为彼此的友元
函数需要访问两个类的私有数据:可以是一个类的成员函数和另一个类的友元,但是有时候成为两个类的友元更合理。
class A;
class B{
friend void sync(A & a, const B & b);
friend void sync(B & b, const A & a);
}
class A {
friend void sync(A & a, const B & b);
friend void sync(B & b, const A & a);
}
inline void sync(A & a, const B & b)
{
}
inline void sync(B & b, const A & a)
{
}
嵌套类
仅当嵌套类为公有的外部才可访问,还需要加上作用域解析运算符。
对类进行嵌套不创建类成员,而是定义了一种类型。
在父类中声明嵌套类没有赋予父类任何对嵌套类的访问特权,也没有赋予嵌套类任何对父类的访问特权。因此,父类只能显式访问嵌套类对象的公有成员。
异常规范:废弃了
特殊的异常规范:关键字noexcept指出函数不会引发异常
double marm() noexcept
好处:帮助编译器优化代码
运算符noexcept() 判断操作符是否会引发异常
栈解退: unwinding the stack
函数返回仅处理该函数放在栈中的对象,throw语句则处理try块和throw块之间的整个函数调用序列放在栈中的对象。自动调用了中间函数的析构函数
函数fun中的返回语句将控制权交给调用fun的函数,throw语句将控制权向上返回到第一个包含能够捕获异常的函数
引发异常时编译器总是创建一个临时拷贝,即使catch块中指定了引用类型。因为函数执行完后那个异常对象将不复存在。那为什么还要用引用呢?因为多态,基类引用可以指向派生类对象。
try{
}catch(...) {//捕获任何异常
}
如果捕获异常时不使用引用,则也将捕获所有派生类对象但是派生特性将被剥去。
exception类,what()
#include <exception>
class bad_hman : public std::exception
{
public :
const char * what() {return "xxx"}
}
try{
}catch(std::exception & e) {
cout << e.what() << endl;
}
头文件stdexcept定义了其他几个具体的异常类,logic_error,runtime_error类继承自exception
- logic_error 典型逻辑错误,通过合理编程可以避免
- domain_error
- invalid_argument
- length_error
- out_of_bounds
- runtime_error 可能在运行期间发生但难以预计和防范的错误
- range_error
- overflow_error
- underflow_error
exit(EXIT_FAILURE)
new失败时抛出bad_alloc异常,可以选择在失败时返回空指针
int * pi = new (std::nothrow) int;
例子p635
未捕获异常:首先调用函数terminate()。默认terminate调用abort()。可以修改其行为:set_terminate()
typedef void (*terminate_handler)();
terminate_handler set_terminate(terminate_handler f) noexcept;
set_terminate:接受的参数类型为terminate_handler。
#include<exception>
using namespace std;
void myQuit()
{
cout << "....";
exit(5);
}
set_terminate(myQuit);
如果程序引发了异常规范里面没有的异常?程序将调用unexcepted(),其将调用terminate(), 默认调用abort()
set_unexpected()
异常降低了程序运行速度,异常规范不适用于模板,异常和动态内存分配并非总能协同工作。
void test(int n)
{
double * ar = new double[n];
if (oh_no)
throow exception();
delete [] ar;//如果引发了异常,此语句不得执行,内存泄漏
return ;
}
//解决方法:先捕获该异常,做处理后重新引发;
//zui'hao
void test(int n)
{
double * ar = new double[n];
try {
if (oh_no)
throw_exception();
}
catch(exception & ex)
{
delete [] ar;
throw;
}
delete [] ar;
return ;
}
RTTI
runtime type identification运行阶段类型识别
3个支持rtti的元素
-
dynamic_cast如果可能的话,使用一个指向基类的指针来生成一个指向派生类的指针 否则返回空指针
不能回答:指针指向的是哪一类对象
能回答:是否可以安全地将对象的地址赋给特定类型的指针
class A {}; class B : public A {}; class C: public B {}; A * pa = new A; A * pb = new B; A * pc = new C; C * p1 = (C *) pc; //safe C * p2 = (C *) pa; //not safe B * p3 = (C *) pc; //safe B * pc = dynamic_castpa;//pa的类型能否被安全转换成B * ?可以的话返回对象地址,否则返回空指针。
也可用于引用,当失败时,抛出bad_cast的异常
Superb & rs = dynamic_cast<Superb &>(rg)
-
typeid:返回一个指出对象类型的值
用来确定两个对象是否为同种类型。类似sizeof,可以接受参数:
- 类名
- 结果为对象的表达式
返回一个对type_info对象的引用。type_info重载了==和!=。比如:
typeid(Mag) == typeid(*pg)
如果pg是一个空指针,引发bad_typeid异常。
包含一个name成员函数。返回表示类型的字符串
如果在扩展的ifelse语句中使用了typeid,则应该考虑虚函数和dynamic_cast
-
type_info结构储存了有关特定类型的信息
注意RTTI只能用于虚函数的类,不然多态的可能都不存在了,怎么在运行阶段识别类型呢?
类型转换运算符
c++设计者认为c的类型转换太过松散
严格限制类型转换:
-
dynamic_cast
-
const_cast:需要这样一个值,在大多情况下是常量,有时候又可以修改,这种情况下可以声明为const,需要修改时再用const_cast改。
这个功能也可以通过通用类型转换实现,但是通用转换可能同时改变了类型,不严格
High bar; const High * pbar = & bar; ... High * pb = (High *) (pbar); //valid Low * pl = (Low *)(pbar);//valid !同时也改了类型
只可以添加 、删除const或voatile特征。
-
static_cast
static_cast <type-name> (expression)
仅当type_name可被隐式转换成expression所属的类型或expression可被隐式转换为type_name所属的类型时,上述转换合法。适用于进行低风险转换,比如Low到High这种继承关系的转换,可以用于数值转换,double到int等,枚举值到整型不用类型转换,因此可以用static_cast将整型转换成枚举值0。不能把Low转换成一个无关类的指针,不能用于在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,当然也不能用于不同类型的引用之间的转换。因为这些属于风险比较高的转换。
-
reinterpret_cast:适用于天生危险的类型转换。
struct dat {short a; short b;}; long value = 0xA224B118; dat * pd = reinterpret_cast
(&value); cout << hex << pd->a;//display first 2 bytes of value 依赖于底层不可移植
支持将指针类型转换成足以储存指针表示的整型,不支持将指针转换为更小的整型或浮点。不能将函数指针转换成数据指针反之亦然。
char ch = char (&d); //c中可以,c++不行,因为char类型太小不能存储指针
()