C++函数内数组修改生效但普通传值变量不生效的原因
C++数组传参修改原内容的原因
问题复现代码
#include <iostream> using std::cout; using std::cin; using std::endl; void func(int arr[5], int n1, int n2) { cout << "INSIDE func()" << endl; arr[0]++, arr[3]++; for(int i = 0; i < 5; i++) { cout << arr[i] << ' '; } cout << endl; n1++, n2++; cout << n1 << ' ' << n2 << endl; } int main() { int a = 3, b = 4; int arr[5] = {0,0,0,0,0}; func(arr,a,b); cout << "INSIDE main()" << endl; for(int i = 0; i < 5; i++) { cout << arr[i] << ' '; } cout << endl; cout << a << ' ' << b << endl; return(0); }
运行现象
代码运行输出如下:
INSIDE func() 1 0 0 1 0 4 5 INSIDE main() 1 0 0 1 0 3 4
可以观察到两个明显差异:
- 函数内对
arr数组元素的修改,回到main函数后仍然保留 - 函数内对普通形参
n1、n2的自增修改,回到main后完全不影响原变量a、b的值
已知要让函数内对普通变量的修改作用到外层实参,可以通过引用传参实现,示例写法如下:
void func(int arr[5], int &n1, int &n2) { /* 内部直接操作n1、n2等价于操作外层原变量 */ }
核心原因
这个差异本质是C++的数组退化规则和默认值传递规则共同导致的:
- 普通变量默认是值传递
调用函数传普通变量时,编译器会为形参开辟独立的内存空间,把实参的值拷贝一份存进去。函数内部对形参的所有修改,都是在操作这份独立的拷贝,和外层的原变量没有关联,离开函数作用域后形参被销毁,修改自然不会保留。如果要修改原变量,就需要给形参加&用引用传递,让形参成为原变量的别名,操作形参就是直接操作原变量的内存。 - 数组传参会自动退化为首元素指针
你写的形参int arr[5]只是C的语法糖,根据C标准,数组类型作为函数形参时会被自动调整为指向数组首元素的指针,也就是说int arr[5]、int arr[]、int* arr这三种形参写法在编译器看来完全等价,都会被处理成int* arr类型的指针形参。
调用func(arr,a,b)时,传给形参arr的不是整个数组的拷贝,而是原数组首元素的内存地址。函数内写arr[0]++本质是指针偏移+解引用操作,等价于*(arr + 0) += 1,直接读写的是原数组所在的内存地址上的值,根本没有操作拷贝副本,修改当然会直接反映到原数组上。
注意点
- 不要误以为数组传参是特殊的“引用传递”:本质上数组形参还是值传递,只不过传的“值”是指针(内存地址),拷贝的是指针本身,不是整个数组。你如果在函数内给
arr指针本身重新赋值(比如写arr = nullptr),外层的原数组不会受任何影响,因为你改的是指针的拷贝;但你通过指针解引用改指向的内存内容,改的就是原数组的数据。 - 如果真的要传递数组的引用,需要写成专门的引用语法:
void func(int (&arr)[5], int n1, int n2),这种写法下数组不会发生指针退化,你在函数内取sizeof(arr)能得到整个数组的真实长度(20字节,按int占4字节算),而不是指针的长度(8字节/64位环境、4字节/32位环境)。 - 普通变量如果传指针也能达到和数组传参一样的效果,比如形参写成
void func(int arr[5], int* n1, int* n2),调用时传func(arr, &a, &b),函数内写(*n1)++、(*n2)++也能修改外层a、b的值,本质和数组传参的逻辑完全一致:传地址过去,解引用改原内存。
内容的提问来源于stack exchange,提问作者fonabyatzz
相关产品推荐
相关产品推荐

