[C.C++] C语言深度解析第一讲:从第一个程序到字符、字符串与转义字符

92 0
Honkers 3 小时前 来自手机 | 显示全部楼层 |阅读模式

✨ 把代码写进星轨,用逻辑丈量宇宙。

导航链接
个人主页🏠 星轨初途
基础语言专栏💻 C语言 、 📚 数据结构
C++ 进阶专栏🏆 C++学习(竞赛类) 、 ⚙️ C++专栏(开发类)
刷题实战专栏🚀 算法及编程题分享 、 📝 LeetCode 每日一题

前言

嗨,欢迎来到 C 语言的第一站!👋

第一次接触 C 语言,我们通常会先敲下这样一段代码:

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. printf("hello C\n");
  5. return 0;
  6. }
复制代码

点一下运行,屏幕上出现 hello C。看起来很顺利,但代码里的每一部分都在冒问题:

  • .c 文件明明只是一份文本,为什么能运行?
  • 程序为什么从 main 开始?
  • printf 是从哪里来的?
  • 单引号和双引号有什么区别?
  • 字符串 "abc" 明明只有 3 个字母,为什么内存里要占 4 个字节?

别急,这一讲就沿着这些问题往下走。我们先认识 C 语言和编译器,再回到第一个程序,最后把字符、字符串、语句与注释这些常见概念串起来。


一、C 语言是什么?

人和人交流会使用汉语、英语等自然语言;想让计算机替我们完成任务,也得找到一种双方约定好的表达方式,这就是计算机语言

C、C++、Java、Go 和 Python 都属于计算机语言。它们的语法和工作方式各不相同,但目标一致:把我们的想法写成程序,再交给计算机执行。

C 语言有一个很鲜明的特点:它既提供函数、变量、循环等方便人理解的语法,又允许我们接触内存、地址和字节这些更靠近机器的东西。

这份控制力很有吸引力,同时也意味着程序员要更仔细。数组越界、野指针、忘记字符串结尾等问题,C 语言不会每次都替我们兜底。

学习 C 语言,不只是记住语法,更要慢慢习惯“这段代码在内存里做了什么”。


二、C 语言从哪里来?

C 语言与 Unix 的关系很深,而且它并不是某一天突然冒出来的。我们顺着时间线,看看它是怎样一步步长出来的。

  • 1969 年:贝尔实验室的 Ken Thompson 和 Dennis Ritchie 参与开发 Unix。为了让系统更容易移植,Ken Thompson 在 BCPL 的基础上设计了 B 语言。
  • 1972 年:Dennis Ritchie 继续改进 B 语言,设计出一门新的语言。它既然接在 B 后面,名字也就顺理成章地变成了 C。
  • 1973 年:Unix 内核的大部分代码开始使用 C 语言重写。C 语言既能处理接近硬件的工作,又比纯汇编更容易阅读和移植,很快跟着 Unix 走向更多平台。
  • 1988 年前后:ANSI 推动 C 语言标准化,C 语言开始有更稳定、统一的规范。后来又形成 C89/C90、C99、C11、C17 等版本。

几十年过去,C 语言仍然活跃在操作系统、嵌入式、编译器和各种基础软件中。它当然不是最年轻的语言,却一直没有真正离开开发者的工具箱。

这段历史不用死记年份。先记住 Dennis Ritchie、Unix 和标准化这三个关键词就够啦。接下来看看更实际的问题:我们写好的代码,到底怎么跑起来?


三、编译器与 Visual Studio 2022

3.1 .c 文件为什么不能直接运行?

先在记事本里打开一份 .c 文件,你会发现它就是普通文本。处理器并不认识 printf、int 或 return,它最终执行的是机器指令。

于是,中间需要一个“翻译员”。这个角色就是编译工具链

注:

  1. 每个源文件(.c)单独经过编译器处理生成对应的目标文件(.obj为后缀的文件)
  2. 多个目标文件和库文件经过链接器处理生成对应的可执行程序(.exe文件)

这就是,在Windows电脑上C语言程序生成的exe可执行文件

