- 谈及背景
int a[3]; sizeof(arr) = 12B
,这个让我很不理解,后来通过一系列调查研究出了sizeof 一些性质:- sizeof()是单目运算符,和‘++’一样,不是库函数。
- sizeof()算结构体的大小 有对应的规则
- 算结构体大小与结构体中成员排列的顺序有关系,这里引入一个概念偏移量(结构体中成员地址与结构体地址的偏移量,结构体中第一个成员变量地址就是结构体地址,所以第一个成员变量的偏移量为0),基于存储变量时地址要求对齐,所以要遵循两个条件:
- 每个成员变量的偏移量是其自身大小整数倍
- 结构体大小要是所有成员变量大小的整数倍
- 结构体中嵌套结构体时,计算sizeof要把所有的成员变量都拿出来再计算,并且同样遵循每个成员变量的偏移量是其自身大小整数倍,和上面不同的是结构体总体的大小要是成员变量中最大的变量的整数倍即可。
- 当结构体中包含数组时 规则同上展开即可,偏移量规则不是结构体整个数组大小,而是结构体中数组中单个元素大小的整数倍,整个结构体大小遵循结构体中单个元素的最大大小,数组整体大小不参与比较,这里同嵌套结构体了。
- 算结构体大小与结构体中成员排列的顺序有关系,这里引入一个概念偏移量(结构体中成员地址与结构体地址的偏移量,结构体中第一个成员变量地址就是结构体地址,所以第一个成员变量的偏移量为0),基于存储变量时地址要求对齐,所以要遵循两个条件:
- 回归正题数组名的含义
int a[3];
- 先给出答案a的类型是int[3],是一个包含三个int的数组类型,但大多数时候编译器都会把数组名隐式转换成一个指向数组首元素的指针来处理,只有在以下两种情况下以int[3]类型处理:
- 第一种情况直接对应问题背景,sizeof函数处理数组名获得的类型就是int[3]所以输出就是12B,解答了背景的疑问。
- 第二种情况则为&a 的含义为整个数组的地址,其类型是 i n t ( ∗ ) [ 3 ] int (*)[3] int(∗)[3] 其在数值上等于数组中第一个元素的地址,这里和结构体也类同,结构体的地址在数值上也等于结构体中第一个元素的地址,但含义却是大不相同了,用数组 i n t a [ 3 ] int a[3] inta[3]来举例,printf(a+1)是增加一个int字节大小,printf(&a+1)是增加整个数组的大小。
- 先给出答案a的类型是int[3],是一个包含三个int的数组类型,但大多数时候编译器都会把数组名隐式转换成一个指向数组首元素的指针来处理,只有在以下两种情况下以int[3]类型处理:
- 课本补充:
- 数组的类型取决于数组元素的类型:如果它们是int类型,那么数组名的类型就是“指向int的常量指针”;如果它们是其他类型,那么数组名的类型就是“指向其他类型的常量指针”。(出自《C和指针》第141页)
- “取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针”(出自《C和指针》第142页)。
- 讨论隐式转为指向数组首元素指针的情况。
- 最直接就是a
=
=
==
== &a[0] 且 a
=
=
==
==&a,但是和普通指针不同的是,a不可以被再次赋值,也就是说数组的地址一经确立就不可改变了,可以看成 const int* 类别,关于常量指针与指针常量,可以这样区分:
const int *p 是常量指针,我们把*号和p归结到一块,意思就是*p是一个常量,即p指向的值不能改变。
int * const p 是指针常量,这里const 修饰的p ,即p是常量,p本身不可以改变
- 索引转化: a [ 2 ] = ∗ ( a + 2 ) a[2] = *(a + 2) a[2]=∗(a+2)
- 多维数组:
i
n
t
a
[
3
]
[
4
]
;
inta[3][4];
inta[3][4];
- a 的类型是 i n t ( ∗ ) [ 4 ] int (*) [4] int(∗)[4] 即 指向包含 4个整型元素的数组的指针,在c语言中声明的多维数组,编译器对第一个维度的大小(行数)不清楚,因为它只知道数组 a 是一个指向包含 4 个整型元素的数组的指针,&a 的类型是指向数组 a 的指针,即 i n t ( ∗ ) [ 3 ] [ 4 ] int ( * ) [3][4] int(∗)[3][4]。同上printf(a+1)只加一列的大小,而printf(&a+1)增加整个二维数组的大小。
- a[2][1] = ((a+2)+1), a[2][1] = ((*(&a)+2)+1)
- 函数调用数组 int a[3];
- 函数名值传递会直接隐式转化为常量指针
void function(auto a) { cout << sizeof(a) << endl }
不管数组多大输出都是8B,即一个指针大小
- 函数名引用传递会将数组本体传过去,类型仍然是int[3]
void function(auto& a) { cout << sizeof(a) << endl
输出的就是整个数组的大小了
- 函数名值传递会直接隐式转化为常量指针
- 最直接就是a
=
=
==
== &a[0] 且 a
=
=
==
==&a,但是和普通指针不同的是,a不可以被再次赋值,也就是说数组的地址一经确立就不可改变了,可以看成 const int* 类别,关于常量指针与指针常量,可以这样区分:
- 最后再阐述一下c语言中‘[]'的含义:
- []中的数字可以看作偏移量,a[b]这表示在内存中以 a 的地址为起点,偏移 b 个元素,然后取得该位置的值。这是由于数组的下标运算是通过指针运算来实现的,即a[b]将被编译器转换为 *(a+b), 然后加法满足交换律,a[b]可以写为b[a] 即0[a] 就是 a[0]。
特别感谢@ Spring.A 大佬
内容参考: 彻底弄懂C语言数组名