第1章 初识C语言
章节摘要
本章将带你走进C语言的世界,了解C语言的历史、特点和应用领域,搭建开发环境,编写第一个C程序,理解编译过程,为后续深入学习打下坚实基础。
1.1 C语言的诞生与发展历史
C语言的诞生
C语言由**丹尼斯·里奇(Dennis Ritchie)**于1972年在贝尔实验室开发,最初是为了开发UNIX操作系统而设计的。C语言的前身是B语言,而B语言又源自BCPL语言。
重要时间节点:
- 1972年:C语言诞生
- 1978年:Brian Kernighan和Dennis Ritchie出版《The C Programming Language》(K&R C)
- 1989年:ANSI发布C语言标准(ANSI C / C89)
- 1990年:ISO采纳ANSI C标准(C90)
- 1999年:发布C99标准,增加了许多新特性
- 2011年:发布C11标准,增加了多线程支持等特性
- 2018年:发布C17标准(C18),主要是bug修复
C语言的影响
C语言对计算机科学产生了深远影响:
- UNIX操作系统用C语言重写,使其具有良好的可移植性
- Linux内核完全用C语言编写
- 许多编程语言受C语言影响:C++、Java、C#、JavaScript、Go等
- 大量系统软件使用C语言开发:数据库、编译器、网络协议栈等
1.2 C语言的特点:高效、灵活、接近底层
核心特点
1. 高效性
- 接近硬件:C语言提供了对内存的直接访问能力
- 执行速度快:编译后的代码效率接近汇编语言
- 资源占用少:适合资源受限的嵌入式系统
2. 灵活性
- 指针操作:可以直接操作内存地址
- 位操作:支持位级别的数据处理
- 类型转换:提供灵活的类型转换机制
3. 可移植性
- 标准化:有明确的语言标准(ANSI C、ISO C)
- 跨平台:同一份代码可以在不同平台上编译运行
- 广泛支持:几乎所有平台都有C编译器
4. 简洁性
- 关键字少:C89只有32个关键字,C99增加到37个
- 语法简单:核心语法规则清晰明了
- 易于学习:适合作为第一门编程语言
5. 强大的标准库
- 丰富的函数:提供输入输出、字符串处理、数学运算等功能
- 可扩展性:可以创建自己的库
C语言的局限性
需要注意的问题:
- 内存管理:需要手动管理内存,容易出现内存泄漏
- 类型安全:类型检查相对宽松,容易出错
- 缓冲区溢出:需要程序员自己保证数组访问安全
- 没有面向对象:不支持类、继承等面向对象特性
1.3 C语言能做什么:系统编程、嵌入式、操作系统、编译器
应用领域
1. 操作系统开发
- Linux内核:完全用C语言编写
- Windows内核:大部分用C语言编写
- macOS/iOS内核:基于Darwin,用C语言编写
2. 嵌入式系统
- 单片机编程:Arduino、STM32等
- 物联网设备:智能家居、可穿戴设备
- 汽车电子:发动机控制、车载系统
3. 系统软件
- 数据库:MySQL、PostgreSQL、SQLite
- Web服务器:Nginx、Apache
- 编译器:GCC、Clang
4. 网络编程
- 网络协议栈:TCP/IP实现
- 网络工具:ping、traceroute、netcat
- 防火墙:iptables
5. 游戏开发
- 游戏引擎:Unreal Engine(C++,但核心是C)
- 图形库:OpenGL、DirectX的底层实现
6. 科学计算
- 数值计算库:BLAS、LAPACK
- 科学软件:MATLAB的底层实现
1.4 C语言与其他语言的对比
C vs C++
| 特性 | C语言 | C++ |
|---|---|---|
| 编程范式 | 面向过程 | 面向对象 + 面向过程 |
| 类和对象 | 不支持 | 支持 |
| 函数重载 | 不支持 | 支持 |
| 模板 | 不支持 | 支持 |
| 异常处理 | 不支持 | 支持 |
| 标准库 | 较小 | 非常丰富(STL) |
| 学习曲线 | 较平缓 | 较陡峭 |
| 编译速度 | 快 | 较慢 |
选择建议:
- 学习底层原理、系统编程 → 选C
- 大型软件开发、需要面向对象 → 选C++
C vs Java
| 特性 | C语言 | Java |
|---|---|---|
| 内存管理 | 手动管理 | 自动垃圾回收 |
| 指针 | 支持 | 不支持(有引用) |
| 平台依赖 | 需要重新编译 | 一次编译,到处运行 |
| 执行方式 | 编译为机器码 | 编译为字节码,JVM执行 |
| 性能 | 非常高 | 较高(但低于C) |
| 安全性 | 较低 | 较高 |
| 应用场景 | 系统编程、嵌入式 | 企业应用、Android开发 |
C vs Python
| 特性 | C语言 | Python |
|---|---|---|
| 类型系统 | 静态类型 | 动态类型 |
| 执行速度 | 非常快 | 较慢 |
| 开发速度 | 较慢 | 非常快 |
| 内存管理 | 手动 | 自动 |
| 语法复杂度 | 中等 | 简单 |
| 适用场景 | 性能关键型应用 | 快速开发、数据分析 |
1.5 开发环境搭建
Windows环境搭建
方案一:MinGW-w64(推荐)
步骤:
下载MinGW-w64
- 访问:https://www.mingw-w64.org/
- 或使用MSYS2:https://www.msys2.org/
安装MSYS2(推荐方式)
# 下载msys2-x86_64-latest.exe并安装# 安装后打开MSYS2终端,执行:pacman-Syupacman-Smingw-w64-x86_64-gcc配置环境变量
- 将
C:\msys64\mingw64\bin添加到系统PATH - 重启命令行窗口
- 将
验证安装
gcc--version
方案二:Visual Studio
- 下载Visual Studio Community(免费)
- 安装时选择"使用C++的桌面开发"
- 包含MSVC编译器和调试工具
Linux环境搭建
Ubuntu/Debian系统
# 更新软件包列表sudoaptupdate# 安装build-essential(包含gcc、g++、make等)sudoaptinstallbuild-essential# 验证安装gcc--versionmake--versionCentOS/RHEL系统
# 安装开发工具组sudoyum groupinstall"Development Tools"# 验证安装gcc--versionmacOS环境搭建
# 安装Xcode Command Line Toolsxcode-select--install# 验证安装gcc--version# 注意:macOS的gcc实际上是clang的别名clang--version1.6 编译器选择
GCC(GNU Compiler Collection)
特点:
- 开源免费
- 跨平台(Linux、Windows、macOS)
- 支持多种语言(C、C++、Fortran等)
- 优化能力强
使用场景:
- Linux开发
- 跨平台项目
- 开源项目
Clang
特点:
- 基于LLVM
- 编译速度快
- 错误信息友好
- 模块化设计
使用场景:
- macOS/iOS开发
- 需要友好错误提示的项目
- 现代化的C/C++项目
MSVC(Microsoft Visual C++)
特点:
- Windows平台原生支持
- 与Visual Studio深度集成
- 调试工具强大
使用场景:
- Windows专属开发
- 需要使用Windows API
- 企业级Windows应用
编译器对比
| 编译器 | 平台 | 开源 | 优化 | 错误提示 |
|---|---|---|---|---|
| GCC | 全平台 | ✓ | 优秀 | 良好 |
| Clang | 全平台 | ✓ | 优秀 | 非常好 |
| MSVC | Windows | ✗ | 良好 | 良好 |
1.7 IDE与编辑器
Visual Studio Code(推荐)
优点:
- 轻量级,启动快
- 插件丰富
- 跨平台
- 免费开源
配置步骤:
- 安装VS Code
- 安装C/C++扩展(Microsoft官方)
- 安装Code Runner扩展(可选)
- 配置编译器路径
推荐插件:
- C/C++ (Microsoft)
- C/C++ Compile Run
- Better C++ Syntax
- Code Runner
CLion(专业IDE)
优点:
- 智能代码补全
- 强大的重构功能
- 内置调试器
- CMake支持
缺点:
- 收费(学生免费)
- 占用资源较多
Dev-C++(初学者友好)
优点:
- 简单易用
- 一键编译运行
- 适合初学者
缺点:
- 功能相对简单
- 更新较慢
Vim/Emacs(高级用户)
优点:
- 效率极高
- 高度可定制
- 轻量级
缺点:
- 学习曲线陡峭
- 需要配置
1.8 第一个程序:Hello, World!
现在让我们一步步创建并运行你的第一个C程序。我们会详细讲解每一个操作步骤。
1.8.1 创建项目目录
首先,我们需要创建一个文件夹来存放我们的代码。
Windows系统:
- 打开"文件资源管理器"
- 导航到你想要存放代码的位置(比如
D:\code) - 右键点击空白处 → 新建 → 文件夹
- 命名为
hello
或者使用命令行:
# 打开命令提示符(Win+R,输入cmd)mkdirD:\code\hellocdD:\code\helloLinux/macOS系统:
# 打开终端mkdir~/code/hellocd~/code/hello1.8.2 创建源代码文件
现在我们需要创建一个.c文件来编写代码。
方法一:使用文本编辑器
- 在
hello文件夹中,右键 → 新建 → 文本文档 - 将文件名改为
hello.c(注意:要显示文件扩展名) - 用记事本或VS Code打开这个文件
方法二:使用VS Code
- 打开VS Code
- 文件 → 打开文件夹 → 选择
hello文件夹 - 点击"新建文件"图标
- 命名为
hello.c
方法三:使用命令行
# Windowstypenul>hello.c# Linux/macOStouchhello.c1.8.3 编写代码
在hello.c文件中,逐行输入以下代码(建议手动输入,而不是复制粘贴,这样能加深记忆):
#include<stdio.h>intmain(){printf("Hello, World!\n");return0;}输入提示:
- 第1行:输入
#include <stdio.h>,注意<和>是英文符号 - 第2行:空行(按回车键)
- 第3行:输入
int main() {,注意有一个空格在main()和{之间 - 第4行:按Tab键缩进,然后输入
printf("Hello, World!\n"); - 第5行:按Tab键缩进,然后输入
return 0; - 第6行:输入
}
保存文件:
- 按
Ctrl+S(Windows/Linux)或Cmd+S(macOS) - 或者:文件 → 保存
1.8.4 代码详解
让我们逐行理解这段代码的含义:
第1行:#include <stdio.h>
#include:这是一个预处理指令,告诉编译器"包含一个文件"<stdio.h>:标准输入输出头文件(StandardInputOutput)- 作用:这个头文件包含了
printf函数的声明,没有它就无法使用printf
第3行:int main() {
int:表示这个函数会返回一个整数main:主函数的名字,每个C程序必须有且只有一个main函数():参数列表,这里是空的,表示不接收参数{:函数体的开始
第4行:printf("Hello, World!\n");
printf:打印函数,用于在屏幕上显示文本"Hello, World!\n":要显示的内容"":双引号表示这是一个字符串\n:换行符,让光标移到下一行
;:语句结束符,每条语句后面都要加分号
第5行:return 0;
return:返回语句0:返回值,表示程序正常结束- 约定:返回0表示成功,返回非0值表示出错
第6行:}
- 函数体的结束
1.8.5 编译程序
编写完代码后,我们需要将它编译成可执行文件。
使用GCC编译(推荐):
打开命令行/终端
进入
hello文件夹:cdD:\code\hello# Windowscd~/code/hello# Linux/macOS执行编译命令:
gcc hello.c-ohello
命令解释:
gcc:调用GCC编译器hello.c:源代码文件名-o hello:指定输出文件名为hello(Windows会自动加.exe后缀)
如果编译成功:
- 不会有任何输出信息
- 会生成一个可执行文件:
- Windows:
hello.exe - Linux/macOS:
hello
- Windows:
如果编译失败:
- 会显示错误信息,比如:
hello.c:4:5: error: expected ';' before 'return' - 仔细检查代码,看是否有拼写错误或缺少分号
- 修改后重新保存,再次编译
1.8.6 运行程序
编译成功后,我们可以运行程序了。
Windows系统:
hello.exe# 或者直接输入helloLinux/macOS系统:
./hello输出结果:
Hello, World!恭喜!你已经成功运行了第一个C程序!
1.8.7 理解整个过程
让我们回顾一下刚才做了什么:
源代码(hello.c) → 编译器(gcc) → 可执行文件(hello.exe) → 运行 → 输出结果- 编写源代码:用文本编辑器写
.c文件 - 编译:用
gcc将源代码翻译成机器能理解的二进制代码 - 运行:执行生成的可执行文件
- 查看结果:在屏幕上看到输出
1.8.8 修改代码
现在让我们修改程序,让它更有趣。
修改hello.c:
#include<stdio.h>intmain(){printf("你好,世界!\n");printf("这是我的第一个C程序\n");printf("我正在学习C语言\n");return0;}保存并重新编译:
gcc hello.c-ohello运行:
./hello# Linux/macOShello.exe# Windows输出:
你好,世界! 这是我的第一个C程序 我正在学习C语言1.8.9 使用变量
让我们进一步改进程序,使用变量来存储信息。
修改hello.c:
#include<stdio.h>intmain(){intage=20;printf("你好,世界!\n");printf("我今年 %d 岁\n",age);printf("我正在学习C语言\n");return0;}新知识点:
//:单行注释,编译器会忽略这一行int age = 20;:声明一个整数变量age,并赋值为20%d:占位符,表示这里要插入一个整数age:要插入的变量
编译并运行:
gcc hello.c-ohello ./hello# Linux/macOShello.exe# Windows输出:
你好,世界! 我今年 20 岁 我正在学习C语言1.8.10 常见问题
问题1:找不到gcc命令
'gcc' 不是内部或外部命令解决方案:
- 检查是否已安装GCC(参考1.5节)
- 检查环境变量PATH是否配置正确
问题2:找不到hello.c文件
gcc: error: hello.c: No such file or directory解决方案:
- 确认当前目录是否正确:
pwd(Linux/macOS)或cd(Windows) - 确认文件名是否正确,注意大小写
问题3:中文乱码
解决方案:
- 使用英文字符串输出(推荐初学者使用)
- Windows系统:确保源文件保存为UTF-8编码,并在终端中设置正确的代码页
# 在命令行中运行此命令后再编译运行chcp65001
问题4:双击.exe文件,窗口闪退
原因:程序执行完毕后立即关闭
解决方案:
- 推荐方式:在命令行/终端中运行程序,而不是双击.exe文件
1.9 编译过程详解
编译的四个阶段
源代码(.c) → 预处理 → 编译 → 汇编 → 链接 → 可执行文件1. 预处理(Preprocessing)
作用:
- 处理以
#开头的预处理指令 - 展开宏定义
- 处理
#include指令 - 删除注释
命令:
gcc-Ehello.c-ohello.i示例:
原始代码:
#include<stdio.h>#definePI3.14159intmain(){printf("PI = %f\n",PI);return0;}预处理后(hello.i,简化版):
// stdio.h的内容被展开(数千行)// ...intmain(){printf("PI = %f\n",3.14159);// PI被替换return0;}2. 编译(Compilation)
作用:
- 将预处理后的代码翻译成汇编代码
- 进行语法检查
- 进行优化
命令:
gcc-Shello.i-ohello.s汇编代码示例(hello.s,简化版):
.section .rodata .LC0: .string "Hello, World!" .text .globl main main: pushq %rbp movq %rsp, %rbp leaq .LC0(%rip), %rdi call puts movl $0, %eax popq %rbp ret3. 汇编(Assembly)
作用:
- 将汇编代码翻译成机器码
- 生成目标文件(.o或.obj)
命令:
gcc-chello.s-ohello.o目标文件:
- 二进制文件,包含机器指令
- 还不能直接执行
- 需要链接才能生成可执行文件
4. 链接(Linking)
作用:
- 将目标文件与库文件链接
- 解析符号引用
- 生成可执行文件
命令:
gcc hello.o-ohello链接过程:
- 链接C标准库(libc)
- 链接启动代码(crt0.o等)
- 解析
printf等函数的地址
一步编译
# 直接从源代码生成可执行文件gcc hello.c-ohello这个命令实际上执行了上述四个步骤。
1.10 如何阅读编译器错误信息
常见错误类型
1. 语法错误(Syntax Error)
示例代码:
#include<stdio.h>intmain(){printf("Hello, World!\n")// 缺少分号return0;}错误信息:
hello.c:4:31: error: expected ';' before 'return' printf("Hello, World!\n") ^ ; return 0; ~~~~~~解读:
hello.c:4:31:错误位置(文件名:行号:列号)error:错误类型expected ';' before 'return':期望在return前有分号^:指向错误位置
2. 未声明的标识符
示例代码:
#include<stdio.h>intmain(){printf("%d\n",x);// x未声明return0;}错误信息:
hello.c:4:20: error: 'x' undeclared (first use in this function) printf("%d\n", x); ^3. 类型不匹配
示例代码:
#include<stdio.h>intmain(){intx="hello";// 类型不匹配return0;}错误信息:
hello.c:4:13: warning: initialization of 'int' from 'char *' makes integer from pointer without a cast int x = "hello"; ^~~~~~~4. 缺少头文件
示例代码:
// 没有包含stdio.hintmain(){printf("Hello\n");return0;}错误信息:
hello.c:3:5: warning: implicit declaration of function 'printf' printf("Hello\n"); ^~~~~~阅读错误信息的技巧
- 从第一个错误开始修复:后续错误可能是连锁反应
- 注意行号和列号:精确定位错误位置
- 理解错误类型:error(必须修复)vs warning(建议修复)
- 查看上下文:有时错误位置不是真正的错误原因
- 使用搜索引擎:搜索错误信息可以找到解决方案
实战练习
练习1:输出个人信息
题目:
编写一个程序,输出你的姓名、年龄、学校/公司信息。
要求:
- 使用多个printf语句
- 输出格式美观
- 使用转义字符
完整解决方案:
#include<stdio.h>intmain(){printf("================================\n");printf(" 个人信息卡片\n");printf("================================\n");printf("姓名:张三\n");printf("年龄:20岁\n");printf("学校:某某大学\n");printf("专业:计算机科学与技术\n");printf("================================\n");printf("学习目标:\n");printf("\t1. 掌握C语言基础语法\n");printf("\t2. 理解计算机底层原理\n");printf("\t3. 能够开发大型项目\n");printf("================================\n");return0;}知识点:
\n:换行符\t:制表符(Tab)- 多个printf语句的使用
练习2:ASCII艺术字
题目:
使用字符绘制ASCII艺术字,输出"HELLO"。
完整解决方案:
#include<stdio.h>intmain(){printf(" H H EEEEE L L OOO \n");printf(" H H E L L O O \n");printf(" HHHHH EEEE L L O O \n");printf(" H H E L L O O \n");printf(" H H EEEEE LLLLL LLLLL OOO \n");return0;}输出效果:
H H EEEEE L L OOO H H E L L O O HHHHH EEEE L L O O H H E L L O O H H EEEEE LLLLL LLLLL OOO练习3:查看编译过程
题目:
编写一个简单程序,查看编译的四个阶段的输出。
源代码:test.c
#include<stdio.h>#defineMAX100intmain(){intx=MAX;printf("x = %d\n",x);return0;}操作步骤:
# 1. 预处理gcc-Etest.c-otest.i# 查看test.i文件,可以看到stdio.h被展开,MAX被替换为100# 2. 编译gcc-Stest.i-otest.s# 查看test.s文件,可以看到汇编代码# 3. 汇编gcc-ctest.s-otest.o# test.o是二进制文件,无法直接查看# 4. 链接gcc test.o-otest# 生成可执行文件# 5. 运行./test查看预处理结果:
# 只查看预处理后的main函数部分gcc-Etest.c|grep-A10"int main"练习4:修复编译错误
题目:
以下代码有多个错误,请找出并修复。
错误代码:
#include<stdio.h>intmain(){printf("Hello, World!\n")intx=10printf("x = %d\n"x);return0}错误分析:
- 第5行:
printf语句末尾缺少分号 - 第6行:变量声明语句末尾缺少分号
- 第7行:
printf的两个参数之间缺少逗号分隔符 - 第8行:
return语句末尾缺少分号
正确代码:
#include<stdio.h>intmain(){printf("Hello, World!\n");intx=10;printf("x = %d\n",x);return0;}