资讯动态

从预处理到指针:C语言进阶之路的今日份复盘

发布时间:2026/8/4 6:28:49 来源:尧图企业网站定制
周末花了一整天啃C语言的进阶内容说实话脑子有点胀但理清楚之后还是很有成就感的。今天的主线其实就两条预处理命令和指针。前者是编译之前的准备工作后者是C语言最硬核的内存操作能力。趁热打铁把今天的收获写下来既是梳理也算是给自己的一份笔记。先说说预处理。平时我们写gcc main.c感觉一键就生成了可执行文件但其实背后分了四个步骤。预处理只是第一步执行的是gcc -E main.c -o main.i做的事情就是把代码里所有带#的指令展开。比如#include stdio.h会把stdio.h的整个内容拷贝进来#define PI 3.1415会把代码里所有的PI替换成3.1415还有#if 0 ... #endif这种条件编译会把中间的代码屏蔽掉不参与后续编译。预处理完之后才是真正的编译转成汇编、汇编转成二进制和链接把库函数实现挂上去。这个流程让我对编译器的理解深了一层。宏定义这块以前我总觉得宏就是简单替换今天才真正意识到它和函数的区别有多大。宏在预处理阶段就完成了展开本质上就是文本替换所以它没有函数调用时的现场保护和恢复开销效率高但也因为不做运算很容易出问题。比如你写#define SQUARE(x) x*x调用SQUARE(32)展开后是32*32结果是11而不是25。所以老师反复强调能加括号就加括号写成#define SQUARE(x) ((x)*(x))。另外宏名最好大写结尾不能加分号这些都是容易踩的坑。相比之下函数是在运行时才调用的代码体积小逻辑清晰但每次调用都有开销。什么时候用宏什么时候用函数得看具体场景。头文件包含也挺有意思。#include stdio.h用尖括号编译器会去系统的路径找比如Linux下的/usr/include。而#include head.h用双引号编译器会先从当前目录找。这个区别之前一直没在意今天才知道是为了区分系统头文件和自定义头文件。条件编译这块最实用的就是防止头文件重复包含。写#ifndef HEAD_H、#define HEAD_H、#endif这三行包裹住头文件内容就能确保无论被#include多少次实际只展开一次。另外#if 0 ... #endif用来注释大段代码比/* */更安全因为可以嵌套而/* */遇到嵌套就乱了。当然今天花时间最多的还是指针部分。说实话指针确实是C语言最让人头疼但又最让人着迷的地方。指针的本质就是地址。计算机给每个内存字节编了号这个编号就是地址而指针就是地址的别名。指针变量就是用来保存这个编号的变量。在64位系统下任何指针变量都占8个字节32位下占4个字节这个和它指向的数据类型无关。定义的时候要写明基类型比如int *p意思是p指向一个int型的数据解引用*p拿到的就是一个int。指针的初始化特别重要。不初始化的指针就是野指针指向哪里完全不可控程序随时可能崩溃。老师给了两个建议要么初始化为NULL要么直接赋一个已有变量的地址比如int *p num。指针的算术运算一开始容易混淆。p1不是简单加1个字节而是偏移sizeof(基类型)个字节。int *p的话p1就向后移动4个字节char *p只移动1个字节。两个指针相减的结果是它们之间间隔的元素个数而不是字节差。这个设计其实很巧妙方便对数组元素进行遍历。指针作为函数参数是我今天觉得最有用的技能。以前写函数只能返回一个值想返回多个结果就不知道怎么弄了。现在知道了把变量的地址传进去子函数通过解引用直接修改主函数里的变量这不就相当于返回了多个结果嘛。经典的交换两个数就是最好的例子传递x和y子函数里*a和*b直接操作的就是x和y本身。指针和数组的关系也很紧密。数组名就是数组首元素的地址a和a[0]完全等价。所以a[2]本质上就是*(a2)。但数组名是地址常量不能被赋值a num是会报错的。当数组作为参数传递给函数时实际上传递的就是首元素的地址所以void print_array(int a[], int len)和void print_array(int *pa, int len)这两种写法完全等价。在函数内部a就是一个指针变量可以随意移动。回想这一天最大的感触是预处理解决的是代码组织和编译控制的问题指针解决的是内存访问和数据传递的问题。前者让代码更灵活后者让程序更高效。当然指针也是最容易出错的地方野指针、悬空指针、越界访问每一个都是大坑。但正是这些特性让C语言保持着无可替代的地位。今天学的东西够消化一阵子了接下来找几个指针相关的练习题做做把理论变成手感。学习编程嘛光看笔记是不够的多敲代码才是硬道理。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价