什么是指针衰减数组?与数组指针有关系吗?
std::decay
从C ++ 14开始,使用一元+衰减数组的方法不太那么晦涩。
+a
和+b
在C是法律++,它是在C(非法C11 6.5.3.3/1“一元的操作+
或-
操作员应算术类型”)
什么是指针衰减数组?与数组指针有关系吗?
std::decay
从C ++ 14开始,使用一元+衰减数组的方法不太那么晦涩。
+a
和+b
在C是法律++,它是在C(非法C11 6.5.3.3/1“一元的操作+
或-
操作员应算术类型”)
Answers:
据说数组会“衰减”到指针中。声明为的C ++数组int numbers [5]
无法重新指向,即您不能说numbers = 0x5a5aff23
。更重要的是,术语“衰减”表示类型和尺寸的损失。numbers
会int*
因丢失尺寸信息(计数5)而衰减为1 ,类型不再存在int [5]
。在这里查看不发生衰减的情况。
如果您要按值传递数组,那么您实际上是在复制指针-将指向数组第一个元素的指针复制到参数(其类型也应该是数组元素类型的指针)。这是由于数组的衰减性质而起作用的。一旦衰减,sizeof
就不再给出完整数组的大小,因为它实际上成为了指针。这就是为什么(除其他原因外)首选通过引用或指针传递。
传递数组1的三种方法:
void by_value(const T* array) // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])
最后两个将提供适当的sizeof
信息,而第一个将不提供信息,因为array参数已衰减为可分配给该参数。
1常数U在编译时应该是已知的。
T a[]
与相同T *a
。by_pointer传递相同的东西,除了指针值现在合格const
。如果要传递指向数组的指针(与指向数组第一个元素的指针相反),则语法为T (*array)[U]
。
a
是的数组char
,则a
是类型char[N]
,并将衰变为char*
;但&a
类型为char(*)[N]
,并且不会衰减。
U
您进行更改,则不必记住在两个位置进行更改,否则就冒着无声错误的风险……自治!
数组与C / C ++中的指针基本相同,但不完全相同。转换数组后:
const int a[] = { 2, 3, 5, 7, 11 };
转换为指针(无需强制转换即可工作,因此在某些情况下可能会意外发生):
const int* p = a;
您将失去sizeof
运算符对数组中元素进行计数的能力:
assert( sizeof(p) != sizeof(a) ); // sizes are not equal
这种丧失的能力称为“衰变”。
有关更多详细信息,请查看有关数组衰减的本文。
这是标准所说的内容(C99 6.3.2.1/3-其他操作数-左值,数组和函数指示符):
除非它是sizeof运算符或一元&运算符的操作数,或者是用于初始化数组的字符串文字,否则将类型为“ array of type”的表达式转换为类型为“ pointer to to”的表达式类型'',它指向数组对象的初始元素,不是左值。
这意味着几乎在表达式中使用数组名称的任何时候,它都会自动转换为指向数组第一项的指针。
请注意,函数名称的作用方式相似,但是使用函数指针的方式少得多,而且使用的方式也更加专门化,不会像将数组名称自动转换为指针那样引起混乱。
C ++标准(4.2数组到指针的转换)放宽了对(强调我)的转换要求:
左值或类型“数组NT的”或“结合的T未知的数组”的右值可以被转换成类型的右值“指针T.”
所以转换不会有发生像它几乎总是用C做(这让函数重载或模板的阵列类型相匹配)。
这也是为什么在C语言中应避免在函数原型/定义中使用数组参数的原因(我认为-我不确定是否有任何一般性协议)。它们会引起混乱,并且仍然是虚构的-使用指针参数,混乱可能不会完全消失,但至少不会声明参数声明。
char x[] = "Hello";
。6个元素的数组"Hello"
不会衰减;而是x
获取size,6
并从的元素初始化其元素"Hello"
。
“衰减”是指表达式从数组类型到指针类型的隐式转换。在大多数情况下,当编译器看到数组表达式时,它将表达式的类型从“ T的N元素数组”转换为“指向T的指针”,并将表达式的值设置为数组第一个元素的地址。该规则的例外情况是:数组是sizeof
or &
运算符的操作数,或者数组是在声明中用作初始化程序的字符串文字。
假设以下代码:
char a[80];
strcpy(a, "This is a test");
表达式a
的类型为“ 80个字符的char数组”,表达式“这是一个测试”的类型为“ 16个元素的char数组”(在C中;在C ++中,字符串文字是const char的数组)。但是,在对的调用中strcpy()
,两个表达式都不是sizeof
或的操作数&
,因此它们的类型被隐式转换为“ char的指针”,并且它们的值设置为每个中第一个元素的地址。什么strcpy()
接收不是数组,但是指针,作为其原型看出:
char *strcpy(char *dest, const char *src);
这与数组指针不同。例如:
char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;
两者ptr_to_first_element
和ptr_to_array
具有相同的值;a的基址。但是,它们是不同的类型,并且区别对待,如下所示:
a[i] == ptr_to_first_element[i] == (*ptr_to_array)[i] != *ptr_to_array[i] != ptr_to_array[i]
请记住,表达式a[i]
被解释为*(a+i)
(仅在将数组类型转换为指针类型时才有效),因此两者a[i]
和都ptr_to_first_element[i]
相同。该表达式(*ptr_to_array)[i]
被解释为*(*a+i)
。表达*ptr_to_array[i]
和ptr_to_array[i]
可能导致的编译器警告或者根据上下文的错误; 如果您期望他们进行评估,他们肯定会做错事a[i]
。
sizeof a == sizeof *ptr_to_array == 80
同样,当数组是的操作数时sizeof
,它不会转换为指针类型。
sizeof *ptr_to_first_element == sizeof (char) == 1
sizeof ptr_to_first_element == sizeof (char *) == whatever the pointer size
is on your platform
ptr_to_first_element
是指向char的简单指针。
"This is a test" is of type "16-element array of char"
一个"15-element array of char"
?(长度14 + 1表示\ 0)
C语言中的数组没有任何价值。
只要对象的值是期望值,但对象是数组,则使用其第一个元素的地址,其类型为 pointer to (type of array elements)
。
在函数中,所有参数均按值传递(数组也不例外)。当您在函数中传递数组时,它“分解为指针”(原文如此);当您将数组与其他数组进行比较时,它再次“衰减为指针”(原文如此);...
void foo(int arr[]);
函数foo需要一个数组的值。但是,在C语言中,数组没有任何价值!因此foo
改为获取数组第一个元素的地址。
int arr[5];
int *ip = &(arr[1]);
if (arr == ip) { /* something; */ }
在上面的比较中,arr
由于没有值,因此它成为一个指针。它成为指向int的指针。该指针可以与变量进行比较ip
。
再次使用数组索引语法,您会看到arr被“分解为指针”
arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */
数组唯一不会衰减为指针的情况是它是sizeof运算符的操作数或&运算符(“ address of”运算符的地址),或者是用作初始化字符数组的字符串文字。
当数组腐烂并指向;-)时
实际上,只是如果您想在某个地方传递一个数组,但是要传递指针(因为谁会为您传递整个数组),人们说可怜的数组会退化为指针。
a + 1
。
数组衰减意味着,将数组作为参数传递给函数时,将其等同于(“衰减为”)指针。
void do_something(int *array) {
// We don't know how big array is here, because it's decayed to a pointer.
printf("%i\n", sizeof(array)); // always prints 4 on a 32-bit machine
}
int main (int argc, char **argv) {
int a[10];
int b[20];
int *c;
printf("%zu\n", sizeof(a)); //prints 40 on a 32-bit machine
printf("%zu\n", sizeof(b)); //prints 80 on a 32-bit machine
printf("%zu\n", sizeof(c)); //prints 4 on a 32-bit machine
do_something(a);
do_something(b);
do_something(c);
}
上面有两个并发症或例外。
首先,在C和C ++中处理多维数组时,仅丢失第一维。这是因为数组在内存中是连续布置的,所以编译器必须知道除第一个维以外的所有维,以便能够计算到该内存块中的偏移量。
void do_something(int array[][10])
{
// We don't know how big the first dimension is.
}
int main(int argc, char *argv[]) {
int a[5][10];
int b[20][10];
do_something(a);
do_something(b);
return 0;
}
其次,在C ++中,您可以使用模板来推断数组的大小。Microsoft将其用于诸如strcpy_s之类的Secure CRT函数的C ++版本,并且您可以使用类似的技巧来可靠地获取数组中的元素数。
我可能非常大胆地认为有四(4)种方法可以将数组作为函数参数传递。这也是供您细读的简短但有效的代码。
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
using namespace std;
// test data
// notice native array init with no copy aka "="
// not possible in C
const char* specimen[]{ __TIME__, __DATE__, __TIMESTAMP__ };
// ONE
// simple, dangerous and useless
template<typename T>
void as_pointer(const T* array) {
// a pointer
assert(array != nullptr);
} ;
// TWO
// for above const T array[] means the same
// but and also , minimum array size indication might be given too
// this also does not stop the array decay into T *
// thus size information is lost
template<typename T>
void by_value_no_size(const T array[0xFF]) {
// decayed to a pointer
assert( array != nullptr );
}
// THREE
// size information is preserved
// but pointer is asked for
template<typename T, size_t N>
void pointer_to_array(const T (*array)[N])
{
// dealing with native pointer
assert( array != nullptr );
}
// FOUR
// no C equivalent
// array by reference
// size is preserved
template<typename T, size_t N>
void reference_to_array(const T (&array)[N])
{
// array is not a pointer here
// it is (almost) a container
// most of the std:: lib algorithms
// do work on array reference, for example
// range for requires std::begin() and std::end()
// on the type passed as range to iterate over
for (auto && elem : array )
{
cout << endl << elem ;
}
}
int main()
{
// ONE
as_pointer(specimen);
// TWO
by_value_no_size(specimen);
// THREE
pointer_to_array(&specimen);
// FOUR
reference_to_array( specimen ) ;
}
我可能还认为这显示了C ++与C的优越性。至少在通过引用传递数组的引用(双关语意)中。
当然,有一些非常严格的项目,没有堆分配,没有异常,也没有std :: lib。有人可能会说,C ++本机数组处理是关键任务语言功能。
int a[10]; int b(void);
,+a
则为int指针,+b
为函数指针。如果要将其传递给接受引用的模板,则很有用。