C语言–指针深入理解–题目篇

  • 1. sizeof 与 strlen 比较
    • 1.1 sizeof
    • 1.2 strlen
    • 1.3 数组名的意义
  • 2. 数组和指针笔试题解析(均以x86环境为例)
    • 2.1 ⼀维数组
    • 2.2 字符数组
    • 2.3 二维数组
  • 3. 指针运算笔试题解析

1. sizeof 与 strlen 比较

1.1 sizeof

sizeof 计算变量所占内存内存空间⼤⼩的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。

sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。

#inculde <stdio.h>int main(){ int a = 10; printf("%d\n", sizeof(a));//结果为 4 printf("%d\n", sizeof a);//结果为 4 printf("%d\n", sizeof(int));//结果为 4 return 0;}

上述代码结果均为 4.

1.2 strlen

strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:

size_t strlen ( const char * str );

strlen 统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。
strlen 函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。

#include int main(){ char arr1[3] = {'a', 'b', 'c'};//结尾没有\0,会造成越界 char arr2[] = "abc";//a b c \0共4个元素 //结尾自带\0,不会越界 printf("%d\n", strlen(arr1));//结果为随机值,因为越界了 printf("%d\n", strlen(arr2));//结果为3 printf("%d\n", sizeof(arr1));//结果为3 printf("%d\n", sizeof(arr2));//结果为4,\0也算作一个字符 return 0;}

1.3 数组名的意义

  1. sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩
  2. &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址
  3. 除此之外所有的数组名都表⽰⾸元素的地址

2. 数组和指针笔试题解析(均以x86环境为例)

2.1 ⼀维数组

