接上一篇,本篇主要是面向对象相关的知识点
类
C++可以使用struct
和class
定义一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct Person { int age; void run { cout << "person run" << endl; } };
class Person { public: int age; void run { cout << "person run" << endl; } }
|
struct
和class
的区别: class默认权限为private,struct默认权限为public,推荐使用class
嵌套类
C++中类支持嵌套定义,本质是语法糖,用于限制作用域
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Person { class Car {
} }
void func() { class Point { int x; int y; } }
|
函数调用
1 2 3
| Person p; p.age = 20; p.run();
|
转成汇编
1 2 3 4 5 6 7
| mov d word ptr [ebp-0Ch], 0Ah
; 传递调用者到exc,也就是p lea ecx, [ebp-0Ch]
; 调用函数 call 00061366
|
C++对象的内存结构如下
对象的本质,其实就是字段(和虚表指针)的内存集合,先定义的对象会放在低地址,后定义的对象防止高地址
this
this
是调用对象的地址,为指针,当对象调用成员函数的时候,会自动传入当前对象的指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Person { private: int m_age; public: void func() { this -> m_age = 0; } };
int main() { Person p = Person(); p.func(); return 0;
}
|
由汇编可以看出,通过寄存器传递this指针,this指针存放对象的地址
堆空间初始化
1 2 3 4 5 6 7 8 9 10
| int *p1 = new int; int *p2 = new int(); int *p3 = new int(5); int *p4 = new int[3]; int *p5 = new int[3](); int *p6 = new int[3]{}; int *p7 = new int[3] {5};
Person *p8 = new Person; Person *p9 = new Person();
|
对象内存分布
1 2 3 4 5 6 7 8 9 10 11
| Person g_person;
int main() { Person person;
Person *p = new Person(); return 0; }
|
构造函数/析构函数
构造函数支持重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| struct Person { int m_age; Person() { m_age = 10; }
Person(int age) { m_age = age; }
~Person() { } };
Person person(20); cout << person.age << endl;
|
如果自定义了构造函数,除了全局区(默认会初始化为0),其他内存空间的成员变量都需要手动初始化
1 2 3 4 5 6 7 8 9
| struct Person { int age; Person() {
msmset(this, 0, sizeof(Person)); } };
|
构造函数和析构函数必须是public
,如果定义为private
,则该类无法被外部创建
构造函数先调用父类,后调用子类
析构函数先调用子类,后调用父类
成员访问权限
权限与JAVA一样,对类的成员变量声明权限(struct
默认为public,class
默认为private)
public
:公开访问(struct默认
)
protected
:当前类和子类访问
private
:私有(class默认
)
继承关系也可以添加权限,表示继承的成员对子类的权限,struct
默认为public,class
默认为private,class要手写public
1 2 3 4
| struct Student: private Person { int m_score; };
|
初始化列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct Person { int m_age; int m_height;
Person(int age, int height): m_age(age), m_height(height) {
} };
|
初始化顺序跟成员变量的声明顺序有关系,和列表顺序无关,下面写法是一样的
1 2 3 4
| Person(int age, int height): m_age(age), m_height(height) { }
Person(int age, int height): m_height(height), m_age(age) { }
|
构造函数互相调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct Person { int m_age; int m_height;
Person() : Person(0, 0) { }
Person(int age, int height) { m_age = age; m_height = height; } };
|
父类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| struct Person { private: int m_age; Person() { m_age = 0; }
Person(int age): m_age(age) {
} };
struct Student : Person {
Student() : Person(10) {
} };
|
- 如果父类没有构造函数,则不调用
- 如果父类有构造函数,子类没有定义构造函数,则子类会隐式生成构造函数,并且调用父类的构造函数
调用父类方法
继承关系中调用父类方法,通过类名调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Person { public: void func() { } }
class Student: public Person { public: void func() { Person::func(); }
Student &operator=(const Student &student) { Person::operator=(student); ... } }
|
拷贝构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Person { public: int m_age;
Person() {}
Person(const Person &person): m_age(person.m_age) { } };
int main() { Person p1 = Person(); Person p2;
Person p3 = p1; Person p4(p1);
p2 = p1; return 0; }
|
隐式构造
1 2 3 4 5 6 7 8 9 10 11
| class Person { private: int m_age; public: Person(int age): m_age(age) { } };
Person p1(10);
Person p2 = 20;
|
可读性差,尽量不用,可以通过关键字explicit
禁用隐式构造
1 2
| explicit Person(int age): m_age(age) { }
|
自动生成构造函数
在下面情况下,编译器会自动为类生成无参构造函数
- 父类存在构造函数,子类没有定义构造函数
- 虚继承(需要做虚表指针的初始化)
- 有虚函数(需要做虚表指针的初始化)
- 类包含了有构造函数的成员,并且没有定义构造函数,则编译器会自动生成构造函数初始化成员
- 类的字段在声明的时候进行了初始化
仿函数
相当于直接调用对象,本质是重载()
运算符
1 2 3 4 5 6 7 8 9 10
| class Sum { public: int operator()(int a, int b) { return a + b; } }
auto sum = Sum();
auto res = sum(10, 20);
|
深拷贝/浅拷贝
多态
C++的多态通过虚函数
来实现
虚函数
需要在父类方法实现virtual函数才能使用多态,否则不是多态(不加virtual
,编译器直接根据类型调用对应的函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| struct Animal { void speak() { cout << "animal speak" << endl; } };
struct Dog: Animal { void speak() { cout << "dog speak" << endl; } };
void func(Animal *ani) { ani.speak(); }
int main() { Dog *dog = new Dog(); func(dog);
delete dog; return 0; }
|
修改Animal::speak
改为virtual
1 2 3 4 5 6 7
| struct Animal { virtual void speak() { cout << "animal speak" << endl; } };
|
- 纯虚函数:没有实现的虚函数,后面用等于0
- 抽象类:含有纯虚函数的类,抽象类不能被实例化
1 2 3 4 5
| struct Animal { virtual void speak() = 0; virtual void run() = 0; }
|
虚表
虚函数
的多态特性是通过虚表
来实现的,如果一个类对象有虚函数,则会多4或8个字节
,32或64位环境不同,并且多出来的内存是放在对象首地址
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct Animal { int m_age; virtual void speak() { cout << "animal speak" << endl; }
virtual void run() { cout << "animal run" << endl; } };
cout << sizeof(Animal) << endl;
|
原理:多出的4/8个字节用来存储虚函数表的地址,虚函数表存放着对象的虚函数地址,编译器在运行时通过该表,找到对应的函数地址执行,从而达到多态的目的
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Animal *ani = new Dog();
ani.speak();
ani.run();
|
从汇编代码可以看出,虚函数的调用过程
- 通过对象存放的
虚表地址
(对象首地址)
- 通过虚表地址找到虚表,从虚表中找到对应函数的地址
- 调用函数
同个类所有对象共用一份虚表,不管对象使用什么指针接收,最终都会调用虚表的方法,也就是对象真正的方法
注意:如果子类没有重写父类的虚函数,父类的虚函数编译的时候也会被放到子类的虚表里面,也就是函数调用在编译的时候就确定了,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct Animal { virtual void speak() { } virtual void run() { } }
struct Dog : Animal { void run() { } }
struct WhiteDog: Dog {
}
WhiteDog *dog = new WhiteDog();
|
多态的调用行为在编译后就确定了,而不是运行时动态确定
多态-析构函数
由于C++多态使用的虚表实现的,对于多态,析构函数也要使用虚函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| using namespace std;
struct Animal { ~Animal { cout << "Animal::~Animal" << endl; } }
struct Dog: Animal { ~Dog { cout << "Dog::~Dog" << endl; } }
Animal *ani = new Dog();
delete ani;
|
需要把Animal的析构函数设置为virtual
1 2 3 4 5 6
| struct Animal { virtual ~Animal { cout << "Animal::~Animal" << endl; } }
|
多态继承
如果要用父类指针指向子类对象,则继承的权限必须是public
1 2 3 4 5 6 7 8 9
| class Person { }
class Student: public Person {
}
Person *person = new Student()
|
调用父类方法
直接通过类名调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| using namespace std;
struct Animal { virtual void speak() { cout << "animal speak" << endl; }
void run() { } }
struct Dog: Animal { void speak() { Animal::speak();
Animal::run();
cout << "animal speak" << endl; } }
|
多继承
C++支持多继承(不建议使用
,会增加程序设计的复杂度)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Student { public: int m_score; void study() { } };
class Worker { public: int m_salary; void work() { } };
class Undergraduate: public Student, public Worker { public: int m_grade; void play() { } };
int main() { Undergraduate ug; ug.m_score = 10; ug.m_salary = 20; ug.m_grade = 30; ug.study(); ug.work(); ug.play();
return 0; }
|
内存分布
多继承-虚函数
多继承的多态也是通过虚函数实现的,单继承一样,但是由于又多个父类,多继承会又多个虚表地址
多继承的子类会产生多张虚表,分别对应不同父类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Student { public: int m_score = 1; virtual void func() { cout << "student func" << endl; } };
class Worker { public: int m_salary = 2; virtual void func() { cout << "worker func" << endl; } };
class Undergraduate : public virtual Student, public virtual Worker { public: int m_grade = 3; void func() { cout << "undergraduate func" << endl; } };
int main() { Undergraduate *undergraduate = new Undergraduate(); Student *stu = undergraduate; cout << stu->m_score; << endl; stu->func(); return 0; }
|
同名函数/变量问题
对于同名变量,C++默认会当成多个变量来存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Student { public: int m_age = 1; } class Worker { public: int m_age = 2; } class Undergraduate { public: int m_age = 3; }
int main() { Undergraduate *under = new Undergraduate(); cout << under->m_age << endl;
Student* stu = under;
cout << stu->m_age << endl; return 0; }
|
内存分布
菱形继承
由于C++支持多继承,所以会出现菱形继承这种情况,我们知道子类会继承父类的所有成员变量(包括同名的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Person { public: int m_age = 1; };
class Student: public Person { public: int m_score = 2; };
class Worker: public Person { public: int m_salary = 3; };
class Undergraduate : public Student, public Worker { public: int m_grade = 4; };
int main() { Undergraduate *under = new Undergraduate();
under->m_age = 10;
under->Student::m_age = 11; under->Worker::m_age = 11;
return 0; }
|
Undergraduate
的内存分布
虚继承
上面Undergraduate
会继承两份Person的成员m_age,为了避免这种情况(通常我们希望只继承一份成员变量),C++使用虚继承
解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Person { public: int m_age = 1; };
class Student: virtual public Person { public: int m_score = 2; };
class Worker: virtual public Person { public: int m_salary = 3; };
class Undergraduate : public Student, public Worker { public: int m_grade = 4; };
int main() { Undergraduate *under = new Undergraduate();
under->m_age = 10; cout << sizeof(Undergraduate) << endl; return 0; }
|
内存分布
可以看到出多了两个虚表,用于声明基类字段m_age的偏移量,m_age
只有一份
静态成员
友元
友元方法
:可以在类外部的方法访问类的所有成员(字段和方法)
友元类
:可以在其他类访问类的所有成员(字段和方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Point { friend Point add(const Point &a, const Point &b);
friend class Math;
private: int m_x; int m_y; void func() { } public: Point(int x, int y): m_x(x), m_y(y) { } };
Point add(const Point &a, const Point &b) { return Point(a.m_x + b.m_x, a.m_y + b.m_y); }
class Math { void func() { Point p = Point(1, 2); p.m_x = 10; p.m_y = 20; p.func(); } };
|
下一篇主要是C++的新特性