目录
引言:知识补充
①数组名
②sizeof函数补充
③strlen函数补充
1.数组题目
1.1一维数组
题目1
1.2字符数组
题目1
题目2
题目3
编辑 题目4
题目5
1.3 二维数组
2.指针笔试题目
题目①
题目②
题目③
题目④
编辑 题目⑤
题目⑥
编辑 题目⑦
题目⑧(重难)
3.结语
数组名是首元素的地址
两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
sizeof实际上是获取了数据在内存中所占用的存储空间,以字节为单位来计数。他只关注类型。一般我们的编程过程是:编译+链接 ——>可执行程序——>结果
但是sizeof在编译期间就已经处理了
而要得到计算结果就要运行阶段才能取出来就是说这里不会去访问内存,sizeof只会在意数据的类型
我们来看一个例子
int main()
{
int a = 7;
short s = 4;
printf("%d \n", sizeof(s = (a + 2)));//这里不会计算就看s 的类型打印2
printf("%d", s);//这里还是打印4,说明没有计算
return 0;
}
求字符串的长度,统计的是字符串中\0之前的字符的个数,如果没有就会一直往后找,直到找到
参数需要的是一个地址,从提供的地址向后访问
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));
问最后的输出结果 ?
解析:
printf("%d\n", sizeof(a));
数组名单独放在sizeof内部,表示整个数组,数组有4个整型数据,每个数据是4个字节,打印16字节
printf("%d\n", sizeof(a + 0));这里的a不满足第一种和第二种特例,那就是首元素的地址,+0还是首元素的地址,那就是4/8
printf("%d\n", sizeof(*a));
数组名是首元素的地址,解引用就是首元素,首元素是整形,4字节
printf("%d\n", sizeof(a + 1));
数组名是首元素的地址,加1,得到第二个元素的地址1,,地址的大小就是4/8
printf("%d\n", sizeof(a[1]));
第一个元素的大小,整型4个字节
printf("%d\n", sizeof(&a));
&a得到的是整个数组的地址,地址就是4/8
非常古老的编程器VC6.0,这里是16
printf("%d\n", sizeof(*&a));
1.&A得到的地址进引用,得到还是sizeof(a) 那就是16
2.&得到数组的地址,指针类型是int* [4] ,解引用得到的就是一个数组
printf("%d\n", sizeof(&a + 1));
打印4/8 跳过了整个数组,即使跳过了整个数组,还是地址
printf("%d\n",sizeof(&a[0]));
打印4/8 ,因为&a[0]是取出数组第一个元素的地址
printf("%d\n", sizeof(&a[0] + 1));
4/8 第二个元素的地址
验证:
//字符数组
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));
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", sizeof(arr));
特例一 数组名单独放在sizeof内部,是整个数组的大小 那就是6
printf("%d\n", sizeof(arr + 0));
数组名不是单独放在sizeof内部,表示是首元素的地址,+0就还是首元素的地址,4/8
printf("%d\n", sizeof(*arr));
这里的arr没有单独放在sizeof内部,没有取地址,说明就是首元素的地址,解引用得到首元素是字符型那就是1
printf("%d\n", sizeof(arr[1]));
数组第二个元素的大小,为1
printf("%d\n", sizeof(&arr));
数组整个地址的大小那就是4/8
printf("%d\n", sizeof(&arr + 1));
整个数组的地址+1还是一个地址,4/8
printf("%d\n", sizeof(&arr[0] + 1));
首元素的地址+1是第二个元素的地址,那还是地址4/8
结果验证:
//字符数组
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之前的字符的个数,如果没有就会一直往后找,直到找到
参数需要的是一个地址,从提供的地址向后访问printf("%d\n", strlen(arr));
要找到/0结束,随机值,数组里面没有\0,会一直往后寻找
printf("%d\n", strlen(arr + 0));
arr+0 首元素的地址,+0还是首元素的地址,也是随机值
printf("%d\n", strlen(*arr));
首元素地址解引用就是首元素,首元素不是地址,是‘a',acsii码值是97,函数就将97作为起始地址去访问,就会非法访问strlen会从97这个地址开始统计字符串长度,就会造成非法访问,引用97作为地址的空间不属于这个操作printf("%d\n", strlen(arr[1]));
首元素地址解引用就是首元素,首元素不是地址,是‘a',acsii码值是97,函数就将97作为起始地址去访问,就会非法访问strlen会从97这个地址开始统计字符串长度,就会造成非法访问,引用97作为地址的空间不属于这个操作
printf("%d\n", strlen(&arr));
&arr 的类型是:char * [] 而strlen的参数类型是 const char*,但是这里不会不匹配,因为strlen会把参数强制类型转换为参数类型拿到地址,直观来说&arr与arr得到的值都是首元素的地址对于strlen来说他就拿到这个地址然后开始向后访问直到\0,还是随机值printf("%d\n", strlen(&arr +1));
地址跳过数组,也是随机制
printf("%d\n", strlen(&arr[0] + 1));
结果也是随机值,比第一个开始小1个
验证:
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));
printf("%d\n", strlen(arr));
分析:
char arr[] = "abcdef";
{a b c d e f \0}
printf("%d\n", sizeof(arr));
打印7,数组名单独放在sizeof里面,得到整个数组的大小
printf("%d\n", sizeof(arr + 0));
首元素地址,4/8 数组名并是不单独放在sizeof内部,所以表示首元素的地址
printf("%d\n", sizeof(*arr));
数组首元素,数组名并是不单独放在sizeof内部,所以表示首元素的地址,解引用得到第一个元素,其大小为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
验证:
char arr[] = "abcdef";
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));
解析:
printf("%d\n", strlen(arr));
打印6,因为数组最后一个元素是/0
printf("%d\n", strlen(arr + 0));
打印6,数组名没有单独放在sizeof里面表示首元素地址,首元素地址+0还是首元素地址
printf("%d\n", strlen(*arr));
报错,strlen参数需要的是一个地址
printf("%d\n", strlen(arr[1]));
报错
printf("%d\n", strlen(&arr));
6,&数组名表示整个数组的地址,但是整个数组的地址传给strlen,关键字从这个地址开始往后找\0.
printf("%d\n", strlen(&arr + 1));
随机值,&数组名表示整个数组的地址,+1跨过了数组地址是数组后面的地址,随机找\0
printf("%d\n", strlen(&arr[0] + 1));
5,&arr[0] + 得到数组第二个元素的地址
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));
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));
、分析
把字符串首字符1的地址也就是a的地址给了p指针
printf("%d\n", sizeof(p));
p是指针变量地址,4/8
printf("%d\n", sizeof(p + 1));
p+1是字符b的地址,4/8
printf("%d\n", sizeof(*p));
得到首字符,是1
printf("%d\n", sizeof(p[0]));
p[0] = *(p+0) 也是1字节
printf("%d\n", sizeof(&p));
得到地址,4/8
printf("%d\n", sizeof(&p + 1));
得到地址,4/8
printf("%d\n", sizeof(&p[0] + 1));
得到b的地址
printf("%d\n", strlen(p));
p里面存a的地址,往后找\0,就是6
printf("%d\n", strlen(p + 1));
+1就是b的地址,就是5
printf("%d\n", strlen(*p));
错误,*p就是a
printf("%d\n", strlen(p[0]));
一样
printf("%d\n", strlen(&p));
随机值
printf("%d\n", strlen(&p + 1));
随机值,
printf("%d\n", strlen(&p[0] + 1));
&p[0] + 1,得到的是b的地址,从b的地址数,5
验证:
//二维数组
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]));
解析:
二维数组的特殊理解:
printf("%d\n", sizeof(a));
数组名单独放在sizeof内部,表示整个数组,12*4 = 48
printf("%d\n", sizeof(a[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]表示数组首元素的地址也就是a[0][0],所以a[0]+1就是第一行第二个元素的地址,是地址就是4/8字节
printf("%d\n", sizeof(*(a[0] + 1)));
计算的是第一行第二个元素的大小
printf("%d\n", sizeof(a + 1));
a是数组首元素的地址,二维数组数组名是首元素的地址,二维数组首元素是第一,+1就是第二行的地址,4/8
printf("%d\n", sizeof(*(a + 1)));
1.*(a+1)-->a[1]->就是第二行的数组名单独放在sizeof内部,计算的就是第二行的大小,16。2.a+1是第二行的地址,数组指针解引用是数组访问的是第二行的数组
printf("%d\n", sizeof(&a[0] + 1));
数组名取地址+1就是第二行的地址,4/8
printf("%d\n", sizeof(*(&a[0] + 1)));
解引用得到第二行数组,16字节
printf("%d\n", sizeof(*a));
a是二维数组首元素地址就是第一行地址,解引用就是第一行就是16,*a-->*(a+0)-->a[0]printf("%d\n", sizeof(a[3]));
越界了,但是这里不会去访问类型,sizeof只会在意他的类型:a[],那就是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;
}
//程序的结果是什么?
分析图解:
a+1,a是数组名表示首元素的地址,+1得到数组第二个元素的地址,解引用得到数组第二个元素,打印就是2.
ptr保存的是数组后一位的地址,-1,ptr是int*,所以减去4个字节,得到数组最后一个元素的地址,打印就是5
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
(struct Test*)p = 0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
分析:
对于第一个输出,我们来看P+0x1.p是一个结构体指针,+1,应该结构体指针题目告知是20字节,那就+20字节,那第一个打印就是 0x100014(16进制数字)
第二个输出,将p强制类型转换,从一个指针变量强制类型转换为一个无符号的长整型在+1,那就是p的值加1,就是0x100001;
第三个输出,将结构体类型的指针强制类型转换为整型指针。那么+1,相当于跨过四个字节,那就是打印0x100004
验证:
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;
}
我们来分析代码:
int* ptr1 = (int*)(&a + 1);
&a得到整个数组的地址,+1得到数组后面的地址,再强制类型转为为int 类型的指针赋值给ptr1;
int* ptr2 = (int*)((int)a + 1);
a是数组名,不是两种特例,代表数组首元素的地址,将他强制类型转换为int在加一得到的地址一个是数组首元素的地址加一个字节的地址,将这个地址强制转换为int类型交给指针ptr2;
*ptr2,访问的就是从ptr2保存的地址往后的4个字节的内容。
*ptr2 因为是小端存储,所以原来的数据应该是:02000000
验证:
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
分析:
a 是一个三行两列的二维数组,那数组中的每一个元素是什么呢?
这里数组中的元素是用小括号并不是大括号来限制所以存放在数组中的数据不是题目所示。小括号中的应该看作是逗号表达式。
逗号表达式:从左到右计算,整个表达式的值是最后表达式的结果。那就是说数组中的元素应该是:1,3,5,0,0,0
a[0],看做二维数组第一行这个一维数组的数组名,代表的是第一行这个一位数组的首元素的地址也就是a[0][0]的地址,打印的也是数组首元素的值,应该打印1
验证:
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]);
return 0;
}
分析:a数组是一个5行5列的二维数组
p是一个数组指针指向的数组的类型是是int (*)[4],数组有4个元素,每个元素是一个int类型的指针。
a是数组名,代表二维数组首元素的地址也就是第一行的地址。
两个地址相差-4,因为数组存储前面是低地址,后面是高地址,当以%d打印就是-4,
但是当以%p来进行打印的时候,不区分符号于是:
原码:1000000 00000000 00000000 000000100
反码:11111111 11111111 11111111 11111011
补码:11111111 11111111 11111111 11111100
打印就是:FF FF FF Fc
验证:
int main()
{
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));
return 0;
}
分析:
&aa得到整个数组的地址,+1得到数组后一位地址,-1得到数组最后一位元素的地址,解引用打印就是10;
aa是数组首元素,也就是第一行的地址,+1的带第二行的地址强制转为为int*类型-1得到第一行最后一个元素的地址,解引用打印就是5
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
分析:
验证:
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;
}
分析:
c 是一个指针数组,保存的是四个字符串的地址
根据这个图来顺藤摸瓜,注意的是前置加加等都会影响后面操作。
比如第一个输出:++cpp,cp内保存的就是cp2的地址,解引用得到p3,p3内保存POINT的地址,解引用得到POINT
*-- * ++cpp + 3.对于这个代码应该先算右边,在算+3,得到的就是ER
以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是nicn,正在c++方向前行的新手,感谢大家的关注与喜欢。