友元类:
两个类不存在继承和包含的关系,但是我想通过一个类的成员函数来修改另一个类的私有成员和保护成员的时候,可以使用友元类。
class A
{
private:
int num;//私有成员
//...
public:
//...
friend class B;//声明一个友元类
}
class B
{
private:
//...
public:
void set(A &t, int c){t.num = c;}//友元类的成员函数可以直接访问其他类的私有成员和保护成员
}
如何将一个类中的成员函数设置为另一个类的友元函数?
class B
{
private:
//...
public:
//...
void set();
}
class A
{
private:
//...
int num;
public:
friend void B::set();//声明另一个类中的方法是友元函数,B中对应的友元函数直接可以访问A中私有和 保护成员,而不用通过A对象的方法
}
如何解决循环引用,循环依赖的问题?
什么是循环引用和循环依赖?比方说类A中的函数对类B的对象进行了操作,类B中又对类A的对象进行了操作,如果类A声明在类B前面,则会导致编译器不认识类A中的B,如果类B在类A前面,则会导致编译器不认识类B中的A,这就是循环引用。
如:
class A
{
public:
int num1;
private:
void Output() {cout << num1};
void Show(const B & b){ b.Output();}
};
class B
{
public:
int num2;
private:
void Output() {cout << num2};
void Show(const A &a){ a.Test();}
};
//显然A中的Show()不认识其参数类型const B &
//如果AB交换位置,又会导致B的Show不认识类A
//这就是循环引用,循环依赖
此时,编译器既不认识B也不认识b.Output()
解决办法:
使用"向前声明"
class B;//声明一下B是一个类,这种方法叫做向前声明
class A
{
public:
int num1;
private:
void Output();
void Show(const B & b);
};
class B
{
public:
int num2;
private:
void Output() {cout << num2};
void Show(const A &a){ a.Test();}
};
但是还有一个问题,那就是编译器不认识Show中b的方法Output,因为现在编译器只是知道B是个类,并不知道B中的成员函数是什么
即此时知道B是个类,但是不知道B的成员函数Output()
解决方法:
将A中的内联函数写在类B的声明之下
class B;//声明一下B是一个类,这种方法叫做向前声明
class A
{
public:
int num1;
private:
void Output();
void Show(const B & b);
}
class B
{
public:
int num2;
private:
void Output() {cout << num2};
void Show(const A &a){ a.Test();}
}
//在类外面定义内联函数,要用inline,并声明属于哪个类
inline void A::Show(const B &b)
{
b.Output();
}
注意:在头文件下定义的成员函数都是内联函数,因此要在函数前面加上inline
模板类里面的成员函数一点都是放在.h文件下进行定义的
嵌套类:
即在一个类里面再声明一个了类作为成员变量:
template<class T>
class Queue
{
private:
class Node
{
T data;
Node *next;
}
}
15.3 异常
1.调用#include<cstdlib>里面的abort()函数,遇到异常时,直接终止程序,并输出核心已转存(core dumped)的错误提示
#include<cstdlib>
//计算调和平均值,调和平均值 = 2.0 * x * y / x + y,注意x和y不能是相反数
/*
在一些新式编译器中,当除数为0时,并不会出现错误信息,程序仍然可以正常运行,此时算出来的调和平均值
是INF,表示一个无穷大的特殊浮点数,而有的编译器可能会运行到生成除数为0就崩溃的程序
*/
double hmean(double a, double b)
{
if(a == -b)
{
cout << "invalid argument to hmean()" << endl;
abort();//程序直接终止运行;
}
else
return 2.0 * x * y / x + y;
}
2.
返回错误码
bool hmean(double a, double b, double *c)//传入指针,指针直接操作内存
{
if(a == -b)
{
cout << "invalid argument to hmean()" << endl;
return false;
}
else
{
*c = 2.0 * x * y / x + y;
return true;//此处的true和false可作为错误码
}
}
3.
使用c++的异常处理机制
1)使用try块
2)异常发生时抛出异常(throw)
3)程序捕抓异常(catch)
double hmean(double a, double b, double *c)//传入指针,指针直接操作内存
{
if(a == -b)
{
throw "bad hmean() argument, a = -b not allowed. ";//抛出异常,throw后面可以跟字符串,对象或其他c++类型,此处的异常类型是const char *类型
//throw抛出异常后hmean函数立即终止,返回main函数中调用hmean的下一行,程序找到能接收抛出异常类的异常处理程序catch(const char *s)
}
else
return 2.0 * a * b / a + b;
}
int main(void)
{
int a, b;
while( cin >> a >> b && a != 0 && b != 0)
{
try//try块,用于注明可能发生异常的代码,在try块外调用的hmean函数,发生时异常不会处理该异常,而是会终止程序的运行
{
hmean(a, b);
}
catch(const char *s)//捕抓异常,异常处理程序
{
cout << s << endl;
cout << "Enter a new pair of arguments." << endl;
continue;
}
}
hmean(10, -10);//在try块外发生异常,异常处理程序不会捕获,而是直接终止程序运行
return 0;
}
通常是类作为异常类型来抛出,因为类可以携带更多的信息,抛出异常捕抓异常try块标注可能发生异常的代码的方式和字符串类型相同,此处不在过多赘述。
栈解退
两种抛出异常
1.直接抛出异常
try块中的函数中,直接有抛出异常的语句,此时抛出异常后函数结束,抛出的异常类型返回main函数找到匹配的异常处理函数捕捉异常并处理,如上面的例子,都是直接抛出异常
2.间接抛出异常
比方说函数a调用函数b,函数b调用函数c,函数c中抛出异常
我们知道函数的调用是一个压栈的过程,此时函数c抛出异常后,将会直接结束函数c运行,退回到上一个函数的调用,即出栈,到函数b,在函数b中寻找能够处理该异常类型的catch异常处理函数,找不到直接退栈不会执行该函数的内容,再往前找,直到找到能够处理该异常类型的catch异常处理函数为止,如果退到最后main函数都没有能处理这个异常类型的catch异常处理函数,则结束程序运行,这个就是栈解退,即异常抛出后就会往回倒,找到能满足条件的函数,找不到就程序运行结束。
一些需要注意的点:
1)如果抛出的异常类型是某个了的对象的话,将会调用该类的构造函数,生成一个该对象的副本,然后找到对应的catch来处理该异常
注意点:
为什么要创建副本?
原因是抛出对象后,函数结束,该对象被销毁,不能抛出对象本身,应该抛出该对象的副本
catch接收的异常类型是什么类型?
是该对象类型的引用
为什么是引用?而不是按值传参?
因为基类的引用既可以指向基类,还可以指向派生类,使用引用的话,该异常类型的派生类也可以被这个catch捕获
如
{
Class test;
throw test;//将会生成test的副本,p指向test的副本而不是test本身是一件好事
}
//...
catch(Class & t)//对应的异常类型是Class类型的引用
{
}
2)在catch中,如果throw后面没有跟异常类型,那么抛出来的异常类型是什么?
是,和catch处理的异常类型是同一异常类型
如
catch<const char *>
{
throw;//此时抛出的异常是const char *的异常
}
3)如果a派生出b,b派生出c,c派生出d,这种叫做层次化派生
有
//假设a是Base1类型的对象, c是Base2, c是Base3, d是Base4,这种叫做层次化派生
{
throw a;
throw b;
throw c;
throw d;
}
//那么catch接收应该是
catch(Base4 &d)
{
}
catch(Base3 &c)
{
}
catch(Base2 &b)
{
}
catch(Base1 &a)
{
}
//catch的顺序应该和throw的顺序相反,原因是基类的引用可以指向基类的对象也可以指向派生类的对象
//如果把Base1 &a放在最前面,那么Base1的这一个catch就能接收Base2, Base3, Base4的异常了,导致
//其他catch无法执行
exception类
1.exception类是c++的异常类,这个异常类里面有一个重要的虚函数what,它返回一个字符串,我们可以使用exception这个异常类来派生出其他的异常类供我们使用。
//exception里面虚函数what的声明
virtual const char* what() const noexcept;
//返回一个c风格的指针, noexcept的意思是说该函数不会抛出任何异常
2.c++库还定义了很多基于exception的异常类,比如在stdexcep头文件下的logic_error和runtime_error.
3.bad_alloc和new
bad_alloc是一个异常类,在头文件new中,其作用是当你用new开辟内存的时候,开辟失败了,库里面的某个函数就会抛出异常(不需要自己抛出),就会返回一个bad_alloc类的对象(在包含头文件new的情况下)
可以使用
catch(bad_alloc &ba)//捕获该异常
{
}
对开辟内存失败后做出一些操作。
我们熟悉的new开辟内存空间一般是返回NULL指针,如果包含了new头文件,就可以有两种选择
int *pi = new (std::nothrow) int;//关闭bad_alloc,返回NULL
int *pa = new (std::nowthrow) int[500];//开辟bad_alloc,抛出bad_alloc异常