父类和子类指针
1 父类指针可以指向子类对象,这是安全的,开发中经常用到,继承方式必须是public方式。
2 子类指针指向父类是不安全的,因为子类指针可能访问到父类以外的数据,而子类对象并没有创建。
class Person {
public:
int m_age;
};
class Student : public Person {
public:
int m_score;
};
int main() {
//父类指针指向子类对象,安全
Person *p = (Person *) new Student();
p->m_age = 10;
//子类指针指向父类对象,不安全
Student *s = (Student *) new Person();
//指向父类对象的子类指针访问了自己的成员变量,指针指向超出了范围
s->m_score = 10;
return 0;
}
多态
多态是面向对象一个非常重要的特性,同一操作作用于不同的对象,产生不同的执行结果,在运行时,可以识别出真正的对象类型,调用对应子类的函数。产生多态的条件如下:子类重写父类的成员函数,父类指针指向子类对象,利用父类指针调用重写的成员函数,这个成员函数必须是由Virtual修饰的成员函数,父类只要声明了Virtual,子类自动转为Virtual函数
class Animal {
public:
virtual void run() {
cout << "Animal::run()" << endl;
}
};
class Dog : public Animal {
public:
void run() {
cout << "Dog::run()" << endl;
}
};
class ErHa : public Dog {
public:
void run() {
cout << "ErHa::run()" << endl;
}
};
int main() {
Dog *dog0 = new Dog();
dog0->run(); //调用dog下的run函数
Dog *dog1 = new ErHa();
dog1->run();//调用ErHa下的run函数
return 0;
}
以上就是虚函数实现的列子,我们再进一步,为什么不加Virtual 就不能实现多态了,加了Virtual 就能实现多态呢,多态的实现是靠虚表实现的。我们先看看这几个类的大小
//8
cout << sizeof(Dog) << endl;
//8
cout << sizeof(ErHa) << endl;
//8
cout << sizeof(Animal) << endl;
这几个类sizeof大小是8,我们这是64位的,其实这个新增加的大小就是虚表的地址,指向虚表,而且这个虚表地址在类的最前面,而虚表里面存着本类虚函数的地址,从而进行真正的调用,每一个类只有一份虚表,多个对象共享一份虚表,无论这个对象是在堆还是栈还是全局对象。我们可以通过反汇编和内存调试也能看出来,这里我就不演示了。当父类实现了虚函数,而子类没有实现该虚函数的时候,我们来看看这个情况:
class Animal {
public:
virtual void run() {
cout << "Animal::run()" << endl;
}
virtual void speak() {
cout << "Animal::speak()" << endl;
}
};
class Dog : public Animal {
public:
int m_age; //dog的age
};
int main() {
Animal *animal = new Animal();
animal->run();会调用Animal::run()
animal->speak();会调用Animal::speak()
Dog *dog0 = new Dog();
dog0->run(); //会调用Animal::run()
dog0->speak(); //会调用Animal::speak()
return 0;
}
当子类没有重写父类虚函数的时候,它也会调用父类的虚函数,其底层实现也是通过虚表查找到函数调用,也就是子类即时没有虚函数,也有自己的虚表,我反汇编看到此时父类子类的虚表地址一样,可能不同的平台有不同的处理,虚表没有继承一说。如果子类想调用父类的虚函数方法的时候,应该显示调用,注意C++ 没有super等类似关键字,正确调用如下:
class Animal {
public:
virtual void run() {
cout << "Animal::run()" << endl;
}
virtual void speak() {
cout << "Animal::speak()" << endl;
}
};
class Dog : public Animal {
public:
int m_age; //dog的age
void run() {
Animal::run(); //直接用类::显示调用
cout << "Dog::run()" << endl;
}
void speak() {
Animal::speak(); //直接用类::显示调用
cout << "Dog::speak()" << endl;
}
};
int main() {
Dog *dog0 = new Dog();
dog0->run();
dog0->speak();
return 0;
}
含有虚虚函数实现的父类时候,父类的析构函数也需要声明为virtual函数,此时析构函数变成了虚析构函数,这样才能够保证销毁对象的时候父类子类析构函数调用,保证析构的完整性,子类可不加(virtaul)。
纯虚函数
没有函数体,且初始化为0的虚函数,用来定义接口规范
class Animal {
public:
virtual void speak() = 0;
virtual void run() = 0;
};
含有纯虚函数的类是抽象类,不可以实例化,不能创建对象,抽象类成也可以包含其它非纯虚函数,以及其他成员变量,抽象类的指针可以指向子类对象,如果父类是抽象类,子类没有完全实现纯虚函数,那么这个子类依然是抽象类
多继承
C++允许一个类继承多个类,可以拥有多个类的特性,多继承增加了设计的复杂性,不建议使用。
#include <iostream>
using namespace std;
class Student {
public:
int m_score;
Student(int score = 0) :m_score(score) { }
void study() {
cout << "Student::study() - score = " << m_score << endl;
}
~Student() {
cout << "~Student" << endl;
}
};
class Worker {
public:
int m_salary;
Worker(int salary = 0) :m_salary(salary) { }
void work() {
cout << "Worker::work() - salary = " << m_salary << endl;
}
~Worker() {
cout << "~Worker" << endl;
}
};
class Undergraduate : public Student, public Worker {
public:
int m_grade;
Undergraduate(
int score = 0,
int salary = 0,
int grade = 0) :Student(score), Worker(salary), m_grade(grade) {
}
void play() {
cout << "Undergraduate::play() - grade = " << m_grade << endl;
}
~Undergraduate() {
cout << "~Undergraduate" << endl;
}
};
int main() {
{
Undergraduate ug;
ug.m_score = 100;
ug.m_salary = 2000;
ug.m_grade = 4;
ug.study();
ug.work();
ug.play();
}
cout << sizeof(Undergraduate) << endl;
return 0;
}
注意:这种多继承,父类的成员变量在子类前面,先继承谁,谁的成员就在前面。多继承的构造函数一样需要使用初始化列表调用父类构造函数,
父类如果都含有虚函数,子类多继承多个父类后,子类对象会产生多个虚函数表,顺序跟继承顺序有关。
#include <iostream>
using namespace std;
class Student {
public:
virtual void study() {
cout << "Student::study()" << endl;
}
};
class Worker {
public:
virtual void work() {
cout << "Worker::work()" << endl;
}
};
class Undergraduate : public Student, public Worker {
public:
void study() {
cout << "Undergraduate::study()" << endl;
}
void work() {
cout << "Undergraduate::work()" << endl;
}
void play() {
cout << "Undergraduate::play()" << endl;
}
};
int main() {
//含有16个字节,因为有2张虚表
cout << sizeof(Undergraduate) << endl;
Student *stu = new Undergraduate();
stu->study();
Worker *worker = new Undergraduate();
worker->work();
return 0;
}
同名成员
C++允许同名成员函数,同名成员变量,子类不会覆盖,访问的时候加上类名表示作用域,如下所示:
#include <iostream>
using namespace std;
class Student {
public:
int m_age;
};
class Worker {
public:
int m_age;
};
class Undergraduate : public Student, public Worker {
public:
int m_age;
};
int main() {
Undergraduate ug;
ug.m_age = 10;
ug.Student::m_age = 20;
ug.Worker::m_age = 30;
ug.Undergraduate::m_age = 40;
//这里等于12
cout << sizeof(Undergraduate) << endl;
return 0;
}
虚继承
虚继承是为了解决菱形继承带来的成员变量冗余,重复。而且最底层子类因为二义性无法访问基类的的成员变量。我们先来看看菱形继承:
#include <iostream>
using namespace std;
class Person {
public:
int m_age;
};
class Student : public Person {
public:
int m_score;
};
class Worker : public Person {
public:
int m_salary;
};
class Undergraduate : public Student, public Worker {
public:
int m_grade;
};
int main() {
Undergraduate ug;
ug.m_grade = 10;
ug.m_score = 20;
ug.Student::m_age = 20;
ug.Worker::m_age = 30;
cout << sizeof(Undergraduate) << endl; //20
return 0;
}
这里我们看到Undergraduate的大小是20,因为这样继承Undergraduate的父类两个类都有m_age成员变量,而且访问的时候我们需要通过作用域去访问,直接访问会报错。这种继承方式,基类的成员变量在最底层子类就冗余了,没有必要,为了解决这种问题,可以使用虚继承。加上virtual关键字:
#include <iostream>
using namespace std;
class Person {
public:
int m_age;
};
class Student :virtual public Person {
public:
int m_score;
};
class Worker :virtual public Person {
public:
int m_salary;
};
class Undergraduate : public Student, public Worker {
public:
int m_grade;
};
class Person1
{
};
int main() {
Undergraduate ug;
ug.m_grade = 10;
ug.m_score = 20;
ug.m_age = 30;
return 0;
}
此时三个类比如:Student、Worker、Undergraduate都有了虚函数表指针,此时成员变量m_age在最底层子类只有一份内存。此时Person类被称为虚基类。
网友评论