一、 类
类成员属性
C++通过 public 、 protect 、 private 三个关键字来控制成员变量和成员函数的访问权限。
成员访问权限:
- public: 在类内部可以互相访问,没有权限限制,在类外可以通过实例化对象进行访问。
- protect: 在类内部可以互相访问,没有权限限制。在类外无法通过实例化对象访问累得protect成员,但可以通过友元函数和友元类进行访问。
- private: 在类内部可以互相访问,没有权限限制。在类外无法通过实例化对象访问累得protect成员,但可以通过友元函数和友元类进行访问。
继承访问权限:
-
public继承 子类的成员函数可以访问父类的public、protect成员,但无法访问父类的private成员 子类的实例对象可以访问父类的public成员,但无法访问protect、private成员 -
protect继承 通过protect继承,父类的public成员在子类中的权限变为protect,其余的不变 子类的成员函数可以访问父类的public、protect成员,但无法访问父类的private成员 子类的实例对象无法访问任何父类成员,因为父类的public成员在子类中已经变成了protect -
private继承 通过private继承,父类中的所有成员在子类中权限都变为private 子类的成员函数可以访问父类的public、protect成员,但无法访问父类的private成员 子类的实例对象无法访问任何父类成员,因为父类的public成员在子类中已经变成了private
构造函数和析构函数
-
构造函数 类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。 -
带参数的构造函数 默认的构造函数是不带参数的,如果需要用到含参构造函数可以通过重载 实现。含参构造函数 可以在实例化对象的时候同时给类成员赋初始值。
C++类内如需定义引用数据成员需要通过含参构造函数初始化列表初始化。
#include<iostream>
class A
{
public:
int &a;
A(&b):a(b){}
};
int main()
{
int b = 21;
A demo(b);
std::cout<<demo.a<<'\n';
return 0;
}
-
拷贝构造函数 拷贝构造函数只有一个参数,就是当前类的实例化对象的引用。如果类的设计者不写拷贝构造函数则编译器会自动生成一个默认拷贝构造函数 其作用是实现从源对象到目标对象逐个字节的复制。拷贝构造函数的参数可以是 const引用也可以是非const引用,甚至可以在一个类中写两个拷贝构造函数,但是参数必须是对象的引用
Q: 拷贝构造函数为什么不能直接进行值传递而必须用引用呢?
A: 如果直接使用值传递的话,在调用拷贝构造函数的时候,会自动生成一个临时对象保存传进来的实参并将其传给形参,在这个过程中有需要调用拷贝构造函数,如此一来就会形成循环调用,最终不但无法完成拷贝,还会造成栈溢出。
-
析构函数 析构函数是一种特殊的成员函数,其名字与类名相同,没有参数和返回值,通过在名字前加一个**“~”** 取反符号声明。一个类可以拥有多个构造函数,但是有且仅有一个析构函数 。析构函数会在对象消亡的时候自动调用,用以在撤销对象占用的内存空间前做一些善后工作。如果类的设计者不写析构函数,编译器会自动生成一个缺省的析构函数,这个析构函数什么也不做。
空类大小
class A{}; //这个空类大小会是0吗?
C++并不允许一个类的大小为0,我们假设一个空类大小为0,当我们用这个类去实例化很多对象的时候,这些对象大小都是0,对应内存中的地址也是一样的,那我们用什么区分这些不同的对象呢?所以为了区分不同的空类实例化对象,编译器会往空类里面隐式的添加一定大小的字节信息,这个大小由编译器决定,在VS下是1个字节.
二、 虚函数
虚函数由 virtual 关键字声明,是实现C++多态的重要手段。虚函数只能借助于指针或者引用来达到多态的效果,直接声明的类对象无法达到多态的目的。虚函数的调用值取决于指针或引用所 指向的对象的类型 ,而与指针本身的类型无关。比如父类指针指向子类对象,调用的是子类重写后的虚函数
虚函数实现多态主要通过虚函数表vtable 和虚表指针 vptr 实现。
虚函数表和虚表指针
-
在拥有虚函数的类中,类的最开始部分是一个虚表指针,这个指针指向该类的一个虚函数表,表中存放的该类中虚函数的地址,实际的虚函数代码存放在内存中的text段中。如果子类继承了父类,那么就会同时继承父类的虚函数表,如果子类对父类中定义的虚函数进行了重写,那么其继承的虚函数表中相应的虚函数地址就会被替换成重写之后的虚函数的地址。 -
使用了虚函数,会增加内存的开销,降低效率,因此对于不会被继承的类,不会为其声明虚函数,这也是为什么默认的析构函数不是虚函数 的原因。 -
对于拥有虚函数的类,编译器都会为其添加一个隐式的虚表指针成员,故拥有虚函数的类sizeof 大小至少都为4(一个指针的大小)
析构函数与虚函数
当一个类有可能会被其他类继承的时候,就应该将此类的析构函数声明为虚函数,这么做可以保证当我们释放一个指向子类的父类指针时可以释放子类的内存,以免出现内存泄漏。
纯虚函数和抽象类
- 纯虚函数: 在父类中只声明不定义并要求其每个子类都进行重写的虚函数。纯虚函数为所有继承它的子类提供了一个可重写的接口,但父类的这个版本绝不会被调用。在父类中纯虚函数的声明方法就是在函数原型后面加 “=0”
- 抽象类: 拥有纯虚函数的类即为抽象类。 抽象类不能实例化对象!
|