数组基础

原文:C++ Arrays

译者:RAAI学习团队


欢迎来到 C++ 数组的学习!数组是编程中非常基础且强大的工具,理解它对于你后续学习更复杂的数据结构和算法至关重要。

本章的学习将围绕以下几个核心主题展开:

1. 数组的基础

  • 理解数组是什么:数组是一种固定大小、存储相同类型数据的连续内存空间。
  • 数组的声明:如何告诉 C++ 你要创建一个什么类型的数组,以及它能存储多少个元素。
  • 数组的索引:理解为什么数组的第一个元素是索引 ,以及如何通过索引访问和修改数组中的特定元素。
  • 数组的初始化:学习多种给数组元素赋值的方法,包括声明时一次性赋值,以及如何将所有元素初始化为零。

2. 多维数组

  • 理解多维数组:将一维数组的概念扩展到多个维度,例如二维数组(表格)和三维数组(立方体)。
  • 多维数组的声明和初始化:学习如何定义和初始化多行多列的数据结构。
  • 通过多重索引访问:掌握使用多个索引来精确地定位多维数组中的每个元素。

3. 字符数组与字符串

  • 字符数组作为字符串:学习 C++ 中传统的字符串表示方法,即使用 char 类型的数组。
  • 空字符 \0 的重要性:理解为什么 C 风格字符串需要以空字符结尾,以及它在字符串操作中的作用。
  • 基本字符串操作:了解如何使用 ` ` 库中的函数(如 `strlen`、`strcpy`、`strcat`、`strcmp`)来处理字符数组的长度、复制、连接和比较。

4. 数组作为函数参数

  • 数组的“退化”现象:理解当数组传递给函数时,实际上是传递了数组的起始地址(指针),而不是整个数组的副本。
  • 对原始数组的影响:掌握函数内部对数组元素的修改会直接反映在函数外部的原始数组上。
  • 传递数组大小的必要性:由于数组退化为指针,函数无法自行判断数组大小,因此需要额外传递一个大小参数。

5. 数组的常见操作

  • 遍历数组:使用循环(例如 for 循环)访问数组中的所有元素。
  • 查找元素:在数组中寻找特定值是否存在,并确定其位置。
  • 计算总和与平均值:对数组中的数值进行基本的算术操作。
  • 反转数组:学习如何将数组元素的顺序颠倒。
  • 计算数组大小:利用 sizeof 运算符计算静态数组的元素数量。

6. 数组的限制和注意事项

  • 固定大小的限制:再次强调数组大小不可变动的特点。
  • 不能直接复制或返回:理解数组不能像普通变量那样直接赋值或作为函数返回值。
  • 越界访问的危险:这是数组最常见的错误之一,理解其概念、危害以及如何避免。
  • 未初始化数组的“垃圾值”:了解未赋初值的数组元素内容是不可预测的。
  • 字符串缓冲区溢出:理解在字符数组中存储过长字符串的风险。
  • 避免错误的技巧:学习使用常量定义数组大小等实践经验。

7. 附加练习题

  • 通过一系列精心设计的练习题,巩固你对数组基础、多维数组、字符数组操作以及基本排序算法的理解。这些题目会帮助你将理论知识应用到实际编程中。

通过这个学习纲要,你将能够清晰地看到 C++ 数组的整体面貌,并逐步掌握各个知识点。祝你学习愉快!


在 C++ 中,数组(Arrays)是一种用来存储固定大小、相同类型元素的连续内存空间的数据结构。你可以把它想象成一系列排好队的小格子,每个格子都装着同一类东西。


数组的声明和初始化

在使用数组前,我们首先要声明它,告诉编译器数组叫什么名字、能装多少个元素、以及元素是什么类型。初始化则是在声明的同时或之后给数组元素赋予初始值。

基本数组声明

要声明一个数组,你需要指定元素的数据类型、数组的名称以及它能容纳的元素数量(大小)

#include 
<iostream> 

using namespace std; 