先把流程压成两步来看:

  1. 每个 .c 源文件分别经过预处理、编译和汇编,生成目标文件;
  2. 链接器把目标文件与需要的库组合起来,生成最终的可执行程序。

Windows 上使用 MSVC 时,经常能看到 .obj 和 .exe;使用 GCC 或 Clang 时,目标文件常见后缀是 .o。后缀虽然不同,所处的阶段是一样的。

源文件是我们写的文本,目标文件已经含有机器代码,链接完成后才得到最终程序。

3.2 编译器和 IDE,别再傻傻分不清啦

初学时很容易把 Visual Studio 直接叫作“编译器”。平时这么说大家能听懂,不过严格一点拆开看,它其实是一个 IDE(集成开发环境)

工具负责什么常见例子
编辑器编写和修改代码VS Code、Vim
编译器把源代码翻译成目标代码MSVC、GCC、Clang
调试器断点、单步执行、观察变量GDB、LLDB、VS Debugger
IDE把编辑、构建、调试等能力装进一个界面Visual Studio、CLion、Xcode

Visual Studio 2022 通常配合 MSVC 工具链工作。我们点击“本地 Windows 调试器”或按下 Ctrl + F5 时,IDE 已经在背后帮忙组织好了编译、链接和运行。

对于刚开始在 Windows 上学习 C 语言的同学,VS 2022 的优点是安装后配置较少、调试功能完整;缺点也很直接:体积比较大,界面里的按钮第一次看会有点多。

不用被它吓到。第一阶段我们会新建项目、添加 .c 文件、运行代码,就已经够用了。


四、VS 项目、源文件和头文件

在 VS 2022 中写代码时,我们通常先创建一个项目,再向项目中添加源文件和头文件。

  • 以 .c 结尾的文件叫作源文件,主要放函数定义和具体实现;
  • 以 .h 结尾的文件叫作头文件,通常放函数声明、类型声明和宏定义。

举个很小的例子。如果我们把加法函数单独放进 add.c,项目可以这样组织:

  1. 项目
  2. ├─ main.c
  3. ├─ add.c
  4. └─ add.h
复制代码

add.h 负责告诉其他源文件:“这里有一个 Add 函数可以使用。”

  1. #ifndef ADD_H
  2. #define ADD_H
  3. int Add(int x, int y);
  4. #endif
复制代码

add.c 再给出它真正的实现:

  1. #include "add.h"
  2. int Add(int x, int y)
  3. {
  4. return x + y;
  5. }
复制代码

最后在 main.c 中包含头文件并调用函数:

  1. #include <stdio.h>
  2. #include "add.h"
  3. int main(void)
  4. {
  5. printf("%d\n", Add(2, 3));
  6. return 0;
  7. }
复制代码

这里先不用死磕头文件保护和链接规则。只要记住一个朴素的分工:头文件偏向“告诉别人有什么”,源文件负责“把它真正做出来”。


五、第一个 C 语言程序

现在回到开头那段代码,这次不只是把它敲出来,我们逐行看看它在做什么。

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. printf("hello C\n");
  5. return 0;
  6. }
复制代码

5.1 #include

这一行包含标准输入输出头文件。后面调用的 printf 函数,就声明在 stdio.h 中。

5.2 int main(void)

这里定义了程序入口 main。前面的 int 表示它执行结束后会返回一个整数,括号里的 void 表示这个版本的 main 不接收参数。

5.3 一对大括号 {}

大括号围出的区域叫作代码块。main 要执行的语句就放在这里。

5.4 printf("hello C\n");

调用 printf 输出一段文字,其中 \n 表示换行。语句末尾的分号别漏掉,它相当于告诉编译器:“这一句到这里结束。”

5.5 return 0;

main 返回 0,通常表示程序正常结束。

第一次写代码时,下面这些小错误特别常见:

  • 把 main 写成 mian;
  • 漏掉 main 后面的括号;
  • 混入中文分号 ; 或中文括号;
  • 忘记在语句末尾写英文分号 ;。

编译报错并不可怕。看清第一条错误提示,再回到对应行检查,往往比盯着整个屏幕有效得多。


六、为什么程序从 main 开始?