int main(){int a[] = { 1,2,3,4 };printf("%d\n", sizeof(a));//a 为数组名,表示整个数组,计算的是整个数组的大小,结果为 16 (字节)printf("%d\n", sizeof(a + 0));//括号中不只是有数组名 a ,此时 a 仅表示数组中首元素的地址//加 0 之后仍表示首元素的地址,地址的大小在x86环境下是4个字节,结果为4printf("%d\n", sizeof(*a));//*a 表示数组中第一个元素,元素类型是 int 类型,结果为4printf("%d\n", sizeof(a + 1));//括号中不只是有数组名 a ,此时 a 仅表示数组中首元素的地址//加 1 之后表示第二个元素的地址,地址的大小在x86环境下是4个字节,结果为4printf("%d\n", sizeof(a[1]));//a[1] 表示数组中第二个元素,元素类型是 int 类型,结果为4printf("%d\n", sizeof(&a));//&a 是整个数组的地址,数组的地址也是地址,结果为 4printf("%d\n", sizeof(*&a));//&arr 是整个数组的地址,*(&a)则是对整个数组的解引用,等价于a,结果为16printf("%d\n", sizeof(&a + 1));//&a+1 跳过了整个数组,指向了数组的后边的地址,结果为4printf("%d\n", sizeof(&a[0]));//&a[0]是数组第一个元素的地址,结果为4 printf("%d\n", sizeof(&a[0] + 1));//&a[0]是数组第一个元素的地址,&a[0]+1 指向第二个元素的地址,结果为4 return 0;}

2.2 字符数组

代码1:

char arr[] = {'a','b','c','d','e','f'};//arr数组中有6个元素printf("%d\n", sizeof(arr));//arr表示整个数组,此时数组中结尾没有\0,结果为6printf("%d\n", sizeof(arr+0));//括号中不只是有数组名 arr ,此时 arr 仅表示数组中首元素的地址//加 0 之后仍表示首元素的地址,地址的大小在x86环境下是4个字节,结果为4printf("%d\n", sizeof(*arr));//*arr 是数组的首元素,这里计算的是首元素的大小,结果为1printf("%d\n", sizeof(arr[1]));//arr[1]是数组的首元素,这里计算的是首元素的大小,结果为1printf("%d\n", sizeof(&arr));//&arr 是整个数组的地址,数组的地址也是地址,结果为 4printf("%d\n", sizeof(&arr+1));//&arr+1 跳过了整个数组,指向了数组的后边的地址,结果为4printf("%d\n", sizeof(&arr[0]+1));//&arr[0]是数组第一个元素的地址,&arr[0]+1 指向第二个元素的地址,结果为4 

代码2:

char arr[] = {'a','b','c','d','e','f'};//arr数组中有6个元素,但无"\0"printf("%d\n", strlen(arr));//arr 为数组首地址,因为数组中无“\0",结果为随机值printf("%d\n", strlen(arr+0));//arr+0 仍为数组首地址,因为数组中无“\0",结果为随机值printf("%d\n", strlen(*arr));//将一个具体字符放入strlen函数中,相当于将其ASCII值放入函数中//*arr结果为字符'a',相当于将97放入strlen中,这是一种错误的用法printf("%d\n", strlen(arr[1]));//将一个具体字符放入strlen函数中,相当于将其ASCII值放入函数中//*arr结果为字符'b',相当于将98放入strlen中,这是一种错误的用法printf("%d\n", strlen(&arr));//&arr虽然是整个数值的地址,但也是指向数组的起始位置,数组中无“\0",结果为随机值printf("%d\n", strlen(&arr+1));//&arr+1指向整个数组地址的下一个地址,无法预料结果,结果为随机值printf("%d\n", strlen(&arr[0]+1));//&arr[0]+1指向第二个元素的地址,但数组中无“\0",结果为随机值

代码3:

char arr[] = "abcdef";//arr数组中有7个元素:a b c d e f \0printf("%d\n", sizeof(arr));//此处arr表示整个数组,结果为 7printf("%d\n", sizeof(arr+0));//arr+0是数组首元素的地址,地址的大小在x86环境下是4个字节printf("%d\n", sizeof(*arr));//*arr 是数组的首元素,这里计算的是首元素的大小,结果为1printf("%d\n", sizeof(arr[1]));//arr[1]是数组的首元素,这里计算的是首元素的大小,结果为1printf("%d\n", sizeof(&arr));//&arr 是整个数组的地址,数组的地址也是地址,结果为 4printf("%d\n", sizeof(&arr+1));//&arr+1 跳过了整个数组,指向了数组的后边的地址,结果为4printf("%d\n", sizeof(&arr[0]+1));//&arr[0]是数组第一个元素的地址,&arr[0]+1 指向第二个元素的地址,结果为4 

代码4:

char arr[] = "abcdef";//arr数组中有7个元素:a b c d e f \0//strlen 遇到\0 就会停下来printf("%d\n", strlen(arr));//arr是数组首元素地址,结果为6printf("%d\n", strlen(arr+0));//arr+0 是数组的首元素的地址,结果为6printf("%d\n", strlen(*arr));//将一个具体字符放入strlen函数中,相当于将其ASCII值放入函数中//传递的是’a'-97//errorprintf("%d\n", strlen(arr[1]));//将一个具体字符放入strlen函数中,相当于将其ASCII值放入函数中//传递的是’b'-98//errorprintf("%d\n", strlen(&arr));//&arr虽然是整个数值的地址,但也是指向数组的起始位置,结果为6printf("%d\n", strlen(&arr+1));//&arr+1 跳过了整个数组,指向了数组的后边的地址,结果为随机值printf("%d\n", strlen(&arr[0]+1));//&arr[0]+1是第二个元素的地址,从第二个元素开始计算,结果为5

代码5:

char *p = "abcdef";//p存放的是第一个字符'a' 的地址,结尾仍有一个'\0'printf("%d\n", sizeof(p));//p是指向'a'的指针,sizeof计算的是一个地址,结果为4printf("%d\n", sizeof(p+1));//p+1是指向'b'的指针,sizeof计算的是一个地址,结果为4printf("%d\n", sizeof(*p));//*p就是'a',大小为一个字节printf("%d\n", sizeof(p[0]));//p[0]=*(p+0)=*p,就是'a',大小为一个字节printf("%d\n", sizeof(&p));//&p是指针p的地址,仍旧是一个地址,大小为4个字节printf("%d\n", sizeof(&p+1));//&p+1是指向指针p后面空间的地址,仍旧是一个地址,大小为4个字节printf("%d\n", sizeof(&p[0]+1));//&p[0]+1是'b'的地址,是地址大小就是4个字节

代码6:

char *p = "abcdef"; // a b c d e f \0printf("%d\n", strlen(p));//p存放的是第一个字符'a' 的地址//strlen会根据第一个元素的地址找到其他所有数据,直到遇到\0,结果为6printf("%d\n", strlen(p+1));//p+1存放的是第2个字符'b' 的地址,从第二个元素开始,结果为5printf("%d\n", strlen(*p));//*p是'a',是将a的ASCII值传入strlen中,错误用法,errprintf("%d\n", strlen(p[0]));//p[0]是'a',是将a的ASCII值传入strlen中,错误用法,errprintf("%d\n", strlen(&p));//&p是首元素地址的地址,会得到随机值printf("%d\n", strlen(&p+1));//&p+1是首元素地址的后面一个地址,会得到随机值printf("%d\n", strlen(&p[0]+1));//&p[0]+1指向的是第二个元素的地址,从第二个元素开始计算,结果为5

2.3 二维数组

int a[3][4] = {0};printf("%d\n",sizeof(a));//数组名单独放在sizeof内部,计算的是整个数组的大小//一共12个元素,共12*4=48个字节printf("%d\n",sizeof(a[0][0]));//a[0][0]是第一个元素,大小为4个字节printf("%d\n",sizeof(a[0]));//a[0]是第一行这个一维数组的数组命,数组命单独放在sizeof内部了//计算的是第一行的大小,共16字节printf("%d\n",sizeof(a[0]+1));//a[0]是第一行这个一维数组的数组命,这里表示数组首元素//也就是a[0][0]的地址,a[0]+1是a[0][1]的地址,大小为4个字节printf("%d\n",sizeof(*(a[0]+1)));//a[0]+1是a[0][1]的地址,*(a[0]+1)是第一行第二个元素,大小为4printf("%d\n",sizeof(a+1));//a是二维数组的数组名,但这里没有&,也没有单独放在sizeof内部//所以这里a是数组第一行的地址,a+1是第二行的地址,大小为4个字节printf("%d\n",sizeof(*(a+1)));//*(a+1)==>a[1]--第二行的数组名,单独放在sizeof内部,计算的是第二行的大小,共16个字节printf("%d\n",sizeof(&a[0]+1));//&a[0]是第一行的地址,&a[0]+1就是第二行的地址,大小为4个字节printf("%d\n",sizeof(*(&a[0]+1)));//访问的是第二行,计算的是第二行的大小,共16个字节printf("%d\n",sizeof(*a));//a是第一行的地址,*a就是第一行,sizeof(*a)计算的是第一行的大小,16个字节printf("%d\n",sizeof(a[3]));//这里不存在越界//因为sizeof 内部的表达式不会真实计算的,它只会根据类型进行计算//计算的是第4行的大小--16

3. 指针运算笔试题解析

题目1:

#include int main(){ int a[5] = { 1, 2, 3, 4, 5 }; int *ptr = (int *)(&a + 1); printf( "%d,%d", *(a + 1), *(ptr - 1)); return 0;}

&a是将整个数组的地址,&a+1是数组地址后面的地址,所以ptr指向数组后面的一个地址,ptr-1指向的就是数组中最后一个元素
a+1中,a是首元素的地址,a+1指向第二个元素的地址
上面结果为 25

题目2:

/在X86环境下//假设结构体的⼤⼩是20个字节//程序输出的结构是啥?struct Test{ int Num; char *pcName; short sDate; char cha[2]; short sBa[4];}*p = (struct Test*)0x100000;int main(){ printf("%p\n", p + 0x1); printf("%p\n", (unsigned long)p + 0x1); printf("%p\n", (unsigned int*)p + 0x1); return 0;}

结构体中将0x100000这个十六进制数强制转化为了结构体类型的地址,并将其赋给了结构体指针p,即此时p存放的是地址0x100000
0x1是十六进制表示的1,p+0x1即为p表示的地址往后移动一个相同类型大小的地址,即往后移动20(结构体的大小)个字节。
(unsigned long) p是将p强制转换成无符号长整型类型,仍属于整型,(unsigned long)p + 0x1表示该整型加 1
(unsigned int*)p是将p强制转换成了无符号整型指针,指针加1,往后移动该类型大小的指针,即往后移动4个字节
本题结果为:
0x100000+1=ox100014十六进制中,二十表示为0x000014
0x100000+1=ox100001
0x100000+1=ox100004

题目3:

#include int main(){ int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int *p; p = a[0]; printf( "%d", p[0]); return 0;}

二维数组中存放的是逗号运算,实际结果为a[3][2]={1,3,5}
a[0]表示的是二维数组第一行数组的地址,即p中放的是第一行的数组{1,2}
p[0]表示的是p代表数组的首元素,即1.

题目4:

#include int main(){ int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int *ptr1 = (int *)(&aa + 1); int *ptr2 = (int *)(*(aa + 1)); printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1)); return 0;}

如下图,&aa+1指向的是数组后面地址的位置,aa+1指向的是第二行的地址
int *ptr1 = (int *)(&aa + 1);表示将&aa+1指向的地址强制转换成 int* 类型,并赋值给ptr1
int *ptr2 = (int *)(*(aa + 1));表示将aa+1指向的地址强制转换成 int* 类型,并赋值给ptr2
*(ptr1 - 1)指向的是数组最后一个元素,即10
*(ptr2 - 1)指向的是数组第一行最后一个元素,即5

题目5:

#include int main(){ char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0;}

如下图,pa最开始指向的是数组a的首地址,p++