int main() {
    // 声明一个包含5个整数的数组,名为 'numbers'
    // 此时,数组中的5个位置已经被分配,但里面的值是不确定的(通常是“垃圾值”)
    int numbers[5]; 

    // 初始化数组元素:逐个赋值
    // 数组的索引(下标)从0开始,这意味着第一个元素是 numbers[0],
    // 第二个是 numbers[1],以此类推。
    numbers[0] = 10;
    numbers[1] = 20;
    numbers[2] = 30;
    numbers[3] = 40;
    numbers[4] = 50; // 因为数组大小是5,所以最后一个元素的索引是 4 (即 5 - 1)

    // 访问数组元素:使用循环遍历并打印每个元素
    // 循环条件 i < 5 确保我们不会访问到数组范围之外的内存,
    // 因为索引最大只到 4。
    for (int i = 0; i < 5; i++) {
        cout << "numbers[" << i << "] = " << numbers[i] << endl;
    }

    return 0;
}

运行结果示例:

numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
numbers[3] = 40
numbers[4] = 50

数组初始化方法

C++ 提供了多种方便的方式来初始化数组,让你的代码更简洁。

#include 
<iostream> 
using namespace std; 

int main() {
    // 方法1:使用初始化列表
    // 这是最常见和推荐的方式。编译器会根据列表中的元素数量自动确定数组大小,
    // 或者如果你指定了大小,它会检查是否匹配。
    int arr1[5] = {1, 2, 3, 4, 5}; 

    // 方法2:自动计算大小
    // 当你不确定数组需要多大,或者想让编译器帮你数时,可以省略方括号里的数字。
    // 编译器会自动计算出 arr2 的大小是 5。
    int arr2[] = {10, 20, 30, 40, 50}; 

    // 方法3:部分初始化(其余元素为0)
    // 如果初始化列表中的元素数量少于数组声明的大小,
    // 那么剩余的元素会自动被初始化为0。
    int arr3[5] = {1, 2, 3}; // arr3 将是 {1, 2, 3, 0, 0}

    // 方法4:全部初始化为0
    // 这是一个便捷的方法,将数组的所有元素都初始化为0。
    int arr4[5] = {0}; // arr4 将是 {0, 0, 0, 0, 0}

    // 方法5:C++11 统一初始化(推荐的现代C++写法)
    // C++11 引入了花括号 {} 作为通用的初始化方式,语法更统一,更安全。
    // 当然有些比赛(比如蓝桥杯)或者做题网站可能使用C++98、C++6之类的老引擎,这时就用不了这种方法了。
    int arr5[5]{1, 2, 3, 4, 5}; // 功能与方法1相同

    // 输出 arr1 的内容
    cout << "arr1: ";
    for (int i = 0; i < 5; i++) {
        cout << arr1[i] << " ";
    }
    cout << endl; 

    // 输出 arr3 的内容,观察未初始化的元素是否为0
    cout << "arr3: ";
    for (int i = 0; i < 5; i++) {
        cout << arr3[i] << " ";
    }
    cout << endl; 

    return 0;
}

运行结果示例:

arr1: 1 2 3 4 5 
arr3: 1 2 3 0 0 

关键点回顾:

  • 索引从0开始: 如果数组有 N 个元素,它们的索引是 N-1
  • 固定大小: C++ 中的普通数组一旦声明,其大小就固定了,不能在运行时改变。
  • 内存连续: 数组的元素在内存中是紧挨着存放的,这使得访问速度非常快。

多维数组

多维数组可以看作是“数组的数组”。最常见的是二维数组(如表格或矩阵)和三维数组(如立方体或空间网格)。

二维数组

二维数组就像一个表格,有行和列,横的是行,竖的是列。声明时,你需要指定行数和列数。访问元素时,则需要两个索引:[行索引][列索引]

#include 
<iostream>
using namespace std; 

