虚函数:由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。
虚函数的核心作用:
实现动态联编,在函数运行阶段动态的选择合适的成员函数。
在定义了虚函数后,可实现在派生类中对虚函数进行重写,从而实现统一的接口和不同的执行过程。总结:
1.在使用继承的方式实现运行时多态时,基类需要将与派生类相同函数名的函数加上virtual关键字,这样才可以在运行时精准识别出子类的虚函数。
2,如果没有继承关系但是需要多态的效果则需要将两个不同类的同名函数都加上virtual关键字;同时,需要将定义的指针指向其他对象时,要进行强制类型转换。
ptr = (A*)&btr;
3.带有多态性质的基类均应该声明一个virtual析构函数。同时如果任一class带有任何virtual函数,它就应该拥有一个virtual析构函数。
4.当class的设计目的如果不是作为base class使用,或不是为了具备多态性,则就不该声明virtual析构函数。(因为如果class中含有virutal函数会使得该class的体积增加,因为添加一个vptr(virtual table pointer)会增加其class大小达50%-100%)联编?
程序调用函数,编译器决定使用哪个可执行代码块。(所谓联编就是将函数名和函数体的程序连接到一起的过程)
静态联编 :在编译的时候就确定了函数的地址,然后call就调用了。(函数重载、函数模板的实例化)
动态联编 :首先需要取到对象的首地址,然后再解引用取到虚函数表的首地址后,再加上偏移量才能找到要调的虚函数,然后call调用。问题:
1,为什么调用普通函数比调用虚函数的效率高?
* 因为普通函数是静态联编的,而虚函数是动态联编的
2,为什么要用虚函数表(存函数指针的数组)?
同一个类的多个对象的虚函数表是同一个,所以这样就可以节省空间,一个类自己的虚函数和继承的虚函数还有重写父类的虚函数都会存在自己的虚函数表。同时,虚函数表本质是一个地图导航,可以清楚告诉一个想要操作子类的父类指针到底该使用哪个函数。
3,为什么要把基类的析构函数定义为虚函数?
编译器对析构函数做了特殊的处理,在内部子类和父类的析构函数名是一致的
在用基类操作派生类时,为了防止执行基类的析构函数,不执行派生类的析构函数。因为这样的删除只能够删除基类对象, 而不能删除子类对象,会造成内存泄漏.需要注意的是:
1. 虚函数不能是静态成员函数,或友元函数,因为它们不属于某个对象。
2. 内联函数不能在运行中动态确定其位置,即使虚函数在类的内部定义,编译时,仍将看作非内联。
3. 构造函数不能是虚函数,析构函数可以是虚函数,而且通常声明为虚函数。
// 继承关系中的虚函数:
#include
using namespace std;
class A
{
public:
A() {};
~A() {};
virtual void show(void) {
cout << "A" << endl;
}
};
class B :public A {
public:
B() {};
~B() {};
void show(void) {
cout << "B" << endl;
}
};
int main()
{
A atr, * p;
B btr;
p = &atr;
p->show();
p = &btr;
p->show();
system("pause>nul");
return 0;
}
// 友元函数
#include
using namespace std;
class A {
private:
double x;
double y;
public:
A(double a, double b) {
x = a;
y = b;
}
// 使用friend关键字
friend double get(A &a, A &b);
};
double get(A &a, A &b) {
double xx;
double yy;
// 访问私有变量
xx = a.x - b.x;
yy = a.y - b.y;
return sqrt(xx * xx + yy * yy);
}
int main()
{
A a(2.0, 3.0);
A b(1.0, 6.0);
get(a, b);
}
构造函数调用顺序:基类->派生类
析构函数调用顺序:派生类->基类
// const 修饰的成员必须进行初始化在构造方法里
// 而static const修饰的成员需要在类外进行初始化
#include
using namespace std;
class A
{
private:
const int age;
int const m;
static const int x;
public:
A(int age, int m) :age(age), m(m)
{
cout << "constuctor called" << endl;
}
void look() {
cout << "look" << endl;
}
};
// 初始化
const int A :: x = 99;
int main()
{
// 常对象
const A lihua(10, 20);
const A huhu(20,21);
//lihua = huhu; 错误,常对象不可以被赋值
// lihua.look(); 错误,常对象不可以访问非常成员函数
// 常成员函数只能调用常成员函数,可以访问但是不可以更改非常成员变量
}
* 在c++中不能把构造函数定义为虚构造函数
* // 虚函数本质是通过虚函数表指针来调用的,还没有对象更没有内存空间当然无法调用虚函数了,所以虚构造函数没有意义
* 析构函数是可以为虚函数的,且大多时候都声明为虚析构函数,实现正确的对象内存释放
* 纯虚函数:没有函数体的虚函数
包含纯虚函数的类就是抽象类,一个抽象类至少有一个纯虚函数
抽象类的特点:
1,无法创建对象
2,因为无法具体化,所以不能做为参数类型,返回值,强转类型
3,可以定义一个指针,引用类型,指向其派生类,来实现多态特性
1. 我们使用new开辟内存时,如果遇到空间不足,则会抛出bad_alloc异常。
2. 我们使用dynamic_cast()进行动态类型转化失败时,则抛出bad_typeid异常。
3. 我们在计算数值超过该类型表示的最大范围时,则抛出overflow_error异常,表示运算上溢,同理,underflow_error表示运算下溢。
4. 我们在使用string类下标但越界时,则抛出out_of_range异常。
程序常见的错误有: 语法错误,逻辑错误,运行异常
异常处理:
导致程序异常错误,虽然无法避免,但是可以预料,进行预见性的处理,来避免程序崩溃,从而保障程序的健壮性try
{
//正常程序执行语句
throw (异常类型表达式);
}
catch(异常类型1)
{
//异常处理代码
}
catch(异常类型2)
{
//异常处理代码
}
catch(异常类型3)
{
//异常处理代码
}
//后续代码
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int a, b;
cin >> a >> b;
try
{
if (b == 0) {
throw "error";
}
}
catch (const char* str)
{
cout << str << endl;
}
catch (int)
{
cout << "throw int" << endl;
}
return 0;
}
#include
#include
#include
using namespace std;
//异常处理
int main()
{
string *s;
try
{
s=new string("www.dotcpp.com");
cout<substr(15,5);
}
catch(bad_alloc &t)
{
cout<<"Exception occurred:"<
#include
#include
using namespace std;
int main()
{
// 读取文件内容
/*char data[100];
ifstream file;
file.open("D:\\demo.txt");
file >> data;
cout << data;
file.close();*/
// 写入
char data[100] = "how are you";
ofstream file;
file.open("D:\\demo.txt");
file << data;
file.close();
return 0;
}