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));//结果为 4printf("%d\n", sizeof a);//结果为 4printf("%d\n", sizeof(int));//结果为 4return 0;
}
上述代码结果均为 4.
1.2 strlen
strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:
size_t strlen ( const char * str );
strlen 统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。
strlen 函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。
#include <stdio.h>
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));//结果为3printf("%d\n", sizeof(arr1));//结果为3printf("%d\n", sizeof(arr2));//结果为4,\0也算作一个字符return 0;
}
1.3 数组名的意义
- sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。
- &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表⽰⾸元素的地址
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,结果为6
printf("%d\n", sizeof(arr+0));
//括号中不只是有数组名 arr ,此时 arr 仅表示数组中首元素的地址
//加 0 之后仍表示首元素的地址,地址的大小在x86环境下是4个字节,结果为4
printf("%d\n", sizeof(*arr));
//*arr 是数组的首元素,这里计算的是首元素的大小,结果为1
printf("%d\n", sizeof(arr[1]));
//arr[1]是数组的首元素,这里计算的是首元素的大小,结果为1
printf("%d\n", sizeof(&arr));
//&arr 是整个数组的地址,数组的地址也是地址,结果为 4
printf("%d\n", sizeof(&arr+1));
//&arr+1 跳过了整个数组,指向了数组的后边的地址,结果为4
printf("%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 \0
printf("%d\n", sizeof(arr));
//此处arr表示整个数组,结果为 7
printf("%d\n", sizeof(arr+0));
//arr+0是数组首元素的地址,地址的大小在x86环境下是4个字节
printf("%d\n", sizeof(*arr));
//*arr 是数组的首元素,这里计算的是首元素的大小,结果为1
printf("%d\n", sizeof(arr[1]));
//arr[1]是数组的首元素,这里计算的是首元素的大小,结果为1
printf("%d\n", sizeof(&arr));
//&arr 是整个数组的地址,数组的地址也是地址,结果为 4
printf("%d\n", sizeof(&arr+1));
//&arr+1 跳过了整个数组,指向了数组的后边的地址,结果为4
printf("%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是数组首元素地址,结果为6
printf("%d\n", strlen(arr+0));
//arr+0 是数组的首元素的地址,结果为6
printf("%d\n", strlen(*arr));
//将一个具体字符放入strlen函数中,相当于将其ASCII值放入函数中
//传递的是’a'-97//error
printf("%d\n", strlen(arr[1]));
//将一个具体字符放入strlen函数中,相当于将其ASCII值放入函数中
//传递的是’b'-98//error
printf("%d\n", strlen(&arr));
//&arr虽然是整个数值的地址,但也是指向数组的起始位置,结果为6
printf("%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计算的是一个地址,结果为4
printf("%d\n", sizeof(p+1));
//p+1是指向'b'的指针,sizeof计算的是一个地址,结果为4
printf("%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 \0
printf("%d\n", strlen(p));
//p存放的是第一个字符'a' 的地址
//strlen会根据第一个元素的地址找到其他所有数据,直到遇到\0,结果为6
printf("%d\n", strlen(p+1));
//p+1存放的是第2个字符'b' 的地址,从第二个元素开始,结果为5
printf("%d\n", strlen(*p));
//*p是'a',是将a的ASCII值传入strlen中,错误用法,err
printf("%d\n", strlen(p[0]));
//p[0]是'a',是将a的ASCII值传入strlen中,错误用法,err
printf("%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)是第一行第二个元素,大小为4
printf("%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 <stdio.h>
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指向第二个元素的地址
上面结果为2
和5
题目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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
int main()
{char *a[] = {"work","at","alibaba"};char**pa = a;pa++;printf("%s\n", *pa);return 0;
}
如下图,pa最开始指向的是数组a的首地址,p++