c++--oop
Table of Contents
- 1. 实现原理
- 2. 堆空间操作
- 3. 构造函数
- 4. 成员初始化
- 5. 析构函数
- 6. 名字空间
- 7. 成员访问权限
- 8. 继承
- 9. 构造函数初始化列表
- 10. 构造函数调用构造函数
- 11. 子类构造函数调用父类构造函数
- 12. 子类父类析构调用顺序
- 13. 父类类型的指针可以指向子类
- 14. 多态(同一操作作用不同对有不同行为)
- 15. 有虚函数的内存布局
- 16. 子类调用父类的成员阐述
- 17. 有父类指针指向子类时,父类析构函数也应该是虚函数
- 18. 纯虚函数
- 19. 多继承使用场景
- 20. static
- 21. const
- 22. 引用类型初始化
- 23. 拷贝构造函数
- 24. 深/浅拷贝
1 实现原理
1.1 原始方式
#include <iostream> using namespace std; struct Person { int mAge; void info(const Person *p) { cout << p->mAge << endl; } }; int main() { Person p1, p2, p3; p1.mAge = 10; p2.mAge = 20; p3.mAge = 30; p1.info(&p1); p2.info(&p2); p3.info(&p3); getchar(); return 0; }
1.2 编译器将调用方法的对象地址传入调用方法中,就是 this 常指针
#include <iostream> using namespace std; struct Person { int mAge; void info() { cout << this->mAge << endl; } }; int main() { Person p1, p2, p3; p1.mAge = 10; p2.mAge = 20; p3.mAge = 30; p1.info(); p2.info(); p3.info(); getchar(); return 0; }
1.3 成员函数中的变量根据作用域链查找变量
- 函数内部变量
- 对象成员
- 对象外层
struct Person { int mAge; void info() { cout << mAge << endl; } };
1.4 本质
类似 js 函数调用,谁莱调用就传递谁(this 就是指向谁)
- 编译器只是把调用对象的地址传入成员函数
- 对象调用传递的是对象地址,指针调用传递的是指针地址中的内容(指针指向的地址)
- 函数使用成员函数会直接使用偏移量而不会管调用对象是个什么东西
#include <iostream> using namespace std; int mAge = 100; struct Person { int mAge; int mId; void info() { cout << "age: " << mAge << " id: " << mId << endl; } }; int main() { Person obj; obj.mAge = 10; obj.mId = 1001; Person* p1 = (Person *)&obj; Person* p3 = (Person*)&obj.mAge; Person* p2 = (Person*)&obj.mId; obj.info(); p1->info(); p2->info(); p3->info(); getchar(); return 0; }
2 堆空间操作
2.1 申请和释放
int* p1 = (int*)malloc(sizeof(int)); free(p1); int* p2 = new int; delete p2; size_t size = sizeof(int) * 10; int* p3 = (int*)malloc(size); free(p3); int* p4 = new int[10]; delete[] p4;
2.2 初始化
size_t size = sizeof(int) * 10; int* p1 = (int*)malloc(size); memset(p1, 0, size);//这个灵活,效率,推荐使用 free(p1); int* p2 = new int(1); delete p2; int* p3 = new int[10]();//只能使用默认值 delete[] p3; int* p4 = new int[10]{};//使用默认值 delete[] p4; int* p5 = new int[10]{1};//除了第一个指定的,其余默认 delete[] p5;
3 构造函数
class Person{ public: Person(int age=0){ m_age = age; } int m_age; };
- 构造函数没有返回值,会自动被调用
- 构造函数可以重载
- 自定义了构造函数,创建对象一定会调用其中的一个,没有就报错
- malloc 申请分配转的对象不会调用构造函数
- 构造函数和析构函数必须放在 public 区
3.1 创建对象
Person *p = new Person();
3.2 函数声明
无参数,返回类型为 Person 的函数
Person person();
4 成员初始化
4.1 没有构造函数
- 栈空间的对象,直接使用成员会报错,未初始化(栈空间的对象是局部对象)
- 全局区数据默认值为0
- 堆分配的空间调用构造函数也会初始化未默认值
4.2 有构造函数,没有赋值操作(未初始化)
- 全局区数据默认值为0,之后还是会调用构造函数,没有操作故值不变
- 堆分配的空间调用构造函数,会执行构造函数,值都是随机的
5 析构函数
- 析构函数和构造函数必须放在 public 区
- 析构函数有且只有一个
- free 释放对空间不会调用析构函数
6 名字空间
int a = 10; namespace TT { void fn(){ int a = 0; } } ::a = 123;//全局名字空间 TT::fn();//使用名字空间的对象 //using namespace TT; // 暴露名字空间的所有对象 //TT::XX::tt;//使用嵌套名字空间XX中的对象tt
7 成员访问权限
7.1 private 一般情况下,外界不能成员及方法
7.2 protected 和 private 基本相同
7.3 public 外界随意访问和修改
//两者等价 struct Person { int m_age; }; struct Person { public: int m_age; }; //两者等价 class Person { int m_age; }; class Person { private: int m_age; };
8 继承
- private 继承,一般情况下,只有自身能访问超类的成员和方法(允许的前提下)
- protected 继承,一般情况下,自身和之后继承的子类都可以直接访问超类(允许的前提下)
- public 继承,不改变超类中的访问权限(推荐使用,一般也是这么用)
- 子类中成员和方法与父类相同会覆盖,优先使用子类的的成员和方法
class Person { private: int m_age; }; //两者等价 class Student : Person{ }; class Student : private Person { }; //两者等价 struct Teacher : Person { }; struct Teacher : public Person { };
例子:Doctor 不能直接访问 Person 的成员 m_age,因为 Student 使用了私有继承
class Person { private: int m_age; }; //两者等价 class Student : Person{ }; struct Doctor : public Student { };
9 构造函数初始化列表
9.1 特殊点
- 初始化列表传入的可以是表达式
- 初始化列表会在函数执行前执行
- 构造函数中是赋值操作(很容易当成初始化)
- 参数初始化顺序与类定义中参数定义顺序一致
class Person { private: int m_age; int m_height; public: Person(int age = 0, int height = 0) :m_age(age + 100), m_height(height * 1000) { } };
9.2 声明和实现分离
- 默认参数要写在声明(函数调用没有参数,编译器会直接插入默认参数)
- 初始化列表写在实现(一般情况下,效果和写在函数体内差别不大)
class Person { private: int m_age; int m_height; public: Person(int age = 0, int height = 0); }; Person::Person(int age, int height) :m_age(age + 1), m_height(height * 1000) { }
10 构造函数调用构造函数
无参构造函数调用有参构造函数
class Person { public: Person():Person(0,0){ //Person(10, 10);生成一个临时 Person 对象 } Person(int age, int height) : m_age(age), m_height(height){ } private: int m_age; int m_height; };
11 子类构造函数调用父类构造函数
11.1 父类没有构造函数
不调用父类构造函数(啥也不干)
11.2 父类有构造函数
- 子类默认调用父类无参构造函数,没有就报错
- 子类构造函数体执行之前调用父类构造函数(先创建父类)
class Person{ public: Person(int age) : m_age(age){ } private: int m_age; }; class Student : public Person{ public: Student(int age, int height) : Person(age), m_height(height){ } private: int m_height; };
12 子类父类析构调用顺序
先创建的后销毁,保证完整性(子类后创建先销毁,故先调用)
13 父类类型的指针可以指向子类
class P { public: void do(){} }; class C : public P{ public: void do(){} }; C child; P *p = &child; p->do();//调用的是父类(P)的方法(编译器根据指针类型使用相应类中的方法)
14 多态(同一操作作用不同对有不同行为)
- 父类是虚函数,子类重写也是虚函数
- 父类指针指向子类对象
- 调用的是虚函数
class P { public: virtual void doing() { cout << 'P' << endl; } }; class C : public P { public: void doing() { cout << 'C' << endl; } }; P p1; C c1; C c2; P* arr[] = { &p1, &c1, &c2 }; arr[0]->doing(); arr[1]->doing(); arr[2]->doing();
15 有虚函数的内存布局
每一个类,如果有虚函数,都有创建一个虚表(创建对象是虚指针指向同一个虚表)
父类内存布局 | 子类内存布局 |
---|---|
虚指针 | 虚指针 |
成员对象的空间 | 父类成员(父类对象空间) |
成员变量的访问空间 |
虚表的布局
父类 | 子类 | 子类 |
---|---|---|
有虚函数 | 完全重写虚函数 | 使用父类的虚函数 |
一个个虚函数地址排列 | 自身虚函数地址排列 | 相应的父类虚函数地址排列 |
子类虚函数地址排列 |
16 子类调用父类的成员阐述
class P{ public: virtual void doing(){} }; class C : public P{ public: void doing(){ P::doing(); } }
17 有父类指针指向子类时,父类析构函数也应该是虚函数
父类的析构函数是虚函数,子类的析构函数也是虚函数
class P{ public: virtual void doing(){} virtual ~P(){} }; class C : public P{ public: void doing(){} ~ C(){} };
18 纯虚函数
- 抽象类,用来定义接口,不能实例化
- 子类需要找到纯虚函数的实现才可以实例化
class P{ public: virtual void doing() = 0; };
19 多继承使用场景
类中只使用纯虚函数,用来定义接口(规范、协议)
20 static
- 子类可以访问父类静态成员和静态方法(类似于全局变量)
- 重复定义成员变量不会覆盖,会有多个
- 也有作用域限制
20.1 static 成员变量
- 创建的实例共用一份(只占一份内存,放在数据段,全局区)
- 必须在类外面初始化
- 声明和实现分离放在实现中初始化,不能带 static 关键字
- 会有多线程安全问题
class P{ static int ms_age; }; int P::ms_age = 10;
20.2 static 成员方法
- 函数内没有 this 变量
- 不能是虚函数(虚函数地址在虚表中,必须是对象才可以调用)
- 能使用静态成员和静态方法
- 构造、析构函数不能是静态的
- 声明和实现分离,实现中不能使用 static 关键字
class P{ public: static void run(){} };
20.3 单例模式
- 构造函数和析构函数使用 private 访问权限
- 私有指针用于保存单例对象
- 静态获取单例对象接口
- 会有多线程安全问题
class A{ public: void doing(){} static A *singleton(){ return ms_a ? ms_a : (ms_a = new A()); } void free(){ if(ms_a != NULL){ delete ms_a; ms_a = NULL; } } private: A(){} ~A(){} static A *ms_a; }; A* A::ms_a = NULL;
21 const
21.1 const 成员变量
class P{ private: const int m_age = 0; };
21.1.1 初始化方式
- 声明时初始化
- 初始化列表中初始化
21.2 const 成员函数
class P{ public: void doing const (){} };
- 非静态函数才能修饰
- 实现和分离,实现也需要 const 修饰
- 函数内部保证不会修改内部成员变量
- 函数内可以调用 static 函数和 const 成员函数
- 非 const 函数 可以调用 const 成员函数
- const 和 非 const 成员函数会构成重载(const 对象调用 const 函数)
- 函数调用权限不能变宽,但可以变窄
- 非 const 对象优先调用非 const 成员函数
22 引用类型初始化
- 声明中初始化
- 初始化列表中初始化
class P{ int &m_ref; };
23 拷贝构造函数
利用已存在的对象创建新对象时时调用
class P{ public: P (const P &rhs){ //doing something } };
- 没有定义拷贝构造函数,拷贝已存在对象的内存数据到新对象的内存
23.1 显示调用父类拷贝函数
class P{ public: P (const P &rhs){} }; class C : public P{ public: C(const C &rhs) : P(rhs){} };
24 深/浅拷贝
堆空间指向栈空间地址很危险(栈空间可能会被回收)
24.1 浅拷贝
- 对象内存的值拷贝
- 指针仅拷贝指针中保存的地址
24.2 深拷贝
- 构造函数 new 相应的堆空间
- 析构函数释放对应的空间
- 复制操作要执行释放和申请操作
- 拷贝函数也要执行申请操作
- 会产生对应的新的存储空间
Created: 2019-12-22 周日 11:37