欢迎光临
我们一直在努力

【2个月 C 语言从入门到精通:零基础系统教程】第十二讲:深入了解指针(五)

文章目录

  • 前言
  • 1.sizeof和strlen的对比
    • 1.1 核心概念速览
      • 1.1.1 sizeof 操作符
      • 1.1.2 strlen 函数
      • 1.1.3 数组名的“双重身份”
    • 1.2 核心代码讲解
      • 1.2.1 案例1
      • 1.2.2 案例2
      • 1.2.3 案例3
      • 1.2.4 案例4
      • 1.2.5 案例5
      • 1.2.6 案例6
      • 1.2.7 案例7
      • 1.2.8 案例8
  • 2. 数组和指针笔试题解析
    • 2.1 案例1
  • 3. 指针运算笔试题解析
    • 3.1 案例1
    • 3.2 案例2
    • 3.3 案例3
    • 3.4 案例4
    • 3.5 案例5
    • 3.6 案例6
    • 3.7 案例7
  • 总结

前言

在 C 语言的学习旅途中,指针和数组犹如一对形影不离的“双胞胎”,既深刻又令人头疼。许多初学者在掌握了基础语法后,往往在指针运算、sizeof 与 strlen 的区别以及二维数组的地址偏移上卡壳,导致笔试和面试频频失利。

本文档正是为了解决这一痛点而编写。我将从最核心的底层逻辑出发,通过 7 个 sizeof/strlen 对比案例 和 7 道经典指针运算笔试题,带你一步步拆解内存模型、厘清数组名的“双重身份”以及多级指针的指向变换。

无论你是正在准备求职笔试,还是想要彻底攻克 C 语言指针的“硬骨头”,这份实战解析都能为你提供清晰、深入的指引。让我们从内存视角开始,重新认识指针与数组的真相。


1.sizeof和strlen的对比

1.1 核心概念速览

在分析代码前,先明确两个核心工具和一条数组与指针的“潜规则”。

1.1.1 sizeof 操作符

功能:计算操作数所占内存的字节数。

时期:编译时确定(除变长数组外)。

返回值:size_t(无符号整型),用 %zu 打印。

对象:可以是类型、变量、表达式。它关心的是“类型占多大内存”。

1.1.2 strlen 函数

功能:求字符串长度,即从首地址开始向后计数,直到遇见空字符 \\0 停止,且不包含 \\0。

时期:运行时扫描内存。

声明:size_t strlen(const char *str);

致命要求:传入的指针必须指向一块包含 \\0 的有效字符数组。否则它会一直向后狂读,返回一个随机值,甚至导致程序崩溃。

1.1.3 数组名的“双重身份”

数组名在大多数表达式中会隐式转换为指向首元素的指针。但有两个例外:

sizeof(数组名):此时数组名表示整个数组,计算出数组总大小。

&数组名:取出的是整个数组的地址,类型是 指向数组的指针。

记住这句话:arr 和 &arr[0] 值相同但意义略有不同;&arr 的值也相同,但类型和步长完全不同。


1.2 核心代码讲解

我将用以下代码向大家展示这两者的不同

1.2.1 案例1

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char arr1[3] = { 'a', 'b', 'c' };//[a b c]
char arr2[] = "abc"; //[a b c \\0]
printf("%d\\n", strlen(arr1));//未知数
printf("%d\\n", strlen(arr2));//3

printf("%d\\n", sizeof(arr1));//3
printf("%d\\n", sizeof(arr2));//4
return 0;
}

在这里插入图片描述 代码解析 在这里插入图片描述

1.2.2 案例2

int main()
{
int a[] = { 1,2,3,4 };//4*4 = 16
printf("%zu\\n", sizeof(a));//16
printf("%zu\\n", sizeof(a + 0));//4/8,a+0是首元素的地址
printf("%zu\\n", sizeof(*a));//4, *a == a[0]
printf("%zu\\n", sizeof(a + 1));//4/8, a + 1 –> &a[1]
printf("%zu\\n", sizeof(a[1]));//4
printf("%zu\\n", sizeof(&a));//4/8, &a是整个数组的地址,依然是地址
printf("%zu\\n", sizeof(*&a));//16
//&a — int (*)[4]
//*&a — a
//sizeof(*&a) == sizeof(a)
printf("%zu\\n", sizeof(&a + 1));//4/8 &a + 1还是地址
printf("%zu\\n", sizeof(&a[0]));//4/8
printf("%zu\\n", sizeof(&a[0] + 1));//4/8
return 0;
}

在这里插入图片描述 核心讲解 在这里插入图片描述

1.2.3 案例3

int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%zu\\n", sizeof(arr));//6
printf("%zu\\n", sizeof(arr + 0));//4/8, arr + 0 == &arr[0]
printf("%zu\\n", sizeof(*arr));//1, *arr == arr[0]
printf("%zu\\n", sizeof(arr[1]));//1
printf("%zu\\n", sizeof(&arr));//4/8 — char(*)[6]
printf("%zu\\n", sizeof(&arr + 1));//4/8– char(*)[6]
printf("%zu\\n", sizeof(&arr[0] + 1));//4/8
return 0;
}

在这里插入图片描述

1.2.4 案例4

#include <stdio.h>
#include <string.h>

int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%zu\\n", strlen(arr));//未知数
printf("%zu\\n", strlen(arr + 0));//未知数
//printf("%zu\\n", strlen(*arr));//*arr == arr[0] == 'a' == 97, 程序会崩溃
//printf("%zu\\n", strlen(arr[1]));//'b'-98,程序会崩溃
printf("%zu\\n", strlen(&arr));//未知数 x
//char(*)[6]-> const char*
printf("%zu\\n", strlen(&arr + 1));//未知数 x-6
printf("%zu\\n", strlen(&arr[0] + 1));//未知数 x-1
return 0;
}