int main() {
    // 声明并初始化一个 2x3 的二维数组 (2行, 3列)
    // 它可以被想象成:
    // [1, 2, 3]
    // [4, 5, 6]
    int matrix[2][3] = {
        {1, 2, 3}, // 第一行(索引 0)
        {4, 5, 6}  // 第二行(索引 1)
    };

    // 访问二维数组元素
    // 外层循环控制行,内层循环控制列
    for (int i = 0; i < 2; i++) { // 遍历行 (i 从 0 到 1)
        for (int j = 0; j < 3; j++) { // 遍历列 (j 从 0 到 2)
            cout << "matrix[" << i << "][" << j << "] = " << matrix[i][j] << endl;
        }
    }

    // 打印矩阵形式:更直观地显示二维数组内容
    cout << "\nMatrix:" << endl; 
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            cout << matrix[i][j] << " "; 
        }
        cout << endl; 
    }

    return 0;
}

运行结果示例:

matrix[0][0] = 1
matrix[0][1] = 2
matrix[0][2] = 3
matrix[1][0] = 4
matrix[1][1] = 5
matrix[1][2] = 6

Matrix:
1 2 3 
4 5 6 

三维数组

三维数组可以看作是二维数组的集合,或者说是一个“立方体”。你需要三个索引来定位一个元素:[第一维索引][第二维索引][第三维索引]

你可以这样理解这些索引:

  • 第一维索引:这相当于你堆叠的二维数组的层数。想象你把多个二维表格(像Excel工作表)一个接一个地叠起来,第一维索引就是用来选择你要看哪一张表格。
  • 第二维索引:在你选定的那一张二维表格(层)里,这代表着表格的行数
  • 第三维索引:这代表着表格中具体某一行里的列数

#include 
<iostream>
using namespace std;

int main() {
    // 声明并初始化一个 2x3x2 的三维数组 (2层, 每层3行, 每行2列)
    int cube[2][3][2] = {
        // 第一层(索引 0)
        {
            {1, 2},  // 0,0,x
            {3, 4},  // 0,1,x
            {5, 6}   // 0,2,x
        },
        // 第二层(索引 1)
        {
            {7, 8},  // 1,0,x
            {9, 10}, // 1,1,x
            {11, 12} // 1,2,x
        }
    };

    // 访问三维数组元素:使用三层嵌套循环
    for (int i = 0; i < 2; i++) {   // 遍历第一维 (层)
        for (int j = 0; j < 3; j++) { // 遍历第二维 (行)
            for (int k = 0; k < 2; k++) { // 遍历第三维 (列)
                cout << "cube[" << i << "][" << j << "][" << k 
                     << "] = " << cube[i][j][k] << endl;
            }
        }
    }

    return 0;
}

运行结果示例:

cube[0][0][0] = 1
cube[0][0][1] = 2
cube[0][1][0] = 3
cube[0][1][1] = 4
cube[0][2][0] = 5
cube[0][2][1] = 6
cube[1][0][0] = 7
cube[1][0][1] = 8
cube[1][1][0] = 9
cube[1][1][1] = 10
cube[1][2][0] = 11
cube[1][2][1] = 12

字符数组和字符串

在 C++ 中,字符串通常以两种方式表示:C 风格字符串(即字符数组)和 C++ 标准库中的 std::string。这里我们先关注基础的字符数组,std::string 将在后面的课程中学习,感兴趣的同学可以提前了解一下。

字符数组char 类型元素的数组,用来存储一系列字符。一个 C 风格字符串以空字符 \0 结尾,这个空字符标志着字符串的结束。

#include 
<iostream>
#include 
<cstring> 
using namespace std;

int main() {
    // 字符数组声明和初始化
    // char str1[10] 可以存储最多9个字符 + 1个空字符\0
    char str1[10] = "Hello"; 
    // char str2[] 编译器会自动计算大小,包括空字符\0
    char str2[] = "World"; 

    // 直接打印字符数组,cout 会识别并打印直到遇到空字符为止
    cout << "str1: " << str1 << endl;
    cout << "str2: " << str2 << endl;

    // 字符串长度:使用 strlen() 函数 (来自 
<cstring>)
    // 它返回字符串中字符的数量,不包括终止空字符。
    cout << "Length of str1: " << strlen(str1) << endl; 

    // 字符串复制:使用 strcpy() 函数 (来自 
