[toc]
继承
◼ 继承,可以让子类拥有父类的所有成员(变量\函数)
对象的内存布局
struct Person {
};
struct Student : Person {
int m_no;
};
struct GoodStudent : Student {
int m_money;
};
◼ 父类的成员变量在前,子类的成员变量在后
成员访问权限
◼ 成员访问权限、继承方式有3种
public:公共的,任何地方都可以访问(struct默认)
protected:子类内部、当前类内部可以访问
private:私有的,只有当前类内部可以访问(class默认)
◼ 子类内部访问父类成员的权限,是以下2项中权限最小的那个
成员本身的访问权限
上一级父类的继承方式
◼ 开发中用的最多的继承方式是public,这样能保留父类原来的成员访问权限
◼ 访问权限不影响对象的内存布局
struct Person {
private:
int m_age;
public:
void setAge(int age) {
m_age = age;
}
int getAge() {
return m_age;
}
};
struct Student : public Person {
};
struct GoodStudent : public Student {
void work() {
}
};
class Person {
private:
int m_age;
public:
void run() {
}
};
struct Student : public Person {
};
class GoodStudent : public Student {
};
初始化列表
◼ 特点
一种便捷的初始化成员变量的方式
只能用在构造函数中
初始化顺序只跟成员变量的声明顺序有关
struct Person {
int m_age;
int m_height;
/*Person(int age, int height) {
m_age = age;
m_height = height;
}*/
// 语法糖
Person(int age = 0, int height = 0) :m_age(age), m_height(height) {}
};
int myAge() {
cout << "myAge()" << endl;
return 10;
}
int myHeight() {
cout << "myHeight()" << endl;
return 10;
}
struct Person {
int m_age;
int m_height;
Person(int age , int height):m_age(myAge()), m_height(myHeight()){
}
};
int main() {
Person person(18, 180);
cout << person.m_age << endl;
cout << person.m_height << endl;
}
myAge()
myHeight()
10
10
- 初始化顺序只跟成员变量的声明顺序有关
说明输出和定义在类中成员的顺序有关,和代码Person(int age , int height):m_age(myAge()), m_height(myHeight())
定义的顺序无关。
初始化列表与默认参数配合使用
◼ 如果函数声明和实现是分离的
初始化列表只能写在函数的实现中
默认参数只能写在函数的声明中
struct Person {
int m_age;
int m_height;
Person(int age = 0, int height = 0);
};
Person::Person(int age, int height) :m_age(age), m_height(height) {
}
int main() {
Person person1;
Person person2(17);
Person person(18, 180);
}
构造函数的互相调用
struct Person {
int m_age;
int m_height;
/*Person() :Person(10, 20) {
}*/
Person() :Person(10, 20) {
// 创建了一个临时的Person对象
// Person(10, 20);
/*Person person;
person.m_age = 10;
person.m_height = 20;*/
}
Person(int age, int height) {
this->m_age = age;
this->m_height = height;
}
};
int main() {
Person person;
cout << person.m_age << endl;
cout << person.m_height << endl;
return 0;
}
父类的构造函数
◼ 子类的构造函数默认会调用父类的无参构造函数
◼ 如果子类的构造函数显式地调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数
◼ 如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数
struct Person {
Person() {
cout << "Person::Person()" << endl;
}
~Person() {
cout << "Person::~Person()" << endl;
}
};
struct Student : Person {
Student() {
// call Person::Person
cout << "Student::Student()" << endl;
}
~Student() {
cout << "Student::~Student()" << endl;
// call Person::~Person
}
};
int main() {
{
Student student;
}
// Student student(18, 34);
return 0;
}
/*
Person::Person()
Student::Student()
Student::~Student()
Person::~Person()
*/
- 子类的构造函数默认会调用父类的无参构造函数
构造、析构顺序
多态
◼ 默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态
◼ 多态是面向对象非常重要的一个特性
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
在运行时,可以识别出真正的对象类型,调用对应子类中的函数
◼ 多态的要素
子类重写父类的成员函数(override)
父类指针指向子类对象
利用父类指针调用重写的成员函数
父类指针、子类指针
struct Person {
int m_age;
};
struct Student : public Person {
int m_score;
};
void test() {
// 父类指针 指向 子类对象
// Person *p = new Student();
// p->m_age = 10;
Student *p = (Student *) new Person();
p->m_age = 10;
p->m_score = 100;//危险,动了age后面的4个字节,修改了非自己内存的数据
}
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::run()" << endl;
}
};
struct Dog : Animal {
// 重写(覆写、覆盖、override)
void speak() {
cout << "Dog::speak()" << endl;
}
void run() {
cout << "Dog::run()" << endl;
}
};
struct Cat : Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
struct Pig : Animal {
void speak() {
cout << "Pig::speak()" << endl;
}
void run() {
cout << "Pig::run()" << endl;
}
};
- 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
//同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
void liu(Animal *p) {
p->speak();
p->run();
}
int main() {
liu(new Dog());
liu(new Cat());
liu(new Pig());
return 0;
}
/*
Dog::speak()
Dog::run()
Cat::speak()
Cat::run()
Pig::speak()
Pig::run()
*/
- 默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态
struct Animal {
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::run()" << endl;
}
};
/*
Animal::speak()
Animal::run()
Animal::speak()
Animal::run()
Animal::speak()
Animal::run()
*/
虚函数
◼ C++中的多态通过虚函数(virtual function)来实现
◼ 虚函数:被virtual修饰的成员函数
◼ 只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数(也就是说子类中可以省略virtual关键字)
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::run()" << endl;
}
};
int main() {
Animal *p = new Pig();
p->speak();
p->run();
}
/*
Pig::speak()
Pig::run()
*/
虚表
◼ 虚函数的实现原理是虚表,这个虚表里面存储着最终==需要调用的虚函数地址==,这个虚表也叫虚函数表
struct Animal {
int m_age;
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::run()" << endl;
}
};
struct Cat : Animal {
int m_life;
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
int main() {
cout<< sizeof (Cat)<<endl;
}
8
struct Animal {
int m_age;
virtual void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::run()" << endl;
}
};
virtual void speak()
cout<< sizeof (Cat)<<endl;
16
虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫虚函数表
(x86环境的图)
int main() {
Animal *cat = new Cat();
cat->m_age = 20;
cat->speak();
cat->run();
}
下面我们通过上述代码的内存数据及汇编指令,来窥探虚表的原理
cat堆内存数据
总结一下Animal
类由于存在虚函数virtual void speak()
和virtual void run()
所以Animal
对象创建完成后,前8个字节是一个地址指针,指向了一个虚表的地址
由于有两个虚函数,所以指向的地址,前8位speak函数的地址,后8个字节为run函数的地址。
Animal
虚表地址之后,跟着为属性成员的地址,movl $0x14, 0x8(%rax)
修改m_age
的内存地址的值为20.
0x10fe79035 <+85>: callq *(%rcx)
及 0x10fe79041 <+97>: callq *0x8(%rcx)
都是间接调用,先找到虚表地址,从内存地址中,取出函数地址间接调用到对应类对象真实实现函数的地址上。