文章目录
- 深入理解指针(六)
-
- 1.sizeof和strlen的对比
-
- [1.1 sizeof](#1.1 sizeof)
- [1.2 strlen](#1.2 strlen)
- [1.3 sizeof和strlen的对⽐](#1.3 sizeof和strlen的对⽐)
- 2.数组和指针笔试题解析
- 3.指针运算笔试题解析
-
-
- [3.1 题⽬1](#3.1 题⽬1)
- [3.2 题⽬2](#3.2 题⽬2)
- [3.3 题⽬3](#3.3 题⽬3)
- [3.4 题目4](#3.4 题目4)
- [3.5 题目5](#3.5 题目5)
- [3.6 题⽬6](#3.6 题⽬6)
- [3.7 题⽬7](#3.7 题⽬7)
-
深入理解指针(六)
1.sizeof和strlen的对比
1.1 sizeof
在学习操作符 的时候,我们学习了 sizeof , sizeof 计算变量所占内存空间⼤⼩ 的,单位是字节 ,如果操作数是类型 的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。
sizeof是单目操作符,不是函数!
sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。
比如:
c
#include <stdio.h>
int main()
{
int a = 10;
printf("%zd\n", sizeof(a));
printf("%zd\n", sizeof a);//两者输出结果一致,证明了sizeof不是函数,函数不能去掉()
printf("%zd\n", sizeof(int));
return 0;
}
1.2 strlen
strlen 是C语⾔库函数 ,功能是求字符串⻓度。函数原型如下:
c
size_t strlen ( const char * str );//返回%zd
c
char str[]="abcdef";
const char* str="abcdef";//两者都可以
printf("%zd\n",strlen(str));
统计的是从 strlen 函数的参数 str 中这个地址开始向后 , \0 之前字符串中字符的个数。 strlen 函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。
c
size_t len=strlen("abc\0def");
//返回3
c
#include<string.h>
#include <stdio.h>
int main()
{
char arr1[3] = {'a', 'b', 'c'};//不知道0在哪
char arr2[] = "abc";//abc\0
printf("%d\n", strlen(arr1));//返回随机值
printf("%d\n", strlen(arr2));//返回3
printf("%d\n", sizeof(arr1));//返回3
printf("%d\n", sizeof(arr2));//返回4(a b c \0)共四个
return 0;
}
1.3 sizeof和strlen的对⽐
sizeof | strlen |
---|---|
1.sizeof是操作符 | 1.strlen是库函数,使⽤需要包含头⽂件 string.h |
2.sizeof计算操作数所占内存的 ⼤⼩,单位是字节 | 2.strlen是求字符串⻓度的,统计的是 \0 之前字符的个数 |
3.不关注内存中存放什么数据 | 3.关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能 会越界 |
对于sizeof
sizeof括号中有表达式时,()里是不计算的
例如:
c
int a=8;
short s=4;
printf("%d\n",sizeof(s=a+2));//2
printf("%d\n",s);//4

a跟2都是int类型,把他们赋给short类型,所以sizeof认为它是short类型,所以返回2,但表达式是不进行真实计算的.
原因如下:
c语言是编译型语言
需要经过编译链接生成可执行程序,sizeof在编译过程完成,s=a+2在运行过程完成,所以还未到运行过程,程序就已经结束,不会运行到s=a+2这一步.

2.数组和指针笔试题解析
数组名的意义:
1.sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。
2.&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
3.除此之外所有的数组名都表⽰⾸元素的地址。
2.1 ⼀维数组
c
int a[] = {1,2,3,4};
printf("%zd\n",sizeof(a));//zizeof(数组名)传递的是整个数组,所以结果为16(4*4)--四个整型
printf("%zd\n",sizeof(a+0));//a是首元素地址,没有单独存放进sizeof,类型是int *,a+0还是首元素地址(不会真的计算),是地址就是4(32位)或者8(64位)个字节
printf("%zd\n",sizeof(*a));//a是首元素地址,解引用就是第一个元素,所以是4个字节
printf("%zd\n",sizeof(a+1));//a是首元素地址,类型是int*,+1跳过一个整型-->第二个元素地址,是地址就是4或8个字节
printf("%zd\n",sizeof(a[1]));//a[1]是第二个元素,为4字节
printf("%zd\n",sizeof(&a));//存放的是数组地址,还是4或8
printf("%zd\n",sizeof(*&a));//两种理解方式(结果为16)
//1.&a是取出整个数组的地址,对数组指针解引用访问数组
//2.*和&相互抵消 d等价于(sizeof)
printf("%zd\n",sizeof(&a+1));//跳过整个数组后的位置的地址 依然是地址 4或者8
printf("%zd\n",sizeof(&a[0]));//地址 4或8
printf("%zd\n",sizeof(&a[0]+1));//第二个元素的地址 4或8
对于数组a
a------数组名
a------首元素地址------int*
&a------数组的地址------int(*)[4]
2.2 字符数组
代码1:
c
#include <stdio.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//数组名单独放sizeof,计算的是数组大小 6字节(一个char类型一个字节)
printf("%d\n", sizeof(arr+0));//地址 4或8
printf("%d\n", sizeof(*arr));//arr是首元素地址,*arr为第一个元素,1个字节
printf("%d\n", sizeof(arr[1]));//第二个元素 1字节
printf("%d\n", sizeof(&arr));//&arr是数组地址,4或8字节 arr------char(*)[6]
printf("%d\n", sizeof(&arr+1));//跳过整个数组,指向数组后边的空间 4或者8
printf("%d\n", sizeof(&arr[0]+1));//第二个元素地址 4或8
return 0;
}

&arr+1为野指针,但是不使用
代码2:
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));//arr是首元素地址,数组中没有\0,会导致越界访问,所以结果是随机值
printf("%d\n", strlen(arr+0));//同上
printf("%d\n", strlen(*arr));//arr是首元素地址,*arr是首元素,就是'a',对应的ASII码值为97,就相当于把97作为地址传递给了strlen,strlen得到的就是野指针,所以该程序错误
printf("%d\n", strlen(arr[1]));//arr[1]--'b'--98,同上错误
printf("%d\n", strlen(&arr));//&arr是数组地址,起始位置是第一个元素位置-------随机值
printf("%d\n", strlen(&arr+1));//随机值
printf("%d\n", strlen(&arr[0]+1));//从第二个元素开始向后统计,也是随机值
return 0;//假设第10行随机值为x,则第11行为x+6,第12行为x-1
}
代码3:
c
#include <stdio.h>
int main()
{
char arr[] = "abcdef";//a b c d e f \0
printf("%d\n", sizeof(arr));//数组总大小7
printf("%d\n", sizeof(arr+0));//首元素地址 4或8
printf("%d\n", sizeof(*arr));//首元素 1字节
printf("%d\n", sizeof(arr[1]));//第二个元素 1字节
printf("%d\n", sizeof(&arr));//数组地址 4或8
printf("%d\n", sizeof(&arr+1));//4或8
printf("%d\n", sizeof(&arr[0]+1));//4或8
return 0;
}
代码4:
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";//a b c d e f \0
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr+0));//首地址开始向后 \0之前 6
printf("%d\n", strlen(*arr));//'a'--97出错
printf("%d\n", strlen(arr[1]));'b'--98出错
printf("%d\n", strlen(&arr));//整个数组地址,也是从第一个元素开始 6
printf("%d\n", strlen(&arr+1));//随机值
printf("%d\n", strlen(&arr[0]+1));//第二个元素开始 5
return 0;
}
代码5:
c
#include <stdio.h>
int main()
{
const char *p = "abcdef";//字符常量不能修改
printf("%d\n", sizeof(p));//p是指针变量 4或8
printf("%d\n", sizeof(p+1));//p是char*类型 +1跳一个字节 是b的地址 4或8
printf("%d\n", sizeof(*p));//p的类型是char* *p的类型就是char 1字节
printf("%d\n", sizeof(p[0]));//p[0]-->*(p+0)-->*p-->'a' 1字节 把常量字符串想象成数组,p可以理解为数组名,p[0]就是首元素
printf("%d\n", sizeof(&p));//4或8 取出的是p的地址
printf("%d\n", sizeof(&p+1));//跳过p后的地址 4或8
printf("%d\n", sizeof(&p[0]+1));//地址 4或8
return 0;
}
代码6:
c
#include <stdio.h>
#include <string.h>
int main()
{
char *p = "abcdef";// a b c d e f \0
printf("%d\n", strlen(p));//6 往后数
printf("%d\n", strlen(p+1));//5
printf("%d\n", strlen(*p));//*p就是'a'--97 错误
printf("%d\n", strlen(p[0]));//p[0]---*(p+0)---*p 错误
printf("%d\n", strlen(&p));//&p取出的是指针变量p的地址,和字符串"abcdef"关系不大,从p这个指针变量起始位置开始向后数------随机值
printf("%d\n", strlen(&p+1));//随机值 但不一定为上一行的随机值+1 因为&p和&p+1之间可能有\0
printf("%d\n", strlen(&p[0]+1));//5
return 0;
}
2.3 ⼆维数组
c
include <stdio.h>
int main()
{
int a[3][4] = {0};
printf("%d\n",sizeof(a));//a是数组名,单独放在sizeof内部,计算的是数组大小 3*4*sizeof(int)=48
printf("%d\n",sizeof(a[0][0]));//第一个元素 4字节
printf("%d\n",sizeof(a[0]));//是第一行数组,数组名单独放在sizeof中,是第一行数组总大小16
printf("%d\n",sizeof(a[0]+1));//arr[0]是第一行数组,但没单独放在sizeof,所以是arr[0][0]的地址,+1后是arr[0][1]的地址 是地址就为4或8个字节
printf("%d\n",sizeof(*(a[0]+1)));//a[0][1]元素 4个字节
printf("%d\n",sizeof(a+1));//a表示首元素地址,a为二维数组,它的首元素为第一行的的地址,a+1则为第二行地址,是地址就是4或者8
printf("%d\n",sizeof(*(a+1)));//第二行元素 16
printf("%d\n",sizeof(&a[0]+1));//第二行地址 4或8
printf("%d\n",sizeof(*(&a[0]+1)));//第二行所有元素 16
printf("%d\n",sizeof(*a));//*a为第一行元素 16
printf("%d\n",sizeof(a[3]));//无需真实存在,仅通过类型推断就能算出长度,是第四行元素 16
return 0;
}
3.指针运算笔试题解析
3.1 题⽬1
c
#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;
}

输出结果为2,5
3.2 题⽬2
c
//在X86环境下 //假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0x100000;
//指针+-整数
int main()
{ //0x1就是1 0x1是十六进制
printf("%p\n", p + 0x1);//结构体指针+1,跳过一个结构体 0x100000+20-->0x100014-->00100014 32位补0
printf("%p\n", (unsigned long)p + 0x1);//转换成整型 整型+1就是+1 0x100000+1-->0x100001
printf("%p\n", (unsigned int*)p + 0x1);//0x100000+4-->0x100004-->00100004
return 0;
}
3.3 题⽬3
c
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };//注意题目中()不是{}
int *p;
p = a[0];// 首元素地址,即第一行地址 其实就是a[0][0]的地址
printf( "%d", p[0]);//p[0]-->*(p+0)-->*p 所以答案为1
return 0;
}

