目录
[1.1 本节主要学习什么?](#1.1 本节主要学习什么?)
[1.2 本节核心结论](#1.2 本节核心结论)
[(1)普通数组本身可以用 sizeof 获取总大小](#(1)普通数组本身可以用 sizeof 获取总大小)
[2.1 main 函数中数组可以用 sizeof 获取总大小](#2.1 main 函数中数组可以用 sizeof 获取总大小)
[2.2 数组传入函数后会变成指针](#2.2 数组传入函数后会变成指针)
[2.3 int datas\[\]、int* datas、int datas10 在函数参数中等价](#2.3 int datas[]、int* datas、int datas[10] 在函数参数中等价)
[2.4 为什么函数内部无法通过 sizeof 获取数组大小?](#2.4 为什么函数内部无法通过 sizeof 获取数组大小?)
[三、普通数组传参时,一般要额外传入 size](#三、普通数组传参时,一般要额外传入 size)
[3.1 为什么要额外传 size?](#3.1 为什么要额外传 size?)
[3.2 更推荐的写法:sizeof(datas) / sizeof(datas0)](#3.2 更推荐的写法:sizeof(datas) / sizeof(datas[0]))
[3.3 为什么不能在函数内部再算 size?](#3.3 为什么不能在函数内部再算 size?)
[3.4 要点小结](#3.4 要点小结)
[4.1 函数能不能直接返回数组?](#4.1 函数能不能直接返回数组?)
[4.2 返回指针时,不能返回局部数组地址](#4.2 返回指针时,不能返回局部数组地址)
[4.3 为什么 return datas 可以?](#4.3 为什么 return datas 可以?)
[4.4 返回指针要确保指向空间仍然有效](#4.4 返回指针要确保指向空间仍然有效)
[5.1 数组引用作为参数:可以保留数组大小](#5.1 数组引用作为参数:可以保留数组大小)
[5.2 数组引用为什么可以拿到 sizeof?](#5.2 数组引用为什么可以拿到 sizeof?)
[5.3 数组引用的限制](#5.3 数组引用的限制)
[5.4 数组引用作为返回值](#5.4 数组引用作为返回值)
[7.1 普通数组可以通过 sizeof 获取总大小](#7.1 普通数组可以通过 sizeof 获取总大小)
[7.2 数组作为普通函数参数时会退化为指针](#7.2 数组作为普通函数参数时会退化为指针)
[7.3 普通数组传参时,通常要额外传入 size](#7.3 普通数组传参时,通常要额外传入 size)
[7.4 不能返回函数内部的局部数组](#7.4 不能返回函数内部的局部数组)
[7.5 数组引用可以保留数组大小](#7.5 数组引用可以保留数组大小)
[7.6 返回数组引用可以先定义数组类型别名](#7.6 返回数组引用可以先定义数组类型别名)
前言
在前面几节中,我们已经学习了函数参数、返回值、指针、引用以及函数重载等内容。本节继续围绕函数展开,重点讲解一个非常容易混淆的问题:
数组作为函数参数时,到底传进去的是整个数组,还是数组首元素地址?
很多初学者会写出这样的代码:
cpp
void TestBaseArr(int datas[10])
{
cout << sizeof datas << endl;
}
然后以为 sizeof datas 可以得到整个数组大小。
但实际运行时,可能会发现:
cpp
main 函数中 sizeof(datas) = 40
函数内部 sizeof(datas) = 8
这就说明:数组作为普通函数参数传递时,会退化为指针。
本节先只讲 基础数组和数组引用,vector 作为参数和返回值的问题,放到下一章单独讲。

一、本节学习内容概要
1.1 本节主要学习什么?
本节主要围绕下面几个问题展开:
- 数组作为函数参数为什么会变成指针?
- int datas\[\]、int* datas、int datas10 在函数参数中有什么区别?
- 为什么普通数组传参后,sizeof 无法得到数组总大小?
- 数组作为函数参数时,为什么通常要额外传入 size?
- 函数能不能返回数组?
- 数组引用 int (&datas)10 为什么可以保留数组大小?
- 数组引用作为返回值应该怎么写?
简单总结:
- 普通数组作为函数参数时,会退化为指针;
- 指针只能保存地址,不能保存数组长度;
- 如果想在函数内部知道数组大小,需要额外传 size;
- 如果想保留数组完整类型,可以使用数组引用;
- 数组引用可以通过 sizeof 得到整个数组大小。
1.2 本节核心结论
(1)普通数组本身可以用 sizeof 获取总大小
cpp
int datas[10] = { 1,2,3,4,5 };
cout << sizeof datas << endl;
如果一个 int 占 4 字节,那么:
sizeof datas = 10 * 4 = 40
(2)数组作为普通函数参数后,会退化为指针
cpp
void TestBaseArr(int datas[])
{
cout << sizeof datas << endl;
}
这里的 datas 已经不是完整数组了,而是一个指针。
在 64 位程序中,指针大小通常是:
8 字节
所以函数内部输出可能是:
8
(3)数组引用可以保留数组完整类型
cpp
void TestBaseArrRef(int (&datas)[10])
{
cout << sizeof datas << endl;
}
这里的**datas 是数组引用,不会退化为指针。**
因此:
sizeof datas
仍然可以得到整个数组大小,也就是:
40
二、普通数组作为函数参数:数组会退化为指针
2.1 main 函数中数组可以用 sizeof 获取总大小
先看一段基础代码:
cpp
#include <iostream>
using namespace std;
int main()
{
int datas[10] = { 1,2,3,4,5 };
cout << sizeof datas << endl;
return 0;
}
如果当前环境中:
sizeof(int) = 4
那么:
int datas[10]
一共占用:
10 * 4 = 40 字节
所以输出结果是:
40
这里要注意:
datas 是一个真正的数组。
它不是指针。
2.2 数组传入函数后会变成指针
再看下面代码:
cpp
#include <iostream>
using namespace std;
// 数组经过函数传参后,会退化为指针
void TestBaseArr(int datas[10])
{
cout << sizeof datas << endl;
}
int main()
{
int datas[10] = { 1,2,3,4,5 };
cout << sizeof datas << endl;
TestBaseArr(datas);
return 0;
}
可能输出:
40
8
这两个结果分别表示:
main 中的 datas 是真正的数组,所以 sizeof(datas) = 40。
函数中的 datas 已经退化为指针,所以 sizeof(datas) = 8。
- 这里的
8是因为在 64 位程序中,指针变量通常占 8 字节。- 如果是 32 位程序,指针变量通常占 4 字节。
2.3 int datas\[\]、int* datas、int datas10 在函数参数中等价
在函数参数中,下面三种写法本质上是一样的:
cpp
void TestBaseArr(int datas[])
{
}
void TestBaseArr(int* datas)
{
}
void TestBaseArr(int datas[10])
{
}
它们在函数参数位置都会被编译器理解成:
cpp
int* datas
所以:
cpp
int datas[]
int* datas
int datas[10]
放在函数参数中时,都不能真正保留数组大小。
也就是说,下面这个 10 只是形式上写了出来:
cpp
void TestBaseArr(int datas[10])
它并不会让函数内部真的知道数组长度是 10。
2.4 为什么函数内部无法通过 sizeof 获取数组大小?
原因很简单:
数组传参后,函数内部拿到的是指针。
指针变量保存的是地址。
例如:
int* p = datas;
p 里面保存的是数组首元素的地址。
但是指针本身并不知道:
- 这个数组有几个元素;
- 这个数组总共占多少字节;
- 这个数组在哪里结束。
所以函数内部写:
cpp
sizeof datas
得到的是:
指针变量本身的大小,而不是数组总大小。
在 64 位程序中:
cpp
sizeof(datas) = sizeof(int*) = 8
这就是为什么函数内部不能直接通过 sizeof 获取普通数组大小。
三、普通数组传参时,一般要额外传入 size
3.1 为什么要额外传 size?
- 既然数组传参后会变成指针,那么函数内部就不知道数组长度。
- 所以一般写函数时,会额外传入数组大小。
例如:
cpp
int* TestBaseArr(int datas[], int size)
{
cout << sizeof datas << endl;
cout << "TestBaseArr size = " << size << endl;
return datas;
}
调用时:
cpp
int datas[10] = { 1,2,3,4,5 };
TestBaseArr(datas, sizeof(datas) / sizeof(int));
这里:
cpp
sizeof(datas)
得到整个数组总字节数。
cpp
sizeof(int)
得到单个元素的字节数。
所以:
cpp
sizeof(datas) / sizeof(int)
就是数组元素个数。
如果 datas 是 int[10]:
sizeof(datas) = 40
sizeof(int) = 4
那么:
40 / 4 = 10
所以传入的 size 就是 10。
3.2 更推荐的写法:sizeof(datas) / sizeof(datas0)
实际写代码时,更推荐这样写:
cpp
int size = sizeof(datas) / sizeof(datas[0]);
完整示例:
cpp
#include <iostream>
using namespace std;
int* TestBaseArr(int datas[], int size)
{
cout << "sizeof datas = " << sizeof datas << endl;
cout << "TestBaseArr size = " << size << endl;
return datas;
}
int main()
{
int datas[10] = { 1,2,3,4,5 };
cout << "main sizeof datas = " << sizeof datas << endl;
TestBaseArr(datas, sizeof(datas) / sizeof(datas[0]));
return 0;
}
可能输出:
bash
main sizeof datas = 40
sizeof datas = 8
TestBaseArr size = 10
这里可以看出:
- main 中能算出数组大小;
- 函数内部不能靠 sizeof 算数组大小;
- 所以要把 size 单独传进去。
3.3 为什么不能在函数内部再算 size?
错误写法:
cpp
void TestBaseArr(int datas[])
{
int size = sizeof(datas) / sizeof(datas[0]);
cout << size << endl;
}
很多初学者会以为这样可以算出数组长度。
但实际上不行。
因为函数内部:
datas
已经是指针。
所以:
sizeof(datas)
得到的是指针大小。
如果是 64 位程序:
sizeof(datas) = 8
而:
sizeof(datas[0]) = sizeof(int) = 4
所以:
sizeof(datas) / sizeof(datas[0]) = 8 / 4 = 2
这显然不是数组真实长度 10。
因此一定要注意:
cpp
sizeof(arr) / sizeof(arr[0])
这个写法只适用于数组还没有退化为指针的时候。
3.4 要点小结
在 main 函数中:
cpp
int datas[10];
sizeof(datas);
这里的 datas 是真正的数组,所以 sizeof(datas) 计算的是整个数组大小。
如果数组有 10 个 int,每个 int 占 4 字节,那么结果就是:
10 * 4 = 40
但是当数组作为函数参数传递时:
cpp
void Test(int datas[])
这里的 datas[] 会退化成:
int* datas
所以在函数内部:
sizeof(datas)
计算的不再是整个数组大小,而是指针变量本身的大小。
在 64 位程序中,指针一般占 8 字节,因此结果是:
8
所以:
cpp
main 中 sizeof(datas) = 40
函数中 sizeof(datas) = 8
本质原因不是 sizeof datas 和 sizeof(datas) 写法不同,而是:
四、返回数组:普通数组不能直接返回,返回指针要保证生命周期
4.1 函数能不能直接返回数组?
C++ 中,基础数组不能像普通变量一样直接作为返回值返回。
例如下面这种写法是不允许的:
cpp
int[10] Test()
{
}
基础数组本身不能直接作为函数返回值。
如果想返回数组相关内容,常见方式有几种:
- 返回指针;
- 返回数组引用
- 使用 std::array
- 使用 vector
本节先讲基础数组,所以这里只讨论指针和数组引用。
4.2 返回指针时,不能返回局部数组地址
看下面代码:
cpp
int* TestBaseArr(int datas[], int size)
{
cout << sizeof datas << endl;
cout << "TestBaseArr size = " << size << endl;
int arr[10]; // 局部数组,不能返回
return datas;
}
这里有一个非常重要的点:
cpp
int arr[10];
这个数组定义在函数内部****,属于局部变量。
局部数组一般位于栈区**。**
当函数执行结束后,函数栈帧会被释放,这个局部数组的生命周期也结束了。
所以不能这样写:
cpp
return arr;
因为**arr 离开函数后就被销毁了。**
如果返回它的地址,就相当于返回了一个已经失效的地址。
这类问题之前在"不能返回栈区变量地址"中也讲过,本质是一样的。
4.3 为什么 return datas 可以?
在下面代码中:
cpp
int* TestBaseArr(int datas[], int size)
{
cout << sizeof datas << endl;
cout << "TestBaseArr size = " << size << endl;
int arr[10]; // 不能 return arr
return datas;
}
这里返回的是:
cpp
datas
而 datas 是从外部传进来的数组首元素地址。
例如:
cpp
int datas[10] = { 1,2,3,4,5 };
int* re = TestBaseArr(datas, 10);
main 函数中的 datas 还没有销毁,所以函数返回它的地址是可以的。
但是要注意:
cpp
return datas;
返回的是指针,不是整个数组。
也就是说,返回值类型是:
cpp
int*
它仍然不知道数组长度。
所以如果后续还要遍历这个返回结果,仍然需要知道数组大小。
4.4 返回指针要确保指向空间仍然有效
返回指针时,一定要考虑生命周期。
可以返回的情况:
- 返回外部传入数组的地址;
- 返回全局数组的地址;
- 返回 static 静态数组的地址;
- 返回堆区动态申请空间的地址。
不能返回的情况:
返回函数内部普通局部数组的地址。
错误示例:
cpp
int* Test()
{
int arr[10] = { 1,2,3 };
return arr; // 错误:arr 离开函数后会销毁
}
这种代码即使编译器有时不立刻报错,运行时也属于危险行为。
正确理解:
返回指针可以;
但是指针指向的空间必须在函数结束后仍然有效。
五、数组引用作为参数和返回值
5.1 数组引用作为参数:可以保留数组大小
如果不想让数组传参时退化为指针,可以使用****数组引用。
写法如下:
cpp
void TestBaseArrRef(int (&datas)[10])
{
cout << sizeof datas << endl;
}
这里的重点是:
cpp
int (&datas)[10]
它表示:
- datas 是一个引用;
- 引用的是一个 int10 类型的数组。
注意括号不能省略。
int (&datas)[10]
和下面这个不是一回事:
int& datas[10]
后者表示"引用数组",而 C++ 中不存在真正的引用数组这种用法。
所以数组引用必须写成:
int (&数组名)[数组长度]
5.2 数组引用为什么可以拿到 sizeof?
因为数组引用没有发生数组退化。
例如:
cpp
#include <iostream>
using namespace std;
void TestBaseArrRef(int (&datas)[10])
{
cout << "TestBaseArrRef: " << sizeof datas << endl;
}
int main()
{
int datas[10] = { 1,2,3,4,5 };
cout << "main sizeof datas = " << sizeof datas << endl;
TestBaseArrRef(datas);
return 0;
}
输出结果:
main sizeof datas = 40
TestBaseArrRef: 40
这说明:
- main 中 datas 是 int10;
- 函数参数 datas 也是 int10 的引用;
- 所以 sizeof datas 仍然得到整个数组大小。
这和普通数组传参不同。
(1)普通数组传参:
cpp
void TestBaseArr(int datas[])
函数内部 datas 是指针。
(2)数组引用传参:
cpp
void TestBaseArrRef(int (&datas)[10])
函数内部 datas 仍然代表原来的数组。
5.3 数组引用的限制
数组引用虽然可以保留数组大小,但是也有一个限制:
数组长度必须匹配。
例如:
cpp
void TestBaseArrRef(int (&datas)[10])
{
}
这个函数只能接收:
int[10]
不能接收:
int[5]
int[20]
例如:
cpp
int a[10];
int b[5];
TestBaseArrRef(a); // 正确
TestBaseArrRef(b); // 错误
因为 b 的类型是:
int[5]
而函数需要的是:
int[10]
所以类型不匹配。
5.4 数组引用作为返回值
如果想返回数组引用,写法会比较复杂。
所以通常可以先使用 using 给数组类型起一个别名。
例如:
cpp
using ArrType = int[10];
这句话表示:
ArrType 等价于 int[10]
然后函数返回数组引用:
cpp
ArrType& TestBaseArrRef(int (&datas)[10])
{
cout << "TestBaseArrRef: "
<< sizeof datas << endl;
return datas;
}
完整调用:
cpp
int datas[10] = { 1,2,3,4,5 };
auto& re = TestBaseArrRef(datas);
cout << "size of return "
<< sizeof re << endl;
运行结果:
TestBaseArrRef: 40
size of return 40
这里:
cpp
auto& re
必须写引用。
如果写成:
cpp
auto re = TestBaseArrRef(datas);
就可能发生数组退化或类型不符合预期。
所以返回数组引用时,建议写:
auto& re = TestBaseArrRef(datas);
六、完整代码示例
cpp
#include <iostream>
using namespace std;
// 函数与数组和引用
// 数组经过函数传参会退化为指针
// int datas[]
// int* datas
// int datas[10]
// 在函数参数中基本等价,都无法直接获得数组大小
int* TestBaseArr(int datas[], int size)
{
cout << "sizeof datas = "
<< sizeof datas << endl;
cout << "TestBaseArr size = "
<< size << endl;
int arr[10]; // 局部数组,不能 return arr
return datas; // 返回外部传入数组的首元素地址
}
// 数组引用作为参数和返回值
using ArrType = int[10];
ArrType& TestBaseArrRef(int (&datas)[10])
{
cout << "TestBaseArrRef: "
<< sizeof datas << endl;
return datas;
}
int main()
{
int datas[10] = { 1,2,3,4,5 };
cout << "main sizeof datas = "
<< sizeof datas << endl;
auto re1 = TestBaseArr(
datas,
sizeof(datas) / sizeof(datas[0])
);
auto& re2 = TestBaseArrRef(datas);
cout << "size of return "
<< sizeof re2 << endl;
return 0;
}
可能输出:
main sizeof datas = 40
sizeof datas = 8
TestBaseArr size = 10
TestBaseArrRef: 40
size of return 40
七、总结
本节主要学习了函数与数组、数组引用之间的关系。
7.1 普通数组可以通过 sizeof 获取总大小
cpp
int datas[10];
sizeof(datas)
如果 int 占 4 字节,那么结果是:
40
7.2 数组作为普通函数参数时会退化为指针
下面几种写法在函数参数中基本等价:
cpp
void Test(int datas[]);
void Test(int* datas);
void Test(int datas[10]);
它们进入函数后,本质上都是:
cpp
int* datas
所以:
sizeof(datas)
得到的是指针大小,不是数组大小。
在 64 位程序中,通常是:
8
7.3 普通数组传参时,通常要额外传入 size
因为函数内部无法知道数组长度,所以通常写成:
cpp
void Test(int datas[], int size);
调用时:
cpp
Test(datas, sizeof(datas) / sizeof(datas[0]));
7.4 不能返回函数内部的局部数组
错误写法:
cpp
int* Test()
{
int arr[10];
return arr; // 错误
}
因为**arr 是局部数组,函数结束后空间会被销毁。**
返回指针时,一定要确保指针指向的空间生命周期有效。
7.5 数组引用可以保留数组大小
数组引用写法:
cpp
void Test(int (&datas)[10]);
它不会退化为指针,所以函数内部:
cpp
sizeof(datas)
仍然可以得到整个数组大小。
7.6 返回数组引用可以先定义数组类型别名
cpp
using ArrType = int[10];
ArrType& Test(int (&datas)[10])
{
return datas;
}
调用时建议使用:
auto& re = Test(datas);
这样可以保留数组引用类型。
最后一句话总结:
普通数组传参会退化为指针,所以拿不到数组大小;
想知道大小,就额外传 size;
想保留完整数组类型,就使用数组引用。