在这里插入图片描述 在这里插入图片描述

1.2.5 案例5

int main()
{
char arr[] = "abcdef";

printf("%zu\\n", sizeof(arr));//7
printf("%zu\\n", sizeof(arr + 0));//4/8
printf("%zu\\n", sizeof(*arr));//1, arr[0] == *arr
printf("%zu\\n", sizeof(arr[1]));//1
printf("%zu\\n", sizeof(&arr));//4/8
printf("%zu\\n", sizeof(&arr + 1));//4/8
printf("%zu\\n", sizeof(&arr[0] + 1));//4/8
return 0;
}

在这里插入图片描述

1.2.6 案例6

int main()
{
char arr[] = "abcdef";
printf("%zu\\n", strlen(arr));//6
printf("%zu\\n", strlen(arr + 0));//6
//printf("%zu\\n", strlen(*arr));//程序会崩溃
//printf("%zu\\n", strlen(arr[1]));//程序会崩溃
printf("%zu\\n", strlen(&arr));//6
printf("%zu\\n", strlen(&arr + 1));//未知数
printf("%zu\\n", strlen(&arr[0] + 1));//5
return 0;
}

在这里插入图片描述 代码解析 在这里插入图片描述在这里插入图片描述 在这里插入图片描述

1.2.7 案例7

#include <stdio.h>

int main()
{
char* p = "abcdef";

printf("%zu\\n", sizeof(p));//4/8
printf("%zu\\n", sizeof(p + 1));//4/8, p+1是b的地址
printf("%zu\\n", sizeof(*p));//1, *p == 'a'
printf("%zu\\n", sizeof(p[0]));//1
//p[0] == *(p+0) == 'a'
printf("%zu\\n", sizeof(&p));//4/8
printf("%zu\\n", sizeof(&p + 1));//4/8
printf("%zu\\n", sizeof(&p[0] + 1));//4/8, 'b'的地址
return 0;
}

在这里插入图片描述

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

1.2.8 案例8

#include <stdio.h>
#include <string.h>
int main()
{
char* p = "abcdef";
printf("%zu\\n", strlen(p));//6
printf("%zu\\n", strlen(p + 1));//5
printf("%zu\\n", strlen(*p));//程序崩溃
printf("%zu\\n", strlen(p[0]));//程序崩溃
printf("%zu\\n", strlen(&p));//未知数
printf("%zu\\n", strlen(&p + 1));//未知数
printf("%zu\\n", strlen(&p[0] + 1));//5
return 0;
}

在这里插入图片描述 在这里插入图片描述

2. 数组和指针笔试题解析

2.1 案例1

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

3. 指针运算笔试题解析

3.1 案例1

int main()
{
int a[5] = { 1, 2, 3, 4, 5 };

int* ptr = (int*)(&a + 1);

printf("%d,%d", *(a + 1), *(ptr 1));

return 0;

在这里插入图片描述

在这里插入图片描述

3.2 案例2

//在X86环境下
//假设结构体的大小是20个字节
//程序输出的结果是啥?
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);
printf("%p\\n", (unsigned long)p + 0x1);
printf("%p\\n", (unsigned int*)p + 0x1);

return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.3 案例3

int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}

在这里插入图片描述

在这里插入图片描述

3.4 案例4

//假设环境是x86环境,程序输出的结果是啥?
#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;
}

在这里插入图片描述

在这里插入图片描述 关键点: p 和 a 的类型是不匹配的!a 的每一行有 5 个元素,但 p 认为它指向的每一行只有 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));
return 0;
}

在这里插入图片描述 在这里插入图片描述

3.6 案例6

int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\\n", *pa);
return 0;
}

在这里插入图片描述 在这里插入图片描述

3.7 案例7

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;

在这里插入图片描述

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述


总结

通过本文的梳理,我们不仅回顾了 sizeof(编译时确定,侧重类型大小)与 strlen(运行时扫描,侧重字符长度)的根本差异,更透过一系列经典的笔试题,揭示了以下几个关键规律:

数组名的“变身”:在 sizeof 和 & 操作下,数组名代表整个数组;而在表达式中(如 a+1),它自动退化为指向首元素的指针。

指针加减的“步长”:指针算术的偏移量由它指向的数据类型大小决定。例如 struct Test* + 1 跳过整个结构体,而 unsigned int* + 1 只跳过 4 字节。

越界与类型不匹配的陷阱:

二维数组名和指向不同宽度的数组指针(如 int(*p)[4] 指向 int a[5][5])进行运算时,会产生意料之外的“提前”或“延后”偏移(例如 &p[4][2] – &a[4][2] 结果为 -4)。

strlen 要求参数必须是指向 \\0 结尾的有效地址。传入字符本身(如 *arr)会导致程序崩溃。

多级指针的“追赶”游戏:在三级指针(如 char*** cpp)的解题中,跟踪 cpp 自身的移动是核心,而 ++ 和 – 操作符的顺序(前置/后置)会直接影响目标变量值的修改时机。

掌握这些底层规则,你就能在复杂的指针代码面前从容拆解,避免“看到指针就发怵”的困境。建议你将文中案例在编译器中亲手运行并调试,这将帮助你建立起更加坚实的 C 语言内存模型认知

赞(0)
未经允许不得转载:171主机测评 » 【2个月 C 语言从入门到精通:零基础系统教程】第十二讲:深入了解指针(五)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址