
🏠个人主页:黎雁
🎬作者简介:C/C++/JAVA后端开发学习者
❄️个人专栏:C语言、数据结构(C语言)、EasyX、游戏、规划
✨ 从来绝巘须孤往,万里同尘即玉京

文章目录
-
- [前景回顾:前五篇指针核心速记 📝](#前景回顾:前五篇指针核心速记 📝)
- [一、sizeof vs strlen:最易混淆的两个关键字 🆚](#一、sizeof vs strlen:最易混淆的两个关键字 🆚)
-
- [1. sizeof:计算"类型/变量/数组"的字节大小 📏](#1. sizeof:计算“类型/变量/数组”的字节大小 📏)
- [2. strlen:计算字符串的有效长度 📜](#2. strlen:计算字符串的有效长度 📜)
- [3. sizeof与strlen核心区别表](#3. sizeof与strlen核心区别表)
- [二、数组与指针笔试题:逐行拆解核心考点 🔍](#二、数组与指针笔试题:逐行拆解核心考点 🔍)
-
- [1. 一维整型数组](#1. 一维整型数组)
- [2. 字符数组(无\0)](#2. 字符数组(无\0))
- [3. 字符串数组(带\0)](#3. 字符串数组(带\0))
- [4. 字符指针(指向常量字符串)](#4. 字符指针(指向常量字符串))
- [5. 二维数组](#5. 二维数组)
- [三、指针运算笔试题:实战拆解核心逻辑 ⚙️](#三、指针运算笔试题:实战拆解核心逻辑 ⚙️)
-
- [1. 数组指针运算](#1. 数组指针运算)
- [2. 结构体指针运算(x86环境)](#2. 结构体指针运算(x86环境))
- [3. 二维数组指针运算](#3. 二维数组指针运算)
- [4. 多级指针与指针数组(高频难题)](#4. 多级指针与指针数组(高频难题))
- [写在最后 📝](#写在最后 📝)
指针系列的收官之作来啦!这一篇我们直击指针类笔试面试的核心考点------sizeof与strlen的区别、数组/指针笔试题拆解、指针运算实战解析,帮你理清所有易混淆的细节,轻松应对各类指针考题!
前景回顾:前五篇指针核心速记 📝
指针第一讲:从内存到运算,吃透指针核心逻辑
指针第二讲:const 修饰、野指针规避与传址调用
指针第三讲:数组与指针深度绑定 + 二级指针 + 指针数组全解析
指针第四讲:字符指针、数组指针、函数指针及转移表应用
指针第五讲:回调函数与 qsort 的使用和模拟
想要吃透本篇的实战内容,先回顾前四篇的关键知识点:
- 指针本质是地址,不同类型的指针指向不同的目标对象,包括变量、数组、函数。
- 数组与指针深度绑定,数组传参本质传递首元素地址;函数指针可存储函数地址,实现对函数的间接调用。
- 函数指针数组可以构建转移表,简化多分支逻辑;
typedef可重命名复杂的指针类型,提升代码可读性。
想要吃透本篇的笔面试题,先回顾前五篇的关键知识点: - 指针本质是地址,不同类型指针的运算规则不同(
+1跳过的字节数由指向类型决定)。 - 数组名默认是首元素地址,仅在
sizeof(数组名)和&数组名时代表整个数组。 - 回调函数是通过函数指针调用的函数,
qsort借助回调函数实现通用排序。 - 指针的解引用、地址运算需严格匹配类型,否则会导致非法访问或逻辑错误。
一、sizeof vs strlen:最易混淆的两个关键字 🆚
sizeof和strlen是笔面试中必考的对比考点,二者的核心区别在于计算目标、终止条件、返回值意义完全不同。
1. sizeof:计算"类型/变量/数组"的字节大小 📏
sizeof是C语言的操作符,不是函数,用于计算数据类型或变量占用的字节数,不关心内存中的实际内容。
- 语法特点:变量名可省略括号,类型必须加括号;计算时不执行表达式(仅推导类型)。
- 核心规则:
sizeof(数组名)计算整个数组的字节大小,其余场景数组名均视为首元素地址。
c
#include <stdio.h>
int main()
{
int a = 10;
printf("%zd\n", sizeof(a)); // 4(int类型大小)
printf("%zd\n", sizeof a); // 4(变量名可省略括号)
printf("%zd\n", sizeof(a+2)); // 4(表达式仅推导类型,a+2仍为int)
printf("%zd\n", sizeof(int)); // 4(类型必须加括号)
return 0;
}
2. strlen:计算字符串的有效长度 📜
strlen是C语言标准库函数(需包含<string.h>),用于计算以\0为终止符的字符串长度(不包含\0)。
- 核心规则:必须从有效地址开始,向后查找
\0,无\0则返回随机值;参数必须是字符指针(地址)。
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abc"; // 内存:a b c \0
char arr2[] = {'a','b','c'}; // 无\0
printf("%zd\n", strlen(arr1)); // 3(找到\0,统计前3个字符)
printf("%zd\n", strlen(arr2)); // 随机值(无\0,越界查找)
return 0;
}
3. sizeof与strlen核心区别表
| 特性 | sizeof | strlen |
|---|---|---|
| 本质 | 操作符 | 库函数 |
| 计算目标 | 字节大小 | 字符串有效长度 |
| 终止条件 | 无(仅看类型/数组) | 以\0为终止符 |
| 参数类型 | 任意类型/变量/数组 | 仅字符指针(地址) |
| 无终止符时 | 正常计算字节数 | 返回随机值 |
二、数组与指针笔试题:逐行拆解核心考点 🔍
数组与指针的笔试题核心是数组名的含义 和指针类型的运算规则,下面按数组类型分类拆解高频考题。
1. 一维整型数组
c
int main()
{
int a[] = {1,2,3,4};
printf("%zd\n", sizeof(a)); // ① 16(sizeof(数组名),4*4)
printf("%zd\n", sizeof(a+0)); // ② 4/8(a+0是首元素地址,地址大小)
printf("%zd\n", sizeof(*a)); // ③ 4(*a是首元素,int类型)
printf("%zd\n", sizeof(a+1)); // ④ 4/8(a+1是第二个元素地址)
printf("%zd\n", sizeof(a[1])); // ⑤ 4(第二个元素,int类型)
printf("%zd\n", sizeof(&a)); // ⑥ 4/8(&a是数组地址,地址大小)
printf("%zd\n", sizeof(*&a)); // ⑦ 16(*&抵消,等价于sizeof(a))
printf("%zd\n", sizeof(&a+1)); // ⑧ 4/8(&a+1跳过整个数组,仍为地址)
printf("%zd\n", sizeof(&a[0])); // ⑨ 4/8(首元素地址)
printf("%zd\n", sizeof(&a[0]+1));// ⑩ 4/8(第二个元素地址)
return 0;
}
2. 字符数组(无\0)
c
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%zd\n", sizeof(arr)); // ① 6(sizeof(数组名),1*6)
printf("%zd\n", sizeof(arr+0)); // ② 4/8(首元素地址)
printf("%zd\n", sizeof(*arr)); // ③ 1(首元素,char类型)
printf("%zd\n", sizeof(arr[1])); // ④ 1(第二个元素,char类型)
printf("%zd\n", sizeof(&arr)); // ⑤ 4/8(数组地址)
printf("%zd\n", sizeof(&arr+1)); // ⑥ 4/8(跳过整个数组,地址)
printf("%zd\n", sizeof(&arr[0]+1));// ⑦ 4/8(第二个元素地址)
// strlen易错点
printf("%zd\n", strlen(arr)); // 随机值(无\0,越界查找)
printf("%zd\n", strlen(*arr)); // 程序崩溃(*arr是'a',ASCII97当作地址,非法访问)
printf("%zd\n", strlen(&arr)); // 随机值(&arr是数组地址,值同首元素地址,无\0)
return 0;
}
3. 字符串数组(带\0)
c
int main()
{
char arr[] = "abcdef"; // 内存:a b c d e f \0
printf("%zd\n", sizeof(arr)); // ① 7(sizeof(数组名),1*7,包含\0)
printf("%zd\n", strlen(arr)); // ② 6(strlen统计到\0前,不含\0)
printf("%zd\n", sizeof(arr+0)); // ③ 4/8(首元素地址)
printf("%zd\n", strlen(arr+0)); // ④ 6(首元素地址开始找\0)
printf("%zd\n", sizeof(*arr)); // ⑤ 1(首元素,char类型)
printf("%zd\n", strlen(*arr)); // ⑥ 程序崩溃(*arr是'a',非法地址)
return 0;
}
4. 字符指针(指向常量字符串)
c
int main()
{
char* p = "abcdef"; // p指向常量字符串首地址
printf("%zd\n", sizeof(p)); // ① 4/8(指针变量大小)
printf("%zd\n", sizeof(p+1)); // ② 4/8(p+1指向'b',地址大小)
printf("%zd\n", sizeof(*p)); // ③ 1(*p是'a',char类型)
printf("%zd\n", strlen(p)); // ④ 6(从'a'开始找\0)
printf("%zd\n", strlen(p+1)); // ⑤ 5(从'b'开始找\0)
printf("%zd\n", strlen(*p)); // ⑥ 程序崩溃(*p是'a',非法地址)
return 0;
}
5. 二维数组
c
int main()
{
int a[3][4] = {0};
printf("%zd\n", sizeof(a)); // ① 48(sizeof(数组名),3*4*4)
printf("%zd\n", sizeof(a[0][0])); // ② 4(第一行第一个元素,int类型)
printf("%zd\n", sizeof(a[0])); // ③ 16(a[0]是第一行数组名,4*4)
printf("%zd\n", sizeof(a[0]+1)); // ④ 4/8(a[0]+1是第一行第二个元素地址)
printf("%zd\n", sizeof(*(a+1))); // ⑤ 16(a+1是第二行地址,解引用为第二行)
printf("%zd\n", sizeof(a[3])); // ⑥ 16(sizeof不执行表达式,推导a[3]为int[4]类型)
return 0;
}
💡 关键:二维数组的数组名a是第一行的地址(int(*)[4]类型),a[i]是第i行的数组名,sizeof(a[i])计算第i行的字节数。
三、指针运算笔试题:实战拆解核心逻辑 ⚙️
指针运算的核心是类型决定步长 ------指针+1跳过的字节数 = 指向类型的大小,下面拆解高频运算考题。
1. 数组指针运算
c
#include <stdio.h>
int main()
{
int arr[5] = {1,2,3,4,5};
int (*p)[5] = &arr+1; // &arr是数组地址,+1跳过整个数组
printf("%d %d", *(arr+1), *(p-1)); // 输出:2 5
return 0;
}
📝 解析:
*(arr+1):arr是首元素地址,+1指向第二个元素,解引用得2。p-1:p是int(*)[5]类型,-1回退整个数组(20字节),指向原数组,*(p-1)等价于arr,解引用后取最后一个元素5。
2. 结构体指针运算(x86环境)
c
#include <stdio.h>
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
}; // 结构体大小20字节(内存对齐)
int main()
{
struct Test* p = NULL;
printf("%p\n", p+0x1); // 输出:00000014(NULL+20字节)
printf("%p\n", (unsigned long)p+0x1); // 输出:00000001(转为数值+1)
printf("%p\n", (unsigned int*)p+0x1); // 输出:00000004(转为int*,+1跳过4字节)
return 0;
}
📝 解析:指针运算步长由类型决定,数值运算直接+1,指针类型不同步长不同。
3. 二维数组指针运算
c
#include <stdio.h>
int main()
{
int aa[2][5] = {1,2,3,4,5,6,7,8,9,10};
int *p1 = (int*)(&aa+1); // &aa+1跳过整个二维数组,转为int*
int *p2 = (int*)(*(aa+1)); // aa+1是第二行地址,解引用为第二行数组名,转为int*
printf("%d %d", *(p1-1), *(p2-1)); // 输出:10 5
return 0;
}
📝 解析:
p1-1:回退4字节,指向二维数组最后一个元素10。p2-1:回退4字节,指向第一行最后一个元素5。
4. 多级指针与指针数组(高频难题)
c
#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); // 输出:IST
printf("%s\n", cpp[-1][-1]+1); // 输出:NTER
return 0;
}
📝 核心解析:
cpp是三级指针,初始指向cp[0];++cpp指向cp[1](c+2),**cpp取c[2]指向的"POINT"。++cpp指向cp[2](c+1),*cpp是c+1,--*cpp是c,*--*cpp是"ENTER",+3取"ER"。cpp[-2]等价于*(cpp-2),指向cp[0](c+3),*cpp[-2]是"FIRST",+3取"IST"。cpp[-1]指向cp[1](c+2),cpp[-1][-1]等价于*(*(cpp-1)-1),是"NEW",+1取"NTER"。
写在最后 📝
到这里,C语言指针系列的所有核心知识点就全部讲解完毕了!从基础的指针变量,到高阶的函数指针、回调函数,再到笔面试高频的sizeof/strlen、指针运算,整个知识体系可以总结为:
- 指针的本质是地址,类型决定运算规则(
+1步长、解引用范围)。 - 数组名的含义分场景,
sizeof(数组名)和&数组名是特殊情况。 sizeof计算字节大小,strlen找\0统计长度,二者不可混淆。- 指针运算笔试题的核心是"类型推导+地址偏移",逐行拆解即可理清逻辑。
指针是C语言的灵魂,也是笔面试的重点难点。想要真正掌握,一定要多敲代码、多调试、多分析内存地址的变化。至此,指针系列完结,希望这些内容能帮你彻底攻克指针难关!
要不要我帮你整理一份指针笔试面试核心考点速记表,把所有易考点、易错点汇总起来,方便考前冲刺?