<cstring>)
    // 将 str1 的内容复制到 str3。str3 必须有足够的空间。
    char str3[20]; // 声明一个足够大的字符数组
    strcpy(str3, str1); 
    cout << "str3 (copied): " << str3 << endl;

    // 字符串连接:使用 strcat() 函数 (来自 
<cstring>)
    // 将一个字符串连接到另一个字符串的末尾。
    strcat(str3, " "); // 在 str3 后连接一个空格
    strcat(str3, str2); // 在 str3 后连接 str2 的内容
    cout << "str3 (concatenated): " << str3 << endl;

    // 字符串比较:使用 strcmp() 函数 (来自 
<cstring>)
    // 如果两个字符串相等,返回0。否则返回非0值。
    if (strcmp(str1, str2) == 0) {
        cout << "str1 equals str2" << endl;
    } else {
        cout << "str1 does not equal str2" << endl;
    }

    return 0;
}

运行结果示例:

str1: Hello
str2: World
Length of str1: 5
str3 (copied): Hello
str3 (concatenated): Hello World
str1 does not equal str2

多维数组和字符数组的总结:

  • 多维数组通过增加方括号来扩展维度,每个维度都有自己的索引。它适用于表示表格、图像、游戏地图等需要多个坐标来定位的数据。
  • 字符数组是 C++ 中处理字符串的传统方式,需要特别注意字符串末尾的空字符 \0。操作字符数组时,通常会用到 ` ` 库中的函数(如 `strlen`, `strcpy`, `strcat`, `strcmp`)。

数组作为函数参数

在 C++ 中,当你将一个数组传递给函数时,实际上数组会“退化”成一个指向其第一个元素的指针。这意味着函数接收到的是数组的内存地址,而不是数组的完整副本。

这个特性有几个重要的含义:

  1. 不会复制整个数组:这对于大型数组来说非常高效,避免了不必要的内存开销和复制时间。
  2. 函数可以修改原始数组:由于函数接收的是地址,它可以通过这个地址直接访问并修改原始数组中的元素。这种行为被称为“传引用”(pass-by-reference)的效果,尽管技术上数组是“传值”了一个指针。
  3. 需要传递数组大小:因为数组退化成了指针,函数内部无法直接知道数组的原始大小。因此,通常需要额外传递一个参数来表示数组的元素数量。
#include 
<iostream>
#include 
<cstring> 
using namespace std; 

// 函数:打印数组元素
// arr[] 表示接收一个整数数组(实际是一个指向int的指针)
// size 表示数组的元素数量
void printArray(int arr[], int size) {
    cout << "Array elements: ";
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

// 函数:修改数组元素
// 注意:对 arr 的修改会直接影响到 main 函数中传递进来的原始数组
void modifyArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2;  // 将每个元素乘以2
    }
}

// 函数:查找数组中的最大值
// 返回数组中的最大整数
int findMax(int arr[], int size) {
    int maxVal = arr[0]; // 假设第一个元素是最大值
    for (int i = 1; i < size; i++) { // 从第二个元素开始比较
        if (arr[i] > maxVal) {
            maxVal = arr[i]; // 更新最大值
        }
    }
    return maxVal;
}

int main() {
    // 声明并初始化一个整数数组
    int numbers[] = {1, 2, 3, 4, 5};
    // 计算数组的元素数量:数组总字节大小 / 单个元素字节大小
    int size = sizeof(numbers) / sizeof(numbers[0]); 

    cout << "Original array:" << endl;
    printArray(numbers, size); // 打印原始数组

    cout << "Maximum value: " << findMax(numbers, size) << endl; // 查找并打印最大值

    modifyArray(numbers, size); // 调用函数修改数组
    cout << "Modified array:" << endl;
    printArray(numbers, size); // 再次打印数组,观察修改后的结果

    return 0;
}

运行结果示例:

Original array:
Array elements: 1 2 3 4 5 
Maximum value: 5
Modified array:
Array elements: 2 4 6 8 10 

关键点回顾:

  • 当数组作为函数参数传递时,它会退化为指向其第一个元素的指针
  • 这意味着函数内部对数组的修改会直接影响到原始数组
  • 为了在函数内部知道数组的大小,你通常需要额外传递一个表示数组大小的参数

C++ 数组:常见操作


数组作为一种基础的数据结构,我们不仅需要学会如何声明和初始化它,更重要的是掌握如何对数组中的数据进行各种常见操作。这些操作是许多算法和程序的基础。


数组的常见操作

下面我们将通过一个例子,演示如何在 C++ 中对数组进行查找、求和、计算平均值以及反转等操作。

#include 
<iostream>  
#include 
<algorithm> 
using namespace std; 

int main() {
    // 声明并初始化一个整数数组
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    // 计算数组的元素数量:
    // sizeof(arr) 返回整个数组在内存中占用的总字节数。
    // sizeof(arr[0]) 返回数组第一个元素(即单个 int 类型)在内存中占用的字节数。
    // 两者相除,即可得到数组中元素的数量。
    // 这种方法只适用于在当前作用域内完整定义的静态数组。
    int size = sizeof(arr) / sizeof(arr[0]);

    // 打印原始数组
    cout << "Original array: ";
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    // --- 查找元素 ---
    // 目标:检查某个特定值是否存在于数组中,并找出其位置。
    int searchValue = 25; // 我们要查找的值
    bool found = false;   // 标记是否找到
    int index = -1;       // 存储找到的索引

    // 遍历数组,逐个比较
    for (int i = 0; i < size; i++) {
        if (arr[i] == searchValue) {
            found = true; // 找到啦!
            index = i;    // 记录它的索引
            break;        // 找到后就可以停止查找了,提高效率
        }
    }

    if (found) {
        cout << "Found " << searchValue << " at index " << index << endl;
    } else {
        cout << searchValue << " not found" << endl;
    }

    // --- 计算总和 ---
    // 目标:将数组中所有元素的值加起来。
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i]; // 累加每个元素
    }
    cout << "Sum: " << sum << endl;

    // --- 计算平均值 ---
    // 目标:计算数组中所有元素的平均值。
    // 注意:需要将 `sum` 转换为 `double` 类型,以避免整数除法造成的精度丢失。
    double average = (double)sum / size;//(double)sum可以把sum强制转换为double形
    cout << "Average: " << average << endl;

    // --- 反转数组 ---
    // 目标:将数组的元素顺序颠倒,例如 {1,2,3,4} 变为 {4,3,2,1}。
    // 思路:交换数组两端的元素,然后逐渐向中间靠拢。
    for (int i = 0; i < size / 2; i++) {
        // 使用 std::swap 交换 arr[i] 和 arr[size - 1 - i]
        // std::swap 用法:std::swap (a,b) 即为把 a 和 b 的值互换。
        // 例如:i=0 交换 arr[0] 和 arr[size-1]
        //       i=1 交换 arr[1] 和 arr[size-2]
        swap(arr[i], arr[size - 1 - i]);
    }

    cout << "Reversed array: ";
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    return 0;
}

运行结果示例:

Original array: 64 34 25 12 22 11 90 
Found 25 at index 2
Sum: 258
Average: 36.8571
Reversed array: 90 11 22 12 25 34 64 

数组的限制和注意事项

尽管数组是 C++ 中非常基础和有用的数据结构,但它也有一些固有的限制和需要特别注意的地方。理解这些限制可以帮助你避免常见的编程错误,编写出更健壮的代码。

1. 固定大小

C++ 中的普通数组一旦声明,其大小(能容纳的元素数量)就固定了,在程序运行时不能改变。这意味着如果你声明了一个 int arr[5]; 的数组,它就只能存储 5 个整数。

尝试访问数组范围之外的内存(称为越界访问)会导致未定义行为。这可能导致程序崩溃、输出错误数据,或者在某些情况下,程序甚至能正常运行,但会在未来引发难以调试的问题。

#include 
<iostream>
using namespace std;

int main() {
    int arr[5]; // 声明一个包含5个元素的数组,索引范围是 0 到 4

    // arr[5] = 10; // 错误且危险:这是一个越界访问!
                  // 数组只有 arr[0] 到 arr[4]

    // 编译时通常不会报错,但运行时会带来问题,切记避免!

    return 0;
}

如何避免越界访问?

  • 始终检查索引:在循环或任何访问数组元素的代码中,确保索引值在 size - 1 的有效范围内。
  • 使用常量定义大小:在算法竞赛(一般会限定最大输入量)或大型项目中,通常会使用 const int 定义数组的最大尺寸,这样可以集中管理和避免硬编码数字。
    const int N = 1000; // 定义一个最大值
    int my_array[N];    // 使用常量声明数组,避免越界

2. 不能直接复制

与基本数据类型不同,C++ 中的数组不能像这样直接通过赋值运算符 = 进行整体复制:

#include 
<iostream>
using namespace std;

int main() {
    int arr1[5] = {1, 2, 3, 4, 5};
    int arr2[5];

    // arr2 = arr1; // 错误:C++ 不允许直接使用赋值运算符复制整个数组

    // 正确的复制方法:逐个元素复制
    for (int i = 0; i < 5; i++) {
        arr2[i] = arr1[i];
    }

    // 验证复制是否成功
    cout << "arr2 after copy: ";
    for (int i = 0; i < 5; i++) {
        cout << arr2[i] << " ";
    }
    cout << endl;

    return 0;
}

提示:

  • 对于字符数组(C 风格字符串),你可以使用 strcpy 函数进行复制(但要注意目标数组空间是否足够)。

3. 不能直接返回

C++ 函数不能直接返回一个完整的数组。当你尝试返回数组名时,实际上返回的是指向该数组第一个元素的指针

#include 
<iostream>
using namespace std;

// 错误示范:不能直接返回数组
// int[] getArrayBad() {
//     int arr[5] = {1, 2, 3, 4, 5}; // 局部数组在函数结束后会被销毁
//     return arr; // 返回一个指向已销毁内存的指针,非常危险!
// }

// 正确的方法:返回一个指向数组的指针
// 注意:这里使用 static 关键字,确保数组在函数结束后依然存在于内存中。
//      但通常不推荐返回局部静态数组,因为它可能引起意想不到的副作用。
int* getArray() {
    static int arr[5] = {1, 2, 3, 4, 5}; // static 局部变量在程序生命周期内都存在
    cout << "Inside getArray: " << arr[0] << endl;
    return arr; // 返回数组第一个元素的地址
}

int main() {
    int* myArrPtr = getArray(); // 接收返回的指针

    cout << "Elements from returned array: ";
    for (int i = 0; i < 5; i++) {
        cout << myArrPtr[i] << " "; // 通过指针访问数组元素
    }
    cout << endl;

    return 0;
}

更安全的替代方案:

  • 通过参数传递:让函数接受一个数组(或指针)作为参数,并在函数内部修改它(正如我们前面“数组作为函数参数”一节所学)。

如何使程序“崩溃”(或产生未定义行为)

了解这些常见错误可以帮助你避免它们。尝试以下操作来观察程序的行为,并理解其危险性:

  1. 数组越界: 试图访问数组的有效索引范围之外的元素。

    #include 
    <iostream>
    using namespace std;
    
    int main() {
        int arr[5] = {1, 2, 3, 4, 5};
        // 尝试访问索引 10,但数组只有索引 0 到 4
        cout << "Attempting to access arr[10]: " << arr[10] << endl; // 越界访问!
        return 0;
    }
    • 你会看到什么? 程序可能会崩溃(段错误),或者打印出一个随机的、无意义的数字。这是因为你正在读取不属于你程序内存区域的数据。
  2. 未初始化的数组: 声明数组后,如果没有明确地初始化所有元素,那么它们会包含“垃圾值”(即之前内存中残留的随机数据)。

    #include 
    <iostream>
    using namespace std;
    
    int main() {
        int arr[5]; // 声明,但未完全初始化
        cout << "Uninitialized array elements: ";
        for (int i = 0; i < 5; i++) {
            cout << arr[i] << " "; // 打印未定义的值
        }
        cout << endl;
        return 0;
    }
    • 你会看到什么? 你会看到一些看起来像随机数的奇怪数字。这些值是不可预测的,并且在每次程序运行时可能都不同。
  3. 字符串缓冲区溢出: 当你尝试将一个比目标字符数组更大的字符串复制进去时,就会发生缓冲区溢出。这会写入到数组之外的内存区域。

    #include 
    <iostream>
    #include 
    <cstring> // For strcpy
    using namespace std;
    
    int main() {
        // str 只能容纳 5 个字符,包括末尾的空字符 '\0'。
        // 所以它最多只能存储 4 个实际字符。
        char str[5]; 
    
        // "Hello World" 有 11 个字符 + 1 个空字符 = 12 个字节。
        // 这远超 str 的 5 个字节容量。
        strcpy(str, "Hello World"); // 缓冲区溢出!
    
        cout << "Overflowed string: " << str << endl;
        return 0;
    }
    • 你会看到什么? 程序可能会崩溃,或者打印出部分字符串,后面跟着一些奇怪的字符,甚至可能影响到其他变量的值。这是非常危险的,因为它可以被恶意利用。

通过了解这些限制和常见的陷阱,你就能更好地掌握 C++ 数组的使用,并编写出更安全、更可靠的代码。当你遇到上述问题时,首先检查你的数组索引、初始化以及是否使用了合适的标准库容器。


外部研究

对于这个练习,你需要:

  1. 查找C++中数组的所有语法规则
  2. 了解std::array容器的用法
  3. 研究动态数组的实现
  4. 学习C++11引入的数组特性

附加题


练习 1: 数组求和

题目描述

给定一个包含 $N$ 个整数的数组,请你计算并输出数组中所有元素的总和。

输入格式

第一行包含一个正整数 $N$ ($1 \le N \le 100$),表示数组中元素的个数。 第二行包含 $N$ 个整数,每个整数之间用空格隔开。每个整数 $a_i$ 的范围在 $0$ 到 $1000$ 之间。

输出格式

输出一个整数,表示数组中所有元素的总和。

输入输出样例 #1

输入 #1

5
1 2 3 4 5

输出 #1

15

说明/提示

  • 你需要先读取 N,然后使用循环将 N 个整数存入一个数组中。
  • 使用 sizeof(arr) / sizeof(arr[0]) 来计算数组大小的方法只适用于在当前函数内完整定义的静态数组。如果数组通过参数传递,你需要单独传递大小。

练习 2: 数组反转打印

题目描述

小明有一个包含若干整数的数组。他想知道如果把这些数字倒过来念会是什么样子。请你编写一个程序,将输入的整数数组按逆序打印出来。

输入格式

第一行包含一个正整数 $N$ ($1 \le N \le 100$),表示数组中元素的个数。 第二行包含 $N$ 个整数,每个整数之间用空格隔开。每个整数 $a_i$ 的范围在 $0$ 到 $1000$ 之间。

输出格式

输出一行,包含 $N$ 个整数,它们是原始数组的逆序,整数之间用一个空格隔开,行末无多余空格。

输入输出样例 #1

输入 #1

5
10 20 30 40 50

输出 #1

50 40 30 20 10

说明/提示

  • 你可以将所有数字先存入一个数组,然后从数组的最后一个元素向前遍历打印。

练习 3: 查找最大值

题目描述

给定一个整数数组,请你找出并输出数组中的最大值。

输入格式

第一行包含一个正整数 $N$ ($1 \le N \le 100$),表示数组中元素的个数。 第二行包含 $N$ 个整数,每个整数之间用空格隔开。每个整数 $a_i$ 的范围在 $0$ 到 $1000$ 之间。

输出格式

输出一个整数,表示数组中的最大值。

输入输出样例 #1

输入 #1

4
15 8 22 10

输出 #1

22

说明/提示

  • 你可以假设数组的第一个元素是最大值,然后遍历其余元素进行比较和更新。
  • 确保你的程序能够处理只有一个元素的数组。

练习 4: 二维数组求和

题目描述

给定一个 $R$ 行 $C$ 列的二维整数数组,请你计算并输出数组中所有元素的总和。

输入格式

第一行包含两个正整数 $R$ 和 $C$ ($1 \le R, C \le 10$),分别表示二维数组的行数和列数。 接下来是 $R$ 行,每行包含 $C$ 个整数,每个整数之间用空格隔开。每个整数 $a_{ij}$ 的范围在 $0$ 到 $100$ 之间。

输出格式

输出一个整数,表示二维数组中所有元素的总和。

输入输出样例 #1

输入 #1

2 3
1 2 3
4 5 6

输出 #1

21

说明/提示

  • 你需要使用嵌套循环来遍历二维数组中的每个元素。

练习 5: 自动修正

题目描述

大家都知道一些办公软件有自动将字母转换为大写的功能。输入一个长度不超过 $100$ 且不包括空格的字符串。要求将该字符串中的所有小写字母变成大写字母并输出。

输入格式

输入一行,一个字符串。

输出格式

输出一个字符串,即将原字符串中的所有小写字母转化为大写字母。

输入输出样例 #1

输入 #1

Nanami ChiakI!

输出 #1

NANAMI CHIAKI!

说明/提示

  • 你可以使用字符数组(C 风格字符串)来存储输入的字符串。
  • 你需要遍历字符串中的每一个字符。
  • 可以利用 ASCII 码的特性进行大小写转换(小写字母的 ASCII 码比对应的大写字母大一个固定值),或者使用 ` ` 头文件中的 `toupper()` 函数。

练习 6: 小鱼的数字游戏

题目描述

小鱼最近被要求参加一个数字游戏,要求它把看到的一串数字 $a_i$(长度不一定,以 $0$ 结束),记住了然后反着念出来(表示结束的数字 $0$ 就不要念出来了)。这对小鱼的那点记忆力来说实在是太难了,你也不想想小鱼的整个脑袋才多大,其中一部分还是好吃的肉!所以请你帮小鱼编程解决这个问题。

输入格式

一行内输入一串整数,以 $0$ 结束,以空格间隔。

输出格式

一行内倒着输出这一串整数,以空格间隔。

输入输出样例 #1

输入 #1

3 65 23 5 34 1 30 0

输出 #1

30 1 34 5 23 65 3

说明/提示

数据规模与约定

对于 $100%$ 的数据,保证 $0 \leq a_i \leq 2^{31} - 1$,数字个数不超过 $100$。

  • 这道题的关键是,你不知道数字的个数,直到遇到 $0$ 才结束。你需要一个足够大的数组来存储这些数字。
  • 读入 $0$ 后,它不应该被打印出来。

练习 7: 排序

题目描述

将读入的 $N$ 个数从小到大排序后输出。

输入格式

第一行为一个正整数 $N$ ($1 \le N \le 100$)。 第二行包含 $N$ 个空格隔开的正整数 $a_i$,为你需要进行排序的数 ($1 \le a_i \le 1000$)。

输出格式

将给定的 $N$ 个数从小到大输出,数之间空格隔开,行末换行且无空格。

输入输出样例 #1

输入 #1

5
4 2 4 5 1

输出 #1

1 2 4 4 5

说明/提示

  • 你可以尝试实现简单的排序算法,如冒泡排序选择排序

  • 冒泡排序 (Bubble Sort): 想象你有一串数字。每次遍历时,你都把最大的数字像泡泡一样“冒”到最后面。你就不断地重复这个过程,直到所有数字都排好了。

  • 选择排序 (Selection Sort): 每次从剩下的数字里找到最小的那个,然后把它放到最前面。你就不断重复这个过程,直到所有数字都排好了。


计算机小白一枚
最后更新于 2025-07-22