C++ 巩固基础之二
大纲
类、对象、指针
类与对象
1 |
|
程序执行后的输出结果:
1 | name: Book |
提示
类的成员函数一经编译,在所有函数的参数列表中,都会隐式自动添加一个 this
指针,用于接收调用该函数的对象的地址。这样在函数被调用时,C++ 才知道是谁调用了该函数。
指向类成员的指针
指向类成员变量的指针
1 |
|
程序执行后的输出结果:
1 | 20, 20 |
指向类成员函数的指针
1 |
|
程序执行后的输出结果:
1 | call Test::func |
构造函数与析构函数
- 构造函数
- 定义对象时,构造函数会自动调用。
- 构造函数是可以重载的,可以有多个构造函数。
- 对象构造完成后,对象就产生了。
- 析构函数
- 析构函数不带参数,不能重载,有且只有一个析构函数。
- 对象析构完成后,对象就不存在了。
- 二者的共同点
- 当开发者没有自定义构造函数和析构函数时,编译器会自动生成一个默认构造函数(无参构造函数)和默认析构函数。
提示
- 在栈上分配内存空间的 C++ 对象(比如
SeqStack s;
),当该对象出了作用域之后(比如函数执行结束之后),C++ 会自动调用该对象的析构函数来释放内存空间。 - 在堆上分配内存空间的 C++ 对象(比如
SeqStack *s = new SeqStack();
),那么必须在该对象出了作用域之前(比如函数执行结束之前),手动执行delete
操作来释放内存空间,这样该对象的析构函数才会被调用。
下面将实现一个顺序栈的数据结构,并结合构造函数与析构函数一起使用。
1 |
|
程序执行后的输出结果:
1 | ===== call test01() ===== |
对象的深拷贝和浅拷贝
使用案例一
下面将实现一个顺序栈的数据结构,并结合拷贝构造函数、深拷贝与赋值运算符重载一起使用。
1 |
|
程序执行后的输出结果:
1 | call SeqStack() |
使用案例二
下面将实现一个循环队列的数据结构,并结合拷贝构造函数、深拷贝与赋值运算符重载一起使用。
循环队列的关键特性:
- 队列特性: 循环队列仍然遵循 “先进先出”(FIFO)的原则。
- 循环特性: 当队尾指针到达数组末尾时,如果队列未满,则可以循环到数组开头继续插入新元素。
- 队空与队满条件: 为了区分队列是空还是满,循环队列通常会牺牲一个数组元素的存储空间:
- 队列为空的条件:
front == rear
- 队列为满的条件:
(rear + 1) % capacity == front
循环队列的应用场景:
- 缓冲区:在生产者 / 消费者模型中用作环形缓冲区。
- 流量控制:如网络数据包的接收缓冲区。
- 任务调度:在任务管理系统中,存储循环调度任务。
1 |
|
程序执行后的输出结果:
1 | 44 71 16 21 11 75 28 29 40 81 86 28 35 43 99 37 45 66 81 53 |
构造函数的初始化列表
1 |
|
程序执行后的输出结果:
1 | name: Book, amount: 100, price: 59.9 |
高频面试题
1 |
|
程序执行后的输出结果:
1 | ma: -858993460, mb: 10 |
特别注意
类成员变量的初始化顺序和它们定义的顺序有关,和构造函数初始化列表中定义的先后顺序无关,更多关于构造函数初始化列表的使用教程请看 这里。
类模板与函数模板
函数模板使用
在 C++ 中,与函数模板相关的专业术语(知识点)有以下几个:
- 函数模板
- 模板的实例化
- 模板函数
- 模板的类型参数
- 模板的非类型参数
- 模板的实参推演
- 模板的特例化(专用化)
- 非模板函数的重载关系
特别注意
- 模板代码是不能在一个
.cpp
源文件中定义,然后在另一个.cpp
源文件中使用的。 - 模板代码在调用之前,一定要看到模板定义的地方,这样模板才能够进行正常的实例化,产生能够被编译器编译的代码。所以,模板代码一般都是写在
.h
头文件中的,然后在.cpp
源文件中使用#include
指令将头文件包含进来。 - 另一种解决办法是,在调用模板函数之前,通过
template bool compare<int>(int, int)
告诉编译器,提前进行指定类型的模板实例化。
使用案例一
函数模板的使用
1 |
|
程序执行后的输出结果:
1 | call compare(T a, T b) |
使用案例二
模板的非类型参数使用
1 |
|
程序执行后的输出结果:
1 | 3 4 9 12 15 23 63 |
类模板的使用
使用案例一
类模板的使用
1 |
|
程序执行后的输出结果:
1 | 6 68 25 5 53 1 20 71 3 7 0 99 2 74 78 99 92 30 24 40 |
使用案例二
类模板的默认类型参数使用
1 | // 类模板(使用默认类型参数) |
使用案例三
使用类模板实现向量容器(Vector)
1 |
|
程序执行后的输出结果:
1 | 49 32 50 26 17 87 26 65 49 83 36 57 97 61 25 44 84 23 41 35 |
特别注意
- 上述代码实现的 vector 容器,如果存放的是 Peson 类的对象,那么在容器初始化的时候,默认会调用 10 次 Peson 类的构造函数,因为在容器的构造函数中使用了
new
操作,比如_first = new T[size]
。 - 上述代码实现的 vector 容器,如果存放的是 Peson 类的对象,当调用
pop_back()
函数来删除容器尾部的元素时,Person 对象所占用的内存空间并没有被释放,这存在内存安全隐患。 - 解决方法可以参考 C++ STL 中的 vector 容器的实现,也就是使用空间配置器(allocator)来解决,空间配置器负责做四件事情,包括内存开辟、内存释放、对象构造、对象析构。
使用空间分配器优化后的代码(重点知识)
1 |
|
程序执行后的输出结果:
1 | ============= test01() ============= |