一个项目可以有很多 .c 文件,也可以写很多函数,但作为一个普通的可执行程序,它需要一个明确的入口。这个入口就是 main。

  1. int main(void)
  2. {
  3. return 0;
  4. }
复制代码

可以先记住三点:

  • main 是程序入口;
  • 一个可执行程序只能有一个 main 定义;
  • main 结束时返回的整数会交给运行环境,0 通常表示成功。

如果一个项目里完全没有 main,链接器会找不到入口;如果写了两个 main,链接器又会不知道该选哪一个。

这就像一栋楼可以有很多房间,但总得先告诉访客正门在哪里。门可以通向更多函数,正门本身只能确定一个。


七、printf 与库函数

7.1 printf 不只能打印一句话

printf 是 C 标准库提供的输出函数。除了直接打印字符串,它还可以借助格式说明符输出不同类型的数据:

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. int age = 18;
  5. char grade = 'A';
  6. double price = 3.14;
  7. printf("age = %d\n", age);
  8. printf("grade = %c\n", grade);
  9. printf("price = %.2f\n", price);
  10. return 0;
  11. }
复制代码

格式常见用途
%d输出 int
%c输出单个字符
%s输出字符串
%f输出浮点数

格式说明符必须和后面的参数匹配。把整数交给 %s,编译器可能会发出警告,运行时也可能直接出问题。

⚠️ 容易混淆:printf 输出 double 使用 %f;scanf 读取 double 才使用 %lf。两个函数长得像,格式要求却不能直接照搬。

7.2 什么是库函数?

像输出文字、计算字符串长度这些常见工作,没必要让每个程序员都从头实现。C 标准规定了一组可供程序使用的函数接口,不同工具链按照标准提供实现,它们共同组成了 C 标准库。

库函数通常按功能声明在不同头文件中:

  • printf 在 中声明;
  • strlen 在 中声明;
  • sqrt 在 中声明。

头文件不是把整个函数实现复制到当前位置,它首先让编译器知道函数叫什么、需要什么参数、会返回什么类型。真正的实现会在后面的链接阶段接进程序。


八、关键字:这些名字已经被 C 语言预订了

int、if、return 看起来都是单词,但在 C 语言里,它们已经被赋予了特殊含义,这些保留名称叫作关键字

因此,下面这种写法不行:

  1. int return = 10; // 错误:return 是关键字
复制代码

经典的 C90 标准有 32 个关键字:

  1. auto break case char const continue
  2. default do double else enum extern
  3. float for goto if int long
  4. register return short signed sizeof static
  5. struct switch typedef union unsigned void
  6. volatile while
复制代码

后来的 C99、C11 和 C23 又增加了新关键字,所以“C 语言只有 32 个关键字”只适用于经典的 C90 范围。

现在不用背整张表。写代码时遇到 int、char、if、for、return,知道它们由语言保留;起变量名时别和关键字撞车,就够用了。

C语言关键字的全部介绍可以从这里查看详细介绍,我们后面也会讲的


九、字符与 ASCII 编码

9.1 字符在内存里也得变成数字

C 语言使用单引号表示字符:

  1. char ch1 = 'A';
  2. char ch2 = '@';
  3. char ch3 = '7';
复制代码

注意,'7' 表示字符,7 表示整数,它们不是同一个值。

计算机最终存储的是二进制数据,所以字符和数字之间需要一套约定。常见运行环境对基础字符采用 ASCII 编码,例如:

  • 'A' 到 'Z' 对应 65 到 90;
  • 'a' 到 'z' 对应 97 到 122;
  • '0' 到 '9' 对应 48 到 57;
  • 换行符 \n 的编码值是 10。

我们不需要把整张表背下来。掌握字母和数字的连续范围,其他值用到时再查。

下面是ASCll码表,便于大家了解,参考ASCll码表

9.2 同一个值,可以按数字看,也可以按字符看

先猜一下下面两行会输出什么:

  1. printf("%c\n", 'Q');
  2. printf("%c\n", 81);
复制代码

结果都是 Q。%c 会把对应的整数值按字符解释,而在 ASCII 中,Q 的编码正好是 81。

