2023/02/13 更新
2023/02/18 更新完成
-
类的访问权限
public private protected // 1. 在一个类中,private 和 public 可以出现多次 // 2. 类成员缺省为 private, 结构体缺省为 public -
类的几个注意点
// 1. 在类的声明中定义的函数自动成为内联函数,在类的声明之外定义的函数,如果使用了inline限定符,也是内联函数 // 2. 在类的外部,一般不直接访问类的成员变量,而是用成员函数 // 3. 对象一般不同memset()清空成员变量,可以写一个专门用于清空成员变量的成员函数 // 4. 类的成员函数可以直接访问该类其他的成员函数(可以递归) -
构造函数和析构函数
// 1. 构造函数 // 1.1 访问权限必须是 public // 1.2 函数名必须与类名相同 // 1.3 没有返回值,不写void // 1.4 可以有参数,可以重载,可以有默认参数 // 1.5 创建对象时自动调用一次,不能手工调用 // 注意点: // 1. 在构造函数名后面加括号和参数不是调用构造函数,而是创建匿名对象 // 2. 尽量不要在构造函数中写太多代码,可以写写到成员函数中,然后在构造函数中调用成员函数 Girl(string name) { cout << "aaa" << endl; Girl(); // 这里并不是调用构造函数,而是创建匿名对象,临时存在 cout << "bbb" << endl; //m_name = name; //m_age = 0; //memset(m_memo, 0, sizeof m_memo); }// 2. 析构函数 // 2.1 没有参数,不能重载 // 2.2 在函数名前加~ // 2.3 销毁对象前会自动调用一次,可以手工调用// 3. new创建对象 // 3.1 new创建对象时需要使用指针接收 // 3.2 new创建的对象使用完成需要用delete进行销毁 // 3.3 new创建的对象使用堆空间,局部不用new创建的对象使用栈空间 -
拷贝构造函数
// 1. 用已存在的对象创建新的对象时,不会调用普通的构造函数,而是调用拷贝构造函数 // 2. 如果类中没有写拷贝构造函数,那么编译器会提供默认的拷贝构造函数,功能是将已存在对象的成员变量赋值给新对象的成员变量 // 3. 用已存在对象创建新对象的语法 // 类名 新对象(已存在对象) // 类名 新对象 = 已存在对象 // 4. 拷贝构造函数语法 // Girl(const Girl& g) {} // 形参必须有类对象的常引用 // 5. 以值传递的方式进行函数调用时,如果实参为对象,则会调用拷贝构造函数 // 6. 函数以值的方式返回对象时,可能会调用拷贝构造函数(VS会调用,linux不会,因为g++做了优化, 可以通过输出地址查看) // 7. 如果类中重载了拷贝构造函数,但是没有默认拷贝构造函数,编译器会提供默认的拷贝构造函数,这点与构造函数不同 -
深拷贝和浅拷贝
// 1. 浅拷贝 // 1.1 默认的拷贝构造函数是浅拷贝,当类中涉及动态成员时,如指针,在进行对象拷贝时,两个对象的指针会指向同一块内存,新产生的对象并没有开辟新的内存,因此在进程结束进行析构时,同一块内存析构两次,会出现异常。 // 2. 深拷贝 // 2.1 对于类中的动态成员,会重新分配内存,然后进行赋值。在类中涉及动态成员时,需要注意拷贝构造函数的编写。 -
初始化列表
// 1. 如果成员是类,使用初始化列表调用的是成员类的拷贝构造函数,而赋值则是先创建成员类的对象,然后再赋值 // 2. 如果成员是类,初始化列表对性能略有提升 // 3. 如果成员是常量或引用,则必须使用初始化列表,因为常量和引用只能在定义的时候初始化 // 4. 类的成员变量可以 不 出现在初始化列表中 -
const修饰成员函数
// 1. 在类的成员函数后面加const关键字,表示在成员函数中保证不会修改调用对象的成员变量(编程规范) // 2. mutable可以突破const的限制,被mutable修饰的成员变量将永远处于可修改的状态 // 3. 构造函数或析构函数上不允许使用类型限定符 -
this指针
- this存放了对象的地址,它被作为隐藏参数传递给了成员函数,指向调用该函数的对象
- *this可以返回对象
- 每个成员函数都有一个this指针
class CGirl { public: string m_name; int m_yz; CGirl() {} CGirl(string name, int yz) : m_name(name), m_yz(yz) {} void show() const { cout << "我是: " << m_name << ", 我最漂亮。" << endl; } const CGirl& pk(const CGirl& g) const { // 注意这里函数名后面的const if (m_yz > g.m_yz) return *this; return g; } }; int main() { CGirl g1("aa", 1); CGirl g2("bb", 4); CGirl g3("cc", 2); const CGirl& gg = g1.pk(g2).pk(g3); gg.show(); return 0; } -
静态成员
- 静态成员属于类,而不属于某个特定对象,使用类名和范围解析运算符就可以访问
- 只能在全局区进行初始化,使用类名和范围解析运算符::,不能使用初始化列表
- 静态成员只有一份
- 静态成员函数不能访问非静态成员
- 私有静态成员在类的外部无法访问
- 静态成员函数没有this指针
-
简单对象模型
- 对象中维护了多个指针表,表中存放了成员及其地址的对应关系
- 对象占用的内存包括
- 非静态成员大小
- 内存对齐而填补的内存大小
- 为了支持virtual成员而产生的额外负担
- 静态成员属于类,不计算在对象的内存之内
- 空指针可以访问没有用到this指针的成员函数
- 空对象大小为1字节,这是为了让对象的实例能够相互区别
- 对象的地址是第一个非静态成员变量的地址,如果没有非静态警员变量,编译器会隐含地增加一个大小为1字节的占位成员
-
友元
-
友元全局函数
- 可以访问类中的所有成员
class CGirl { friend void func(); // 友元全局函数 public: string name_; CGirl() { name_ = "Mary"; age_ = 18; } void showname() const { cout << "姓名:" << name_ << endl; } private: int age_; void showage() const { cout << "年龄:" << age_ << endl; } }; void func() { CGirl g; g.showname(); g.showage(); } -
友元类
- 友元类中的所有成员函数可以访问另一个类的所有成员
- 友元关系是单向的
- 友元关系不能被继承
class CGirl { friend class CBoy; // 友元类 public: string name_; CGirl() { name_ = "Mary"; age_ = 18; } void showname() const { cout << "姓名:" << name_ << endl; } private: int age_; void showage() const { cout << "年龄:" << age_ << endl; } }; class CBoy { public: void Bfunc(const CGirl& g) { cout << "My girlfriend's name is: " << g.name_ << endl; cout << "Her age is: " << g.age_ << endl; g.showage(); } }; -
友元成员函数
- 友元类中指定的成员函数才能访问另一个类中的成员。声明和定义的顺序如下:
class CGirl; // 前置声明 class CBoy {...}; class CGirl {...};class CGirl; // first part class CBoy1 { // second part public: void func1(const CGirl& g); void func2(const CGirl& g); }; class CGirl { // third part friend void func(); // 友元全局函数 friend class CBoy; // 友元类 friend void CBoy1::func1(const CGirl& g); friend void CBoy1::func2(const CGirl& g); public: string name_; CGirl() { name_ = "Mary"; age_ = 18; } void showname() const { cout << "姓名:" << name_ << endl; } private: int age_; void showage() const { cout << "年龄:" << age_ << endl; } }; // fourth part void CBoy1::func1(const CGirl& g) { cout << "func1: "; g.showage(); } void CBoy1::func2(const CGirl& g) { cout << "func2: "; g.showage(); }
-
-
运算符重载
- 重载函数是非成员函数
class Student { friend Student& operator+(Student& stu, int sc); friend Student& operator+(int sc, Student& stu); private: string name_; int score_; public: Student(): name_("小明"), score_(80) {} Student(string name, int score): name_(name), score_(score) {} void show() { cout << "姓名: " << name_ << endl; cout << "分数: " << score_ << endl; } }; Student& operator+(Student& stu, int sc) { stu.score_ += sc; return stu; } Student& operator+(int sc, Student& stu) { // 更改参数的位置 stu.score_ += sc; return stu; }- 重载函数是成员函数
class Student { private: string name_; int score_; public: Student(): name_("小明"), score_(80) {} Student(string name, int score): name_(name), score_(score) {} void show() { cout << "姓名: " << name_ << endl; cout << "分数: " << score_ << endl; } Student& operator+(int sc) { // 直接返回this指针的解引用 this->score_ += sc; return *this; } }; -
重载关系运算符
class Student { private: string name_; int lan_; int math_; int eng_; public: Student(string name, int lan, int math, int eng) : name_(name), lan_(lan), math_(math), eng_(eng) {} // 比较两个学生的总分 bool operator==(const Student& stu) const { if ((lan_ + math_ + eng_) == (stu.lan_ + stu.math_ + stu.eng_)) return true; return false; } bool operator<(const Student& stu) const { if ((lan_ + math_ + eng_) < (stu.lan_ + stu.math_ + stu.eng_)) return true; return false; } bool operator>(const Student& stu) const { if ((lan_ + math_ + eng_) > (stu.lan_ + stu.math_ + stu.eng_)) return true; return false; } }; -
重载左移运算符
- 只能用全局函数的方式进行重载,不能用成员函数(因为重载要求cout对象在第一个参数的位置,但是成员函数中省略的自定义类型实际上是第一个参数,导致cout对象成为第二个参数,因此输出时cout就要写在右边)
ostream& operator<<(ostream& out, const Student& s) { out << "姓名:" << s.name_ << endl; out << "语文成绩:" << s.lan_ << endl; out << "数学成绩:" << s.math_ << endl; return out; } -
重载下标运算符
- 只能以成员函数的方式呈现
- 实际开发中一般提供两种,带const和不带const(给常对象使用)
class Student { friend ostream& operator<<(ostream& cout, const Student& s); private: string name_; int lan_; int math_; int eng_; string gf[3]; public: Student(string name, int lan, int math, int eng) : name_(name), lan_(lan), math_(math), eng_(eng) { gf[0] = "Amy"; gf[1] = "Tom"; gf[2] = "Dming"; } string& operator[](int index) { return this->gf[index]; } const string& operator[](int index) const { return this->gf[index]; } }; // cout << stu1[0] << endl; -
重载赋值运算符
- 默认赋值函数是浅拷贝,如果对象中存在堆区内存,那么需要自定义赋值函数
- 赋值函数是在两个已存在的对象间进行
- 拷贝构造函数是用已存在的对象去初始化不存在的对象
class Student { public: string name; int score; int* kk; public: Student() { kk = nullptr; } Student& operator=(const Student& s) { if (this == &s) { // 目标对象与原对象相同 return *this; } if (s.kk == nullptr) { // 原对象指针为空 if (kk != nullptr) { // 若目标对象指针不为空,那么删除目标对象指针 delete kk; } } else { if (kk == nullptr) { // 原对象指针不为空,那么若目标对象指针为空,则分配空间 kk = new int; } memcpy(kk, s.kk, sizeof(int)); } return *this; } void show() const { cout << "姓名: " << name << ",分数: " << score << ", kk值: " << kk << endl; } ~Student() { if (kk) { delete kk; } } }; -
重载new&delete运算符
- 如果写在类中,那么无论写不写static,new和delete都是static函数,不能调用非静态成员对象
void* operator new(size_t size) { cout << "调用了全局new函数" << endl; cout << "申请的内存大小: " << size << " 字节" << endl; void* ptr = malloc(size); cout << "申请到的内存地址: " << ptr << endl; return ptr; } void operator delete(void* p) { cout << "调用了全局delete函数" << endl; if (p == nullptr) return; free(p); } -
内存池
class Student { public: int score; int age; static char* pool; //内存池需要有一个指针 public: Student(int sc, int a) { score = sc; age = a; cout << "构造函数" << endl; } ~Student() { cout << "析构函数" << endl; } static bool initpool() { pool = (char*)malloc(18); if (pool == nullptr) return false; memset(pool, 0, 18); return true; } static void freepool() { if (pool[0] == 0 && pool[9] == 0) return; free(pool); cout << "释放了内存池" << endl; } void* operator new(size_t size) { if (pool[0] == 0) { cout << "------------------------" << endl; cout << "分配了第一块内存" << endl; cout << "内存起始地址: " << static_cast<void*>(pool + 1) << endl; cout << "------------------------" << endl; pool[0] = 1; return pool + 1; } if (pool[9] == 0) { cout << "------------------------" << endl; cout << "分配了第二块内存" << endl; cout << "内存起始地址: " << static_cast<void*>(pool + 10) << endl; cout << "------------------------" << endl; pool[9] = 1; return pool + 10; } void* ptr = malloc(size); cout << "------------------------" << endl; cout << "分配了系统内存" << endl; cout << "内存起始地址: " << static_cast<void*>(ptr) << endl; cout << "------------------------" << endl; return ptr; } void operator delete(void* p) { if (p == 0) return; if (p == pool + 1) { pool[0] = 0; cout << "释放了第一块内存" << endl; } else if (p == pool + 10) { pool[9] = 0; cout << "释放了第二块内存" << endl; } else { cout << "释放了系统内存" << endl; free(p); } } }; char* Student::pool = nullptr; int main() { if (Student::initpool() == false) { cout << "内存池分配失败" << endl; return 1; } Student* s1 = new Student(1, 2); Student* s2 = new Student(1, 2); Student* s3 = new Student(1, 2); Student* s4 = new Student(1, 2); delete s1; Student* s5 = new Student(1, 2); delete s2; delete s3; delete s4; delete s5; Student::freepool(); return 0; } -
重载括号运算符
- 必须以成员函数的方式进行重载
- 对象名可以当作函数使用(又称为函数对象、仿函数)
int add(int a, int b) { int c = a + b; cout << "调用了全局函数" << endl; return c; } class MyAdd { public: int operator()(int a, int b) { int c = a + b; cout << "调用了重载函数" << endl; return c; } }; int main() { MyAdd add; cout << add(1, 2) << endl; // 重载函数 cout << ::add(1, 2) << endl; // 全局函数 (前面加范围解析运算符) return 0; } -
重载一元运算符
- 参数中写一个int表示后置自增运算符
- 注意后置自增运算符不能嵌套
- 前置++返回的是左值,后置++返回的是右值
class Student { public: int age; int sc; public: Student() { age = 8; sc = 10; } Student& operator++() { // 前置自增运算符 sc++; return *this; } Student& operator++(int) { // 后置自增运算符 Student tmp = *this; // 记录原始值 sc++; return tmp; } }; -
自动类型转换
- explicit 加在构造函数前面可以关闭自动类型转换
class Student { public: int age; int score; public: explicit Student(int sc) { age = 0; score = sc; } }; Student s = 8; // explicit 限制,不能隐式转换 Student s1 = (Student)8; // 可以显式转换 -
转换函数
class Student { public: int age; string name; double weight; public: Student(int a, string n, double w) { age = a; name = n; weight = w; } operator int() { // 不能有返回值类型,不能有参数 return age; } operator string() { return name; } operator double() { return weight; } }; -
继承的三种方式
-
继承的对象模型
-
查看内存布局命令
cl 源文件名 /d1 reportSingleClassLayout类名 -
派生类的内存空间包括基类的成员,基类的私有成员也在其中
-
如果通过修改内存,派生类是可以修改基类的私有成员的值的,例如memset函数
class A { public: int m_a; protected: int m_b; private: int m_c; public: A() { m_a = 1; m_b = 2; m_c = 3; cout << "调用了A的构造函数" << endl; cout << "A this指针地址: " << this << endl; cout << "A m_a的地址: " << &m_a << endl; cout << "A m_b的地址: " << &m_b << endl; cout << "A m_c的地址: " << &m_c << endl; } ~A() { cout << "调用了A的析构函数" << endl; } void show1() const { cout << m_a << " " << m_b << " " << m_c << endl; } }; class B : public A { public: int m_d; public: B() { m_d = 4; cout << "调用了B的构造函数" << endl; cout << "B this 指针地址: " << this << endl; cout << "B m_a的地址: " << &m_a << endl; cout << "B m_b的地址: " << &m_b << endl; //cout << "B m_c的地址: " << &m_c << endl; cout << "B m_d的地址: " << &m_d << endl; } ~B() { cout << "调用了B的析构函数" << endl; } void show2() const { cout << m_a << " " << m_b << " " << m_d << endl; } }; int main() { B* p = new B(); p->show1(); p->show2(); memset(p, 0, sizeof B); // 这里修改了A的私有成员的值 p->show1(); p->show2(); delete p; return 0; } -
-
如何构造基类
- 基类中的成员尽量在基类中初始化
```c++
class A {
public:
int m_a;
int m_b;
A() : m_a(0), m_b(0) {
cout << "调用了A的默认构造函数A()" << endl;
}
A(int a, int b) : m_a(a), m_b(b) {
cout << "调用了A的构造函数A(int a, int b)" << endl;
}
A(const A& a) : m_a(a.m_a), m_b(a.m_b) {
cout << "调用了A的拷贝构造函数A(const A& a)" << endl;
}
void show1() const {
cout << "A m_a: " << m_a << ", A m_b: " << m_b << endl;
}
};
class B : public A {
public:
int m_c;
B() : m_c(0) {
cout << "调用了B的默认构造函数" << endl;
}
B(int a, int b) : A(a, b), m_c(0) {
cout << "调用了B的构造函数B(int a, int b)" << endl;
}
B(const A& a) : A(a), m_c(1) {
cout << "调用了B的构造函数B(const A& a)" << endl;
}
};
```
26. 名字遮蔽和类作用域
- 基类和派生类之间不存在函数重载,派生类的函数会覆盖基类的所有同名函数
- 成员如果在派生类的作用域中找到了,就不会再去基类的作用域中寻找
- 可以通过范围解析运算符$::$来指定作用域
27. 继承的特殊关系
- 可以将派生类对象赋值给基类对象
- 基类指针可以在不进行显示转换的情况下指向派生类,但是基类指针只能操纵基类中有的成员,假如有同名函数的话也是调用的基类的成员函数
- 如果函数的形参是基类,那么实参可以传入派生类
28. 多继承与虚继承
- 多继承格式
```c++
class A1 {
public:
int a = 10;
};
class A2 {
public:
int b = 20;
};
class B : public A1, public A2 {
};
```
- 虚继承
- 主要解决多继承下造成的菱形继承问题
- 没有虚继承的时候需要这样写
```c++
class A {
public:
int m_a;
};
class B : public A {
};
class C : public A {
};
class D : public B, public C {
};
int main() {
D d; // d的内存模型中有两个m_a,因此在访问时可以通过域名解析运算符指定访问哪个
d.B::m_a = 10;
d.C::m_a = 20;
return 0;
}
```
- 有虚继承时这样写
```c++
class A {
public:
int m_a;
};
class B : virtual public A {
};
class C : virtual public A {
};
class D : public B, public C {
};
int main() {
D d;
d.m_a = 10;
cout << "B::m_a的地址: " << &d.B::m_a << endl; //这两个地址相同,此时A是虚基类
cout << "C::m_a的地址: " << &d.C::m_a << endl;
return 0;
}
```
29. 多态的基本概念
- 在一般情况下,基类的指针指向派生类的对象时,基类指针是无法访问派生类的成员变量和成员函数的
- 但是如果在基类中的同名函数前面加上virtual 关键字,那么当基类指针指向基类对象时,就会访问基类的成员函数;指向派生类时,就会访问派生类的成员函数,表现出了多态的性质
- 如果派生类重定义了基类的虚函数,那么这两个函数的函数名和参数必须完全相同,否则不构成多态
- 普通成员函数的效率比虚函数效率高
- 当有虚函数时,基类和派生类的内存模型中会有虚函数表,在基类的虚函数表中存放的是基类的成员函数;在派生类的虚函数表中,存放的是派生类的成员函数,因此当基类指针指向派生类时,调用的是派生类的成员函数。
30. 如何析构派生类
- 调用派生类的析构函数会自动调用基类的析构函数
- 当基类指针指向派生类时,如果不把基类的析构函数设置成虚函数,那么将无法调用派生类的成员函数。如果基类析构函数被设置成虚函数,那么就可以调用派生类的析构函数,根据第一条规则,基类的析构函数也会调用
- 基类若不需要析构函数的话最好提供一个空的虚析构函数
```c++
// 在析构函数中delete 指针之后,还要把指针设置成空指针,因为有可能手工调用多次析构函数
delete ptr;
ptr = nullptr;
```
31. 纯虚函数和抽象类
- 含有纯虚函数的类被称为抽象类,不能实例化对象,但是可以创建指针和引用
- 基类中不需要给出纯虚函数的实现,给出了也没问题。格式如下:
```c++
virtual void func() = 0; // {} 后面的实现可要可不要
```
- 纯虚析构函数一定要有实现。它存在的意义是当想定义一个抽象类,但是有没有内容需要写时,可以简单的定义一个纯虚函数来达到目的
```c++
class A {
public:
A() {
cout << "调用了A的构造函数" << endl;
}
virtual void func() = 0; // { cout << "调用了A的func()" << endl; }
virtual ~A() = 0 { cout << "调用了A的析构函数" << endl; }
};
class B : public A {
public:
B() {
cout << "调用了B的构造函数" << endl;
}
void func() {
cout << "调用了B的func()" << endl;
}
~B() {
cout << "调用了B的析构函数" << endl;
}
};
```