征服C语言指针系列重点笔试题详解
1.指针与数组结合题型
1.一维数组
1.1.sizeof和整型数组
先上代码,大家可以先自己做一下这些题,
注意:
1.在32位平台下,指针大小为4个字节,在64位平台下,指针大小为8个字节
2.在这里所说的指针就是地址,地址就是指针
int a[] = { 1,2,3,4 };
//1.
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]));
//2.
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. 16
4或者8
4
4或者8
4
2. 4或者8
16
4或者8
4或者8
4或者8
解析:
1.答案:16
知识点:
考查对于sizeof(数组名)的理解,
注意:数组名一般情况下代表数组首元素的地址,只有两种情况代表整个数组的地址:
(1).sizeof(数组名)
(2).&数组名
解析:
sizeof(数组名):求整个数组的大小(即所占字节数):4(数组元素个数)*4(int类型占4个字节的单位)=16
2.答案:4或者8:
注意:只有当sizeof(只有数组名这一个符号)时,sizeof(数组名)才代表整个求数组的大小
所以:此时a为数组首元素的地址,(a+0)为数组首元素的地址,而地址就是指针,就是4个字节或者8个字节
3.答案:4
此时:a为数组首元素的地址,*a为数组首元素,首元素为int类型,大小4个字节
4.答案:4或者8
与第二题如出一辙,只不过是代表数组第二个元素的地址
5.答案:4
a[1]代表数组第二个元素,为int类型,大小为4个字节
6.答案:4或者8
&a代表整个元素的地址,是地址,就是指针类型,就是4个或8个字节
7.答案:16
&a代表整个数组的地址,*&a等价于a,也就是说*&a就是a,所以等价于sizeof(a)也就是求整个数组的大小,也就是16个字节
8.答案:4或者8
&a+1,
&a+1,是地址,只要是地址大小就是4或者8个字节
补充:
其中&a的类型是int(*a)[4],也就是说&a的类型是一个数组指针,指向的数组的元素为int类型,数组元素的个数为4个,
所以:&a+1所"迈出的步长"为4个int类型,也就是说&a+1指向数组a末尾位置
9.答案:4或8
&a[0],是地址,所以大小为4或者8个字节
补充:
a[0]:数组首元素,&a[0]就是数组首元素的地址
10.答案:4或8
&a[0]+1,是地址,所以大小为4或者8个字节
补充:
&a[0]是数组首元素的地址,所以&a[0]+1就是数组第二个元素的地址
总结:
1.数组名一般情况下代表数组首元素的地址,只有两种情况代表整个数组的地址:
(1).sizeof(数组名)
(2).&数组名
2.sizeof(数组名):其中数组名代表整个数组的地址,那么为什么sizeof(数组名)不是计算地址的大小呢?
是sizeof这个操作符本身的特性,使得出现了这个特例
其实这个特例的出现才是sizeof用处最多的方面,
例如:在计算数组中所含元素个数时,我们就通常使用
int sz = sizeof(arr)/sizeof(arr[0]);来计算数组中所含元素个数
也就是说求一个类型所含字节大小才是sizeof设计的核心原因和用途,所以规定了sizeof(数组名)就是求整个数组的大小这么一个规定
补充:
&a是整个数组的地址,不过具体值还是数组首元素的地址,
只不过这个指针类型和a这个指针类型不同,前者是int(*)[4]类型,后者是int*类型
1.2.sizeof和字符数组
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
//1.
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
//2.
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
答案:
1. 6
4或8
1
1
2. 4或8
4或8
4或8
解析:
1.答案:6
sizeof(数组名):求整个数组的大小
arr一共只有6个元素,每个元素为char类型,所以计算结果为6*1=6
注意:
将arr与char arr[]="abcdef"相区分开来,
其中前者在数组末尾没有'\0',而后者有'\0'
补充:
1.使用sizeof 计算字符串大小时'\0'也会包含在所计算的字符串大小之内
2.strlen计算字符串长度时,遇到'\0'后才会停止,求得的长度不包含'\0'
---------------------------------------------------------------
2.答案:4或8
arr+0为数组首元素的地址,是地址,大小就为4或者8个字节
3.答案:1
arr是数组首元素的地址,*arr就是数组首元素,数组首元素为char类型,大小为1个字节
4.答案:1
arr[1]就是数组第二个元素,为char类型,大小为1个字节
5.答案:4或8
&arr:是整个数组的地址,是地址,大小就为4或者8个字节
6.答案:4或8
&arr+1:是整个数组末尾的地址,是地址,大小就为4或者8个字节
7.答案:4或者8
&arr[0]+1:是数组第二个元素的地址,是地址,大小就为4或者8个字节
其中第6,7题可见1.2sizeof和整型数组中的第8,10题的补充
> 补充:
> size_t strlen ( const char * str );
> 也就是说strlen的参数是一个char*类型的指针,
> 所以在计算一个字符串的长度的时候传入一个地址,
> strlen函数通过传入的地址开始依次往下开始读取地址所指向的内容
> 一直读到'\0',返回在读到'\0'之前的元素的个数
因为参数为char*类型,所以如果传入了其他类型的指针变量,均转化为char*类型,也就是限制了"指针变量的步长"为1
1.3 字符数组和strlen
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));
在这里为了表示方便,我们假定随机值为x
答案是:
x
x
非法访问
非法访问
x
x-6
x-1
解析:
1.答案:x.
上面提到过strlen只有读到'\0'时才会停止,而这种字符数组的末尾是不会自动追加'\0'的
所以strlen读到了该字符串末尾后发现没有'\0',所以继续向后读取知道读到了'\0',所以计算出的'\0'之前的字符个数为随机值x
2.答案:x.
传入的是arr+0,也就等同于传入arr,计算出从数组首元素开始往后读取到'\0'之前的字符个数,也就完全等同于第一题
3.答案:非法访问
前提知识:前面提到过:
1.arr代表数组首元素地址,*arr代表数组首元素
所以传入的是字符'a'
2.strlen的参数类型是char*,也就是需要传入一个地址,我们将字符'a'传入后,字符'a'在内存中实际存储的是字符'a'所对应得ascii码值,即97
题解:
所以strlen函数就认为97是个地址,于是访问97所对应的地址空间,
而97所对应的内存空间里面存放的是什么,是不清楚的,
有可能存放的是很重要的数据,所以不能被任意访问,
所以编译器会报错,因为操作系统为了保护电脑,不允许编译器访问这个地址空间,所以为非法访问
4.答案:非法访问
与第3题如出一辙,只不过传入的是arr[1],也就是数组第二个元素,即字符'b',所对应的ascii码值为98
5.答案:x
前置条件:前面补充过,详见1.1末尾补充内容
&arr是整个数组的地址,不过具体值还是数组首元素的地址,是不过这个指针类型和arr这个指针类型不同,前者是char(*)[6]类型,后者是char*类型
题解:
所以传入strlen这个函数的参数与传入arr这个参数所对应的值一样,
又因为前面提到过strlen的参数类型是char*,所以&arr被转化为char*类型,
限制了"指针变量的步长"为1
所以在strlen这个函数中传入&数组名和数组名和数组名+0,
三者本质上完全相同,没有差别
6.答案:随机值x-6
前面补充过,&arr的类型是char(*)[6]类型,
所以该指针变量的"步长"为6,所以&arr+1指向了数组末尾位置,
所以它是从f后面的地址开始计算,所以为x-6
7.答案:x-1
&arr[0]+1:就是数组第二个元素,所以strlen函数从数组第二个元素开始计算长度,所以少计算了字符'a'
1.4 字符串(另一类型的字符数组)与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));
答案:
注意:
这里的字符数组末尾有'\0',是C语言规定,编译器自动追加的,只不过没有显示出来而已
1. 7
4或8
1
1
4或8
4或8
4或8
解析
1.答案:7
前面提到过,sizeof(数组名):计算整个数组的大小,包括'\0'\
2.答案:4或8
arr+0是数组首元素的地址,是地址,大小就为4或8个字节
3.答案:1
*arr:数组首元素,为char类型,大小为1
4.答案:1
arr[1]:数组第二个元素,为char类型,大小为1
5.答案:4或8
&arr:整个数组的地址,是地址,大小就为4或8个字节
6.答案:4或8
&arr+1:整个数组末尾的地址,是地址,大小就为4或8个字节
7.答案:4或8
&arr[0]+1:数组第二个元素的地址,是地址,大小就为4或8个字节
1.5 字符串(另一类型的字符数组)与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));
这里为了表示方便:随机值为x
答案:
6
6
非法访问
非法访问
6
x
5
解析:
1.答案:6
前面提到过:strlen函数求'\0'之前的字符个数,这里为6个
2.答案:6
前面提到过,对于strlen函数而言,传入arr,arr+0,&arr,本质均相同,所以同第一题
3.答案:非法访问,同1.3的第三题
4.答案:非法访问,同1.3的第四题
5.答案:6,同第一题
6.答案:随机值x,
因为&arr+1是数组末尾的地址,也就是在这个字符串末尾的'\0'之后那个元素的首地址
7.答案:5
&arr[0]+1:数组第二个元素的地址,所以为6-1=5
1.5 指针指向的字符串与sizeof
补充:对于一个数组而言,以arr数组为例
int arr[]={1,2,3,4,5,6,7,8,9};
int i 代表从0到8的数字
arr[i] 就等价于 *(arr+i)
而且,同理,对于而言:
char* p = "abcdef";
p[i] 就等价于 *(p+i)
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));
答案:
4或者8
4或者8
1
1
4或者8
4或者8
4或者8
解析:
1.答案:4或者8
p就是数组首元素的地址,是地址,大小就为4或者8个字节
2.答案:4或者8
p+1是数组第二个元素的地址,是地址,大小就为4或者8个字节
3.答案:1
P是数组首元素的地址,*p就是数组的首元素,也就是字符'a',char类型,大小为1个字节
4.答案:1
前面补充过:p[i] 就等价于 *(p+i)
所以p[0]就是*(p+0),也就是*p,也就等同于第3题
5.答案:4或8
&p是指向指针p的二级指针,指针就是地址,大小就为4或者8个字节
6.答案:4或8
&p+1是指向指针p之后的空间的二级指针,指针就是地址,大小就为4或者8个字节.
7.答案:4或8
&p[0]+1是数组第二个元素的地址,是地址,大小就为4或者8个字节
我们可以通过这张图来仔细看一下:第5,6题
看红色箭头,也就是说,&p是指向p的二级指针,&p+1是指向p末尾的二级指针,但它们都是指针,是指针,大小就为4或8个字节
1.5 指针指向的字符串与strlen
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));
答案是:
6
5
非法访问
非法访问
x
y
5
解析:
1. 答案:6
p是字符数组的首元素地址,
而把p传给strlen函数,也就是把字符数组首元素地址传给strlen,
所以strlen函数可以从字符'a'开始向后读取字符,一直读到'\0',所以答案为6
2.答案:5
p是char*类型的指针,"步长"为1,所以p+1就是数组第二个元素的地址也就是字符'b'的地址,所以从字符'b'开始向后读取直到'\0'
3.答案:非法访问,同1.3的第三题
4.答案:非法访问,同1.3的第四题,只不过这里指向的还是第一个元素,因为走的步长为0
5.答案:随机值x
注意:请与1.3的第5题区分开来,在后面我们看一下"内存图"
6.答案:随机值y
在后面我们看一下"内存图"
7.答案:5
&p[0]+1,数组第二个元素的地址,同第二题
从这里我们可以看出&p指向的地方的元素是未知的,
所以strlen(&p)和strlen(&p+1)求出的分别是随机值x和随机值y,
分别对应蓝色箭头和紫色箭头,
其中随机值x和随机值y无必然联系,
也就是说如果p内存在'\0',则x和y无任何关系,
如果p内不存在'\0',则x和y满足:x=y+6
注意:
这里所说的'\0',
因为'\0'的ASCII码值为0,而字符在内存中是以ASCII码值的形式存在的,
所以说只要p内存在0即表示存在'\0'
2.二维数组
首先:我们先说一下两个很好的关于二维数组理解方法
1.二维数组是一维数组,只不过二维数组的元素是一维数组,而一维数组的元素是int/char/double…类型
2.在同类型的情况下,对指针解引用就是得到指针所指向的目标
//二维数组
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]));
答案:
48
4
16
4或8
4
4或8
16
4或8
16
16
16
解析:
1.答案:48
a是整个二维数组的地址,sizeof(a)就是求整个二维数组的大小
3*4*4=48
2.答案:4
a[0][0]:就是二维数组第一行的第一个元素,是int类型,大小为4字节
3.答案:16
a[0]:就是二维数组首元素的地址,而二维数组的首元素是一个一维数组,
所以a[0]就是这个一维数组的地址,所以sizeof(a[0])就是求这个一维数组的大小,即16
4.答案:4或8
a[0]是第一个一维数组的首元素的地址,
a[0]+1就是该一维数组第二个元素的地址,
是地址,大小就是4或者8个字节
5.答案;4
*(a[0]+1)就是第一个一维数组的第二个元素,为int类型,大小为4个字节
6.答案:4或8
a是二维数组首元素的地址,
a+1就是二维数组第二个元素的地址,就是第二个一维数组的地址
是地址,大小就是4或者8个字节
7.答案;16
a+1是第二个一维数组的地址,*(a+1)就是第二个一维数组,
sizeof(数组名)就是求整个数组的大小,
也就是说sizeof(*(a+1))就是求第二个一维数组的大小,也就是16
8.答案:4或者8
法一:内存理解:
本题后面给大家画了"内存图",其中&a[0]+1和a[0]+1所指向空间的起止位置都已经给大家标出来了,所以每个均有两个箭头,
&a[0]+1是指向了第二个一维数组的数组指针,其"步长"为4*4=16byte
法二:概念理解
前面说过:a[0]等价于*(a+0),所以&a[0]+1就等价于&*(a+0)+1,
也就等价于(a+0)+1 ,也就等价于a+1
其实&a[0]+1 等价于a+1 无论a是一维数组还是二维数组!!!!!!!!!!
总之,&a[0]+1是指针,指针式地址,地址就是4或8个字节
9.答案:16
由第八题概念法得:其实*(&a[0]+1)就是*(a+1),也就是a[1]
10.答案:16
其实*a就是*(a+0)就是a[0]
11.答案:16
a[i]是第i个一维数组的地址,sizeof(a[i])就是求第i个一维数组的大小,即16
注意:尽管a[3]并不存在(因为a是三行四列的二维数组,没有第四行),
但是sizeof(表达式),其中表达式并不参与运算,
也就是说表达式只负责告诉sizeof:(我是什么类型的数据,你只需要根据我的数据类型来计算字节数即可)
我们可以看下面的一个代码,它能够帮助你有更深刻的理解
第8题的图片
第11题的补充
void exam009()
{
//sizeof()内部的表达式是不算的
short s = 5;
int a = 4;
printf("%d\n", sizeof(s = a + 6));//2
//说明了:这里不会因为a+6是int类型就将short改为int类型!!
printf("%d\n", s);//5,也就说明了:并未进行sizeof()内部的表达式
}
总结:
1.sizeof()内部的表达式是不算的,
也就是说表达式只负责告诉sizeof:(我是什么类型的数据,你只需要根据我的数据类型来计算字节数即可)
2.其实&a[0]+1 等价于a+1 无论a是一维数组还是二维数组!!!!!!!!!!
3.重申一遍
> 1.二维数组是一维数组,只不过二维数组的元素是一维数组,而一维数组的元素是int/char/double.......类型
> 2.在同类型的情况下,对指针解引用就是得到指针所指向的目标
2.指针经典笔试题详解
1.
考点:
1.内存图
2.指针类型决定步长
void exam010()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
}
答案是:2,5
> &a是数组指针类型,即int(*)[5],步长为4*5=20byte,
> 所以&a+1就指向了数组末尾的位置,
> 而强制类型转换为int*类型后,步长变为4,所以指向了最后一个元素的起始位置,所以*(ptr-1)就是最后一个元素
2.
考点:
指针类型决定了指针加减运算的步长
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节,%p是打印地址
void exam011()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
}
答案是:
0x100014
0x100001
0x100004
第一行:p是struct Test*类型的指针,又因为Test结构体的大小是20个字节,
所以步长为20,转为16进制是14
第二行:p强制类型转换为(unsigned long)类型,
unsigned long就是整型,整型加1就相当于1+1,所以答案就是0x100001
第三行:p强制类型转换为(unsigned int*)类型,步长为4,所以加4
3.
考点:
指针类型决定了指针的加减整数运算的步长
大小端存也要大小端取(具体大小端的知识可以看我的另一篇博客,里面深度剖析了数据在内存中的存储方式,当然也包括了非常详细的大小端问题的说明
https://blog.csdn.net/Wzs040810/article/details/130900893?spm=1001.2014.3001.5501
void exam012()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
}
从中我们可以看出ptr2指向的数据取出来后是02 00 00 00,
所以将其转为16进制就是最终答案,
因为int* ptr2 = (int*)((int)a + 1)中我们先将a强制类型转换为了int类型,
所以加1操作就是将地址加一,也就是将地址加一个字节,
而int类型占4个字节,所以前移四分之一,指向了图中所示位置.
4.
考点:
逗号表达式
二维数组与指针
void exam013()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
}
答案:1
注意:这里的二维数组不是
0 1
2 3
4 5
而是
1 3
5 0
0 0
因为逗号表达式整体的最终取值为逗号表达式的最后一项,不过每个表达式都会执行.
例如
int main()
{
int b = 9;
int a = (0, b = 20, 6 - 3);
printf("%d %d", a, b);
return 0;
}
//最终a的值为3
//b的值为20
而p就是第一个一维数组,p[0]就是第一个一维数组的第一个元素,也就是1
5.
考点:
内存图
指针的类型决定加减运算的步长
void exam014()
{
int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
}
答案:FFFFFFFFFFFFFFFC,-4
其中FFFFFFFFFFFFFFFC是-4的补码的16进制表示形式
解析:
p[4][2] == *(*(p+4)+2)
由图可以看出具体的过程,
p=a这一句的含义是:
a是二维数组首元素的地址,也就是第一个一维数组的地址,
也就是说a是一个数组指针,其类型为int(*)[5],
而p的类型是int(*)[4],尽管二者类型不同,但是a最终还是将第一个一维数组的地址传给了p
而p的类型不变,a的类型不变
p的步长为4*4=16byte
a的步长为5*4=20byte,
6.
考点:
二维数组
内存图
void exam015()
{
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));
}
答案是:10,5
注意:1.*(aa+1)等价于aa[1]
&aa是整个二维数组的地址,所以&aa+1后就指向了整个二维数组的末尾
2.定义ptr1和ptr2之前已经进行了强制类型转换,
将&aa+1和*(&aa+1)均转换为int*类型了,
所以后来进行指针的加减运算时步长为4byte
7.
考点:
内存图
二级指针
指针数组
void exam016()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
}
答案是:at,从图中就可以看出来,注意:printf("%s")打印时,
传入字符串首元素的地址即可,下面这一题就会用到这个知识点
8.
考点
内存图
多级指针
void exam017()
{
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);
}
注意:所有的最外面的加减的常数在最后一步进行!!!!!!!!
可以简化代码
printf("%s\n", *cpp[-2] + 3);//**(cpp-2)+3
printf("%s\n", cpp[-1][-1] + 1);//*(*(cpp-1)-1)+1
c 以上就是征服C语言指针系列(1),希望能对大家有所帮助