如果想看看可打印的 ASCII 字符,可以写一个小循环:

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. for (int code = 32; code <= 126; ++code)
  5. {
  6. printf("%c ", code);
  7. if (code % 16 == 15)
  8. {
  9. printf("\n");
  10. }
  11. }
  12. return 0;
  13. }
复制代码

这里把上限写成 126,因为 ASCII 中的 127 是 DEL 控制字符,并不是普通的可打印字符。

字符可以参与整数运算,因为它在内存中保存的本来就是一个整数编码。


十、字符串与藏在末尾的 \0

10.1 单引号和双引号差在哪?

单引号括起来的是一个字符:

  1. char ch = 'a';
复制代码

使用双引号括起来的一串字符就被称为字符串:

  1. char word[] = "abc";
复制代码

问题来了:"abc" 看起来只有 3 个字符,word 数组到底有多大?

答案是 4。

C 字符串会在末尾自动保存一个空字符 \0,它的数值是 0,用来标记字符串结束。因此 word 的实际内容是:

  1. 'a' 'b' 'c' '\0'
复制代码

可以用代码验证:

  1. #include <stdio.h>
  2. #include <string.h>
  3. int main(void)
  4. {
  5. char word[] = "abc";
  6. printf("strlen = %zu\n", strlen(word));
  7. printf("sizeof = %zu\n", sizeof(word));
  8. return 0;
  9. }
复制代码

输出为:

  1. strlen = 3
  2. sizeof = 4
复制代码

strlen 数到 \0 就停,只统计前面的有效字符;sizeof 看的是整个数组实际占用的字节数,所以会把 \0 算进去。

10.2 少了 \0,%s 就不知道该停在哪

看这里的两种初始化:

  1. char arr1[] = {'a', 'b', 'c'};
  2. char arr2[] = "abc";
复制代码

arr1 只有 3 个字符,没有结尾标记;arr2 则由字符串字面量初始化,编译器会自动补上 \0。

如果把 arr1 直接交给 %s:

  1. printf("%s\n", arr1); // 错误示例:不要这样使用
复制代码

printf 会继续向后读取,试图寻找并不存在于数组范围内的 \0。截图里出现了随机字符,但那只是未定义行为的一种表现;它也可能看似正常,甚至可能让程序崩溃。

正确做法是给数组留出并写入结束标记:

  1. char arr1[] = {'a', 'b', 'c', '\0'};
  2. char arr2[] = "abc";
复制代码

还有一个很直观的例子:

  1. printf("%s\n", "abc\0def");
复制代码

屏幕上只会看到 abc,因为 %s 遇到中间的 \0 就停下来了。

\0 不是可有可无的装饰,它是 C 字符串能够被正确识别的结束标志。


十一、转义字符:反斜杠让字符换了一份工作

转义字符参考:转义字符参考

先看两段非常像的文本:

  1. printf("abcndef");
  2. printf("abc\ndef");
复制代码

第一行中的 n 只是普通字母,输出 abcndef;第二行里的 \n 是一个整体,表示换行,因此 def 会跑到下一行。

反斜杠 \ 会和后面的字符组合成转义序列。常见的几种如下:

转义序列含义示例用途
\n换行打印下一行
\t水平制表对齐文本
\r回到当前行开头控制终端输出位置
\'单引号在字符常量中表示 '
\"双引号在字符串中打印 "
\\反斜杠输出 Windows 路径
\0数值为 0 的空字符标记字符串结束

例如,路径里的反斜杠不能直接随便写:

  1. printf("C:\\test\\code\\main.c\n");
复制代码

如果想在屏幕上打印双引号:

  1. printf("他说:"hello C"\n");
复制代码

字符还可以使用八进制或十六进制转义表示:

  1. printf("%c\n", '\130'); // 八进制 130,即十进制 88,对应 'X'
  2. printf("%c\n", '\x30'); // 十六进制 30,即十进制 48,对应 '0'
复制代码

这些写法现在不必全部背住。看到反斜杠时先别把后面的字符拆开读,问一句“它们是不是一个转义序列?”,很多字符串问题就能躲过去。


十二、语句与语句分类

C 程序由一条条语句组成。初学阶段可以先认识下面几类。

12.1 空语句

