一.一维数组
int a[] = { 1,2,3,4 }; printf("%d\n", sizeof(a)); printf("%d\n", sizeof(a + 0)); printf("%d\n", sizeof(*a)); printf("%d\n", sizeof(a + 1)); printf("%d\n", sizeof(a[1])); printf("%d\n", sizeof(&a)); printf("%d\n", sizeof(*&a)); printf("%d\n", sizeof(&a + 1)); printf("%d\n", sizeof(&a[0])); printf("%d\n", sizeof(&a[0] + 1));
(1) sizeof()的操作对象是数组名
数组名表示的是数组首元素的地址,但是有两个例外:
1.数组名是 sizeof 的操作对象
2. & + 数组名 ,
上述两种情况比较特殊,此时的数组名表示的是,整个数组。也就是说,sizeof(数组名)计算的是整个数组的大小,& + 数组名,代表是的取出整个数组的地址。
printf("%d\n", sizeof(a)); printf("%d\n", sizeof(*&a));
&a是数组的地址,是数组指针类型,*&a是都数组指针解引用,访问一个数组的大小。
(2)sizeof()操作对象是指针
针对 sizeof 这个操作符,是用来求类型的大小的(单位:字节),当然也可以求指针的大小,我们知道一个普通指针在内存中占 4 个字节或者 8 个字节,至于到底是几个字节,取决于计算机平台,在32位平台是 4 字节,在 64 位平台是 8 字节。
printf("%d\n", sizeof(a + 0)); printf("%d\n", sizeof(a + 1)); printf("%d\n", sizeof(&a)); printf("%d\n", sizeof(&a + 1)); printf("%d\n", sizeof(&a[0])); printf("%d\n", sizeof(&a[0] + 1));
这里(a+0)如果是 a 单独是sizeof的操作对象,那么此时 a 就是代表整个数组,但是(a+0)就不代表整个数组。(a+0)就是一个普通指针,指向数组的第一个元素。也就是数组的首元素的地址。(a+1)就是数组第二个元素的地址。(&a)也是一个指针,是一个数组指针类型的指针,但仍然是一个指针。(&a +1 ): &a 是取出了整个数组的地址,是一个数组指针类型的指针,(&a +1 )跳过整个数组的地址。(这里的针对指针加上一个整数的运算,后面会单拎出来好好讲讲)所以(&a+1)在实际上还是一个指针。是指针大小就是,4 或 8 字节。&arr[0]实际上也是一样,拿到是首元素的地址,(&a[0] + 1):就是在首元素的指针上往后跳过一个元素,也就是第二个数组元素的地址,无论 &a[0] , &a[0] + 1都是指针类型。所以大小自然都是 4 或者 8 字节。
32平台(X86)展示:
64平台(X64 )展示:
(3)sizeof()的操作对象是普通变量或者普通变量类型
printf("%d\n", sizeof(*a)); printf("%d\n", sizeof(a[1]));
a并没有单独放在sizeof()里面,所以 a 是首元素的地址,(*a)也就是首元素了,a[1],数组访问第二个元素,都是每一个数组元素又都是 int 类型。所以这两个结果都是 4 。
二.指针类型对指针的影响
刚才我们遇到了,&a + 1 是越过了整个数组的地址,那到底为什么呢?下面我们就来好好分析一下。
1.指针的类型决定了指针在加减一个整数,指针移动的空间长度。
举例:
int main() { int a = 0; char ch = 0; int arr[3] = { 0 }; int* pa = &a; char* pc = &ch; int(*parr)[3] = &arr; printf("pa=%p,pa+1=%p\n", pa, pa + 1); printf("pc=%p,pc+1=%p\n", pc, pc + 1); printf("parr=%p,parr+1=%p\n", parr, parr + 1); return 0; }
这个地方明显看出,pa 是整形指针,pa + 1只越过了 4 个字节。pc 是一个字符型指针,pc + 1越过了 1 个字节。parr 是一个数组指针,数组有三个元素,每个元素都是一个 int 型数据。所以数组的大小是 4*3=12,所以 parr + 1 越过了 12 个字节。
2.指针的类型决定了指针在解引用时,一次能访问多少字节的空间。
举例:
int main() { int a = 0x11223344; int* pa = &a; char* pc = &a; short* ps = &a; printf("%x\n", *pa); printf("%x\n", *ps); printf("%x\n", *pc); return 0; }
我们将十六进制数 11223344 存入a的四个字节空间,因为pa是整形指针,(*pa)一次就可以访问四个字节,所以,十六进制打印出来的(*pa)就是原数据11223344,ps 是短整型指针,(*ps)一次就可以访问两个字节,由于我的机器采用的是小端存储模式,所以在存储时是将数据的权重低位,放在了地址的低地址处,而读取内存还是从地址在高地址读取,所以十六进制打印出来的恢复数据的高低位就是 3344 ;pc是 char * 类型,(*pc)一次访问一个字节,也就是内存低位的一个字节,在我的机器上就是数据的权重低位,也就是44;
三.字符数组
char arr[] = { 'a','b','c','d','e','f' }; printf("%d\n", strlen(arr)); printf("%d\n", strlen(arr + 0)); printf("%d\n", strlen(*arr)); printf("%d\n", strlen(arr[1])); printf("%d\n", strlen(&arr)); printf("%d\n", strlen(&arr + 1)); printf("%d\n", strlen(&arr[0] + 1));
(1)strlen()的底层原理
strlen()是一个计算字符串长度的库函数,计算的原理是:接受字符串的首字符指针,直到找到字符串的结束标志也就是‘ \0 ’ 结束。
(2)strlen()和字符数组
由于字符数组的没有 ‘ \0 ’作为结束标志,所以strlen()函数在什么位置寻找到 ‘ \0 ’也是不能知道的。所以在计算长度的时候,自然也就是一个随机值。
char arr[] = { 'a','b','c','d','e','f' }; printf("%d\n", strlen(arr)); // 从 arr 出开始找‘ \0 ’ printf("%d\n", strlen(arr + 0)); //仍然是从 arr 处开始找‘\0’ printf("%d\n", strlen(&arr)); // 由于 &arr 和 arr 虽然类型不同,但是都是指向数组的首元素地址, // 都是从数组的首元素地址处开始的寻找‘ \0 ’ printf("%d\n", strlen(&arr + 1)); // &arr + 1 从越过整个数组后的地址处开始寻找‘ \0 ’ printf("%d\n", strlen(&arr[0] + 1)); // &arr[0]仍然是指向首元素的地址,&arr[0] + 1 是从数组第一个元素开始寻找‘\0’
因为&arr+1 跨过了一个数组,所以strlen(&arr + 1)的随机值会比strlen(arr),
strlen(arr + 0),strlen(&arr) 小 6.而&arr[0]+1只只是跨过了一个字符,strlen(&arr[0]+1)所以会比strlen(arr),strlen(arr + 0),strlen(&arr) 小 1。
代码运行结果:
由于strlen()参数需要的是指针,所以下面的代码就是错误的,由于内存访问冲突引起的。
printf("%d\n", strlen(*arr)); printf("%d\n", strlen(arr[1]));
代码运行结果:
四.字符串数组
(1)sizeof()和字符串数组
char arr[] = "abcdef"; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof(arr+0)); printf("%d\n", sizeof(*arr)); printf("%d\n", sizeof(arr[1])); printf("%d\n", sizeof(&arr)); printf("%d\n", sizeof(&arr+1)); printf("%d\n", sizeof(&arr[0]+1));
我们要知道字符串在数组里面是怎么存储的:
我们知道注意:
printf("%d\n", sizeof(arr));
这里的arr是整个数组,而这里的数组有在最后多存了一个‘ \0 ’。所以加上‘ \0 ’一共七个字节。
其他的还是和数组一样的分析。
运行结果:
X86环境
X64环境:
(2)strlen()和字符串数组
char arr[] = "abcdef"; printf("%d\n", strlen(arr)); printf("%d\n", strlen(arr+0)); printf("%d\n", strlen(*arr)); printf("%d\n", strlen(arr[1])); printf("%d\n", strlen(&arr)); printf("%d\n", strlen(&arr+1)); printf("%d\n", strlen(&arr[0]+1));
这里大家只要注意,strlen()求字符串的长度的原理就行了。
六.二维数组
int a[3][4] = {0}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a[0][0])); printf("%d\n",sizeof(a[0])); printf("%d\n",sizeof(a[0]+1)); printf("%d\n",sizeof(*(a[0]+1))); printf("%d\n",sizeof(a+1)); printf("%d\n",sizeof(*(a+1))); printf("%d\n",sizeof(&a[0]+1)); printf("%d\n",sizeof(*(&a[0]+1))); printf("%d\n",sizeof(*a)); printf("%d\n",sizeof(a[3]));
(1)二维数组的数组名
二维数组的数组名,还是和一维数组差不过,除了sizeof( 数组名 )和 & + 数组名。其他的数组名都是,首元素的地址。注意:这个时候二维数组的首元素,不再是第一个元素,而是把二维数组看成一维数组,每一行看作一个一维数组,那么数组名是首元素的地址,也就是第一行一维数组的地址。
int main() { int a[3][4] = { 0 }; printf("%d\n", sizeof(a)); //sizeof(a)数组名代表整个数组, //代表求整个数组的大小。 printf("%d\n", sizeof(a[0][0])); //代表是求第一个数组元素的大小。 printf("%d\n", sizeof(a[0])); //a是二维数组的数组名,是数组的第一行的地址, // a[0]相当于*(a+0),找到了数组第一行。 //所以sizeof(a[0])算的是二维数组第一行的大小。 printf("%d\n", sizeof(a[0] + 1)); //a[0]其实就是二维数组第一行的数组名, // (a[0]+1)就是一个指向二维第一行第二个元素的指针。 //算的是一个指针的大小。 printf("%d\n", sizeof(*(a[0] + 1))); //在(a[0]+1)的基础上在解引用, //就是二维第一行第二个元素。 printf("%d\n", sizeof(a + 1)); //a是一个指向第一行数组的指针, // a+1就是一个指向第二行数组的指针。 //算的是一个指针的大小 printf("%d\n", sizeof(*(a + 1))); //(a+1)是指向二维数组第二行的地址, // 解引用以后就是找到了第二行 //所以算的是第二行的大小 printf("%d\n", sizeof(&a[0] + 1)); //&a[0]取出第一行的地址, //等价于数组名 a ,所以等价于 a + 1 printf("%d\n", sizeof(*(&a[0] + 1))); //等价于*(a+1) printf("%d\n", sizeof(*a)); //等价于a[0] printf("%d\n", sizeof(a[3])); //等价于 *(a+3),虽然数组没有第四行, // 但是*(a+3)类型能够确定, // 就是 int [4],就是整形四元素数组。 return 0; }
运行效果(X86):
最后:
月是人间散客,卿是人间绝色,亦是人间难得。