这篇文章重点解析了指针和数组相关笔试题,细读可以大大提高自己对指针和数组的理解.
一. 相关知识前瞻回顾
首先复习一下相关重要知识:
- 数组名的意义:
sizeof(数组名)
,这里的数组名表示的是整个数组,计算的是整个数组的大小.&数组名
,这里的数组名表示的是整个数组,表示的是整个数组的地址.- 除此之外所有的数组名都表示数组首元素的地址.
sizeof
sizeof
是C语言的一个运算符,用于计算数据类型或者变量所占据的内存大小(以字节Byte为单位)sizeof
运算符在编译时求值,不会进行真正的计算.它在编译阶段根据类型信息来确定所占用的内存大小,并将结果作为一个常量.
有如下例子:
int a = 5; short s = 4; printf("%d\n", sizeof(s = a + 2)); printf("%d\n", s);
sizeof
算出值为short
类型所占内存大小,即为2;
s
最终值为4.
strlen
函数
strlen
是一个C语言标准库函数,用于计算字符串的长度(不包括\0
)- 函数会从指定地址(传入参数)开始计算连续的字符数量,直到遇到空字符为止,并返回计算出的长度值.
- 在使用
strlen
前,必须确保传入的参数是const char *
即合法的地址值;必须确保以空字符结尾,否则会导致不可预测的结果.
- 本章所有试题都在
x86
环境下运行,这表示每个指针变量所占内存空间为4
个字节,而x64
环境下每个指针变量所占内存空间则为8
个字节.
二. 指针和数组笔试题解析
一维数组
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));
//16 int[4] sizeof(数组名), a表示整个数组, 得到整个数组所占内存大小
printf("%d\n", sizeof(a + 0));
//4 int* a表示数组首元素地址, 加0后仍然表示数组首元素地址
printf("%d\n", sizeof(*a));
//4 int a表示数组首元素地址, 对该地址使用*解引用得到首元素
printf("%d\n", sizeof(a + 1));
//4 int* a表示数组首元素地址, a+1表示第二个元素即a[1]的地址
printf("%d\n", sizeof(a[1]));
//4 int a[1]是数组元素, 数组元素类型为整型
printf("%d\n", sizeof(&a));
//4 int(*)[4] &数组名, a表示整个数组, 得到整个数组的地址
printf("%d\n", sizeof(*&a));
//16 int[4] *&a等于没有操作,还是sizeof(a)
printf("%d\n", sizeof(&a + 1));
//4 int(*)[4] &数组名, a表示整个数组, &a+1得到&a后移一个数组大小后的地址
printf("%d\n", sizeof(&a[0]));
//4 int* a表示数组首元素地址, &a[0]取得数组首元素地址
printf("%d\n", sizeof(&a[0] + 1));
//4 int* a表示数组首元素地址, &a[0] + 1得到 &a[1]
结果如下:
字符数组
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));
//6 char[6] sizeof(数组名), arr表示整个数组, 得到整个数组所占内存大小
printf("%d\n", sizeof(arr + 0));
//4 char* arr表示数组首元素地址, arr+0仍然表示数组首元素地址
printf("%d\n", sizeof(*arr));
//1 char arr表示数组首元素地址, 对该地址*解引用得到数组首元素'a'
printf("%d\n", sizeof(arr[1]));
//1 char arr[1]表示数组第二个元素'b'
printf("%d\n", sizeof(&arr));
//4 char(*)[6] &数组名, arr表示整个数组, 取到的是整个数组的地址
printf("%d\n", sizeof(&arr + 1));
//4 char(*)[6] &数组名, arr表示整个数组, &arr+1得到&a后移一个数组大小后得到的地址
printf("%d\n", sizeof(&arr[0] + 1));
//4 char* &arr[0] + 1得到 &arr[1]
结果如下:
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
//随机值 字符数组中没有存放空字符,不知道空字符在后面具体哪个位置,会一直往后找,结果为随机值
printf("%d\n", strlen(arr + 0));
//随机值 arr + 0表示首元素地址, 一样是随机值
printf("%d\n", strlen(*arr));
//err *arr 得到的是数组首元素'a', 'a'的ASCII码值是97, 即在内存中存放的是 0x00000061
//size_t strlen(const char*)接收的是一个地址, 当我们传入'a'的时候, strlen()函数会把'a'表示的内容即 0x00000061 传入函数
//但0x00000061是不允许访问的,所以会产生非法访问内存的错误
printf("%d\n", strlen(arr[1]));
//err 同上,只不过这次传的是第二个元素'b' 即 0x00000062
printf("%d\n", strlen(&arr));
//随机值 arr表示整个数组, &arr得到整个数组的地址, 数组的地址和数组首元素地址是一样的, strlen()还是会一直往后找
printf("%d\n", strlen(&arr + 1));
//随机值 &arr+1 得到&a后移一个数组大小的地址, 得到的是 随机值-6
printf("%d\n", strlen(&arr[0] + 1));
//随机值 &arr[0]+1 是 &arr[1], 得到的是数组第二个元素的地址, 随机值-1
结果如下:
char arr[] = "abcdef"; //arr数组存放了{'a', 'b', 'c', 'd', 'e', 'f', '\0'}
printf("%d\n", sizeof(arr));
//7 char[7] sizeof(数组名), arr表示整个数组, 得到的是整个数组占据内存大小
printf("%d\n", sizeof(arr + 0));
//4 char* arr表示数组首元素地址, arr+0 仍然表示数组首元素地址
printf("%d\n", sizeof(*arr));
//1 char arr表示数组首元素地址, *arr得到数组首元素 'a'
printf("%d\n", sizeof(arr[1]));
//1 char arr[1]表示数组第二个元素
printf("%d\n", sizeof(&arr));
//4 char(*)[7] &数组名, arr表示整个数组, 得到的是整个数组的地址
printf("%d\n", sizeof(&arr + 1));
//4 char(*)[7] &数组名, 数组名表示整个数组的地址, &arr+1 得到&a后移一个数组大小后的地址
printf("%d\n", sizeof(&arr[0] + 1));
//4 char* &arr[0] + 1 即 &arr[1]
结果如下:
char arr[] = "abcdef";
//arr数组存放了{'a', 'b', 'c', 'd', 'e', 'f', '\0'}
printf("%d\n", strlen(arr));
//6 strlen()取到整个字符串的长度(不包括\0)
printf("%d\n", strlen(arr + 0));
//6 arr表示数组首元素地址, arr+0 仍然表示数组首元素地址, 得到结果仍然是6
printf("%d\n", strlen(*arr));
//err arr表示数组首元素地址, *arr得到数组首元素'a', 传入strlen()的参数 即为 0x00000061, 内存非法访问
printf("%d\n", strlen(arr[1]));
//err arr[1]得到数组第二个元素'b', 传入strlen()的参数 即为 0x00000062, 内存非法访问
printf("%d\n", strlen(&arr));
//6 &数组名, arr表示整个数组, 得到数组的整个地址, 这个地址和数组首元素地址是一样的, 结果为6
printf("%d\n", strlen(&arr + 1));
//随机值 &数组名, arr表示整个数组, &arr+1 得到&a后移一个数组大小后的地址, 结果为随机值
printf("%d\n", strlen(&arr[0] + 1));
//5 &arr[0]+1 即 &arr[1], strlen()从第二个元素开始向后数有几个字符,直到遇到\0, 结果为 6 - 1 = 5
结果如下:
下面的指针变量p
并不是存放了字符串"abcdef"
,而是指针变量p
存放了字符串"abcdef"
的地址
char* p = "abcdef";
//指针变量p 存放了字符串"abcdef"的地址
printf("%d\n", sizeof(p));
//4 char* sizeof(p), 计算了指针变量p所占内存大小
printf("%d\n", sizeof(p + 1));
//4 char* p+1, 指向了'b'所在的内存空间即'b'的地址
printf("%d\n", sizeof(*p));
//1 char p指向了字符串首元素的地址, 即'a'的地址, 对地址*解引用得到元素 'a'
printf("%d\n", sizeof(p[0]));
//1 char p[0]即'a'
printf("%d\n", sizeof(&p));
//4 char** p是一级指针, &p即p的地址
printf("%d\n", sizeof(&p + 1));
//4 char** &p+1得到&p后移一个指针变量大小后的地址, 仍然是地址
printf("%d\n", sizeof(&p[0] + 1));
//4 char* &p[0] + 1 即 &p[1], 表示字符串第二个元素即'b'的地址
结果如下:
char* p = "abcdef";
//指针变量p 存放了字符串"abcdef"的地址
printf("%d\n", strlen(p));
//6 指针变量p 存放了字符串首元素的地址, 使用strlen(), 即得到整个字符串的长度
printf("%d\n", strlen(p + 1));
//5 指针变量p 存放了字符串首元素的地址, p+1则存放了第二个元素的地址, strlen()从第二的元素开始向后数有5个字符
printf("%d\n", strlen(*p));
//err 指针变量p 存放了字符串首元素的地址, 使用*解引用得到首元素'a', ASCII码值即 0x00000061, 作为参数传入strlen(), 内存非法访问
printf("%d\n", strlen(p[0]));
//err p[0]即字符串首元素'a', 一样是内存非法访问
printf("%d\n", strlen(&p));
//随机值 &p得到指针变量p的地址, 该位置后什么位置出现'\0'是未知的, 得到随机值
printf("%d\n", strlen(&p + 1));
//随机值 &p得到指针变量的地址, &p+1指向&p sizeof(&p)后(即4字节)后的地址, 仍然是随机值
printf("%d\n", strlen(&p[0] + 1));
//5 &p[0] + 1 即 &p[1], strlen()从字符串第二个元素开始向后数有多少个'\0', 得到5
结果如下:
二维数组
int a[3][4] = { 0 };
//二维数组 3行4列
printf("%d\n", sizeof(a));
//48 int[3][4] sizeof(数组名), a表示整个数组, 得到整个数组占据内存的大小
printf("%d\n", sizeof(a[0][0]));
//4 int a[0][0]指数组第一个元素, 得到该元素占据内存的大小
printf("%d\n", sizeof(a[0]));
//16 int[4] a[0]表示数组第一个元素, 该数组是二维数组, 第一个元素即"第一行"的一维数组, sizeof(数组名)得到整个数组占据的空间
printf("%d\n", sizeof(a[0] + 1));
//4 int* a[0] 表示二维数组"第一行"一维数组名,即"第一行"首元素数组地址, a[0]+1表示"第一行"第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1)));
//4 int a[0]+1表示"第一行"第二个元素的地址, *解引用, 得到元素即a[0][1]
printf("%d\n", sizeof(a + 1));
//4 int(*)[4] a表示数组首元素地址, 即"第一行"一维数组的地址, a+1 即"第二行"一维数组的地址
printf("%d\n", sizeof(*(a + 1)));
//16 int[4] a表示数组首元素地址, a+1即"第二行"数组的地址, *解引用得到"第二行"一维数组占据内存的大小
// sizeof(*(a + 1)) -> sizeof(a[1])
printf("%d\n", sizeof(&a[0] + 1));
//4 int(*)[4] &a[0]指"第一行"整个一维数组的地址, &a[0]+1 则指"第二行"整个一维数组的地址
printf("%d\n", sizeof(*(&a[0] + 1)));
//16 int[4] &a[0]+1 指"第二行"整个一维数组的地址, 对该地址*解引用得到"第二行"整个一维数组, 即sizeof(a[1])
printf("%d\n", sizeof(*a));
//16 int[4] a表示二维数组首元素 即"第一行"一维数组, sizeof(a[0])是sizeof(数组名), 得到整行一维数组占据的空间
printf("%d\n", sizeof(a[3]));
//16 int[4] a[3]表示"第三行"一维数组, 但不会造成越界访问, sizeof只关心a[3]的类型, 在编译的时候就已经将整个表达式转换成了16
// 运行该程序的时候, sizeof(a[3]) 已经被替换成了16, 并不会真正访问到a[3]的位置
运行结果如下:
三. 指针笔试题
题目1
#include <stdio.h>
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
,a
代表整个数组,&a+1
则得到了&a
后移一个数组大小后的地址即&a[5]
.原本类型是int(*)[5]
,将该地址强制类型转换为int*
,存放到ptr
这个指针变量内. -
a+1
中a
代表数组首元素地址,那么a+1
指向了数组第二个元素,通过*
解引用得到第二个元素的值2
-
ptr
是int*
类型的, 步长为sizeof(int)
, 那么ptr-1
正好指向了数组最后一个元素的地址,*
解引用得到5
-
-
程序运行如下:
题目2
#include <stdio.h>
//x86环境下, 该结构体的大小是20个字节
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);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
p + 0x1
中,p
是结构体指针变量, 加减步长为p
指向变量占据内存空间的大小即20
字节,p
的值为0x100000
. 结果为0x100000 + 20 = 0x100000 + 0x14 = 0x100014
.按%p
占位符,打印出00100014
.(unsigned long)p + 0x1
,将p
强制类型转换为unsigned long
, 此时数字直接加减.结果为0x100000 + 0x1 = 0x100001
.按%p
占位符,打印出00100001
.(unsigned int*)p + 0x1
,将p
强制类型转换为unsigned int*
, 步长为sizeof(int)
即4
个字节;结果为0x100000 + 0x4 = 0x100004
.按%p
占位符,打印出00100004
.- 程序运行如下:
题目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);
return 0;
}
(int*)(&a + 1)
中,&a
表示整个数组的地址,&a+1
则是&a
后移一个数组大小得到的地址,将这个int(*)[4]
类型的指针强制类型转换为int*
类型,int*
类型的ptr1
存放了这个地址.(int*)((int)a + 1)
中,a
表示数组首元素即1
的地址, 将该地址强制类型转换为int
类型, 整数类型直接相加,(int)a + 1
等于将数组首元素地址直接加1
.int*
类型的ptr2
存放了这个地址.printf("%x,%x", ptr1[-1], *ptr2);
中ptr1[-1]
,即*(ptr-1)
,即a[4]
,按%x
打印得到4
.*ptr2
,本机器小端存储,a
所指内存空间内容为01000000
,则int(a) + 1
即ptr2
指向了00000002
.按%x
打印得到20000000
.
- 程序运行结果如下:
题目4
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
()
表达式, 括号内的表达式依次被执行,整个括号表达式的结果取最右边的表达式的结果.则(0, 1), (2, 3), (4, 5)
结果为1,3,5
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
实质为int a[3][2] = {1, 3, 5};
p = a[0];
,int*
类型的指针变量p
,指向了二维数组第一个元素即"第一行"一维数组.p[0]
即*(p + 0) = *(a[0] + 0) = a[0][0]
, 最终结果为1
.- 程序运行结果如下:
题目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]);
return 0;
}
-
二维数组在内存中真实是连续排列的
-
int(*p)[4]
,p
指向的是int[4]
的空间 -
p = a
,p
存放了数组a
的首元素地址 -
&p[4][2]
和&a[4][2]
所指位置如下图所示:
-
&p[4][2] - &a[4][2]
的真值为-4
,-4
的补码为0xFFFFFFFC
. -
最终打印
FFFFFFFC,-4
-
程序运行结果如下:
题目6
#include <stdio.h>
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;
}
int* ptr1 = (int*)(&aa + 1)
中,&aa
取出整个数组的地址,&aa+1
是&aa
后移一个数组大小的地址.将这个int(*)[5]
类型的指针变量强制类型转换为int*
类型,存放到int*
类型的ptr1
中.int* ptr2 = (int*)(*(aa + 1))
中,aa
表示数组首元素地址即数组第一行的地址,aa+1
则是数组第二行的地址,使用*
解引用操作符*(aa+1)
即aa[1]
表示第二行数组的数组名,也是第二行首元素地址,将这个地址存放到int*
类型的ptr2
中.*(ptr1 - 1)
即5
,*(ptr2 - 1)
即10
- 程序运行结果如下:
题目7
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
char* a[] = { "work","at","alibaba" }
中,a
是一个指针数组
char** pa = a
中,pa
是一个二级指针变量,存放了a
的首元素地址pa++
,pa
指向的位置后移一个步长(sizeof(char*
),pa++
后pa
指向了数组第二个元素的地址*pa
对第二个元素的地址解引用,得到第二个元素"at"
的首元素地址,即'a'
的地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CaR62c2y-1689317984122)(image/指针笔试题/1689309608848.png)]printf("%s\n", *pa)
按%s
打印,依次打印该地址后的每个字节,直至遇到\0
,最后打印at
- 程序运行结果如下:
题目8
#include<stdio.h>
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;
}
具体解析如图:
程序运行结果如下:
本章完.