基础
内存分区
代码区 | 全局区 | 栈区 | 堆区 |
---|
存放二进制代码,由操作系统管理 | 存放全局变量、静态变量、常量 | 由编译器自动分配和释放,存放函数参数值、局部变量 | 由程序员分配和释放,若程序员不处理,程序结束时由操作系统回收 |
1. 程序运行前就存在; 2. 存放 CPU 执行的机器指令; 3. 共享:对于需要频繁执行的程序,只需在内存中有一份即可; 4. 只读:防止程序意外修改指令。 | 1. 程序运行前就存在; 2. 存放全局变量、静态变量、常量; 3. 程序结束由操作系统释放。 | 1. 由编译器自动分配和释放,存放函数参数值、局部变量; 2. 注意不要返回局部变量的地址,因为栈区开闭的数据由编译器自动释放。 | 1. 由程序员分配和释放; 2. 利用 new 在堆区开辟数据。 |
🤔通过代码分析:
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| int b = 20; const int d = 30;
int *func() { int *p = new int(10); return p; }
void test01() { int *p = func(); cout << *p << endl; cout << *p << endl; cout << *p << endl; delete p; }
void test02() { int *arr = new int[10]; for (int i = 0; i < 10; i++) { arr[i] = i + 100; cout << arr[i] << endl; } delete[] arr; }
int main() { int a = 10;
cout << (int)&a << endl; cout << (int)&b << endl;
static int c = 15; cout << (int)&c << endl; cout << (int)&"hello world" << endl;
const int e = 25; cout << (int)&d << endl; cout << (int)&e << endl;
int *q = func(); cout << *q << endl; cout << *q << endl; cout << *q << endl; delete q;
test01(); test02();
system("pause"); 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| void swap01(int a, int b) { int temp = a; a = b; b = temp; }
void swap02(int *a, int *b) { int temp = *a; *a = *b; *b = temp; }
void swap03(int &a, int &b) { int temp = a; a = b; b = temp; }
int &test02() { static int a2 = 10; return a2; }
void showValue(const int &val) { cout << "val= " << val << endl; }
int main() { int a = 10; int &b = a; cout << a << endl; cout << b << endl;
b = 20; cout << a << endl; cout << b << endl;
int m = 4; int n = 6;
swap01(m, n); cout << m << " " <<n << endl;
swap02(&m, &n); cout << m << " " << n << endl;
swap03(m, n); cout << m << " " << n << endl;
int &ref2 = test02(); cout << ref2 << endl; cout << ref2 << endl;
test02() = 1000; cout << ref2 << endl; cout << ref2 << endl;
int d = 30; int &ref3 = d; const int &ref4 = 30;
int e = 40; showValue(e);
system("pause"); 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
| int func( int a, int b = 20, int c = 30) { return a + b + c; }
void func2(int a, int) { cout << "111" << endl; }
void func3(int a, int = 30) { cout << "222" << endl; }
int main() { int f = func(10); cout << f << endl;
func2(10, 20); func3(10);
system("pause"); 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| void func() { cout << "func的调用" << endl; }
void func(int a) { cout << "func(int a)的调用" << endl; }
void func(double a) { cout << "func(double a)的调用" << endl; }
void func(int a, int b) { cout << "func(int a, int b)的调用" << endl; }
void func(int a, double b) { cout << "func(int a, double b)的调用" << endl; }
void func(double a, int b) { cout << "func(double a, int b)的调用" << endl; }
void func1(int &a) { cout << "func1(int &a)的调用" << endl; }
void func1(const int &a) { cout << "func1(const int &a)的调用" << endl; }
void func02(int a) { cout << "func02的调用" << endl; }
void func02(int a, int b = 10) { cout << "func02(a,b)的调用" << endl; }
int main() { func(); func(3); func(4, 5); func(3.14); func(5, 7.8); func(5.6, 8);
int a = 10; func1(a); func1(20);
func02(20, 30);
system("pause"); return 0; }
|
封装
相关基础知识
C++面向对象的三大特性:封装、继承、多态
。
封装的意义:
将属性和行为作为一个整体,写在一起,表现生活中的事物;将属性和行为加以权限控制(public/protected/private);语法:class 类名{访问权限: 属性/行为};
class 和 struct 的区别:
1. 默认的访问权限不同,class 默认为私有,struct 默认为公共;
2. 类可以继承,结构体不可以。
Getter 和 Setter 一般放在头文件中,权限为 public,通过这个方式来 get/set 保护或者私有变量。
对象特性
构造、析构与拷贝函数
对象的初始化和清理:
构造函数
:创建对象时给成员属性赋值,编译器自动调用;
析构函数
:清理,对象销毁前编译器自动调用。
构造函数分类:有参、无参;普通、拷贝。
如果用户定义有参构造,C++不再提供默认构造,但是会提供拷贝构造;
如果用户定义拷贝构造,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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| class Person { public: Person() { printf("Person的默认构造函数调用 \n"); }
Person(int a) { age = a; printf("Person的有参构造函数调用 \n"); }
Person(const Person &p) { age = p.age; printf("Person的拷贝构造函数调用 \n"); }
~Person() { printf("Person的析构函数调用 \n"); }
int getage() { return age; }
private: int age; };
void test001() { Person p0; Person p1(10); Person p2(p1); printf("p1的年龄为:%d \n", p1.getage()); printf("p2的年龄为:%d \n", p2.getage());
Person p00; Person p01 = Person(30); Person(40); printf("匿名对象\n"); Person p02 = Person(p01); printf("p01的年龄为:%d \n", p01.getage()); printf("p02的年龄为:%d \n", p02.getage());
Person p3 = 25; printf("p3的年龄为:%d \n", p3.getage()); Person p4 = p3; printf("p4的年龄为:%d \n", p4.getage()); }
void doWork(Person p) {
}
void test002() { Person p; doWork(p); printf("p \n"); }
Person doWork2() { Person p5; return p5; }
void test003() { Person p = doWork2(); printf("doWork2 \n"); }
int main() { test001(); test002(); test003();
system("pause"); 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| class Person01 { public: Person01() { printf("Person01的默认构造函数调用 \n"); }
Person01(int a, int h) { m_Age = a; printf("Person01的有参构造函数调用 \n"); m_Height = new int(h); }
Person01(const Person01 &p) { printf("Person01的拷贝构造函数调用 \n"); m_Age = p.m_Age; m_Height = new int(*p.m_Height); }
~Person01() { printf("Person01的析构函数调用 \n"); if (m_Height != nullptr) { delete m_Height; m_Height = nullptr; } }
int getm_age() { return m_Age; }
int *getHeight() { return m_Height; }
private: int m_Age; int *m_Height; };
void test02_01() { Person01 P1(18, 175); printf("P1的年龄是: %d, 身高是: %d \n", P1.getm_age(), *P1.getHeight());
Person01 P2(P1); printf("P2的年龄是: %d, 身高是: %d \n", P2.getm_age(), *P2.getHeight()); }
int main() { test02_01();
system("pause"); 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
| class Person02 { public:
Person02(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {
}
int m_A; int m_B; int m_C; };
|
🚀注意:
- 定义顺序:成员变量的初始化顺序遵循它们在类声明中的顺序,而不是初始化列表中的顺序。
- 必要情况:如果成员变量是常量 const或者没有默认构造函数的类类型,则必须使用初始化列表进行初始化。
- 优点:初始化列表可以提高效率,因为它避免了构造函数体中的赋值操作,特别是在处理不需要或不能通过默认构造函数创建的复杂对象时。
- 执行顺序:基类的构造函数按照它们在类继承列表中声明的顺序依次调用
->
成员对象的构造函数按照它们在类体中声明的顺序调用 ->
执行构造函数体内的代码。 - 使用条件:仅构造函数可以使用初始化列表。
现在具体解释注意点。
第 2 点:
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 BaseClass { public: BaseClass() : m_Value(0) {} BaseClass(int value) : m_Value(value) {} int m_Value; };
class MyClass { public: MyClass(int baseValue, int constVal) : m_BaseMember(baseValue), m_ConstMember(constVal) { cout << "Base member initialized with: " << m_BaseMember.m_Value << endl; cout << "Const member initialized with: " << m_ConstMember << endl; } BaseClass m_BaseMember; const int m_ConstMember; };
int main() { MyClass obj(10, 20); return 0; }
|
第 4 点:
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 33 34
| class Base { public: Base() { cout << "Base class constructor" << endl; } };
class Member { public: Member() { cout << "Member object constructor" << endl; } };
class Derived : public Base { Member obj; public: Derived() : obj() { cout << "Derived class constructor body" << endl; } };
int main() { Derived d; }
|
🚩类对象作为成员
构造函数顺序:父类->成员类->自身构造函数
析构函数顺序:反之。后进先出,析构函数释放的顺序和构造函数相反。
代码详见“初始化列表—注意—第 4 点”。
静态成员
静态成员:就是在成员变量或成员函数前面加上 static
静态成员变量:
- 所有对象共享同一份数据,不单独属于某个对象(但这不意味着都是 public 或者说初始化的时候不需要加上作用域)
- 在编译阶段分配内存(全局区)
- 类内声明,类外初始化
- 如果静态成员变量是
const
整数类型或 const
枚举类型,可以在类内部直接定义并初始化
静态成员函数:
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| class Person04 { public: static int m_A; int m_C;
static void func() { m_A = 400; printf("static void func的调用 \n"); } private: static int m_B; static void func1() { printf("static void func1的调用 \n"); } };
int Person04::m_A = 100; int Person04::m_B = 300;
void test_12_01() { Person04 p1; printf("p1A= %d \n", p1.m_A);
Person04 p2; p2.m_A = 200; printf("p2A= %d \n", p1.m_A); }
void test_12_02() { Person04 p; printf("pA= %d \n", p.m_A); printf("pA= %d \n", Person04::m_A); }
void test_12_03() { Person04 p; p.func(); Person04::func(); }
|
🚀注意:
- 可以在类内部以
static const int var = 10;
这样的代码直接初始化常量静态成员变量。 - 对于其他类型的静态成员(例如
double
或其他类类型),必须在类外如 double MyClass::staticVar = 10;
进行定义和初始化,不能写作 double staticVar = 10;
。 - C++11 开始,允许内联初始化静态成员变量。
inline
关键字允许静态成员变量的定义在一个 .h
文件中(或任何包含它的翻译单元)进行,编译器会在需要时将其复制到多个翻译单元中。这样可以避免多个翻译单元中定义相同的静态成员变量,从而解决了多重定义的问题。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12
| class MyClass { public: static int staticVar; };
inline int MyClass::staticVar = 10;
inline int MyClass::staticVar = 10;
|
成员变量和成员函数分开储存
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 test {
};
class Person05 { int m_A = 1; static int m_B;
void func() {}; static void func1() {}; }; int Person05::m_B = 0;
void test_13_01() { test p; cout<<"sizeof p="<<sizeof(p)<<endl; }
void test_13_02() { Person05 p; cout << "sizeof p=" << sizeof(p) << endl; }
|
this 指针
特点:
- 当形参和成员变量同名时,解决名称冲突;
- this 指针指向的是被调用的成员函数所属的对象,谁调用就指向谁;
- 在类的非静态成员函数中,返回对象本身用
*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 24 25 26 27
| class Person07 { public: void showClassName() { cout << "this is person class" << endl; }
void showPersonAge() { if (this == nullptr) { return; } cout << "age=" << this->m_Age << endl; }
int m_Age;
};
void test_15_01() { Person07 *p = nullptr; p->showClassName(); p->showPersonAge(); }
|
const 修饰成员函数
常对象 (const object) 无法直接修改属性,除非使用这几个方式:
- 将类成员变量声明为
mutable/static
, 则对应变量可以修改; - 使用
const_cast
移除 const 限定符。
注意,无法修改指向常对象的指针或引用。
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 33 34
| class Person08 { public: void showPerson() const { m_B = 200; }
void func() {
}
int m_A; mutable int m_B; };
void test_16_01() { Person08 p; p.showPerson(); }
void test_16_02() { const Person08 p; p.m_B = 400; p.showPerson(); }
|
了解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class MyClass { public: void setValue(int v) { value = v; } int value; };
void modifyValue(MyClass* const cptr) { cptr->setValue(10); }
int main() { const MyClass obj; modifyValue(const_cast<MyClass*>(&obj)); }
|
🚩友元
定义:让一个函数或者类,访问另一个类中的所有成员。
🚀注意:
- 友元函数一定要在类外实现,不然会报错!
- 需要先对友元类进行前向声明 (forward declare,可在类内类外), 然后再声明友元函数。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| class Building;
class Goodguy { public: Goodguy();
void visit();
void sleep();
public: Building *building; };
class Building { friend void goodguy(Building &building); friend void Goodguy::sleep();
public: Building();
public: string m_SittingRoom;
private: string m_BedRoom; };
Building::Building() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; }
void goodguy(Building &building) { cout << "01好友正在访问:" << building.m_SittingRoom << endl; cout << "01好友正在访问:" << building.m_BedRoom << endl; }
void test_01_01() { Building b; goodguy(b); }
Goodguy::Goodguy() { building = new Building(); }
void Goodguy::visit() { cout << "02好友正在访问:" << building->m_SittingRoom << endl; }
void Goodguy::sleep() { cout << "03好友正在:" << building->m_SittingRoom << "里睡觉" << endl; cout << "03好友正在:" << building->m_BedRoom << "里睡觉" << endl; }
void test_01_02() { Goodguy g; g.visit(); g.sleep(); }
|
🚩运算符重载
成员函数重载+号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Person01 { public: Person01 operator+(Person01 &p) { Person01 temp; temp.m_A = this->m_A + p.m_A; temp.m_B = this->m_B + p.m_B; return temp; }
public: int m_A; int m_B; };
|
全局函数重载+号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Person01 operator+(Person01 &p1, Person01 &p2) { Person01 temp; temp.m_A = p1.m_A + p2.m_A; temp.m_B = p1.m_B + p2.m_B; return temp; }
Person01 operator+(Person01 &p1, int num) { Person01 temp; temp.m_A = p1.m_A + num; temp.m_B = p1.m_B + num; return temp; }
|
全局函数重载左移运算符<<
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
| ostream &operator<<(ostream &out, Person01 &p) { out << "m_A=" << p.m_A << endl; out << "m_B=" << p.m_B << endl; return out; }
void test_02_01() { Person01 p1; p1.m_A = 10; p1.m_B = 15;
cout << p1 << "hello world" << endl;
Person01 p2; p2.m_A = 20; p2.m_B = 30;
Person01 p3 = p1 + p2; cout << "p3.m_A = " << p3.m_A << endl; cout << "p3.m_B = " << p3.m_B << endl;
Person01 p4 = p1 + 100; cout << "p4.m_A = " << p4.m_A << endl; cout << "p4.m_B = " << p4.m_B << endl; }
|
友元函数重载右移运算符>>
1 2 3 4 5 6 7 8 9 10 11 12
| class MyClass { public: int value; friend istream& operator>> (istream& is, MyClass& obj); };
istream& operator>> (istream& is, MyClass& obj) { is >> obj.value; return is; }
|
重载递增运算符++
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| class myint { friend ostream &operator<<(ostream &cout, myint m);
public: myint() { m_Num = 0; }
myint &operator++() { m_Num++; return *this; }
myint operator++(int) { myint temp = *this; m_Num++; return temp; }
myint &operator--() { m_Num--; return *this; }
myint operator--(int) { myint temp = *this; m_Num--; return temp; }
private: int m_Num; };
ostream &operator<<(ostream &cout, myint m) { cout << "数据的值是:" << m.m_Num; return cout; }
void test_02_02() { myint newint; cout << newint << endl; cout << ++newint << endl; cout << ++(++newint) << endl; cout << newint << endl; }
void test_02_03() { myint newint; cout << newint << endl; cout << newint++ << endl; cout << newint << endl; }
void test_02_04() { myint newint; cout << newint << endl; cout << --newint << endl; cout << --(--newint) << endl; cout << newint << endl; }
void test_02_05() { myint newint; cout << newint << endl; cout << newint-- << endl; cout << newint << endl; }
|
重载赋值运算符=
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
class Person02 { public: Person02(int age) { m_Age = new int(age); } ~Person02() { if (m_Age != nullptr) { delete m_Age; m_Age = nullptr; } }
Person02 &operator=(Person02 &p) { if (m_Age != nullptr) { delete m_Age; m_Age = nullptr; }
m_Age = new int(*p.m_Age);
return *this; }
int *m_Age; };
void test_02_06() { Person02 P1(18); cout << "P1年龄是:" << *P1.m_Age << endl; Person02 P2(20); cout << "P2年龄是:" << *P2.m_Age << endl;
P2 = P1; cout << "P1年龄是:" << *P1.m_Age << endl; cout << "P2年龄是:" << *P2.m_Age << endl;
Person02 P3(30); P3 = P2 = P1; cout << "P3年龄是:" << *P3.m_Age << endl; }
|
重载关系运算符
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| class Person03 { public: Person03(string name, int age) { m_Name = name; m_Age = age; }
bool operator==(Person03 &p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return true; } else { return false; } }
bool operator!=(Person03 &p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return false; } else { return true; } }
bool operator<(Person03 &p) { if (this->m_Age < p.m_Age) { return true; } else { return false; } }
bool operator>(Person03 &p) { if (this->m_Age > p.m_Age) { return true; } else { return false; } }
string m_Name; int m_Age; };
void test_02_07() { Person03 p1("Tom", 18); Person03 p2("Jerry", 18); if (p1 == p2) { cout << "p1和p2相等" << endl; } else if(p1 != p2) { cout << "p1和p2不等" << endl; } }
void test_02_08() { Person03 p3("Vettel", 35); Person03 p4("Kimi", 40); if (p3 < p4) { cout << "p3比p4小" << endl; } else if (p3 > p4) { cout << "p3比p4大" << endl; } }
|
重载函数调用运算符 ()
也叫做仿函数。
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 33
| class MyPrint { public: void operator()(string test) { cout << test << endl; } };
void test_02_09() { MyPrint print; print("hello world"); }
class MyAdd { public: int operator()(int num1, int num2) { return num1 + num2; } };
void test_02_10() { MyAdd add; int ret = add(100, 200); cout << "ret=" << ret << endl;
cout << MyAdd()(200, 300) << endl; }
|
🤔总结
运算符 | 说明 | 友元函数重载 | 全局函数重载 | 成员函数重载 |
---|
+ | 一元加 | 否 | ✅ | 否 |
- | 一元减 | 否 | ✅ | 否 |
! | 逻辑非 | ✅ | ✅ | ✅ |
+ + | 自增 | 否 | ✅ | ✅ |
- - | 自减 | 否 | ✅ | ✅ |
= | 赋值 | ✅ | 否 | ✅ |
* | 解引用 | 否 | 否 | ✅ |
& | 取地址 | 否 | 否 | ✅ |
[ ] | 索引/下标 | ✅ | ✅ | ✅ |
-> | 箭头 | 否 | 否 | ✅ |
( ) | 类型转换 | ✅ | ✅ | ✅ |
<< | 左移 | ✅ | ✅ | 否 |
>> | 右移 | ✅ | ✅ | 否 |
+, -, *, / | 二元加、减、乘、除等 | ✅ | ✅ | 否(构造函数可以部分实现这些) |
==, != | 比较 | ✅ | ✅ | ✅ |
<, >, <=, >= | 比较 | ✅ | ✅ | ✅ |
++(作为后缀) | 自增后缀 | 否 | ✅ | ✅ |
—(作为后缀) | 自减后缀 | 否 | ✅ | ✅ |
🚀注意:
- 一元运算符(如
+
, -
, !
, ++
, --
)不通过成员函数重载,因为它们没有足够的参数来使得它们成为成员函数。(+a) - 二元运算符可以使用任意类型的组合进行重载,只需要符合运算的逻辑,并且可以作为友元函数或全局函数,也可以作为成员函数。(a+b)
- 三位运算符和一些特定情性需要特殊注意,比如赋值运算符
=
和下标运算符 [ ]
通常通过成员函数重载。 - 构造函数用做类型转换,它实际上重载了转换运算符
()
。 - 对于某些特殊的运算符,如赋值运算符和下标运算符,常常会通过成员函数的方式来重载。
继承
类与类上下级之间有共性和特性,使用继承可以减少很多重复代码。
继承中的构造和析构顺序,同名成员属性、同名静态成员的处理
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
| class Base1 { public: Base1() { cout << "Base1的构造函数" << endl; m_F = 100; }
~Base1() { cout << "Base1的析构函数" << endl; }
void func() { cout << "Base1下成员函数func()调用" << endl; }
void func(int a) { cout << "Base1下成员函数func(int a)调用" << endl; }
static void func1() { cout << "Base1下静态成员函数func1()调用" << endl; }
int m_F; static int m_G; }; int Base1::m_G = 50;
class Son4 : public Base1 { public: Son4() { cout << "Son4的构造函数" << endl; }
~Son4() { cout << "Son4的析构函数" << endl; } };
void test_03_07() { Son4 s; }
class Son5 :public Base1 { public: Son5() { m_F = 200; }
void func() { cout << "Son5下成员函数func()调用" << endl; }
static void func1() { cout << "Son5下静态成员函数func1()调用" << endl; }
int m_F; static int m_G; }; int Son5::m_G = 60;
void test_03_08() { Son5 s; cout << "子类 Son5下 m_F=" << s.m_F << endl; cout << "父类 Base1下 m_F=" << s.Base1::m_F << endl; }
void test_03_09() { Son5 s; s.func(); s.Base1::func(); s.Base1::func(20); }
void test_03_10() { cout << "通过创建对象来访问静态变量" << endl; Son5 s; cout << "子类 Son5下 m_G=" << s.m_G << endl; cout << "父类 Base1下 m_G=" << s.Base1::m_G << endl; cout << endl;
cout << "通过类名来访问静态变量" << endl; cout << "子类 Son5下 m_G=" << Son5::m_G << endl; cout << "父类 Base1下 m_G=" << Son5::Base1::m_G << endl; }
void test_03_11() { cout << "通过创建对象来访问静态成员函数" << endl; Son5 s; s.func1(); s.Base1::func1(); cout << endl;
cout << "通过类名来访问静态成员函数" << endl; Son5::func1(); Son5::Base1::func1(); }
class Base01 { public: Base01() { m_A1 = 100; } int m_A1; };
class Base02 { public: Base02() { m_B1 = 100; m_A1 = 700; } int m_B1; int m_A1; };
class Son06 : public Base01, public Base02 { public: Son06() { m_C1 = 300; m_D1 = 400; } int m_C1; int m_D1; };
void test_03_12() { Son06 s; cout << "sizeof Son06 =" << sizeof(s) << endl; cout << "Base01 m_A1=" << s.Base01::m_A1 << endl; cout << "Base02 m_A1=" << s.Base02::m_A1 << endl; }
|
菱形继承
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 33
|
class Animal { public: int m_Age; };
class Sheep : virtual public Animal {
};
class Camel : virtual public Animal {
};
class Alpaca : public Sheep, public Camel {
};
void test_03_13() { Alpaca al; al.Sheep::m_Age = 3; al.Camel::m_Age = 5; cout << "Sheep age=" << al.Sheep::m_Age << endl; cout << "Camel age=" << al.Camel::m_Age << endl; cout << "Alpaca age=" << al.m_Age << endl; }
|
菱形继承的弊端:
- 二义性:当基类中的两个子类都重写了基类的某个成员时,由此产生的最子类在选择要访问哪个版本会引发二义性。
- 内存冗余:两个子类分别继承了自身的副本基类,增加了内存占用。
- 构造和析构的复杂性:成员的构造和析构可能变得复杂,需要多个构造函数和析构函数来处理不同继承链的初始化和清理。
😊改进方案:
- 虚继承:通过使用
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| class Animal { public: int m_Age; };
class Sheep { private: Animal animal;
public: Sheep(int age) : animal(age) {}
void setAge(int age) { animal.m_Age = age; }
int getAge() const { return animal.m_Age; } };
class Camel { private: Animal animal;
public: Camel(int age) : animal(age) {}
void setAge(int age) { animal.m_Age = age; }
int getAge() const { return animal.m_Age; } };
class Alpaca { private: Sheep sheep; Camel camel;
public: Alpaca(int sheepAge, int camelAge) : sheep(sheepAge), camel(camelAge) {}
int getSheepAge() const { return sheep.getAge(); }
int getCamelAge() const { return camel.getAge(); }
void setSheepAge(int age) { sheep.setAge(age); }
void setCamelAge(int age) { camel.setAge(age); } };
int main() { Alpaca al(3, 5); cout << "Sheep age=" << al.getSheepAge() << endl; cout << "Camel age=" << al.getCamelAge() << endl; al.setSheepAge(4); al.setCamelAge(6); cout << "Updated Sheep age=" << al.getSheepAge() << endl; cout << "Updated Camel age=" << al.getCamelAge() << endl; return 0; }
|
多态
基本概念
静态多态:函数重载、运算符重载。函数地址早绑定:编译阶段确定函数地址。
动态多态:派生类和虚函数实现运行时的多态。函数地址晚绑定:运行阶段确定函数地址。
💭动态多态的满足条件:
- 有继承关系;
- 子类重写父类的虚函数,不是重载,是完全相同的函数。
动态多态的使用:父类的指针或者引用指向子类的对象。
🚀重写(overload)和重载(override)的区别:
- 范围和作用域:重写发生在派生类与其基类之间,需在不同的类中定义;而重载发生在同一个类或同一个作用域中,涉及多个具有相同名称但参数不同的函数。
- 参数列表:重写要求派生类中重写的函数与基类中的虚函数参数列表完全相同,以实现真正的函数替代;重载的函数则有不同参数列表,根据参数的类型、数量、顺序来区分。
- 关键字
virtual
:进行重写的基类函数必须有virtual
关键字,以确保它能够接收动态绑定(Runtime Polymorphism);而重载则不强制要求virtual
关键字。 - 返回类型:尽管返回类型相同可以作为重写和重载的一种情况,但重载更侧重于参数的不同,不同的返回类型并不影响函数的重载关系。
- 访问级别:在重写中,派生类的函数不能有比基类中相应函数更严格的访问控制(如基类方法为
public
,而派生类方法为private
)。 - 概念上的区别:重写实现多态性(Polymorphism),一个基类引用或指针可以指向不同派生类的对象,并调用相应的重写函数;重载允许编译时基于参数列表使用函数名称来区分多个函数。
- 隐藏(Hiding):与重写和重载不同,在隐藏情况下基类函数被派生类同名函数盖掉,即使基类函数为
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class Animal { public:
virtual void speak() { cout << "动物在说话" << endl; } };
class Cat : public Animal { public: void speak() { cout << "猫在说话" << endl; } };
class Dog : public Animal { public: void speak() { cout << "狗在说话" << endl; } };
void doSpeak(Animal &animal) { animal.speak(); }
void test04_01() { Cat c; doSpeak(c);
Dog d; doSpeak(d); }
|
🚩纯虚函数与抽象类
父类中的虚函数没用时,可以写成纯虚函数,因为主要都是调用子类重写的函数。有纯虚函数的类称为抽象类。C++中常使用纯虚函数和抽象类来实现接口。
语法:virtual 返回值 函数名(参数列表) = 0;
特点1:无法实例化对象;
特点2:子类必须重写抽象类中的纯虚函数,否则也属于抽象类!
多态使用的条件:父类的指针或者引用指向子类对象。
抽象类特点:
- 不能直接被实例化。
- 可以拥有成员变量。
- 可以拥有普通成员函数以及构造函数和析构函数。
- 用作基类,为派生类提供共同的接口。
- 必须有纯虚函数。
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 Base { public: virtual void func() = 0; };
class Son : public Base { public: virtual void func() { cout << "func函数调用" << endl; } };
void test04_02() { Son s; Base *p = new Son(); p->func(); delete p; }
|
🤔思考:如果 Base 并不是抽象类,即并没有纯虚函数,此时 func ()
的代码为:
1 2 3
| virtual void func() { cout << "父类" << endl; }
|
输出结果依然为:
分析函数执行顺序:(不包括 Son s
的情况下)- 基类构造函数
Base()
- 派生类构造函数
Son()
- 派生类重写的函数
Son::func()
- 派生类析构函数
~Base()
- 基类析构函数
~Son()
由此可见,即使 Base 不是抽象类,p->func() 仍然只调用子类对象的对应函数。结果和 Son s; s.func()
的输出结果一致。
进行验证:
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 33 34 35 36 37 38 39 40
| class Base { public: Base() { cout << "Base constructor called." << endl; }
virtual void func() { cout << "Base func called." << endl; }
virtual ~Base() { cout << "Base destructor called." << endl; } };
class Son : public Base { public: Son() { cout << "Son constructor called." << endl; }
void func() override { cout << "Son func called." << endl; }
~Son() override { cout << "Son destructor called." << endl; } };
void test04_02() { Base* p = new Son(); p->func(); delete p; }
int main() { test04_02(); return 0; }
|
🚩虚析构与纯虚析构
如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
💡解决方法:将父类中的析构函数写成虚析构或纯虚析构函数。
💭虚析构与纯虚析构共性:
1,可以解决父类指针释放子类的堆区对象;
2,都需要有具体的函数实现。
💭虚析构与纯虚析构区别:纯虚析构,该类属于抽象类,无法实例化对象。
如果子类中没有堆区数据(new),可以不写虚析构。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| class Animal1 { public: Animal1() { cout << "Animal1构造函数调用" << endl; }
virtual ~Animal1() = 0;
virtual void speak() = 0; };
Animal1::~Animal1() { cout << "Animal1纯虚析构函数调用" << endl; }
class Lion : public Animal1 { public: Lion(string name) { cout << "Lion构造函数调用" << endl; m_Name = new string(name); }
virtual void speak() { cout << *m_Name << "狮子在说话" << endl; }
~Lion() { if (m_Name != nullptr) { cout << "Lion析构函数调用" << endl; delete m_Name; m_Name = nullptr; } }
string *m_Name; };
void test04_03() { Animal1 *p = new Lion("Simba"); p->speak(); delete p; }
|