C++ 巩固基础之六
大纲
近容器
概念介绍
在 C++ 中,常见的近容器有以下几种:
- 数组
- string
- bitset
迭代器
概念介绍
在 C++ 中,常见的迭代器有以下几种:
iterator
和const_iterator
reverse_iterator
和const_reverse_iterator
提示
在 C++ 中,iterator
迭代器是从 const_iterator
迭代器继承而来的。
案例代码
1 |
|
程序运行输出的结果如下:
1 | 25 93 69 85 75 19 79 39 13 19 |
函数对象
概念介绍
- C++ 中的函数对象,其作用类似 C 语言中的函数指针。
- 在 C++ 中,将拥有
operator()
小括号运算符重载函数的对象称作 “函数对象”,或者称作 “仿函数”。 - 通过函数对象调用
operator ()
,会产生内联的效果,其执行效率比较高,因为没有函数调用的开销。 - 由于函数对象是使用类生成的,因此函数对象可以拥有相关的成员变量,比如可以通过成员变量来记录函数对象的使用次数。
- 在 C++ 中,常见的函数对象有:
less
、greater
。
案例代码一
这里主要演示 C++ 中函数指针的使用。
1 |
|
程序运行输出的结果如下:
1 | 0 |
案例代码二
这里主要演示 C++ 中函数对象的使用。
1 |
|
程序运行输出的结果如下:
1 | 0 |
案例代码三
这里主要演示如何在 C++ 的 STL 中使用函数对象。
1 |
|
程序运行输出的结果如下:
1 | ============ test01() ============ |
泛型算法
概念介绍
- C++ 泛型算法 = 模板(Template) + 迭代器 + 函数对象。
- C++ 泛型算法的参数接收的都是迭代器,而且还可以接收函数对象。
- C++ 常见的泛型算法有以下几种:
sort
:排序算法find
:查找算法find_if
:条件查找算法binary_search
:二分查找算法for_each
:遍历算法
特别注意
二分查找算法(binary_search
)并不适用于降序排序(从大到小)的容器,只适用于升序排序(从小到大)的容器,因为它默认是按照升序排序来查找元素的。
案例代码一
1 |
|
程序运行输出的结果如下:
1 | 12 23 7 11 39 25 45 48 58 |
案例代码二
1 |
|
程序运行输出的结果如下:
1 | 8 21 22 33 35 55 59 63 70 |
案例代码三
1 |
|
程序运行输出的结果如下:
1 | 46 81 98 43 35 60 61 68 77 96 |
常见面试题
提示
- 下述面试题都来自 "商汤科技" 的一面,难度属于是简单级别。
(1) 程序的内存布局
- 从下往上分别是:
.text
、.rodata
、.data
、.bss
、堆、栈、内核空间,如图所示
- 从下往上分别是:
(2) 堆和栈的区别
- 堆内存由用户分配(
new
),而栈内存由系统分配(函数调用时) - 堆内存的数据结构通常是二叉堆、大根堆、小根堆,而栈内存的数据结构是栈
- 堆内存由用户分配(
(3) 函数调用的参数是怎样传递的
- 通过汇编代码的分析,可以知道底层是通过压栈的方式来传递参数
(4) 函数调用的参数是按什么顺序传递的
- 函数调用是从右往左传递参数
(5) 为什么函数调用的参数要从右往左压栈
- 因为 C/C++ 需要支持可变参函数(即函数的参数数量不确定)
- C 语言的可变参数函数,缺乏类型安全
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using namespace std;
// 传统 C 语言风格的可变参数函数
void printNumbers(int num, ...) {
va_list args;
va_start(args, num); // 初始化 args,num 是可变参数的第一个参数
for (int i = 0; i < num; i++) {
int n = va_arg(args, int); // 获取下一个参数
cout << n << " ";
}
va_end(args); // 清理
cout << endl;
}
int main() {
printNumbers(3, 10, 20, 30); // 输出:10 20 30
printNumbers(5, 1, 2, 3, 4, 5); // 输出:1 2 3 4 5
return 0;
} - C++ 可变模板参数函数,提供了类型安全,并且能更灵活地处理不同类型的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using namespace std;
// 可变模板参数函数
template<typename... Args>
void printNumbers(Args... args) {
// 使用折叠表达式打印参数
((cout << args << " "), ...);
cout << endl;
}
int main() {
printNumbers(10, 20, 30); // 输出:10 20 30
printNumbers(1, 2, 3, 4, 5); // 输出:1 2 3 4 5
return 0;
}
(6) 有以下一个函数
func
- 主函数里面通过
string s = func(s1, s2);
调用该函数,说一下调用了什么构造函数和调用顺序,以及析构函数的调用顺序- 关键考点:如果用临时对象去拷贝构造新对象,那么临时对象就不会产生,也就是直接构造新对象就行,这是任意 C++ 编译器都会做的优化,如图所示
- 如果在
func
函数内写成return s1 + s2;
,这与原来的写法有什么区别- 省略了原来字符串对象
tmp
的构造函数和析构函数的调用1
2
3
4string func(string s1, string s2) {
string tmp = s1 + s2;
return tmp;
}
- 省略了原来字符串对象
- 主函数里面通过
(7) 在一个结构体里面定义一个
char
和double
变量,它的内存布局是怎样的1
2
3
4
5
6struct Data {
char a;
double b;
};
cout << sizeof(Data) << endl; // 输出 16char a
占 1 字节double b
占 8 字节,并且通常需要 8 字节对齐。- 由于
char a
只有 1 字节,而double b
需要 8 字节对齐,因此编译器会在a
之后填充 7 个字节,使b
在 8 字节边界对齐,最终sizeof(Data) = 16
。
(8) 空结构体占用多少个字节
- C++ 中,空结构体占用 1 个字节
- C 语言中,空结构体占用 0 个字节
(9) 如何防止指针使用带来的内存泄漏
- 使用带引用计数的智能指针:
share_ptr
- 使用不带引用计数的智能指针:
auto_ptr
、unique_ptr
- 使用特殊的智能指针:
weak_ptr
(不增加引用计数,但可用于观察shared_ptr
管理的资源)
智能指针 所有权 引用计数 适用场景 unique_ptr
独占 ❌ 资源独占,生命周期明确 shared_ptr
共享 ✅ 资源共享,生命周期不固定 weak_ptr
观察 shared_ptr
❌ 避免 shared_ptr
循环引用auto_ptr
独占(拷贝时转移) ❌ ⚠ 已废弃,建议改用 unique_ptr
- 使用带引用计数的智能指针: