C++数组从入门到精通:定义、内存、越界与vector对比

发布时间:2026/7/13 6:17:01

C++数组从入门到精通:定义、内存、越界与vector对比 1. 项目概述从零开始彻底搞懂C数组最近在整理黑马程序员2024新版《C零基础入门》的笔记正好翻到第三章关于数组的部分。数组这玩意儿可以说是C里最基础、也最容易让人“一看就会一写就废”的知识点之一。很多新手朋友学到这里觉得不就是int arr[10];嘛简单。结果一到自己写代码不是下标越界就是内存访问出错要么就是搞不清数组名到底是个啥。我当年学的时候也踩过不少坑比如试图用变量去定义数组大小或者想当然地给整个数组重新赋值。所以今天这篇笔记我就结合黑马课程的内容加上我自己十多年写C的经验把数组的定义、初始化、使用以及那些教科书上不讲的“坑”掰开揉碎了讲清楚。无论你是刚入门的新手还是想巩固基础的老手相信都能从这篇笔记里找到你需要的东西。2. 数组的本质为什么说它是“连续内存的盒子”2.1 数组到底是什么在C里数组Array是一种数据结构用来存储固定大小的、相同类型的元素的顺序集合。这句话里有三个关键词固定大小、相同类型、顺序集合。你可以把它想象成一排连在一起的、大小完全相同的储物柜。每个柜子元素只能放一种类型的东西比如全是书或者全是衣服而且柜子的数量数组大小在你造好这排柜子的时候就定死了不能再加也不能减。更重要的是这些柜子是紧挨着建的门牌号索引从0开始连续编号。为什么设计成这样核心是为了效率。因为内存是连续的CPU可以通过“基地址 偏移量”的方式用一次计算就快速定位到任何一个元素这种随机访问的速度是O(1)的非常快。相比之下链表这类非连续存储的结构访问某个元素就需要从头遍历。2.2 数组定义的语法与内存布局定义一个数组的基本语法是数据类型 数组名[常量表达式];例如int scores[5]; // 定义一个可以存放5个整数的数组名字叫scores double temperatures[24]; // 定义一个可以存放24个双精度浮点数的数组记录一天的温度这里有个新手必踩的坑[ ]里的必须是常量表达式。什么叫常量表达式就是在编译时就能确定值的表达式。比如数字10、用const修饰的常量const int N 100;或者sizeof运算符等。注意很多初学者会写成int n 10; int arr[n];这在标准的C里是不合法的虽然有些编译器如GCC扩展支持称为“变长数组VLA”但这不属于标准C可移植性差强烈不建议使用。数组大小必须在编译期确定。当写下int arr[5];这行代码时编译器会在内存中为你分配一块连续的空间。假设一个int占4个字节那么这块空间就是20个字节。arr这个名字本质上代表的是这块连续内存空间的首地址。内存布局可以这样理解内存地址: 0x1000 0x1004 0x1008 0x100C 0x1010 [ arr[0] ][ arr[1] ][ arr[2] ][ arr[3] ][ arr[4] ] 索引: 0 1 2 3 4arr的值就是0x1000。当你访问arr[2]时编译器实际上是在计算arr的首地址 2 * sizeof(int)0x1000 2*40x1008然后去这个地址存取数据。2.3 数组的初始化几种姿势及其背后的逻辑数组不初始化里面的值是未定义的俗称“垃圾值”。直接使用可能导致程序行为不可预测。所以初始化是好习惯。1. 声明时完全初始化int arr1[5] {1, 2, 3, 4, 5}; // 最标准的方式5个元素依次赋值大括号{}里的初始值个数可以少于数组长度但不能多于。少于时剩余的元素会被自动初始化为0对于基本数据类型。int arr2[5] {1, 2}; // arr2[0]1, arr2[1]2, arr2[2]arr2[3]arr2[4]02. 声明时省略大小由编译器推断int arr3[] {10, 20, 30}; // 编译器会自动计算数组长度定为3这种方式非常方便特别是当你需要修改初始值列表时不用手动去改数组大小。但要注意这样定义的数组大小就固定了后续无法扩展。3. 全部初始化为0的快捷写法int arr4[100] {0}; // 第一个元素显式赋0其余元素因为“少于数组长度”的规则也被自动赋0 int arr5[100] {}; // C11及以后的标准空的大括号也会将所有元素值初始化对基本类型就是0这是一个非常实用的小技巧特别是需要清空一个大数组的时候。4. 字符数组初始化的特殊性字符数组用于存储字符串它有一个特殊的规则字符串末尾会自动添加一个空字符\0作为结束标志。char str1[] {H, e, l, l, o}; // 长度为5只是一个字符数组不是C风格字符串因为没有\0 char str2[] {H, e, l, l, o, \0}; // 长度为6是C风格字符串 char str3[] Hello; // 最常用的写法。编译器会自动在末尾添加\0所以str3的长度是6这里最容易出错的是char str4[5] Hello;这行代码会编译失败因为Hello实际需要6个字节5个字符1个\0而数组只分配了5个字节的空间放不下。5. 关于“聚合初始化”的误区数组只能在定义的时候使用大括号{}进行“聚合初始化”。一旦数组定义完成就不能再用{}对整个数组赋值了。int arr[5]; arr {1,2,3,4,5}; // 错误编译不通过如果想在定义后给数组所有元素赋新值只能通过循环逐个元素赋值。3. 数组的操作访问、遍历与越界这个“大坑”3.1 如何访问数组元素访问数组元素的语法是数组名[索引]。索引也叫下标从0开始到数组长度-1结束。int arr[5] {10, 20, 30, 40, 50}; int first arr[0]; // 访问第一个元素值为10 arr[2] 100; // 修改第三个元素的值现在数组是 {10, 20, 100, 40, 50} int last arr[4]; // 访问最后一个元素值为50 // int error arr[5]; // 危险访问了不存在的第6个元素下标越界3.2 遍历数组的几种方法遍历就是按顺序访问数组的每一个元素。这是数组最常用的操作。1. 经典的for循环int arr[] {2, 4, 6, 8, 10}; int length sizeof(arr) / sizeof(arr[0]); // 计算数组长度 for (int i 0; i length; i) { cout arr[ i ] arr[i] endl; }这里用到了一个关键技巧sizeof(arr) / sizeof(arr[0])。sizeof(arr)获取整个数组占用的字节数sizeof(arr[0])获取数组中单个元素占用的字节数两者相除就得到了元素个数。这个方法只对在当前作用域内定义的数组有效如果数组作为参数传递给函数在函数内部它会退化成指针sizeof得到的是指针的大小而不是数组的大小。2. 范围for循环C11引入for (int element : arr) { cout element ; }这种方式更简洁、更安全不容易出现下标越界。它会自动遍历数组的每一个元素。如果你想修改元素的值需要使用引用for (int element : arr) { element * 2; // 将每个元素的值翻倍 }3. 使用指针遍历int arr[] {1, 3, 5, 7, 9}; int *ptr arr; // 数组名arr就是首元素的地址 for (int i 0; i 5; i) { cout *(ptr i) ; // 通过指针偏移访问 } // 或者更“指针”的写法 for (int *p arr; p ! arr 5; p) { cout *p ; }这揭示了数组和指针的深层关系arr[i]在编译器看来等价于*(arr i)。甚至i[arr]这种看似荒谬的写法在语法上也是合法的因为*(i arr)但千万别这么写除非你想让同事看不懂你的代码。3.3 数组越界沉默的杀手数组越界是C/C程序中最常见、最危险的错误之一。它指的是访问了不属于数组分配范围内的内存位置。int arr[5] {0}; arr[5] 10; // 越界有效索引是0-45已经超出了范围 arr[-1] 20; // 越界索引不能为负数为什么危险不会立即崩溃C为了效率默认不对数组边界做检查。访问arr[5]时程序会老老实实地去计算arr首地址 5*sizeof(int)这个地址然后进行读写。这个地址可能属于其他变量、函数调用栈甚至是程序代码区。写入数据会破坏其他数据导致程序出现各种匪夷所思的错误且难以调试。安全漏洞精心构造的越界写入可能覆盖函数返回地址导致缓冲区溢出攻击这是很多安全漏洞的根源。如何避免牢记索引范围始终记住数组索引从0开始最大有效索引是size - 1。使用安全的遍历方法优先使用范围for循环或者确保循环条件严格使用i 数组长度。使用标准库容器对于需要动态大小或更安全边界检查的场景强烈推荐使用std::vector或std::arrayC11它们提供了at()方法会在越界时抛出异常。4. 数组与指针剪不断理还乱的亲缘关系这是理解C数组的关键也是难点。很多混淆都源于此。4.1 数组名是什么在大多数情况下数组名会被编译器隐式转换退化为一个指向其首元素的常量指针。int arr[5] {1, 2, 3, 4, 5}; int *p arr; // 合法arr被转换为arr[0]这里arr的类型是int[5]一个包含5个int的数组但在赋值给指针p时它“退化”成了int*类型值等于第一个元素的地址arr[0]。但是有几个重要的例外情况数组名不会退化为指针使用sizeof运算符时sizeof(arr)返回的是整个数组的大小5 * sizeof(int) 20字节而不是指针的大小通常4或8字节。使用取地址运算符时arr的类型是int (*)[5]指向整个数组的指针而不是int**。它的值和arr即arr[0]相同但类型不同指针运算的步长不同。作为字符串字面量初始化字符数组时。4.2 指针运算与数组访问正因为数组名可以当指针用所以指针运算和数组访问是相通的。int arr[5] {10, 20, 30, 40, 50}; int *ptr arr; // ptr指向arr[0] cout *ptr endl; // 输出10解引用得到arr[0]的值 cout *(ptr 2) endl; // 输出30等价于arr[2] ptr; // ptr现在指向arr[1] cout *ptr endl; // 输出20 cout ptr[1] endl; // 输出30指针也可以使用下标运算符等价于*(ptr 1)ptr[1]这种写法揭示了[]运算符的本质E1[E2]在语法上完全等价于*((E1) (E2))。所以arr[i]和i[arr]在编译器看来都是*(arr i)。4.3 数组作为函数参数退化的必然当把数组传递给函数时它总会退化为指针。这就是为什么在函数内部无法用sizeof获取数组实际长度的原因。void printArray(int arr[], int size) { // 这里的int arr[] 实际上就是 int *arr for (int i 0; i size; i) { cout arr[i] ; } } // 调用 int myArr[] {1,2,3,4,5}; printArray(myArr, 5); // 必须额外传递数组大小函数声明中的int arr[]只是为了可读性编译器一律将其视为int *arr。因此在函数内对arr使用sizeof得到的是指针的大小而不是原数组的大小。必须显式地传递数组大小作为另一个参数。如果想在函数中保留数组的类型信息包括大小可以使用数组的引用Cvoid printArray(int (arr)[5]) { // arr是对一个大小为5的int数组的引用 for (int i 0; i sizeof(arr)/sizeof(arr[0]); i) { // 这里sizeof可以正确工作 cout arr[i] ; } } // 只能传递大小为5的int数组 int arr5[5] {...}; printArray(arr5); // 正确 int arr10[10] {...}; printArray(arr10); // 错误类型不匹配或者使用模板更灵活template size_t N void printArray(int (arr)[N]) { for (int i 0; i N; i) { cout arr[i] ; } } // 可以传递任意大小的int数组5. 多维数组当数组的元素也是数组当数组的元素本身也是数组时就形成了多维数组。最常用的是二维数组可以把它想象成一个表格或矩阵。5.1 二维数组的定义与初始化// 定义一个3行4列的二维数组 int matrix[3][4];在内存中二维数组仍然是连续存储的按“行优先”顺序排列即先存第一行的所有元素再存第二行的所有元素以此类推。初始化方式// 1. 按行用大括号分组初始化推荐清晰 int matrix1[2][3] { {1, 2, 3}, // 第一行 {4, 5, 6} // 第二行 }; // 2. 连续初始化编译器按顺序填充 int matrix2[2][3] {1, 2, 3, 4, 5, 6}; // 效果同上 // 3. 部分初始化未指定的元素自动为0 int matrix3[3][4] { {1}, {0, 2}, {0, 0, 3} }; // matrix3的结果是 // {1, 0, 0, 0} // {0, 2, 0, 0} // {0, 0, 3, 0} // 4. 省略第一维行数由编译器推断 int matrix4[][3] {{1,2,3}, {4,5,6}, {7,8,9}}; // 编译器推断出行数为3 // 注意第二维列数不能省略5.2 二维数组的访问与遍历访问二维数组需要两个索引数组名[行索引][列索引]。int matrix[2][3] {{1,2,3}, {4,5,6}}; int value matrix[1][2]; // 访问第二行第三列value 6 matrix[0][1] 99; // 修改第一行第二列的值为99遍历二维数组通常需要嵌套循环const int ROWS 2; const int COLS 3; int matrix[ROWS][COLS] {{1,2,3}, {4,5,6}}; // 外层循环控制行内层循环控制列 for (int i 0; i ROWS; i) { for (int j 0; j COLS; j) { cout matrix[i][j] \t; } cout endl; // 每输出完一行换行 } // 输出 // 1 2 3 // 4 5 65.3 理解二维数组的指针理解二维数组的指针是进阶的关键。对于int matrix[3][4]matrix数组名类型是int [3][4]。在大多数表达式中退化为指针但它指向的是整个第一行即matrix[0]类型是int (*)[4]指向一个包含4个int的数组的指针。matrix[i]第i行本身是一个一维数组类型是int [4]退化为int*指向该行的第一个元素matrix[i][0]。matrix[i][j]第i行第j列的元素。matrix指向整个二维数组的指针类型是int (*)[3][4]。这个很少用。int matrix[2][3] {{1,2,3}, {4,5,6}}; cout matrix endl; // 地址A指向第一行 cout matrix[0] endl; // 地址A指向matrix[0][0] cout matrix[0][0] endl; // 地址A cout matrix 1 endl; // 地址A sizeof(int[3])即地址A 12字节指向第二行 cout matrix[0] 1 endl; // 地址A sizeof(int)即地址A 4字节指向matrix[0][1]可以看到matrix 1移动的步长是“一行”的大小3个int而matrix[0] 1移动的步长是一个int的大小。这就是指针类型决定指针运算的典型例子。6. 数组的局限性 vs. 动态数组与vector6.1 静态数组的局限性我们上面讨论的数组大小在编译期固定存储在栈内存或全局/静态区称为静态数组。它的主要局限有固定大小一旦定义容量无法改变。如果程序运行时需要更多空间静态数组无能为力。栈空间有限在函数内部定义的大数组可能导致栈溢出。栈内存通常只有几MB。需要手动传递大小作为函数参数时会退化为指针丢失大小信息。没有边界检查访问越界是未定义行为极易出错。6.2 动态数组运行时决定大小为了解决固定大小的问题C提供了new和delete运算符可以在堆内存上创建动态数组。int size; cout 请输入数组大小; cin size; // 动态分配数组 int *dynamicArray new int[size]; // 在堆上分配size个int的空间 // 像普通数组一样使用 for (int i 0; i size; i) { dynamicArray[i] i * 10; } // 使用完毕后必须手动释放内存防止内存泄漏 delete[] dynamicArray; // 注意是delete[]不是delete dynamicArray nullptr; // 好习惯释放后将指针置空注意事项new和delete必须成对使用。new分配delete[]释放对于数组一定要用delete[]用delete会导致资源未完全释放。动态数组的生命周期由程序员控制忘记释放会导致内存泄漏。动态数组同样没有边界检查。6.3 更优选择标准库vector对于现代C开发绝大多数情况下应该优先使用std::vector而不是原生数组或new分配的动态数组。vector是标准模板库STL提供的动态数组容器它解决了原生数组的所有主要痛点。#include vector using namespace std; // 1. 创建vector vectorint vec; // 空的vector vectorint vec2(10); // 包含10个元素每个元素初始化为0 vectorint vec3 {1, 2, 3, 4, 5}; // 列表初始化 // 2. 动态增长 vec.push_back(10); // 在末尾添加元素10vector自动扩容 vec.push_back(20); cout vec.size() endl; // 输出2获取当前元素个数 cout vec.capacity() endl; // 获取当前容量 size // 3. 像数组一样访问 cout vec[0] endl; // 使用下标不检查边界快 cout vec.at(1) endl; // 使用at()成员函数会进行边界检查越界抛出std::out_of_range异常安全 // 4. 获取大小非常方便 for (int i 0; i vec.size(); i) { cout vec[i] ; } // 5. 自动管理内存无需手动delete // 当vec离开作用域时其析构函数会自动释放所有内存vector的核心优势动态扩容当空间不足时自动分配更大的内存块拷贝数据释放旧内存。程序员无需关心扩容细节。知道自己的大小vec.size()随时可用。丰富的成员函数push_back,pop_back,insert,erase,clear,empty等操作方便。自动内存管理遵循RAII原则离开作用域自动释放杜绝内存泄漏。与算法库完美配合可以和algorithm中的sort,find,copy等函数无缝协作。什么情况下还用原生数组对性能有极端要求且大小固定、生命周期简单的场景例如在嵌入式系统或内核代码中。与C语言的API交互时很多C库函数接受指针。作为某些底层数据结构的实现细节。对于初学者和绝大多数应用开发请养成使用vector的习惯。它更安全、更方便、更现代。7. 典型习题精讲与避坑指南结合黑马课程和常见面试题我们来看几个数组相关的典型问题。7.1 习题一数组逆置题目将一个长度为N的数组元素顺序反转。#include iostream using namespace std; int main() { int arr[] {1, 2, 3, 4, 5, 6, 7, 8}; int length sizeof(arr) / sizeof(arr[0]); cout 原始数组; for (int i 0; i length; i) { cout arr[i] ; } cout endl; // 逆置算法双指针法 int start 0; int end length - 1; while (start end) { // 交换首尾元素 int temp arr[start]; arr[start] arr[end]; arr[end] temp; // 指针向中间移动 start; end--; } cout 逆置后数组; for (int i 0; i length; i) { cout arr[i] ; } cout endl; return 0; }避坑点循环条件是start end而不是start end。如果是奇数个元素中间那个元素不需要和自己交换如果是偶数个元素start和end会交错而过条件不成立循环结束。交换时一定要用临时变量temp不能直接arr[start] arr[end]; arr[end] arr[start];因为第一句执行后arr[start]的原始值就丢失了。7.2 习题二寻找数组中的最值题目找出一个整型数组中的最大值和最小值及其下标。#include iostream #include climits // 使用INT_MIN和INT_MAX using namespace std; int main() { int arr[] {12, 35, 1, 10, 34, 1, 99, -5}; int length sizeof(arr) / sizeof(arr[0]); if (length 0) { cout 数组为空 endl; return 0; } // 初始化最大值和最小值 int maxVal arr[0]; // 不能初始化为0如果数组全是负数0就比它们都大了 int minVal arr[0]; int maxIdx 0; int minIdx 0; // 遍历数组从第二个元素开始 for (int i 1; i length; i) { if (arr[i] maxVal) { maxVal arr[i]; maxIdx i; } // 注意这里不能用else if因为同一个元素可能同时是最大值和最小值比如数组只有一个元素 if (arr[i] minVal) { minVal arr[i]; minIdx i; } } cout 最大值是 maxVal 位于下标 maxIdx endl; cout 最小值是 minVal 位于下标 minIdx endl; return 0; }避坑点初始化maxVal和minVal不能想当然地初始化为0或INT_MAX/INT_MIN。最安全的做法是初始化为数组的第一个元素arr[0]。如果数组可能为空需要先判断长度。遍历起点既然用第一个元素初始化了循环就可以从i 1开始避免了一次无意义的比较。条件判断寻找最大值和最小值的两个if语句是独立的不能用else if连接。考虑数组{5}第一个元素既是最大值也是最小值。7.3 习题三冒泡排序题目使用冒泡排序算法对数组进行升序排序。#include iostream using namespace std; int main() { int arr[] {5, 3, 8, 1, 9, 2}; int length sizeof(arr) / sizeof(arr[0]); cout 排序前; for (int i 0; i length; i) { cout arr[i] ; } cout endl; // 冒泡排序 // 外层循环控制排序的“轮数”n个元素最多需要n-1轮 for (int i 0; i length - 1; i) { // 内层循环进行相邻元素比较和交换 // 优化每一轮都会把当前最大的元素“冒泡”到最后所以后续的比较可以少一次 for (int j 0; j length - 1 - i; j) { if (arr[j] arr[j 1]) { // 交换 int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } // 可以在这里打印每一轮的结果观察“冒泡”过程 /* cout 第 i1 轮后; for (int k 0; k length; k) { cout arr[k] ; } cout endl; */ } cout 排序后; for (int i 0; i length; i) { cout arr[i] ; } cout endl; return 0; }算法理解 冒泡排序的核心是相邻元素两两比较顺序错误就交换。每一轮外层循环一次都会将当前未排序部分的最大值“冒泡”到正确位置数组末尾。因此第1轮将第1大的数放到倒数第1位。第2轮将第2大的数放到倒数第2位。...第n-1轮将第n-1大的数放到正数第2位最小的数自然就在第1位了。优化点内层循环的终止条件是j length - 1 - i。因为经过i轮后数组末尾的i个元素已经有序无需再比较。可以增加一个bool标志位如果某一轮内层循环没有发生任何交换说明数组已经有序可以提前终止排序。7.4 习题四二维数组应用——矩阵转置题目将一个M行N列的矩阵转置得到N行M列的新矩阵。#include iostream using namespace std; int main() { const int ROWS 2; const int COLS 3; int matrix[ROWS][COLS] { {1, 2, 3}, {4, 5, 6} }; // 转置后是3行2列 int transposed[COLS][ROWS]; cout 原始矩阵 endl; for (int i 0; i ROWS; i) { for (int j 0; j COLS; j) { cout matrix[i][j] \t; } cout endl; } // 转置操作原矩阵的[i][j]元素变成新矩阵的[j][i]元素 for (int i 0; i ROWS; i) { for (int j 0; j COLS; j) { transposed[j][i] matrix[i][j]; } } cout \n转置矩阵 endl; for (int i 0; i COLS; i) { for (int j 0; j ROWS; j) { cout transposed[i][j] \t; } cout endl; } return 0; }关键转置的本质是下标交换。原矩阵中位于(i, j)的元素在新矩阵中位于(j, i)。注意转置后行数和列数互换所以需要定义一个新的数组transposed[COLS][ROWS]来存放结果。8. 常见问题排查与实战心得8.1 编译错误“表达式必须含有常量值”int n 10; int arr[n]; // 错误MSVC编译器会报错表达式必须含有常量值原因与解决在标准C中数组定义时[]内必须是常量表达式。改用动态数组或vector。// 方法1使用常量 const int N 10; int arr[N]; // 方法2使用动态数组需手动管理内存 int n 10; int* arr new int[n]; // ... 使用 ... delete[] arr; // 方法3使用vector推荐 #include vector int n 10; std::vectorint arr(n);8.2 运行时崩溃栈溢出Stack Overflowvoid foo() { int hugeArray[1000000]; // 在栈上分配约4MB空间可能超出栈大小限制 // ... }原因在函数内部定义过大的数组消耗了过多栈空间通常只有几MB。解决将大数组定义为static使其位于全局/静态区。使用new在堆上分配动态数组。最佳实践使用std::vector它内部数据存储在堆上。8.3 逻辑错误数组越界访问这是最难调试的错误之一因为程序可能不会立即崩溃而是表现出随机、诡异的行为。排查技巧使用调试器在IDE如Visual Studio, CLion中设置数据断点或内存监视当特定内存地址被修改时中断。边界检查在访问数组前手动添加条件判断。或者在开发阶段使用vector::at()代替[]利用其抛出的异常定位问题。静态分析工具使用如Clang-Tidy、PVS-Studio等工具它们能检测出一些潜在的越界风险。防御性编程定义数组长度时使用命名常量遍历时严格使用该常量作为边界。const int ARRAY_SIZE 100; int arr[ARRAY_SIZE]; for (int i 0; i ARRAY_SIZE; i) { // 清晰不易出错 // ... }8.4 内存错误忘记释放动态数组int* createArray(int size) { int* arr new int[size]; // ... 初始化 ... return arr; } void someFunction() { int* myArr createArray(100); // ... 使用myArr ... // 忘记 delete[] myArr; // 内存泄漏 }解决严格遵守“谁申请谁释放”的原则。对于简单的动态数组确保每个new[]都有对应的delete[]。对于复杂的生命周期管理强烈推荐使用智能指针C11及以上或直接使用std::vector。#include memory // 使用unique_ptr管理动态数组 std::unique_ptrint[] createArray(int size) { return std::make_uniqueint[](size); // C14 // 或者 return std::unique_ptrint[](new int[size]); } // 当unique_ptr离开作用域时会自动调用delete[]释放内存。8.5 关于“数组作为函数参数”的混淆很多初学者困惑于函数内部修改数组是否会影响外部实参。void modifyArray(int arr[], int size) { arr[0] 999; // 修改会影响到外部实参 } int main() { int myArr[3] {1, 2, 3}; modifyArray(myArr, 3); cout myArr[0]; // 输出999数组被修改了 }理解因为数组作为参数传递的是指针首地址函数内部通过这个指针操作的是同一块内存。所以在函数内对数组元素的修改会直接影响调用处的原始数组。这与传递普通变量值传递的行为不同。如果不想函数修改原数组可以传递const指针void printArray(const int arr[], int size) { // 参数声明为const // arr[0] 999; // 错误不能修改const数组 for (int i 0; i size; i) { cout arr[i] ; } }数组是C的基石理解它对于理解指针、内存布局乃至更复杂的数据结构至关重要。虽然在实际项目中std::vector和std::array已经很大程度上替代了原生数组但底层原理是相通的。把数组这块硬骨头啃下来后面学习字符串、链表、容器都会轻松很多。最重要的是多写代码多调试亲自去踩一踩“越界”的坑印象才会深刻。

相关新闻