《C++ Primer Plus(第六版)》(31)(第十五章 友元、异常和其他 笔记)
1.友元类,就在类中声明个friend class className;就行了。
这个是对所有成员有效的。
class A { public: A(int a){ _a = a; } friend class B; private: int _a; }; class B { public: int get(A a){ return a._a; } protected: private: };B类并不需要向前声明,因为已经指出,这是一个了类了,但是友元成员函数就需要了。
2.友元成员函数
class A; class B { public: int get(A& a); protected: private: }; class A { public: A(int a){ _a = a; } friend int B::get(A& a); private: int _a; };必须要注意的是,在声明友元类函数的时候,必须已经知道了那个类的细节。所以那个类需要已经声明。
因为友元类函数是在别的类中声明了一次,所以在自己的类中必须是public的,否则看不到吧。
3.友元可以互相指向,即按上面的,B是A的友元类,同时A也可以为B的友元类。
但是要注意,使用对方的属性或者函数的时候,必须知道类的细节了。
4.在一个类中声明的类叫做嵌套类。
5.异常类型可以是字符串,或者其他类型。
执行throw相当于return,但是并不是返回调用的程序,而是沿着调用序列一直后退,知道被catch住。
6.异常规范,在函数原型和定义中的那个throw()
void a()throw("xxxxxx"); void b()throw();throw()就是异常规范了。C++11已经摒弃了。
但是C++11新增了一个noexcept关键字。
void c()noexcept;这是向编译器说明,这里不会引发异常,相当于程序员对编译器做出的一种承诺。
为了方便编译器进行优化。
7.引发异常后,对于中间调用放在栈中的自动对象,其析构函数将不会被调用。
但是通过下面程序。
Test.h
class A { public: A(int a){ _a = a; cout << "a:" << _a << endl; } ~A(){ cout << "A:" << _a << endl; } friend int B::get(A& a); private: int _a; };main.cpp
#include <iostream> #include "Test.h" #include <string> using namespace std; using namespace FableGame; void f1() { A a(1); throw a; } void f2() { A a(2); f1(); } void f3() { A a(3); f2(); } int main(int argc, const char * argv[]) { try { f3(); } catch (A& a) { cout << "main" << endl;//在这里断点 } return 0; }在抛出异常后,直接进入catch模块了。而且通过打印输出,可以发现栈解退了,看VS的调用堆栈,f1,f2,f3的栈信息也被清空了。
所以说,程序进行栈解退,以回到捕捉异常的地方时,将释放栈中的自动存储变量。如果是类对象,将调用对象的析构函数。
但是对于new出来的对象,后面的delete可能没有执行到,导致内存泄漏。
8.捕捉到的异常,都是对象的副本,因为异常本身已经在栈解退时析构了。
使用引用,是因为抛出的异常,可能是有多重的继承,通过捕捉基类,可以捉到这条继承链的所有异常。
9.在程序没有捕获异常终止的时候,将会调用abort()函数。但是我们可以替换这个被调用的函数。
#include <exception> void myQuit() { cout << "Fable Game" << endl; exit(123456); } set_terminate(myQuit);只要在程序开头的地方执行这些就可以了。
10.RTTI运行阶段类型识别(Runtime Type Identification)
dynamic_cast运算符,尝试类型转换,如果不行就返回空指针。
typeid运算符使得能够确定两个对象是否为相同类型。typeid运算符返回一个type_info对象。
typeid运算符可以接受类名或者对象。
A a(123); if (typeid(a) == typeid(A)) { cout << typeid(a).name() << endl;//输出类名 class FableGame::A }name()输出完整的类名。
11.类型转换运算符
dynamic_cast,用得最多的了,如果能转就返回地址,如果不能就返回空指针。
const_cast ,可以转换const和非const
void change(const int* p, int value) { int *p2 = const_cast<int*>(p); *p2 = value; } int main(int argc, const char * argv[]) { int a = 123; const int b = 456; cout << a << b << endl; change(&a, 1); change(&b, 2); cout << a << b << endl; return 0; }const_cast只能改变指针,但是如果对象本身就是const的话,里面的值是无法更改的。
结果输出是:
123456
1456
static_cast,编译器可以隐式转换的,这个都可以完成。只在编译的时候确保安全性。
reinterpret_cast,通常适合于依赖具体实现的底层编程,不可移植的。