类与类之间的关系
- 嵌套:一个类中声明了另一个类------一个类是另一个类的一部分
class B
{
public:
class A
{};
A a;
};
//访问A时要通过B的作用域
- 代理:一个类的接口是另一个类的子集------一个类的功能需要依赖另一个类的功能实现
- 继承:一个类是另一个类的一种
class Fish
{
public:
string _name;
};
class GoldFish:public Fish //继承
{
public:
string _color;
};
int main()
{
Fish fish;
fish._name = "yu";
GoldFish gfish;
gfish._name = "jinyu";
glish._color = "gold";
return 0;
}
继承
- 父类的私有成员不可以被子类所继承
- 子类中先构造父类,再构造子类,先析构子类,再析构父类
- 如果构造函数需要传参的话,必须将参数放在初始化列表中
- 父类的构造如果需要传参,就必须要写到初始化列表中
- 子类从父类继承来的成员的属性不可能超过继承的属性,eg:子类protected继承父类,则继承来的父类的成员中不可能有共有的属性
- 公有继承,父类的公有成员放在子类的公有成员中,保护成员放在子类的保护成员中,父类的私有成员不可以被继承
- 保护继承,父类的公有成员和保护成员放在子类的保护成员中,私有成员不能被继承
- 私有继承,父类的公有成员与保护成员放在了子类的私有成员中
- 在类外访问的时候,类内的私有成员和保护成员不可以被访问,公有成员可以被访问,相当于对外的接口
- 在类内访问的时候,类内的公有成员可以访问私有成员与保护成员
class Person
{
public:
void eat();
{
cout<<"eat ..."<<endl;
}
void work();
{
cout<<"work ..."<<endl;
}
protected:
private:
string _name;
int _age;
string _sex;
};
class Student:public Person
{
public:
void show()
{
cout<<"name:"<<_name<<endl;//报错,子类无法访问父类的私有成员,将父类的这三个成员属性放到protected中就没有问题了
cout<<"age:"<<_age<<endl;//报错
cour<<"sex:"<<_sex<<endl;//报错
cout<<"num:"<<_num<<endl;
}
protected:
private:
string _num;
}
#include<iostream>
using namespace std;
class Person
{
public:
Person(string name, int age, string sex, string wife = string())
{
_name = name;
_age = age;
_sex = sex;
}
void eat()
{
cout << "eat......" << endl;
}
protected:
string _name;
int _age;
string _sex;
private:
string wife;//私有成员不能被继承
};
class Student :private Person//私有继承后,父类的公有保护成员全部继承到子类的私有成员当中,在子类的公有函数当中,可以访问这些私有的成员,但是在类外不可以访问
{
public:
Student(string name, int age, string sex, string num, string wife = string())
:Person(name,age,sex,wife)//引用、成员对象需要传参和父类的构造如果需要传参,就必须要写到初始化列表中
{
_num = num;
}
protected:
private:
string _num;
};
int main()
{
Student s("zhangsan", 21, "nan", "19010101");
return 0;
}
- 隐藏:派生类会隐藏父类的同名成员------同名隐藏 ,访问父类被隐藏的成员需要加上父类的作用域
class Base
{
public:
void fun1()
{
cout << "Base::void fun1()" <<endl;
}
void fun2(int a)
{
cout << "Base::void fun2(int a)" << endl;
}
private:
int _a;
};
class Derive : public Base
{
public:
void fun1()
{
cout << "Derive::void fun1()" <<endl;
}
void fun1(int a)
{
cout << "Derive::void fun1(int a)" <<endl;
}
void fun2()
{
fun2(10);//依然会报错
cout << "Derive::void fun2()" << endl;
}
void fun2(int a,int b)
{
cout << "Derive::void fun2(int a,int b)" << endl;
}
private:
int _b;
};
int main()
{
Derive d;
d.fun1();//调用的是子类的fun1()
d.Base::fun1();//调用基类的fun1要加作用域
d.fun2(10);//会报错,为什么呢? //隐藏,调用父类被隐藏的成员时要加作用域
return 0;
}
- 构造与析构顺序: 先构造父类,在构造子类,先析构子类,再析构父类
- 父类指针指向子类对象,可以直接赋值,子类指针不可以指向父类对象
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
void fun1(int a)
{
cout << "Base::void fun1(int a)" <<endl;
}
private:
int _a;
};
class Derive : public Base
{
public:
Derive()
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
private:
int _b;
};
int main()
{
Derive d;//构造顺序,先构造父类,在构造子类,先析构子类,再析构父类
Base* pb = new Derive();//父类指针指向子类对象,可以直接赋值
pb->fun1(10);
delete pb; //在析构的时候没有析构子类的对象,为什么?
//因为编译器根据类型直接调用该类型的析构
//子类的析构函数调用完后,会自动调用父类的析构,而父类的并不会
//如何解决这一问题?虚函数virtual
return 0;
}
动多态
动多态的产生
使用指针或者引用调用虚函数,就会产生动多态
动多态的调用过程
- 使用指针或者引用调用虚函数
- 在对象中找到vfptr
- 根据vfptr找到vftable
- 在vftbale中找到要调用的函数
- 调用
vftable的产生时间
- 编译时期如果发现类中有虚函数,就会对这个类生成vftable(虚函数表)
- 将当前虚函数的指针放到vftable中
- 补充:vftable在编译时期会被放到只读数据段(.rodata)
vfptr什么时候有指向
- 当对象构造的时候,如果发现该类有vftable,就会将vftable的地址写入到这个对象之中
静多态的整个过程
- 父类先编译,如果遇到虚函数,则在数据段创建虚函数表,并且将虚函数的指针加进去
- 编译子类时,子类继承父类的虚函数表,若有同名的虚函数,则覆盖虚函数表中父类的虚函数,若子类中有新的虚函数,则加入这个虚函数表中
- 创建一个对象时,看到有虚函数表,则会自动生成一个指向虚函数表的成员指针vfptr,然后将虚函数表的指针赋值给vfptr
Base* pb = new Derive();//创建一个子类的对象,pb这个指针指向的是Derive这个对象
delete pb; //通过指针调用析构函数,此时的析构函数是虚函数,就产生了动多态的调用,
//指针pb先找到vfpte这个成员,再通过vfptr找到vftable,然后再vftable中找到析构函数
//子类函数的虚构函数调用完后,会自动调用父类的虚构函数
虚函数
- 虚函数具有传递性
- 父类中如果有虚函数,那么子类中对应的相同的函数会被传递为虚函数
- 相同的函数:同返回值,同参数名,同参数列表
- 子类的析构函数与父类的析构函数是相同的函数,都叫“析构函数”,所以父类的析构函数的可以传递给子类
覆盖(重写)
- 覆盖发生的地点:vftable
- 覆盖的原因:父类中的虚函数会被子类中相同而函数覆盖-------在子类的需函数表中覆盖的
重载,隐藏于覆盖三者之间的区别
- 重载------函数名相同,参数列表不同
- 隐藏------子类当中如果有与父类中同名的函数,则父类中同名函数会被隐藏,如果需要使用,则需要加作用域
- 覆盖------相同的函数如果在父类中加了vitual关键字,则在子类当中会将父类的同名函数覆盖
- 覆盖的函数一定会存在隐藏关系