目录
前言:
一、一维数组
二、字符数组
三、二维数组
四、指针笔试题
笔试题(1)
笔试题(2)
笔试题(3)
笔试题(4)
笔试题(5)
笔试题(6)
笔试题(7)
笔试题(8)
本篇会列出全部有关数组和指针含义的内容、实例及分析,并引入八道笔试真题进行实操练习,干货满满。
贯穿本篇的核心知识就是数组名的意义:
1、sizeof(数组名),这里的数组名表示整个数组,sizeof计算的是整个数组的大小,注意括号中必须为数组名才满足这一意义,比如sizeof(arr+1)就不是整个数组的大小了,&数组名同理。
2、&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3、除此之外所有的数组名都表示首元素的地址。
int main()
{
//一维数组
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a)); //16
//a为数组名,sizeof(数组名)计算的是整个数组的大小,即4*4=16
printf("%d\n", sizeof(a + 0)); //4/8
//a为数组名,代表首元素地址,a+0还是首元素地址,只要是地址,大小即为4/8
printf("%d\n", sizeof(*a)); //4
//*a为数组中的元素1,为int类型,sizeof(int)为4
printf("%d\n", sizeof(a + 1)); //4/8
//a+1指向数组中的元素2,为int*,只要是指针类型,大小即为4/8
printf("%d\n", sizeof(a[1])); //4
printf("%d\n", sizeof(&a)); //4/8
//&数组名取出的是整个数组的地址,但是仍为地址,所以大小为4/8
printf("%d\n", sizeof(*&a)); //16
//*&a相当于a,sizeof(数组名)计算的是整个数组的大小,为16
printf("%d\n", sizeof(&a + 1)); //4/8
//&a+1指向4后面一个元素,但是&a+1是指针,所以大小为4/8
printf("%d\n", sizeof(&a[0])); //4/8
//首元素地址,大小为4/8
printf("%d\n", sizeof(&a[0] + 1));//4/8
//&a[0]+1指向元素2,但他也是指针,所以大小为4/8
return 0;
}
经过对一维数组的分析, 我们可以知道:
1、在判断时,只要是指针,那么他的大小即为4/8,32位为4,64位为8,可以加快我们做题判断的速度。
2、指针的步长取决于指针的类型,指针的类型为int,那么指针加+1就是跳过一个int类型,指针类型为数组,那么指针+1就是跳过整个数组。
字符数组的分析除了sizeof我们再引入strlen库函数进行更深入的分析。
strlen函数的参数为const char*,功能为统计'\0'前的元素个数。
int main()
{
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr)); //6
printf("%d\n", sizeof(arr + 0)); //4/8
printf("%d\n", sizeof(*arr)); //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
printf("%d\n", strlen(arr)); //随机
//该字符数组不是字符串,'\0'的位置不确定,所以为随机值
printf("%d\n", strlen(arr + 0)); //随机
printf("%d\n", strlen(*arr)); //err
//strlen需要的参数是指针,而*arr是字符'a'。ASCII值为97
//字符'a'的ASCII值为97,strlen会从97这个地址开始计算字符串长度
//会造成非法访问
printf("%d\n", strlen(arr[1])); //err
printf("%d\n", strlen(&arr)); //随机
printf("%d\n", strlen(&arr + 1)); //随机
printf("%d\n", strlen(&arr[0] + 1));//随机
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); //7
printf("%d\n", sizeof(arr + 0)); //4/8
printf("%d\n", sizeof(*arr)); //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
printf("%d\n", strlen(arr)); //6
printf("%d\n", strlen(arr + 0)); //6
printf("%d\n", strlen(*arr)); //err
printf("%d\n", strlen(arr[1])); //err
printf("%d\n", strlen(&arr)); //6
printf("%d\n", strlen(&arr + 1)); //随机
//该指针步长为该字符串,+1后指针指向'\0'的后一个位置,所以值为随机
printf("%d\n", strlen(&arr[0] + 1));//5
//该指针步长为一个字符,+1后指针指向字符'b',所以计算值为5
char* p = "abcdef";
//经过前面的学习我们知道p存放的是该字符串首个字符的地址
printf("%d\n", sizeof(p)); //4/8
printf("%d\n", sizeof(p + 1)); //4/8
printf("%d\n", sizeof(*p)); //1
printf("%d\n", sizeof(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)); //4/8
printf("%d\n", strlen(p)); //6
printf("%d\n", strlen(p + 1)); //5
printf("%d\n", strlen(*p)); //err
printf("%d\n", strlen(p[0])); //err
printf("%d\n", strlen(&p)); //随机
printf("%d\n", strlen(&p + 1)); //随机
printf("%d\n", strlen(&p[0] + 1)); //5
return 0;
}
char* p="hello world";
字符指针p存放的是该字符串首个字符的地址。
我想单独讲解一下这段代码:
int main
{
char* p = "abcdef";
printf("%d\n", sizeof(&p)); //4/8
printf("%d\n", sizeof(&p + 1)); //4/8
//思考:这里指针指向了哪里??
return 0;
}
首先我们知道p指向的是字符'a',即p的类型为char*,在&p后,&p的类型就是char**,此时给&p+1,&p的步长应为char*,也就是指针&p+1相对于&p跳过了一个char* ,比如p的地址是0x0012ff40,如图:
也就是&p是指针的指针,所以他的步长应为一个指针的长度。
其中相似原理的我并未重复注释,大家有问题的可以评论或者私信问我
首先我们需要知道:
假定一个二维数组int a[3][4],对于二维数组a来说,a[0]就是该二维数组第一行的一维数组的数组名,同理a[1]就是该二维数组第二行的一维数组的数组名,又因为数组名就是数组首元素的地址,所以a[0]就是该二维数组第一行的一维数组的首元素地址,同理a[1]就是该二维数组第二行的一维数组的首元素地址。
在了解了上面的概念后,接下来的代码就迎刃而解了。
int main()
{
//二维数组
int a[3][4] = { 0 };
printf("%d\n", sizeof(a)); //48
printf("%d\n", sizeof(a[0][0])); //4
printf("%d\n", sizeof(a[0])); //16
printf("%d\n", sizeof(a[0] + 1)); //4/8
//即a[0]其实就是a[0][0]的地址,+1后变为a[0][1]的地址,只要是地址,大小就是4/8
printf("%d\n", sizeof(*(a[0] + 1))); //4
printf("%d\n", sizeof(a + 1)); //4/8
printf("%d\n", sizeof(*(a + 1))); //16
printf("%d\n", sizeof(&a[0] + 1)); //4/8
printf("%d\n", sizeof(*(&a[0] + 1)));//16
//a[0]是第一行数组的数组名,所以&a[0]取出的是该一维数组的地址
//+1后指向了第二行的一维数组,解引用后是第二行一维数组,大小为4*4=16
printf("%d\n", sizeof(*a)); //16
printf("%d\n", sizeof(a[3])); //16
//越界会影响sizeof的计算么??
return 0;
}
sizeof是如何计算的?
我们发现最后一个题目中数组越界了,但并没有影响程序的运行,也就是说,sizeof的计算只考虑类型,不会真的访问计算,a[3]的类型是int [4],所以大小为4*4=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;
}
//程序的结果是什么?
答案:2,5
分析:
a代表首元素地址,指针类型为int*,步长为int,+1后指向元素'2',解引用后为元素'2',所以打印值为2。
&a拿到的是整个数组的地址,指针类型为int(*)[5],步长为int [5],+1后ptr指向元素'5'后面的位置,并将指针强制转换为int*类型,ptr指针类型为int*,步长为int,-1后ptr指向元素'5',解引用得到元素'5',所以打印值为5。
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
答案:0x100014 0x100001 0x100004
分析:
p的类型为结构体指针struct Test*,步长为struct Test,0x1是十六进制数字,转为十进制为1,+1后相当于+20(整型),转化为十六进制加到0x100000后即为0x100014。
p被转化为数字,+1就是0x100000+1,为0x100001。
p被转化为unsigned int*类型,步长为4个字节,+1就是跳过4个字节,即0x100000+4=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;
}
//程序的结果是什么?
答案:4,2000000
对于ptr1:
&a+1指针指向元素'4'后面的位置,将该指针强制转换为int*类型,ptr1[-1]相当于*(ptr1-1),又因为ptr1的步长为int,所以解引用得到元素为'4',用%x(十六进制打印)得到值为4。
对于ptr2:
涉及到了内存的存储,为了更好地讲解,请看下图:
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
//程序输出的结果是什么?
答案:1
这里有一个陷阱,我们知道二维数组的定义方法应为int a[3][2]={{0,1},{2,3},{4,5}};
但是本题为int a[3][2] = { (0, 1), (2, 3), (4, 5) };
他实际上为int a[3][2] = { 1, 3, 5 };
即:
所以值为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;
}
//程序的结果是什么?
答案:FFFFFFFC,-4
根据前面学过的知识,我们知道p是数组指针,指向的元素类型为int [4]。
我们将p[4][2]也理解为二维数组,那么&p[4][2]就是指向p数组第5行第三列的元素,即为图中所示。
指针相减得到两个指针之间的元素个数,又因为地址是由低到高变化的,所以小地址减去大地址得到负数,为-4。
%p是打印地址的操作符,又因为-4在内存中存储的是补码:
1111 1111 1111 1111 1111 1111 1111 1100 //-4的补码
F F F F F F F C
转化为16进制数字0xFFFFFFFC
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;
}
//程序的结果是什么?
答案:10,5
ptr1:
&aa取出的是整个数组的地址,+1指向元素'10'后面的元素,并强制转化为int*类型,打印时-1指向元素'10',解引用得到元素'10',所以值为10。
ptr2:
aa+1指向二维数组的第二行的一维数组,解引用得到该一维数组首元素的地址,也可以将*(aa+1)理解为aa[1],即为6的地址,打印时-1指向元素'5',解引用得到元素'5',所以值为5。
解引用一个数组(非数组名)的地址,得到的是该数组首元素的地址。
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
//程序的结果是什么?
答案:at
a本质上是指针数组,该数组存放着三个字符串首个字符的地址。
pa指向的是数组a的首元素的地址,所以pa是一个二级指针,pa指向指向字符'w'的指针。
即:
所以得到的值为at。
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;
}
//程序的结果是什么?
答案:POINT ER ST EW
与第七题同理,难度略高。
如图,当我们知道指针之间的关系后,那么还需要攻克的就是符号优先级的问题。
1、**++cpp,++的优先级高于*,所以cpp先+1在连续两次解引用,得到元素'P'的地址,打印得到POINT。
2、*--*++cpp+3,cpp先++,再解引用,再--,再解引用,最后+3,得到元素'E'的地址,打印得到ER。
3、*cpp[-2]+3,可以理解为**(cpp-2)+3,得到元素'S'的地址,打印得到ST。
4、cpp[-1][-1]+1,可以理解为*(*(cpp-1)-1) +1,得到元素'E'的地址,打印得到EW。
小结:进阶指针的学习到这里就全部结束了,想要学习关于进阶指针的全部知识,欢迎到我的主页查询进阶指针(一)与进阶指针(二),接下来博主会持续更新C语言的更多知识, 关注博主不迷路