单独一个分号就是空语句:

  1. ;
复制代码

它什么也不做,但语法上确实是一条语句。以后看到这样的代码要格外小心:

  1. while (condition); // 循环体是空语句
  2. {
  3. // 这个代码块已经不属于 while 的循环体
  4. }
复制代码

那个不起眼的分号,可能会让循环一直空转。💣

12.2 表达式语句

表达式后面加上分号,就构成表达式语句:

  1. b = a + 5;
  2. count++;
  3. Add(2, 3);
复制代码

这里顺便修正一个容易混淆的说法:函数调用后加分号,常被教学资料单独叫作“函数调用语句”,便于理解;从 C 语法分类看,它仍然属于表达式语句。

12.3 复合语句

一对大括号中的若干声明和语句组成一个代码块,也叫复合语句:

  1. {
  2. int value = 10;
  3. printf("%d\n", value);
  4. }
复制代码

函数体、循环体和分支中的大括号,都是我们经常遇到的复合语句。

12.4 控制语句

程序如果只能从上往下一路执行,很快就不够用了。控制语句负责改变执行流程:

  • 分支:if、switch;
  • 循环:while、do while、for;
  • 跳转:break、continue、goto、return。

这一讲先认识名字,后面学习分支和循环时再逐个拆开。


十三、注释:写给人看的代码说明

注释会被编译器忽略,不参与程序的实际运行。它主要写给未来的自己、队友,以及接手代码的人看。

不过注释也不是越多越好:

  1. count++; // count 加 1
复制代码

这句注释只是把代码翻译了一遍,没有提供新信息。更有价值的注释应该解释“为什么”:

  1. // 先跳过下标 0,因为它保存的是哨兵节点
  2. for (int i = 1; i < size; ++i)
  3. {
  4. // ...
  5. }
复制代码

13.1 /* ... */ 块注释

块注释可以跨行:

  1. /* 这是一段注释 */
  2. /*
  3. 这是一段
  4. 跨行注释
  5. */
复制代码

它也能写在一行代码中间:

  1. int open_file(const char *path /* 文件路径 */, int mode);
复制代码

使用时注意两件事:

  1. /* 开始后必须有对应的 */;
  2. 块注释不能嵌套,遇到第一个 */ 就结束。

13.2 // 单行注释

从 // 开始到当前行末尾都属于注释:

  1. // 这是一行注释
  2. int value = 1; // 也可以放在语句后面
复制代码

这种写法从 C99 开始进入 C 标准,日常代码里非常常见。

注释符号如果出现在双引号中,就只是字符串的一部分:

  1. printf("// hello /* world */\n");
复制代码

这行代码会把斜杠和星号都打印出来,它们不会突然把字符串“注释掉”。

13.3 注释被去掉后,不一定直接拼接

在预处理阶段,注释会被替换为空白。因此:

  1. min/* 这里是注释 */Value
复制代码

处理后相当于:

  1. min Value
复制代码

而不是 minValue。这个细节平时不常用,但能帮助我们理解:注释不是简单把字符粗暴删除,编译器还要避免它两边的记号意外粘在一起。


总结与思考

好啦,C 语言第一讲到这里就走完了!🎉

我们沿着顺序,认识了 C 语言的来历和开发工具,也亲手拆开了第一个程序。现在再看到那几行代码,里面已经不只是一堆陌生符号了:

  • .c 源文件要经过翻译和链接,才能变成可执行程序;
  • 项目可以有多个源文件和头文件,但可执行程序只有一个 main 入口;
  • printf 等库函数需要正确的头文件和格式说明符;
  • 字符在内存中保存为编码值,字符串末尾则藏着决定边界的 \0;
  • 转义字符、语句和注释虽然基础,却埋着不少很容易踩中的小坑。

我觉得这一讲最值得记住的,不是 32 个关键字,也不是整张 ASCII 表,而是两个习惯:看到代码时多问一句“它怎样变成机器能执行的程序”,处理字符串时永远别忘了检查 \0。

第一站拿下啦。下一讲,我们就可以正式进入变量、数据类型和运算符,开始让程序处理真正的数据了!🚀

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2026 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行