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),希望能对大家有所帮助