前言
指针是C的精髓,没有指针,那么C就没有它的灵魂。
因为指针的灵活性,使它有更多的玩法,也导致了学习指针的难度。
因此,本文特别选了几组常见的笔试题,来深入体会指针。
一、重温要点
sizeof--关键字,编译时运算符,用来判断变量和数据的大小
注意:sizeof是在编译时执行,将数据替换为相应类型,所以不会执行结果
strlen--库函数
用于计算字符串的长度,遇到'\0'停止
数组名
在一般情况下数组名表示首元素地址
在俩种特殊情况下除外
- sizeof(数组名) sizeof后单独放数组名,这里计算的是整个数组的大小
- &数组名 取出的是整个数组的地址。如果要接收结果,那么应该用数组指针来接收
指针的大小
不论指针的类型,在32为平台上为4,64位平台上位8
二、指针和一维数组
求下列各组的结果
int main()
{
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));
return 0;
}
题解:
一维整形数组,有4个元素,每个元素大小为4
- sizeof单独放数组名,计算整个数组大小,得4*4=16字节
- sizeof后没有单独放数组名,所以a表示首元素地址,首元素地址+0,依旧表示首元素地址,大小为 4或者8(下文简写4/8)
- sizeof后没有单独放数组名,所以a为首元素地址,*a表示首元素,相当于a[0],为整形 4
- a表示首元素地址,首元素地址+1,表示第二个元素的地址,大小为 4 / 8
- a[1] 表示第二个元素,就是 数据2 的大小 4
- &a &a,为整个数组的地址,是指针 4/8
- *&a *和&是一对互逆运算符,二者作用后相当于a,可以写成sizeof(a) 16
- &a+1 &a表示整个数组的地址,+1后还是地址,是指针 4/8
- &a[0] 取出第一个元素的地址,是指针 4/8
- &a[0]+1 先取出第一个元素的地址,再+1,第二个元素的地址,是指针 4/8
答案:16 4/8 4 4/8 4 4/8 16 4/8 4/8 4/8
三、字符数组
1.字符数组和sizeof
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
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));
}
题解:
字符数组,元素为{a,b,c,d,e,f}, 6个元素,每个元素大小为1字节
- 数组名单独放在sizeof内部,计算整个数组的大小 6
- arr+0 arr是首元素地址,+0后还是首元素地址,是指针 4/8
- *arr 首元素地址解引用,是第一个元素 char类型 1
- arr[1] ,第二个元素 1
- &arr 取出整个数组的地址, 是指针 4/8
- &arr+1
再内存中,跳过6个字节,但是&a+1 依旧是指针 4/8
- &a[0]+1, &a[0]表示第一个元素地址,+1 为第二个元素地址 4/8
答案:6 4/8 1 1 4/8 4/8 4/8
2.字符数组和strlen
int main()
{
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));
}
题解:
strlen求长度,遇到'\0' 停止
- 从首元素地址往后找,直到遇到'/0' 长度是随机值
- arr是首元素地址,首元素地址+0 ,还是首元素地址 随机值
- *arr 是字符 'a' 字符A的ASCii为97,编译器就从地址97处访问 ,空间不属于数组,非法访问
- arr[1],是字符‘b’,同上 非法访问
- &arr 取出整个数组的地址,但是strlen的参数会强制类型转换为char* 每次还是访问一个字节,所以 同1,2 长度是 随机值
- &arr+1 从字符f 的后一个元素向后访问 长度为随机值减去 6
- &arr[0]+1 第一个元素的地址+1 第二个元素的地址 随机值-1
答案:随机值 随机值 非法访问 非法访问 随机值 随机值-6 随机值-1
3.字符串和sizeof
int main()
{
char* p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p + 1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p + 1));
printf("%d\n", sizeof(&p[0] + 1));
}
题解:字符串数组元素为{a,b,c,d,e,f,'\0'} 7个元素,p为char类型的指针变量
- p为首元素地址 是指针 4/8
- p+1 首元素地址+1 第二个元素地址 是指针 4/8
- *p第一个元素 字符'a' char类型 1
- p[0] 相当于*p 是字符'a' char类型 1
- &p p为首元素地址, &p是一个二级指针,是指针 4/8
- &p+1 一级指针的地址 +1 是指针 4/8
- &p[0]+1 &p[0]是 首元素地址 +1 是第二个元素的地址 是指针 4/8
答案:4/8 4/8 1 1 4/8 4/8 4/8
4.字符串和strlen
int main()
{
char* p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p + 1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p + 1));
printf("%d\n", strlen(&p[0] + 1));
return 0;
}
题解:p为字符串首元素地址,字符串元素{a,b,c,d,e,f,'\0'}
- 求字符串长度 6
- p+1 首元素地址+1 从第二个元素开始计算,直到'\0' 5
- *p 字符 ’a' ,ascii为97 ,从地址97处向后访问 是非法访问
- p[0] 相当于*p 同上 非法访问
- &p 是首元素地址的地址 二级指针 随机值
- &p+1 首元素地址的地址+1 ,跳过4/8个字节 是随机值,并且与&p没有关系,因为不能知道在这4/8字节中存在 '/0'
- &p[0]+1,第一个元素的地址+1 第二个元素的地址 5
答案:6 5 非法访问 非法访问 随机值 随机值 5
四、二维数组
难点
int main()
{
int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
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]));
}
题解:
二维数组 ,在内存中是一块连续的空间,12个元素,每个元素为int类型
a[1]相当于一维数组名,指向第一行首元素的地址
a[2]相当于一维数组名,指向第二行首元素的地址
- sizeof后单独数组名,表示整个数组的大小,4*12=48
- a[0][0] a[0]是第一个一维数组的数组名,a[0][0]表示第一行第一个元素 int 类型 4
- a[0] 相当于第一个元素的数组名, sizeof(数组名)表示整个第一行数组大小 16
- a[0]+1 a[0]没有单独放在sizeof内部 表示第一行第一个数组的地址,+1 是第二个数组的地址,是指针 4/8
- *(a[0]+1)第一行第二个元素地址解引用,第一行第二个元素 int 类型 4
- a没有单独放在sizof内部,表示首元素地址,二维数组首元素是第一行地址,第一行地址+1,是第二行地址,是指针 4/8
- (a+1)是第二行地址 类型是 int(*)[4],解引用后是int [4],大小是 16
- &a[0] a[0] 相当于维数组数组名 &数组名的类型是数组指针,+1跳过一个数组指针的大小,是指针 4/8
- *(&a[0]+1)这里不存在越界访问,因为sizeof在编译时进行数据替换,计算一个数组值的大小。 类型是 int(*)[4], 大小是16
- *a a是首元素地址,二维数组首元素的地址是第一行地址 类型是int(*)[4] , *a就是第一行 16
- a[3] 不存在数组越界访问 理由同上 a[3]的类型是int (*) [4] 大小是16
答案:48 4 16 4/8 4 4/8 16 4/8 16 16 16
五、常见笔试
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
创建一个5个元素的整形数组
&a表示整个元素的地址 类型是int(*)[5], +1跳过4*5 20个字节,指向5后面一位的地址
然后将ptr强制类型转换为int* 指针解引用从原本访问20字节变为访问4字节,所以ptr-1访问就是元素5的地址
答案 : 2 5
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
&a取出整个数组的地址,类型是int(*)[4],+1跳过16个字节,强制类型装换为int*
ptr1[-1]就是元素4
a表示首元素地址,a强制类型转换为整形 整形+1,跳过一个字节 ,在强制类型转换为int*类型的指针,解引用时,一次访问4个字节(默认小端存储)得到16进制数字 2 00 00 00
答案:4,2 00 00 00
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
二维数组中存在逗号表达式,所以相当于 {1,3,5,0,0,0}
a[0]相当于一维数组的数组名,p[0]是第一行一个元素 1
答案:1
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
题解:
a是指针数组,数组有三个元素 。
数组名a是首元素地址,用二级指针pa来接收
pa++,跳过一个指针的字节(32位4,64位8),指向下一个指针 at的地址
打印字符串at
答案:at
总结
指针是C的重点,也是难点
熟练使用指针,是必要的
指针和数组,二级指针和二维数组经常让我们搞不清,唯有多练多写,才能柔韧有余!
本文旨在帮助大家不再害怕指针,指针的练习远远还不够,希望大家能亲历亲为
代码++
offer++