深入C语言:指针与数组的经典笔试题剖析
1. sizeof和strlen的对比
1.1 sizeof
sizeof 是C语言中的一个操作符,用于计算变量或数据类型所占内存空间的大小,单位是字节。它不关心内存中存储的具体数据内容,只关注内存空间的大小。
#include <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a)); // 输出:4(int类型通常占4个字节)
printf("%d\n", sizeof a); // 输出:4(可以省略括号)
printf("%d\n", sizeof(int));// 输出:4(计算int类型的大小)
return 0;
}
在上面的代码中,
sizeof(a)和sizeof(int)都返回4,因为int类型通常占用4个字节。
1.2 strlen
strlen 是C语言标准库中的一个函数,用于计算字符串的长度。它的函数原型如下:
size_t strlen(const char *str);
strlen 从传入的字符串指针开始,向后查找直到遇到 \0 字符为止,统计 \0 之前的字符个数。如果字符串中没有 \0,strlen 会继续向后查找,可能导致越界访问。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[3] = {'a', 'b', 'c'}; // 没有\0结尾
char arr2[] = "abc"; // 自动添加\0
printf("%d\n", strlen(arr1)); // 输出:不确定,arr1没有\0
printf("%d\n", strlen(arr2)); // 输出:3
printf("%d\n", sizeof(arr1)); // 输出:3
printf("%d\n", sizeof(arr2)); // 输出:4(包含\0)
return 0;
}
在上面的代码中,strlen(arr1) 的结果是不确定的,因为 arr1 没有以 \0 结尾,strlen 会继续向后查找,直到遇到 \0 为止。而 strlen(arr2) 返回3,因为 arr2 是以 \0 结尾的字符串。
1.3 sizeof和strlen的对比
| sizeof | strlen |
|---|---|
| 是操作符 | 是库函数,需包含头文件 <string.h> |
| 计算操作数所占内存的大小,单位是字节 | 计算字符串长度,统计 \0 之前的字符个数 |
| 不关心内存中存储的数据内容 | 关注内存中是否有 \0,如果没有 \0 可能会越界 |
2. 数组和指针笔试题解析
2.1 一维数组
int a[] = {1, 2, 3, 4};
printf("%d\n", sizeof(a)); // 输出:16(4个int,每个4字节)
printf("%d\n", sizeof(a + 0)); // 输出:8(a + 0是首元素地址,指针大小)
printf("%d\n", sizeof(*a)); // 输出:4(*a是首元素,int类型)
printf("%d\n", sizeof(a + 1)); // 输出:8(a + 1是第二个元素的地址,指针大小)
printf("%d\n", sizeof(a[1])); // 输出:4(a[1]是第二个元素,int类型)
printf("%d\n", sizeof(&a)); // 输出:8(&a是整个数组的地址,指针大小)
printf("%d\n", sizeof(*&a)); // 输出:16(*&a是整个数组,sizeof(a))
printf("%d\n", sizeof(&a + 1)); // 输出:8(&a + 1是跳过整个数组后的地址,指针大小)
printf("%d\n", sizeof(&a[0])); // 输出:8(&a[0]是首元素地址,指针大小)
printf("%d\n", sizeof(&a[0] + 1));// 输出:8(&a[0] + 1是第二个元素地址,指针大小)
2.2 字符数组
代码块1
char arr[] = {'a', 'b', 'c', 'd', 'e', 'f'};
printf("%d\n", sizeof(arr)); // 输出:6(6个char,每个1字节)
printf("%d\n", sizeof(arr + 0)); // 输出:8(arr + 0是首元素地址,指针大小)
printf("%d\n", sizeof(*arr)); // 输出:1(*arr是首元素,char类型)
printf("%d\n", sizeof(arr[1])); // 输出:1(arr[1]是第二个元素,char类型)
printf("%d\n", sizeof(&arr)); // 输出:8(&arr是整个数组的地址,指针大小)
printf("%d\n", sizeof(&arr + 1));// 输出:8(&arr + 1是跳过整个数组后的地址,指针大小)
printf("%d\n", sizeof(&arr[0] + 1));// 输出:8(&arr[0] + 1是第二个元素地址,指针大小)
代码块2
char arr[] = {'a', 'b', 'c', 'd', 'e', 'f'};
printf("%d\n", strlen(arr)); // 输出:不确定,arr没有\0
printf("%d\n", strlen(arr + 0)); // 输出:不确定,arr没有\0
printf("%d\n", strlen(*arr)); // 错误:*arr是char,不是指针
printf("%d\n", strlen(arr[1])); // 错误:arr[1]是char,不是指针
printf("%d\n", strlen(&arr)); // 输出:不确定,arr没有\0
printf("%d\n", strlen(&arr + 1));// 输出:不确定,&arr + 1指向数组末尾之后
printf("%d\n", strlen(&arr[0] + 1));// 输出:不确定,arr没有\0
代码块3
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); // 输出:7(包含\0)
printf("%d\n", sizeof(arr + 0)); // 输出:8(arr + 0是首元素地址,指针大小)
printf("%d\n", sizeof(*arr)); // 输出:1(*arr是首元素,char类型)
printf("%d\n", sizeof(arr[1])); // 输出:1(arr[1]是第二个元素,char类型)
printf("%d\n", sizeof(&arr)); // 输出:8(&arr是整个数组的地址,指针大小)
printf("%d\n", sizeof(&arr + 1));// 输出:8(&arr + 1是跳过整个数组后的地址,指针大小)
printf("%d\n", sizeof(&arr[0] + 1));// 输出:8(&arr[0] + 1是第二个元素地址,指针大小)
代码块4
char arr[] = "abcdef";
printf("%d\n", strlen(arr)); // 输出:6
printf("%d\n", strlen(arr + 0)); // 输出:6
printf("%d\n", strlen(*arr)); // 错误:*arr是char,不是指针
printf("%d\n", strlen(arr[1])); // 错误:arr[1]是char,不是指针
printf("%d\n", strlen(&arr)); // 输出:6
printf("%d\n", strlen(&arr + 1));// 输出:不确定,&arr + 1指向数组末尾之后
printf("%d\n", strlen(&arr[0] + 1));// 输出:5
代码块5
char *p = "abcdef";
printf("%d\n", sizeof(p)); // 输出:8(p是指针,指针大小)
printf("%d\n", sizeof(p + 1)); // 输出:8(p + 1是指针,指针大小)
printf("%d\n", sizeof(*p)); // 输出:1(*p是char类型)
printf("%d\n", sizeof(p[0])); // 输出:1(p[0]是char类型)
printf("%d\n", sizeof(&p)); // 输出:8(&p是指针的地址,指针大小)
printf("%d\n", sizeof(&p + 1)); // 输出:8(&p + 1是指针的地址,指针大小)
printf("%d\n", sizeof(&p[0] + 1));// 输出:8(&p[0] + 1是指针,指针大小)
代码块6
char *p = "abcdef";
printf("%d\n", strlen(p)); // 输出:6
printf("%d\n", strlen(p + 1)); // 输出:5
printf("%d\n", strlen(*p)); // 错误:*p是char,不是指针
printf("%d\n", strlen(p[0])); // 错误:p[0]是char,不是指针
printf("%d\n", strlen(&p)); // 错误:&p是指针的地址,不是字符串
printf("%d\n", strlen(&p + 1)); // 错误:&p + 1是指针的地址,不是字符串
printf("%d\n", strlen(&p[0] + 1));// 输出:5
2.3 二维数组
int a[3][4] = {0};
printf("%d\n", sizeof(a)); // 输出:48(3行4列,每个int占4字节)
printf("%d\n", sizeof(a[0][0])); // 输出:4(a[0][0]是int类型)
printf("%d\n", sizeof(a[0])); // 输出:16(a[0]是第一行数组,4个int)
printf("%d\n", sizeof(a[0] + 1));// 输出:8(a[0] + 1是第二行首元素地址,指针大小)
printf("%d\n", sizeof(*(a[0] + 1)));// 输出:4(*(a[0] + 1)是第二行首元素,int类型)
printf("%d\n", sizeof(a + 1)); // 输出:8(a + 1是第二行地址,指针大小)
printf("%d\n", sizeof(*(a + 1)));// 输出:16(*(a + 1)是第二行数组,4个int)
printf("%d\n", sizeof(&a[0] + 1));// 输出:8(&a[0] + 1是第二行地址,指针大小)
printf("%d\n", sizeof(*(&a[0] + 1)));// 输出:16(*(&a[0] + 1)是第二行数组,4个int)
printf("%d\n", sizeof(*a)); // 输出:16(*a是第一行数组,4个int)
printf("%d\n", sizeof(a[3])); // 输出:16(a[3]是第四行数组,4个int)
3. 指针运算笔试题解析
3.1 题目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)); // 输出:2,5
return 0;
}
-
*(a + 1):a是数组首元素地址,a + 1是第二个元素地址,*(a + 1)是2。 -
*(ptr - 1):ptr指向数组末尾之后,ptr - 1是最后一个元素地址,*(ptr - 1)是5。
3.2 题目2
#include <stdio.h>
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
} *p = (struct Test *)0x100000;
int main()
{
printf("%p\n", p + 0x1); // 输出:0x100014(结构体大小20字节)
printf("%p\n", (unsigned long)p + 0x1);// 输出:0x100001(unsigned long类型加1)
printf("%p\n", (unsigned int *)p + 0x1);// 输出:0x100004(unsigned int*类型加1)
return 0;
}
-
p + 0x1:p是结构体指针,结构体大小为20字节,p + 1是0x100000 + 20 = 0x100014。 -
(unsigned long)p + 0x1:p被强制转换为unsigned long,加1后为0x100001。 -
(unsigned int *)p + 0x1:p被强制转换为unsigned int*,加1后为0x100004
3.3 题目3
#include <stdio.h>
int main()
{
int a[3][2] = {(0, 1), (2, 3), (4, 5)};
int *p;
p = a[0];
printf("%d", p[0]); // 输出:1
return 0;
}
-
a[3][2]的初始化使用了逗号表达式,实际初始化为{1, 3, 5}。 -
p = a[0],p[0]是a[0][0],值为1。
3.4 题目4
#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]); // 输出:FFFFFFFC,-4
return 0;
}
-
p是指向int[4]的指针,p[4][2]相当于*(*(p + 4) + 2)。 -
&p[4][2] - &a[4][2]计算的是两个指针之间的元素个数差,结果为-4
3.5 题目5
#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)); // 输出:10,5
return 0;
}
-
(ptr1 - 1):ptr1指向数组末尾之后,ptr1 - 1是最后一个元素,值为10。 -
*(ptr2 - 1):ptr2指向第二行首元素,ptr2 - 1是第一行最后一个元素,值为5。
3.6 题目6
#include <stdio.h>
int main()
{
char *a[] = {"work", "at", "alibaba"};
char **pa = a;
pa++;
printf("%s\n", *pa); // 输出:at
return 0;
}
-
pa是指向a[0]的指针,pa++后指向a[1],*pa是"at"。
3.7 题目7
#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); // 输出:POINT
printf("%s\n", *--*++cpp + 3); // 输出:ER
printf("%s\n", *cpp[-2] + 3); // 输出:ST
printf("%s\n", cpp[-1][-1] + 1); // 输出:EW
return 0;
}
-
**++cpp:cpp指向cp[1],*cpp是c + 2,**cpp是"POINT"。 -
*--*++cpp + 3:cpp指向cp[2],*cpp是c + 1,--*cpp是c,*--*cpp是"ENTER",+3后是"ER"。