3.4 题目4
c
//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
int main()
{
int a[5][5];
int(*p)[4];//p是一个数组指针,p指向的是4个整型元素
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//指针-指针的绝对值是指针之间的元素个数
return 0;//p[4][2]等价于*(*(p+4)+2)
}

a的类型是int * [5],p的类型是int * [4]

所以结果为%d打印的值为**-4**,%p打印的值为(补码)十六进制 把-4转换成十六进制的补码 结果为FFFFFFFC
3.5 题目5
c
#include <stdio.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);//&aa是整个数组的地址 +1跳过整个数组 然后强制类型转换为int*
int *ptr2 = (int *)(*(aa + 1));//得到一维数组名aa[1]=&aa[1][0] 第二行数组名表示第二行首元素地址
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}

所以答案为10 5
3.6 题⽬6
c
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};//a是指针数组
char**pa = a;//pa是二级指针,指向的是a的首元素地址
pa++;
printf("%s\n", *pa);//%s打印的是字符串,给一个地址,从这个地址向后打印字符串,直到遇到\0
return 0;
}

所以本题答案为at
3.7 题⽬7
这题为本文章最难的一题,但一旦理解,会有醍醐灌顶之感
c
#include <stdio.h>
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
根据题图可画出下图

对于 printf("%s\n", **++cpp);来说,++cpp拿到c+2的值----也就是c+2的地址 再解引用------------------PONIT

然后进行printf("%s\n", *--*++cpp+3);//再次++cpp指向c+1 然后解引用得到c+1 然后进行--操作变为c 再解引用 再加3 指向E 打印ER

然后运行这一行 printf("%s\n", *cpp[-2]+3);//*cpp[-2]等价于**(cpp-2) 先进行cpp-2 重新指向c+3 解引用得到c+3 再解引用 再+3 指向S 最终输出ST

最后 printf("%s\n", cpp[-1][-1]+1);//由于上一行代码没有改变cpp的值 所有cpp初始还是指向c+1 cpp[-1][-1]等价于*(*(cpp-1)-1) cpp-1指向c+2 解引用得到c+2 再---1 得到c+1 再解引用 再+1指向E 最终输出EW
