最近在学C++,遇到许多有关虚字的名词,特此记录下,先来看虚函数,虚函数的应用场景是什么呢,通常发生在多态中,什么是多态呢,通俗的来将是为了实现某种功能,不同的对象可以表现出不同的状态。请看以下代码,
#include<iostream>
#include<stdio.h>
using namespace std;
/*
静态多态的地址早绑定->编译阶段确定函数地址
动态多态的地址晚绑定->运行阶段缺点函数地址
*/
class Animal
{
public:
void say()
{
cout<<"动物在说话"<<endl;
}
};
class Dog:public Animal
{
public:
void say()
{
cout<<"狗在说话"<<endl;
}
};
void work(Animal *animal)
{
animal->say();
}
int main()
{
Dog dog = Dog();
work(&dog);
return 0;
}
创建了一个Animal类,其成员函数为say,子类Dog也有一个成员函数say,然后通过animal类指向dog对象。我们来看下这段程序的结果
PS C:\Users\14499\Desktop\vsProjects> & 'c:\Users\14499\.vscode\extensions\ms-vscode.cpptools-1.13.9-win32-x64\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-ahdubnzu.c2g' '--stdout=Microsoft-MIEngine-Out-db2dfebr.omi' '--stderr=Microsoft-MIEngine-Error-njlx5n3r.b01' '--pid=Microsoft-MIEngine-Pid-40upwgii.0fr' '--dbgExe=C:\Users\14499\Desktop\mingw64\bin\gdb.exe' '--interpreter=mi'
动物在说话
PS C:\Users\14499\Desktop\vsProjects>
这里执行了父类的成员函数,如何访问子类dog的成员函数呢,在父类成员函数加个关键字virtual,此时子类dog的成员函数重写或者覆盖了父类的成员函数。总之虚函数的目的是让父类可以访问子类的成员函数->实现重写->泛型编程->接口的统一性。那纯虚函数有是什么呢?抽象类由纯虚函数实现的,代码如下
#include<stdio.h>
#include<iostream>
using namespace std;
class Base
{
public:
virtual void func() = 0;//纯虚函数->只要有一个纯虚函数,这个类称为抽象类,无法实例化
//抽象类的子类,必须重写父类的纯虚函数,否则也为抽象类,无法实例化
};
class Son:public Base
{
public:
void func()
{
cout<<666<<endl;
}
};
int main()
{
Son s;
s.func();
return 0;
}
抽象类不能实例化,子类要想实例化必须重写纯虚函数。再来看虚继承,来看下案例。
像这种B,C继承A,DD继承B和C,一般也成为菱形继承。代码如下所示
#include<stdio.h>
#include<iostream>
using namespace std;
class A
{
public:
int a_ = 10;
};
class B: public A
{
};
class C: public A
{
};
class DD:public B,public C
{
};
int main()
{
DD d = DD();
cout<<d.a_<<endl;
return 0;
}
这样输入a_属性会报错,因为不知道是B类还是C类的成员。当然,你可以直接使用类作用域来区分,像这样
cout<<d.B::a_<<endl;
cout<<d.C::a_<<endl;
但是为了更好的封装,一般在继承的公共父类前加上virtual关键字,如下所示
#include<stdio.h>
#include<iostream>
using namespace std;
class A
{
public:
int a_ = 10;
};
class B: virtual public A//虚继承
{
};
class C: virtual public A
{
};
class DD:public B,public C
{
};
int main()
{
DD d = DD();
cout<<d.a_<<endl;
return 0;
}
我们来看下内部实现,我们利用vs的命令行工具来看,使用命令cl /d1 reportSingleClassLayoutDD "virtualinherit.cpp"来查看其结构,如图所示
用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.34.31937 版
版权所有(C) Microsoft Corporation。保留所有权利。
virtualinherit.cpp
virtualinherit.cpp(1): warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失
class DD size(12):
+---
0 | +--- (base class B)
0 | | {vbptr}
| +---
4 | +--- (base class C)
4 | | {vbptr}
| +---
+---
+--- (virtual base A)
8 | a_
+---
DD::$vbtable@B@:
0 | 0
1 | 8 (DDd(B+0)A)
DD::$vbtable@C@:
0 | 0
1 | 4 (DDd(C+0)A)
vbi: class offset o.vbptr o.vbte fVtorDisp
A 8 0 4 0
D:\Program Files\vs2022\VC\Tools\MSVC\14.34.31933\include\ostream(287): warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc
D:\Program Files\vs2022\VC\Tools\MSVC\14.34.31933\include\ostream(272): note: 在编译 类 模板 成员函数“std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(int)”时
virtualinherit.cpp(28): note: 查看对正在编译的函数 模板 实例化“std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(int)”的引用
virtualinherit.cpp(28): note: 查看对正在编译的 类 模板 实例化“std::basic_ostream<char,std::char_traits<char>>”的引用
Microsoft (R) Incremental Linker Version 14.34.31937.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:virtualinherit.exe
virtualinherit.obj
再对比下没用用virtual关键字的对应的内存结构
用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.34.31937 版
版权所有(C) Microsoft Corporation。保留所有权利。
virtualinherit.cpp
virtualinherit.cpp(1): warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失
class DD size(8):
+---
0 | +--- (base class B)
0 | | +--- (base class A)
0 | | | a_
| | +---
| +---
4 | +--- (base class C)
4 | | +--- (base class A)
4 | | | a_
| | +---
| +---
+---
Microsoft (R) Incremental Linker Version 14.34.31937.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:virtualinherit.exe
virtualinherit.obj
没用virtual时DD类有2份数据,使用了virtual通过vbptr指针指向一份数据,实现共用的效果,减少了内存的开销,我们用代码来验证看下改变类中的一个,另一个类中a_是否和此类中的a_一致,代码如下
#include<stdio.h>
#include<iostream>
using namespace std;
class A
{
public:
int a_ = 10;
};
class B: virtual public A//虚继承
{
};
class C: virtual public A
{
};
class DD:public B,public C
{
};
int main()
{
DD d = DD();
d.B::a_ = 20;
d.C::a_ = 30;
cout<<d.a_<<endl;
return 0;
}
vscode运行结果如下
PS C:\Users\14499\Desktop\vsProjects> & 'c:\Users\14499\.vscode\extensions\ms-vscode.cpptools-1.13.9-win32-x64\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-ox4pnws1.01m' '--stdout=Microsoft-MIEngine-Out-gnqpfih5.mbz' '--stderr=Microsoft-MIEngine-Error-msnjczvp.zgb' '--pid=Microsoft-MIEngine-Pid-fbvxfvcz.0pt' '--dbgExe=C:\Users\14499\Desktop\mingw64\bin\gdb.exe' '--interpreter=mi'
30
得到验证!现在我们来看虚析构函数,代码如下
#include<iostream>
#include<stdio.h>
using namespace std;
//多态:把子类对象伪装成父类类型
//使用虚析构函数->多态中释放子类的堆区的数据
class Father
{
public:
~Father();//虚析构
Father();
virtual void func() = 0;//抽象类
};
class Son:public Father
{
public:
Son(string name)
{
p_name = new string(name);//从堆区开辟内存
cout<<"son构造函数"<<endl;
}
~Son()
{
cout<<"son析构函数"<<endl;
}
void func()
{
if(p_name !=NULL)//释放堆区数据
{
delete p_name;
p_name = NULL;
}
cout<<"儿子"<<endl;
}
string *p_name;
};
Father::Father()
{
cout<<"父类构造函数"<<endl;
}
Father::~Father()
{
cout<<"父类析构函数"<<endl;
}
int main()
{
//多态特性
Father *f = new Son("lyp");//堆区开辟内存
f->func();
delete f;
return 0;
}
运行结果如下所示
父类构造函数
son构造函数
儿子
父类析构函数
可以看见,在多态中无法释放子类中的堆区数据,容易造成内存泄露,因此必须使用virtual关键字,注意必须在父类的析构函数前面加上virtual,如下所示
#include<iostream>
#include<stdio.h>
using namespace std;
//多态:把子类对象伪装成父类类型
//使用虚析构函数->多态中释放子类的堆区的数据
class Father
{
public:
virtual ~Father();//虚析构
Father();
virtual void func() = 0;//抽象类
};
class Son:public Father
{
public:
Son(string name)
{
p_name = new string(name);//从堆区开辟内存
cout<<"son构造函数"<<endl;
}
~Son()
{
cout<<"son析构函数"<<endl;
}
void func()
{
if(p_name !=NULL)//释放堆区数据
{
delete p_name;
p_name = NULL;
}
cout<<"儿子"<<endl;
}
string *p_name;
};
Father::Father()
{
cout<<"父类构造函数"<<endl;
}
Father::~Father()
{
cout<<"父类析构函数"<<endl;
}
int main()
{
//多态特性
Father *f = new Son("lyp");//堆区开辟内存
f->func();
delete f;
return 0;
}
点击此处登录后即可评论