a. 在类的继承关系中,当基类指针指向派生类对象或者基类引用派生类对象的时候,通过虚函数,可以实现在系统运行的时候根据具体对象的类型来确定所调用的成员方法的版本(基类指针指向的是什么派生类,则调用该派生类的成员方法)
b. 在实现的时候,基类的成员方法的原型使用virtual修饰,在派生类中对虚函数进行实现的时候必须保证原型和基类中的定义一致(函数的返回类型、参数列表完全一致),否则如果是参数列表不同意味着重载,如果是函数的返回类型不同意味着错误
c. 派生类中如果对基类的虚函数没有重新实现则直接使用基类的成员方法
d. 虚函数只能是类的成员方法
e. 构造函数不能是虚函数;而析构函数往往都应该是虚函数
f. 类中的虚函数仅对派生类中重新实现的函数起作用,对其他函数没有影响(虚函数的机制是通过在对象的起始位置增加一个虚表指针,该指针指向虚函数表,在虚函数表中存储的是当前类型所有虚函数的入口地址) -- 虚函数是动态绑定的,而普通函数是静态绑定的
虚函数和重载的比较:
a. 重载是同名不同参,虚函数是重写(函数的原型完全相同)
b. 重载可以是成员函数之间、普通函数之间;而虚函数只能是成员函数
c. 重载函数的调用是根据传递的实际参数的类型来选择重载版本;而虚函数是根据对象的实际类型来确定调用的版本 -- 重载是静态多态(编译时多态),虚函数是动态多态(运行时多态)
纯虚函数和抽象类
纯虚函数的定义格式:
virtual 函数的返回类型 函数名(参数列表) = 0;
#include <iostream>
using namespace std;
//当类中定义了纯虚函数的时候,这个类就成为抽象类;抽象类不能实例化对象
class Base
{
public:
virtual void show() = 0;
};
class Drivded : public Base
{
public:
void show()
{
}
};
int main()
{
cout << "sizeof(Base) = " << sizeof(Base) << endl;
//Base obj;
Drivded obj;
return 0;
}
说明:
当类中定义了纯虚函数的时候,这个类就成为抽象类;抽象类不能实例化对象, 抽象类只能作为基类,抽象类中定义的纯虚函数在派生类中必须实现(否则派生类也会成为抽象类),所以抽象类可以用来定义所有派生类的标准(抽象类中的所有的纯虚函数在派生类中都必须实现)
虽然抽象类不能实例化对象,但可以定义抽象类指针指向派生类对象或者抽象类应引用派生类来实现多态
一般情况下要在抽象类中定义析构函数的版本
异常:
程序中常出现三类错误:
Bug, 逻辑错误, 异常 - 可以预料并预先处理的
C++提供的异常处理机制:
Exception是一个对象
出现意外的地方将会产生一个异常:抛出一个异常对象
该对象被传递到负责意外处理的地方
由负责意外处理的代码专门进行统一的异常处理
异常对象包含有意外发生的详细信息
C++语言异常处理机制的基本思想是将异常的检测与处理分离。当在一个函数体中检测到异常条件存在,但无法确定相应的处理方法时,将引发一个异常,并由函数的直接或间接调用检测并处理这个异常。这一基本思想用3个保留字实现:throw、try和catch。其作用是:
(1)try:标识程序中异常语句块的开始。 -- 异常监测,将可能发生异常的代码放到try中进行监测
(2)throw:用来创建用户自定义类型的异常错误。
抛出异常: throw 异常对象
(3)catch:标识异常错误处理模块的开始。-- 异常处理
throw <表达式>; //throw(type e);
try
{
要检测的可能发生异常的代码
}
catch (类型 参数) //根据异常对象的类型来进行捕获
...
实例:
#include <iostream>
using namespace std;
int main()
{
int n1, n2;
float res;
cout << "请输入两个整数: ";
cin >> n1 >> n2;
try
{
/*在try块中放所有可能或者我们需要监测异常的代码*/
cout << "异常监测try块开始..." << endl;
if(n1 == 0)
{
//抛出一个异常
throw "除数为零";
}
else
{
res = (float)n2 / n1;
cout << "res = " << res << endl;
}
cout << "try块结束." << endl;
}
catch(const char *e)
{
cout << "异常的类型为字符串常量,信息为: " << e << endl;
}
cout << "异常处理结束." << endl;
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_08 抽象类&异常处理> ./test
请输入两个整数: 23 3
res = 0.130435
try块结束.
异常处理结束.
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_08 抽象类&异常处理> ./test
请输入两个整数: 0 4
异常监测try块开始...
异常的类型为字符串常量,信息为: 除数为零
异常处理结束.
#include <iostream>
using namespace std;
int main()
{
int n1, n2;
float res;
cout << "请输入两个整数: ";
cin >> n1 >> n2;
try
{
/*在try块中放所有可能或者我们需要监测异常的代码*/
cout << "异常监测try块开始..." << endl;
if(n1 == 0)
{
//抛出一个异常
throw "除数为零";
}
else if(n2 > n1 * 100 || n2 < 0)
{
cout << "成绩非法" << endl;
throw 99;
}
else
{
res = (float)n2 / n1;
cout << "res = " << res << endl;
}
cout << "try块结束." << endl;
}
catch(const char *e)
{
cout << "异常的类型为字符串常量,信息为: " << e << endl;
}
cout << "异常处理结束." << endl;
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_08 抽象类&异常处理> g++ .\异常处理.cpp -o test
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_08 抽象类&异常处理> ./test
请输入两个整数: 4 456
异常监测try块开始...
成绩非法
terminate called after throwing an instance of 'int'
说明:
a. 在try块中一旦执行到throw抛出异常以后,try块剩下的与会放弃执行,程序直接跳转到catch捕获去进行异常捕获
b. 在catch中按照异常的类型来进行捕获(如果抛出的异常的类型和某个catch指定的异常类型一致则该异常就会被这个catch捕获并执行该catch下面的异常处理代码)
c. 异常一旦被catch捕获并处理以后就不是异常了(异常处理了以后就是正常)
d. 异常会向上进行传递(在函数中发生的异常如果在函数内部没有处理这该异常会传递到函数调用的位置), 如果到程序的最外层的catch中有不能匹配的异常(程序中发生的异常不能被捕获),系统会自动的调用terminate来终止程序的执行
#include <iostream>
using namespace std;
float div_int(int n1, int n2)
{
if(n1 == 0)
{
//抛出一个异常
throw "除数为零";
}
else if(n2 > n1 * 100 || n2 < 0)
{
throw 99;
}
else
{
return (float)n2 / n1;
}
cout << "函数执行结束." << endl;
return 0.0f;
}
int main()
{
int n1, n2;
float res;
cout << "请输入两个整数: ";
cin >> n1 >> n2;
try
{
/*在try块中放所有可能或者我们需要监测异常的代码*/
cout << "异常监测try块开始..." << endl;
div_int(n1, n2);
cout << "try块结束." << endl;
}
catch(const char *e)
{
cout << "异常的类型为字符串常量,信息为: " << e << endl;
}
catch(int e) //如果是用户自定义类型的异常,一般都使用引用
{
cout << "成绩非法" << endl;
}
catch (...) //...可以捕获任何类型的异常; 异常的默认处理程序只能是try...catch结构的最后一个catch分支
{
cout << "未知异常" << endl;
}
cout << "异常处理结束." << endl;
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_08 抽象类&异常处理> ./test
请输入两个整数: 0 12
异常的类型为字符串常量,信息为: 除数为零
异常处理结束.
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_08 抽象类&异常处理> ./test
请输入两个整数: 3 450
成绩非法
异常处理结束.
练习:
在三角形类中,在计算面积的成员函数中当输入三个整数不能成为三角形的时候抛出一个异常,然后在对象的使用中进行异常处理
自定义异常类:
#include <iostream>
#include <cstring>
using namespace std;
class MyException
{
private:
int err_no; //异常编号
char err_msg[32]; //异常信息
public:
MyException() : err_no(0)
{
strcpy(err_msg, "系统正常");
}
MyException(int n) : err_no(n)
{
switch(err_no)
{
case 0:
strcpy(err_msg, "系统正常");
break;
case 1:
strcpy(err_msg, "主机不可达");
break;
case 2:
strcpy(err_msg, "连接被拒绝");
break;
case 3:
strcpy(err_msg, "地址在使用");
break;
case 4:
strcpy(err_msg, "用户名或者密码错误");
break;
default:
strcpy(err_msg, "未知异常");
}
}
int get_errno()
{
return err_no;
}
char *get_errmsg()
{
return err_msg;
}
};
void init(int n)
{
if(n < -50)
{
throw MyException(4);
}
if(n > 800)
{
throw MyException(2);
}
cout << "系统初始化正常" << endl;
}
int main()
{
int code;
cout << "请输入一个编号: ";
cin >> code;
try
{
init(code);
}
catch(MyException &e) //当异常类型为自定义类型的时候,参数采用传引用的方式,更加高效
{
std::cerr << e.get_errmsg() << '\n';
}
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_08 抽象类&异常处理> g++ .\自定义异常类.cpp -o test
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_08 抽象类&异常处理> ./test
请输入一个编号: 923
连接被拒绝
内部类 -- 内部异常类
在一个类中定义一个专门处理该类异常情况的类
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
class Triangle
{
private:
int a, b, c;
float s;
public:
class MyException
{
private:
int err_no; //异常编号
char err_msg[32]; //异常信息
public:
MyException() : err_no(0)
{
strcpy(err_msg, "系统正常");
}
MyException(int n) : err_no(n)
{
switch(err_no)
{
case 0:
strcpy(err_msg, "三角形正常");
break;
case 1:
strcpy(err_msg, "三角形的边不能小于等于0");
break;
case 2:
strcpy(err_msg, "任意两条边之和必须大于第三条边");
break;
default:
strcpy(err_msg, "未知异常");
}
}
int get_errno();
char *get_errmsg()
{
return err_msg;
}
};
Triangle()
{
}
Triangle(int a, int b, int c) : a(a), b(b), c(c)
{
}
bool judgement() //判定给定的三条边是否可以构成三角形
{
if(a <= 0 || b <= 0 || c <= 0)
{
throw MyException(1);
}
if(a + b <= c || a + c <= b || b + c <= a)
{
throw MyException(2);
}
return true;
}
float get_s()
{
float d = (a + b + c) / 2.0f;
judgement();
return sqrt(d * (d - a) * (d - b) * (d - c));
}
};
//内部类的成员方法如果在类的外部实现的时候要指定内部类名和外部类的名称
int Triangle::MyException::get_errno()
{
return err_no;
}
int main()
{
//Triangle triangle(3, 4, 5);
Triangle triangle(1, 2, 3);
float s = 0.0f;
try
{
s= triangle.get_s();
}
catch(Triangle::MyException &e)
{
std::cerr << e.get_errmsg() << '\n';
}
cout << "三角形的面积为: " << s << endl;
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_08 抽象类&异常处理> g++ .\内部类.cpp -o test
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_08 抽象类&异常处理> ./test
任意两条边之和必须大于第三条边
三角形的面积为: 0
思考:
怎样定义一个不能被继承的类?
在外部类的private中定义一个内部类,该内部类就不能被继承
class Triangle
{
private:
int a, b, c;
float s;
class InClass_prv
{
private:
int a;
public:
InClass_prv() : a(0)
{}
InClass_prv(int n) : a(n)
{
}
};
protected:
class InClass
{
private:
int a;
public:
InClass() : a(0)
{}
InClass(int n) : a(n)
{
}
};
public:
class MyException
{
private:
int err_no; //异常编号
char err_msg[32]; //异常信息
public:
MyException() : err_no(0)
{
strcpy(err_msg, "系统正常");
}
MyException(int n) : err_no(n)
{
switch(err_no)
{
case 0:
strcpy(err_msg, "三角形正常");
break;
case 1:
strcpy(err_msg, "三角形的边不能小于等于0");
break;
case 2:
strcpy(err_msg, "任意两条边之和必须大于第三条边");
break;
default:
strcpy(err_msg, "未知异常");
}
}
int get_errno();
char *get_errmsg()
{
return err_msg;
}
};
Triangle()
{
}
Triangle(int a, int b, int c) : a(a), b(b), c(c)
{
}
bool judgement() //判定给定的三条边是否可以构成三角形
{
if(a <= 0 || b <= 0 || c <= 0)
{
throw MyException(1);
}
if(a + b <= c || a + c <= b || b + c <= a)
{
throw MyException(2);
}
return true;
}
float get_s()
{
float d = (a + b + c) / 2.0f;
judgement();
return sqrt(d * (d - a) * (d - b) * (d - c));
}
};
//内部类的成员方法如果在类的外部实现的时候要指定内部类名和外部类的名称
int Triangle::MyException::get_errno()
{
return err_no;
}
//内部类也可以派生其他类
class Test : public Triangle::MyException
{
};
/*
//外部类private下的内部类是不能被继承的
class Test1 : public Triangle::InClass_prv
{
};
*/
//外部类中protected下的内部类可以在外部类的派生类中被继承
class T1 : public Triangle
{
public:
class A : public Triangle::InClass
{
};
};
作业:
定义一个myArray的类,要求重载operator[]用于检查给进来的参数是不是超过数组的界限,如果是的话,抛出一个异常,否则返回相应的值的引用。
operator[] 重载的原型
int& operator[ ] (int index) { …. …. }
编写一个程序测试异常。
提示:异常可以写成inner class,也可以写成outer class。