20.x 第二十章概要与测验(tod0)
章节回顾
又完成一章!只剩这烦人的测验要过关了……
函数参数可通过值传递、引用传递或地址传递。基础数据类型和枚举类型应采用值传递。结构体、类或需要函数修改参数时应采用引用传递。传递指针或内置数组时应采用地址传递。尽可能将引用传递和地址传递参数设为const。
返回值可通过值、引用或地址传递。多数情况下值返回即可,但处理动态分配数据、结构体或类时,引用或地址返回更有效。采用引用/地址返回时,务必确保返回值不会超出作用域。
函数指针允许将函数传递给另一个函数。这有助于调用方自定义函数行为,例如调整列表排序方式。
动态内存分配于堆区。
调用栈记录从程序启动到当前执行点所有活跃函数(已调用但未终止的函数)。局部变量分配在栈上。栈空间有限,可使用std::vector实现栈式行为。
递归函数是调用自身的函数,所有递归函数都需要终止条件。
命令行参数允许用户或其他程序在启动时向程序传递数据。命令行参数始终采用C风格字符串形式,若需处理数值则必须转换为数字类型。
省略号允许向函数传递可变数量的参数。但省略号参数会暂停类型检查,且无法知晓实际传递的参数个数,这些细节需由程序自行追踪。
Lambda函数是可嵌套在其他函数内部的函数。它们无需命名,与算法库结合使用时尤为实用。
测验时间
问题 #1
为以下情况编写函数原型。必要时使用 const。
a) 一个名为 max() 的函数,接受两个双精度数并返回其中较大的那个。
显示解决方案
double max(double x, double y);
b) 一个名为 swap() 的函数,交换两个整数。
显示解决方案
void swap(int& x, int& y);
c) 函数 getLargestElement() 接受动态分配的整型数组,返回最大数值,且调用方可修改返回元素的值(注意长度参数)。
显示解决方案
// Note: array can't be const in this case, because returning a non-const reference to a const element would be a const violation.
int& getLargestElement(int* array, int length);
问题 #2
下列程序存在什么问题?
a)
int& doSomething()
{
int array[]{ 1, 2, 3, 4, 5 };
return array[3];
}
显示答案
doSomething() 返回对局部变量的引用,该变量将在 doSomething 终止时被销毁。
b)
int sumTo(int value)
{
return value + sumTo(value - 1);
}
显示答案
函数 sumTo () 没有终止条件。变量 value 最终会变为负值,函数将无限循环直至栈溢出。
c)
float divide(float x, float y)
{
return x / y;
}
double divide(float x, float y)
{
return x / y;
}
显示答案
这两个除法函数并非独立函数,因为它们具有相同的名称和相同的参数。此外还存在潜在的除以零问题。
d)
#include <iostream>
int main()
{
int array[100000000]{};
for (auto x: array)
std::cout << x << ' ';
std::cout << '\n';
return 0;
}
显示答案
该数组过大,无法在栈上分配。应采用动态分配方式。
e)
#include <iostream>
int main(int argc, char* argv[])
{
int age{ argv[1] };
std::cout << "The user's age is " << age << '\n';
return 0;
}
显示解答
argv[1] 可能不存在。若存在,则 argv[1] 是字符串参数,无法通过赋值转换为整数。
问题 #3
用于判断某个值是否存在于排序数组中的最佳算法称为二分搜索。
二分搜索的工作原理如下:
- 查看数组的中间元素(若数组元素个数为偶数,则向下取整)。
- 若中间元素大于目标元素,则舍弃数组上半部分(或对下半部分进行递归处理)
- 若中心元素小于目标元素,则舍弃数组下半部分(或对上半部分递归搜索)
- 若中心元素等于目标元素,则返回中心元素的索引
- 若遍历整个数组仍未找到目标元素,则返回表示“未找到”的哨兵值(此处使用-1,因其为无效数组索引)
由于每次迭代都能剔除半数元素,该算法效率极高。即使面对百万元素的数组,最多仅需20次迭代即可判定目标值是否存在!但需注意:此算法仅适用于已排序数组。
修改数组(例如舍弃半数元素)代价高昂,因此通常不直接修改数组。取而代之的是使用两个整数(min和max)来保存待检数组中最小值和最大值的索引。
以下通过数组 { 3, 6, 7, 9, 12, 15, 18, 21, 24 } 和目标值 7 演示该算法:初始设置 min = 0, max = 8(因需搜索整个长度为 9 的数组,末元素索引为 8) 。
- 第一轮:计算最小值(0)与最大值(8)的中点,即4。第4个元素值为12,大于目标值。由于数组已排序,我们知道索引大于或等于中点(4)的所有元素都过大。因此保持最小值不变,将最大值设为3。
- 第二轮:计算最小值(0)与最大值(3)的中点,即1。第1个元素值为6,小于目标值。由于数组已排序,所有索引小于或等于中点(1)的元素必然过小。故将最小值设为2,最小值保持不变。
- 第三轮:计算min(2)与max(3)的中点,即2。元素#2的值为7,正是目标值。因此返回2。
在 C++20 中,可通过调用 std::midpoint 计算中点。C++20 之前需自行计算——此时可使用 min + ((max - min) / 2) 避免溢出(当 min 和 max 均为正值时)。
给定以下代码:
#include <iostream>
#include <iterator>
// array is the array to search over.
// target is the value we're trying to determine exists or not.
// min is the index of the lower bounds of the array we're searching.
// max is the index of the upper bounds of the array we're searching.
// binarySearch() should return the index of the target element if the target is found, -1 otherwise
int binarySearch(const int* array, int target, int min, int max)
{
}
int main()
{
constexpr int array[]{ 3, 6, 8, 12, 14, 17, 20, 21, 26, 32, 36, 37, 42, 44, 48 };
// We're going to test a bunch of values to see if the algorithm returns the result we expect
// Here are the test values
constexpr int testValues[]{ 0, 3, 12, 13, 22, 26, 43, 44, 48, 49 };
// And here are the results that we expect for those test values
int expectedResult[]{ -1, 0, 3, -1, -1, 8, -1, 13, 14, -1 };
// Make sure we have the same number of test values and expected results
static_assert(std::size(testValues) == std::size(expectedResult));
int numTestValues { std::size(testValues) };
// Loop through all of the test values
for (int count{ 0 }; count < numTestValues; ++count)
{
// See if our test value is in the array
int index{ binarySearch(array, testValues[count], 0, static_cast<int>(std::size(array)) - 1) };
// If it matches our expected value, then great!
if (index == expectedResult[count])
std::cout << "test value " << testValues[count] << " passed!\n";
else // otherwise, our binarySearch() function must be broken
std::cout << "test value " << testValues[count] << " failed. There's something wrong with your code!\n";
}
return 0;
}
a) 编写二分搜索函数的迭代版本。
提示:当最小索引大于最大索引时,可安全断定目标元素不存在。
显示解决方案
#include <cassert>
#include <iostream>
#include <numeric> // for std::midpoint
// array is the array to search over.
// target is the value we're trying to determine exists or not.
// min is the index of the lower bounds of the array we're searching.
// max is the index of the upper bounds of the array we're searching.
// binarySearch() should return the index of the target element if the target is found, -1 otherwise
int binarySearch(const int* array, int target, int min, int max)
{
assert(array); // make sure array exists
while (min <= max)
{
// implement this iteratively
int midpoint{ std::midpoint(min, max) };
// Before C++20
// int midpoint{ min + ((max-min) / 2) }; // this way of calculating midpoint avoids overflow
if (array[midpoint] > target)
{
// if array[midpoint] > target, then we know the number must be in the lower half of the array
// we can use midpoint - 1 as the upper index, since we don't need to retest the midpoint next iteration
max = midpoint - 1;
}
else if (array[midpoint] < target)
{
// if array[midpoint] < target, then we know the number must be in the upper half of the array
// we can use midpoint + 1 as the lower index, since we don't need to retest the midpoint next iteration
min = midpoint + 1;
}
else
{
return midpoint;
}
}
return -1;
}
b) 编写二分搜索函数的递归版本。
显示解决方案
#include <cassert>
#include <numeric> // for std::midpoint
// array is the array to search over.
// target is the value we're trying to determine exists or not.
// min is the index of the lower bounds of the array we're searching.
// max is the index of the upper bounds of the array we're searching.
// binarySearch() should return the index of the target element if the target is found, -1 otherwise
int binarySearch(const int* array, int target, int min, int max)
{
assert(array); // make sure array exists
// implement this recursively
if (min > max)
return -1;
int midpoint{ std::midpoint(min, max) };
// Before C++20
// int midpoint{ min + ((max-min) / 2) }; // this way of calculating midpoint avoids overflow
if (array[midpoint] > target)
{
return binarySearch(array, target, min, midpoint - 1);
}
else if (array[midpoint] < target)
{
return binarySearch(array, target, midpoint + 1, max);
}
return midpoint;
}
提示:
std::binary_search 在排序列表中存在指定值时返回 true。
std::equal_range 返回包含给定值的首个和最后一个元素的迭代器。解答本题时请勿使用这些函数,但未来需要二分搜索时可加以利用。

浙公网安备 33010602011771号