=========================================================================
相关代码gitee自取:C语言学习日记: 加油努力 (gitee.com)
=========================================================================
接上期:
学C的第二十六天【指针的进阶(二)】_高高的胖子的博客-CSDN博客
=========================================================================
复习巩固:
数组名:
数组名是数组首元素的地址,
但是有两个例外:
1 . sizeof(数组名) :这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
2 . &数组名 :这里的数组名表示整个数组,取出的是整个数组的地址
9. 指针和数组笔试题解析
补充(回顾):
sizeof()
是一个运算符,而strlen()
是一个函数。sizeof()
计算的是变量或类型所占用的内存字节数,而strlen()
计算的是字符串中字符的个数。sizeof()
可以用于任何类型的数据,而strlen()
只能用于以空字符 '\0' 结尾的字符串。sizeof()
计算字符串的长度,包含末尾的 '\0',strlen()
计算字符串的长度,不包含字符串末尾的 '\0'。
(1). 一维数组相关题:
对应代码:
#include <stdio.h> int main() { int a[] = { 1,2,3,4 }; printf("%d\n", sizeof(a)); //特殊情况,这里的a为整个数组,为4*4=16字节 printf("%d\n", sizeof(a + 0)); //数组名a是首元素地址,a+0还是首元素地址,地址的大小为4/8字节 printf("%d\n", sizeof(*a)); //数组名a为首元素地址,*a就是首元素,大小是4个字节 printf("%d\n", sizeof(a + 1)); //数组名是首元素地址。a+1是第二个元素地址,为4/8字节 printf("%d\n", sizeof(a[1])); //a[1]是数组的第二个元素,为4个字节 printf("%d\n", sizeof(&a)); //&a是整个数组的地址,数组地址也只是个地址,为4/8字节 printf("%d\n", sizeof(*&a)); //解引用整个数组的地址,相当于sizeof(a),为16个字节 printf("%d\n", sizeof(&a + 1)); //相当于跳过了整个数组,虽然跳过了整个数组,&a+1依然是个地址,为4/8个字节 printf("%d\n", sizeof(&a[0])); //取数组首元素的地址,为4/8个字节 printf("%d\n", sizeof(&a[0] + 1)); //&a[0]是首元素地址,&a[0]+1就是第二个元素的地址,是地址就为4/8个字节 return 0; }
(2). 字符数组和指针相关题:
(2.1). 明确赋值的字符数组(无结束符\0):
对应代码:
char arr[] = { 'a','b','c','d','e','f' }; //六个字符的字符数组,没有结束符\0 printf("%d\n", sizeof(arr)); //数组名单独放在sizeof中,这里的arr为整个数组,计算的是整个数组大小,为6个字节 -- char[6] printf("%d\n", sizeof(arr + 0)); //arr表示数组首元素地址,arr+0还是数组首元素地址,是地址就为4/8个字节 -- char* printf("%d\n", sizeof(*arr)); //arr表示数组首元素地址,*arr就是首元素,为1个字节 -- char printf("%d\n", sizeof(arr[1])); //arr[1]就是第二个元素,为1个字节 -- char printf("%d\n", sizeof(&arr)); //&arr是整个数组的地址,数组地址也是地址,是地址就为4/8个字节 printf("%d\n", sizeof(&arr + 1)); //&arr+1是跳过整个数组后的地址,是地址就为4/8个字节 printf("%d\n", sizeof(&arr[0] + 1)); //为第二个元素的地址,为4/8个字节 printf("%d\n", strlen(arr)); //因为字符数组arr中没有\0,所以在求字符串长度时, //会一直往后数到未知的\0为止,是不确定的,所以会产生随机值 printf("%d\n", strlen(arr + 0)); //arr+0是首元素地址,所以和上面的一样会产生随机值 printf("%d\n", strlen(*arr)); //arr是数组首元素地址,*arr是数组的元素,是 'a' ,ACSII码为97, //所以strlen会从 '97' 的地址开始统计字符串长度,为非法访问内存 printf("%d\n", strlen(arr[1])); //arr[1]为数组首元素,即 'a' ,所以和上面一样为非法访问内存 printf("%d\n", strlen(&arr)); //&arr是数组地址,数组地址和数组首元素的地址,两者的值是一样的, //数组地址类型char*[6],会强制转化为char*类型,所以依然从数组的首元素开始向后统计,结果为随机值 printf("%d\n", strlen(&arr + 1)); //&arr+1为跳过这个数组,所以从这个数组后的地址开始统计,结果为随机值 printf("%d\n", strlen(&arr[0] + 1)); //&arr[0]+1是第二个元素的地址,往后统计直到遇到\0,结果为随机值
(2.2). 未明确赋值的字符数组(有结束符\0):
对应代码:
char arr[] = "abcdef"; //未明确定义的字符数组,有结束符\0 // {a b c d e f \0} printf("%d\n", sizeof(arr)); //sizeof(数组名):计算整个数组的大小,为7个字节,包含\0 -- char [7] printf("%d\n", sizeof(arr + 0)); //arr+0是首元素的地址,是地址就是4/8个字节 -- char* printf("%d\n", sizeof(*arr)); //*arr为数组首元素,为1个字节 -- char printf("%d\n", sizeof(arr[1])); //arr[1]为第二个元素,为1个字节 -- char printf("%d\n", sizeof(&arr)); //&arr是数组的地址,是地址就是4/8个字节 -- char* [7] printf("%d\n", sizeof(&arr + 1)); //&arr为跳过arr这个数组后的地址,是地址就是4/8个字节 -- 随机地址 printf("%d\n", sizeof(&arr[0] + 1)); //&arr[0]+1是第二个元素的地址,是地址就是4/8个字节 -- char* printf("%d\n", strlen(arr)); //统计到\0为止,即 a b c d e f ,所以结果为6 printf("%d\n", strlen(arr + 0)); //和上题一样,都是从数组首元素开始,\0结束,所以结果为6 printf("%d\n", strlen(*arr)); //strlen应该接收一个地址,从这个地址向后计算长度, //这里接收的是数组首元素字符'a'(char),不是地址,所以会报错 printf("%d\n", strlen(arr[1])); //这里和上面一样接收的也是数组首元素字符'a',不是地址,会报错 printf("%d\n", strlen(&arr)); //接收数组指针char*[7],但是会被强制转化为const char*类型, //值还是首元素的地址,所以统计的长度还是6 printf("%d\n", strlen(&arr + 1)); //&arr+1是跳过这个数组后的一个地址,从这个地址往后不知道什么时候后有\0,所以结果为随机值 printf("%d\n", strlen(&arr[0] + 1)); //&arr[0]+1为数组的第二个元素地址,从后计算长度直到\0,所以结果为5
(2.3). 未明确赋值的字符指针(有结束符\0):
对应代码:
char* p = "abcdef"; //这样写是把字符串的首字符地址放入指针中,即‘a’的地址 printf("%d\n", sizeof(p)); //p为一级指针,存放的是地址,是地址就是4/8个字节 printf("%d\n", sizeof(p + 1)); //p+1是字符'b'的地址,是地址就是4/8个字节 printf("%d\n", sizeof(*p)); //相当于‘b’的地址,是地址就是4/8个字节 printf("%d\n", sizeof(p[0])); //p[0]相当于*(p+0),即*p,为字符‘a’,为1个字节 printf("%d\n", sizeof(&p)); //取出一级指针p的地址,相当于p的二级指针,存放的是指针p的地址,是地址就是4/8个字节 printf("%d\n", sizeof(&p + 1)); //相当于指针p地址的后面一个地址,是地址就是4/8个字节 printf("%d\n", sizeof(&p[0] + 1)); //相当于a的地址的后一个地址,即b的地址,是地址就是4/8个字节 printf("%d\n", strlen(p)); //指针p存放的是‘a’的地址,所以从a的地址往后数,所以长度为6 printf("%d\n", strlen(p + 1)); //p+1是‘b’的地址,所以长度为5 printf("%d\n", strlen(*p)); //*p是字符‘a’,strlen的参数应该是const char*指针,所以会报错 printf("%d\n", strlen(p[0])); //字符串可以理解为一个字符数组,所以p[0]相当于字符‘a’,会报错 printf("%d\n", strlen(&p)); //&是一级指针p的地址,从该地址往后数直到\0,所以结果为随机值 printf("%d\n", strlen(&p + 1)); //从一级指针的地址的后一个地址开始数直到\0,结果为随机值 printf("%d\n", strlen(&p[0] + 1)); //相当于a的地址的后一个地址,即b的地址,直到\0,所以结果为5
(3). 二维数组相关题:
对应代码:
//二维数组 int a[3][4] = { 0 }; //创建三行四列的二维数组 printf("%d\n", sizeof(a)); //三行四列每个4个字节,所以一个是3*4*4=48字节 printf("%d\n", sizeof(a[0][0])); //求该二维数组0行0列的元素大小,为4个字节 printf("%d\n", sizeof(a[0])); //a[0]是二维数组的第一个元素地址,即第一行地址(第一行数组的数组名) //相当于把数组名放入sizeof中,所以计算的是整个数组的大小,为16个字节 printf("%d\n", sizeof(a[0] + 1)); //这里a[0]不是单独放在sizeof中的,所以是第一行数组的首元素地址,即a[0][0]地址 //所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节 printf("%d\n", sizeof(*(a[0] + 1))); //相当于解引用第一行数组的第二个元素地址,元素是int类型,为4个字节 printf("%d\n", sizeof(a + 1)); //a是二维数组首元素地址,即第一行的地址 -- int(*)[4] //a+1就是第二行的地址,是地址就是4/8个字节 printf("%d\n", sizeof(*(a + 1))); //a+1是第二行地址--int(*)[4],*(a+1)解引用后是4个int类型的元素 //为16个字节 printf("%d\n", sizeof(&a[0] + 1)); //&a[0]是二维数组第一行数组地址--int(*)[4] //&a[0] + 1是第二行的地址,是地址就是4/8个字节 printf("%d\n", sizeof(*(&a[0] + 1))); //&a[0] + 1是第二行的地址,解引用后是4*4=16个字节 printf("%d\n", sizeof(*a)); //*a是解引用二维数组首元素地址,即第一行地址,为4*4=16个字节 printf("%d\n", sizeof(a[3])); //a[3]看起来是越界了,但sizeof()运行时是不会访问内存的(类型属性),只看类型 //这里a[3]的类型是int[4],为4*4=16个字节 //表达式都有两个属性:1.值属性 2.类型属性 //代码运行时:编译+链接 --> 可执行程序 --> 运行 --> 结果 //sizeof是在 编译 时就已经有结果了(类型属性),根据类型为16个字节 //a[3]是在 运行 是才有结果(值属性),根据运行结果应该为数组越界报错
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10. 指针笔试题
(1). 笔试题一:
对应代码:
#include <stdio.h> int main() { int a[5] = { 1, 2, 3, 4, 5 }; int* ptr = (int*)(&a + 1); //&a+1: 跳过该数组后的地址,再强制转化成int*类型,方便后面移动时是4个字节 printf("%d,%d", *(a + 1), *(ptr - 1)); //*(a+1): 是数组的第二个元素 -- 2 //*(ptr-1): ptr刚好在整个数组后,ptr-1后指针在 5 的地址位置,所以解引用后是5 return 0; }
(2). 笔试题二:
对应代码:
#include <stdio.h> //结构体的大小是20个字节(x86) struct Test { int Num; char* pcName; short sDate; char cha[2]; short sBa[4]; }* p = (struct Test*)0x100000;//结构体指针变量 //假设p 的值为0x100000。 如下表表达式的值分别为多少? //已知,结构体Test类型的变量大小是20个字节 int main() { printf("%p\n", p + 0x1); //结构体指针 +1 跳过一个结构体(20个字节) //所以 结果 == 0x100000 + 0x14(20的十六进制数) // == 0x100014 printf("%p\n", (unsigned long)p + 0x1); //把原来的结构体指针的类型强制转化为(unsigned long)整数, //相当于普通的数值计算,+1 就是 +1 // 结果 == 0x100000 + 0x1 == 0x100001 printf("%p\n", (unsigned int*)p + 0x1); //转化为无符号整型指针,所以每次跳过4个字节(x86) // 结果 == 0x100000 + 0x4 == 0x100004 return 0; }
(3). 笔试题三:
对应代码:
#include <stdio.h> 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); //ptr1[-1] 等价于 *(ptr1-1) //%x : 以十六进制打印 return 0; }
(4). 笔试题四:
对应代码:
#include <stdio.h> #include <stdio.h> int main() { int a[3][2] = { (0, 1), (2, 3), (4, 5) }; //注:这里用的是(),而不是{},()表达式的结果是最后一个 //所以a的实际情况是:{1,3,5,0,0,0} int* p; p = a[0]; //a[0]是二维数组的第一行第一列的地址,相当于a[0][0] printf("%d", p[0]); //p[0] 相当于 *(p+0),即*p -- *a[0][0],解引用后为 1 return 0; }
(5). 笔试题五:
对应代码:
#include <stdio.h> int main() { 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]); //%d: //这里是小地址-大地址,两个相差4,又因为以%d打印 //所以是 -4 (把补码转换为原码后打印) //%p: //以 %p 打印的话,就不考虑无符号和有符号了,直接以补码打印: //-4是负数,内存中存的是补码: //原码:10000000000000000000000000000100 //反码:11111111111111111111111111111011 //补码:1111 1111 1111 1111 1111 1111 1111 1100 -- 内存中存储的-4 // F F F F F F F C return 0; }
(6). 笔试题六:
对应代码:
#include <stdio.h> int main() { //二维数组: int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //第一行: 1 2 3 4 5 第二行: 6 7 8 9 10 int* ptr1 = (int*)(&aa + 1); //&aa+1:跳过整个二维数组后的当前位置的地址(10后面的位置) int* ptr2 = (int*)(*(aa + 1)); //aa+1:跳过二维数组的首元素(第一行),指向第二行首元素地址 //*(aa+1) 相当于 *aa[1][0],解引用后拿到第二行的首元素地址(6的位置) printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1)); //ptr1-1:“10”后面的地址-1,指向了10的地址 //ptr2-1:“6”的前一个地址,指向5的地址 return 0; }
(7). 笔试题七:
对应代码:
#include <stdio.h> #include <stdio.h> int main() { char* a[] = { "work","at","alibaba" }; //相当于用三个字符指针分别存放三个字符串: //char* p1 = "work" -- 存放w的地址 //char* p2 = "at" -- 存放a的地址 //char* p3 = "alibaba" -- 存放a的地址 char** pa = a; //存放a的首元素"p1"的地址 pa++;//指向"p2"的地址 printf("%s\n", *pa); //*pa:解引用指针p2的地址,p2指向"a"的地址,以%s打印为 at return 0; }
(8). 笔试题八:
对应代码:
#include <stdio.h> int main() { char* c[] = { "ENTER","NEW","POINT","FIRST" }; //相当于用四个字符指针分别存放四个字符串: //(下标为0)char* p1 = "ENTER" -- 存放“E”的地址 //(下标为1)char* p2 = "NEW" -- 存放“N”的地址 //(下标为2)char* p3 = "POINT" -- 存放“P”的地址 //(下标为3)char* p4 = "FIRST" -- 存放“F”的地址 char** cp[] = { c + 3,c + 2,c + 1,c }; //相当于用四个二级字符指针分别存放四个一级指针: //(下标为0)char** pp1 = c+3 = &p4 -- 存放一级指针p4的地址 //(下标为1)char** pp2 = c+2 = &p3 -- 存放一级指针p3的地址 //(下标为2)char** pp3 = c+1 = &p2 -- 存放一级指针p2的地址 //(下标为3)char** pp4 = c = &p1 -- 存放一级指针p1的地址 char*** cpp = cp; //三级指针存放二级指针cp首元素的地址,即pp1地址 printf("%s\n", **++cpp); //cpp指向pp1地址,++后指向pp2地址, //第一次*得到pp2内容:p3的指针(地址) //第二次*得到p3内容:“P”的地址 //所以打印 "POINT" //因为++,此时cpp已经指向了pp2,不是pp1 printf("%s\n", *-- * ++cpp + 3); //先进行++,cpp指向pp3 //再进行第一次*:得到pp3内容 -- p2的指针(地址) //再--:p2-1 ,指向p1 //再进行第二次*:得到p1内容 -- “E”的地址("ENTER") //最后进行+3:“E”的地址+3,指向“ENTER”第二个“E” //所以打印"ER" //因为++,此时cpp已经指向了pp3,不是pp2 printf("%s\n", *cpp[-2] + 3); //cpp[-2] 相当于 *(cpp-2) //所以题目可以写成:**(cpp-2)+3 //先进行 cpp-2 : 从指向pp3转为指向pp1 //再进行第一次*:得到pp1内容 -- p4的指针(地址) //再进行第二次*:得到p4的内容 -- “F”的地址(“FIRST”) //最后+3:指向“FIRST”的“S”的地址 //所以打印“ST” //此时cpp还是指向pp3 printf("%s\n", cpp[-1][-1] + 1); //cpp[-1] 相当于 *(cpp-1) //所以题目可以转化为:*(cpp-1)[-1]+1 //再转化:*(*(cpp-1)-1)+1 //先进行cpp-1:cpp从pp3转为指向pp2 //再进行*:得到pp2内容 -- p3的指针(地址) //再进行-1:p3-1 -- 得到p2的地址 //再进行*:得到p2的内容 -- “N”的地址(“NEW”) //最后+1:指向“NEW”的"E" //所以打印“EW” return 0; }