你可能已经看到指针与数组之间的一些重叠。自由内存空间分配的数组由其第一个元素的指针进行访问。栈上的数组通过使用数组语法([])或者正常变量声明来访问。你还会看到的是,其重叠不仅如此,指针与数组有更复杂的关系。
1、数组退化至指针
自由内存空间数组不是指向数组所用的指针的唯一位置。也可以使用指针的语法来访问栈上数组。数组的地址实际上就是第一个元素(索引0)的地址。编译器知道当你通过变量名指向数组时,你实际上就是指向了第一个元素的地址。用这种方式,指针像在自由内存空间上的数组一样工作。下面的代码生成了一个以0初始化的栈上数组,使用了一个指针来访问它:
int myIntArray[10] {};
int* myIntPtr { myIntArray };// Access the array through the pointer.
myIntPtr[4] = 5;
通过指针访问栈上数组的能力在传递数组给函数时非常有用。下面的函数接受整型数组作为指针。注意调用者需要显示地传递数组的大小,因为指针无法知道数组大小。这也是为什么你需要使用现代比如标准库提供的函数的另一个原因。
void doubleInts(int* theArray, size_t size)
{
for (size_t i { 0 }; i < size; ++i)
{
theArray[i] *= 2;
}
}
函数调用者可以传递栈上数组或者自由内存空间数组。对于自由内存空间数组,指针早就存在,通过其值传入函数。对于栈上数组,调用者可以传递数组变量,编译器自动把数组变量当作指向数组的指针,或者你可以显示地将第一个元素的地址传进去。这三种形式的代码如下:
size_t arrSize { 4 };
int* freeStoreArray { new int[arrSize]{ 1, 5, 3, 4 } };
doubleInts(freeStoreArray, arrSize);
delete [] freeStoreArray;
freeStoreArray = nullptr;
int stackArray[] { 5, 7, 9, 11 };
arrSize = std::size(stackArray); // Since C++17, requires <array>
//arrSize = sizeof(stackArray) / sizeof(stackArray[0]); // Pre-C++
doubleInts(stackArray, arrSize);
doubleInts(&stackArray[0], arrSize);
数组参数传递语法与指针非常类似,因为当它传递给函数时,编译器把数组当作指针。把数组作为参数的函数,并且在数组内修改其值,实际上修改的就是原来的数组,而不是其副本。与指针一样,传递数组有效地模仿了引用传递功能,因为你实际上传递给函数的是原有数组的地址,而不是副本。下面对doubleInts()的实现即使参数是数组不是指针,其改变的依然是原有数组的值。
void doubleInts(int theArray[], size_t size)
{
for (size_t i { 0 }; i < size; ++i) { theArray[i] *= 2; }
}
函数原型中theArray之后在方括弧之间的任何数字都会被忽略。下面的三个版本是一样的:
void doubleInts(int* theArray, size_t size);
void doubleInts(int theArray[], size_t size);
void doubleInts(int theArray[2], size_t size);
你可能会想怎么会这样呢?当数组语法在函数定义时这样用,编译器为什么不对数组进行拷贝呢?主要还是效能的考虑----拷贝数组的元素是需要时间的,也会占用大量的内存。只要传递一个指针,编译器不需要包含拷贝数组的代码。
传递已知长度栈上数组还有一种方法就是“通过引用”给到函数,虽然语法不明显。这个对于在自由内存空间的数组不管用。例如,下面的doubleIntsStack()只接受栈上大小为4的数组。
void doubleIntsStack(int (&theArray)[4]);
我们以后会讨论的函数模板,用于让编译器自动地推算出栈上数组的大小:
template <size_t N>
void doubleIntsStack(int (&theArray)[N])
{
for (size_t i { 0 }; i < N; ++i) { theArray[i] *= 2; }
}
不要直接给函数传递C风格的数组,推荐函数用类型std::span,这个以后会讨论,“标准库函数”span把数组指针与其大小进行了组合。
2、不是所有的指针都是数组!
因为编译器允许你在期待指针的地方传递数组,就像前面的doubeInts()函数,你可能会错误地认为指针与数组是一样的。实际上,它们会有微小的但是很重要的区别。指针与数组共享许多属性,有时候可以互换使用(如前所示),但它们真的不是完全一样的。
指针本身没有任何意义。它可能指向随机的内存地址,一个单独的对象,或者是一个数组。你问题可以用数组语法来使用指针,但这样做并不是总是合适的,因为指针不总是数组。例如,考虑一下下面的这一行代码:
int* ptr { new int };
指针ptr是一个有效的指针,但它不是一个数组。你可以使用数组语法ptr[0]来指向其值,但这样做从风格上是值得怀疑的,并且没有什么好处啊。实际上,对于非数组指针使用数组语法是向错误开了方便之门。Ptr[1]可以是任何东东!
数组可以退化为指针,但不是所有的指针都是数组。