第一章 C语言入门与环境搭建
一、编程语言介绍:嵌入式开发该选哪一种?
1.1 什么是编程语言?
简单说:编程语言是人和计算机 / 嵌入式硬件沟通的 “翻译器”。
你想让单片机点亮 LED、让传感器采集数据,不能直接说 “点亮 LED”,需要用计算机能理解的 “语言” 写出指令 —— 这就是编程语言的核心作用。
打个比方:
- 计算机 / MCU 是 “外国人”,只懂二进制(0 和 1);
- 编程语言是 “翻译”,把你写的 “人话指令” 翻译成二进制;
- 不同编程语言 = 不同国家的翻译,有的适合聊天(简单场景),有的适合谈生意(复杂工程),有的适合对接工厂(硬件控制)。
1.2 编程语言的核心分类
新手不用死记复杂分类,先掌握 2 类核心即可(按执行方式分类):
1.2.1 编译型语言(嵌入式开发主力)
- 原理:写好的代码先通过 “编译器” 翻译成机器码(二进制),生成可执行文件后再运行;
- 特点:运行速度极快、占用资源少,代码需编译后才能运行;
- 代表语言:C、C++、Go;
- 嵌入式场景:99% 的嵌入式固件、驱动、RTOS 核心代码都用编译型语言(尤其是 C),适配 MCU / 单片机的有限资源。
1.2.2 解释型语言(嵌入式辅助 / 上层应用)
- 原理:代码不用提前编译,运行时由 “解释器” 逐行翻译、逐行执行;
- 特点:开发快、跨平台,但运行速度慢、占用资源多;
- 代表语言:Python、Java、JavaScript、Lua;
- 嵌入式场景:用于嵌入式 Linux 上层应用、脚本控制、数据可视化(如 Python 处理传感器数据),极少用于裸机 / MCU 开发。
| 类型 | 速度 | 资源占用 | 开发效率 | 嵌入式核心用途 |
|---|---|---|---|---|
| 编译型(C) | 极快 | 极少 | 中等 | 裸机 / 驱动 / RTOS / 固件开发 |
| 解释型(Python) | 较慢 | 较多 | 极高 | 上层应用 / 脚本 / 数据处理 |
1.3 嵌入式开发常用编程语言(按优先级排序)
1.3.1 C 语言(核心中的核心,必学)
- 定位:嵌入式开发 “母语”,编译型语言;
- 核心优势:
- 硬件级控制:能直接操作内存地址、寄存器(如 STM32 的 GPIO 寄存器);
- 极致高效:编译后代码体积小、运行快,适配 51 单片机 / STM32/ESP32 等 MCU 的 KB 级内存;
- 生态完善:99% 的嵌入式芯片手册、驱动库、RTOS(FreeRTOS/RT-Thread)都基于 C 语言;
- 典型场景:
- 裸机程序(如 STM32 点亮 LED、串口通信);
- 嵌入式驱动(如传感器驱动、LCD 屏驱动);
- RTOS 任务编写、工业协议实现(Modbus/MQTT)。
1.3.2 C++(进阶,可选)
- 定位:C 语言的超集,兼容 C,增加面向对象特性;
- 核心优势:模块化、封装性更好,适合复杂嵌入式项目(如汽车电子、工业控制器);
- 典型场景:嵌入式 Linux 应用开发、大型工控系统、机器人控制。
1.3.3 Python(辅助,推荐学)
- 定位:解释型语言,“胶水语言”;
- 核心优势:开发效率极高,有丰富的库(如 pyserial 串口通信、numpy 数据处理);
- 典型场景:
- 上位机开发(如电脑和单片机通信、数据可视化);
- 嵌入式 Linux 脚本(如自动化测试、配置管理);
- 机器学习模型部署前的原型验证(TinyML)。
1.3.4 其他语言(了解即可)
- 汇编语言:最接近硬件的语言,速度最快但开发效率极低,仅用于 MCU 启动文件、极致性能优化(如中断入口);
- Lua:轻量级解释型语言,用于嵌入式脚本(如物联网设备的逻辑配置);
- Java:用于嵌入式 Linux 上层应用(如智能家居 APP 后端),极少用于底层开发。
1.4 不同场景编程语言选择(嵌入式实战指南)
| 学习 / 开发场景 | 首选语言 | 原因 |
|---|---|---|
| 零基础入门嵌入式、MCU 裸机开发 | C | 门槛适中、用途最广 |
| 复杂嵌入式项目(汽车电子 / 工控) | C++ | 面向对象,适配大型项目 |
| 上位机 / 数据处理 / 快速原型 | Python | 开发快、库多 |
| 极致性能优化(如中断处理) | 汇编 | 速度最快,仅局部使用 |
| 嵌入式 Linux 应用开发 | C/C++ | 兼容底层,效率高 |
1.5 为什么嵌入式开发不优先选 Python/Java?(新手必懂)
很多新手会问:“Python 这么简单,为什么不用它做嵌入式底层?” 核心原因 2 点:
- 资源占用问题:Python 解释器本身需要几十 MB 内存,而 STM32/51 单片机内存只有几 KB~ 几 MB,根本装不下;
- 硬件控制能力:Python 无法直接操作寄存器、内存地址,只能通过封装好的库间接控制,失去嵌入式 “底层操作” 的核心需求;
- 实时性问题:解释型语言逐行执行,无法满足嵌入式实时控制(如电机调速、工业总线通信)的毫秒级要求。
简单说:Python 适合 “指挥” 嵌入式设备,C 语言适合 “操控” 嵌入式设备。
二、C 语言的前世今生
想要真正理解 C 语言,先搞懂它的诞生背景 —— 所有特性都源于 “解决实际问题”。
2.1 诞生:为解决痛点而生
- 时间:1972 年,由丹尼斯・里奇(Dennis Ritchie)在贝尔实验室开发;
- 初衷:为编写 UNIX 操作系统而生。当时汇编语言开发效率极低,而早期高级语言(如 B 语言)性能差、硬件控制能力弱,C 语言完美平衡了 “开发效率” 和 “硬件控制能力”;
- 里程碑:1978 年《C 程序设计语言》(K&R C)出版,奠定 C 语言基础;1989 年 ANSI 制定 C89 标准(C90),1999 年 C99、2011 年 C11、2018 年 C17,核心语法基本稳定。

2.2 地位:编程语言的 “万能钥匙”
C 语言不是 “最年轻” 的,但却是 “最核心” 的:
- 操作系统:UNIX/Linux、Windows 内核绝大部分代码由 C 语言编写;
- 嵌入式:99% 的 MCU / 单片机固件、驱动、RTOS(FreeRTOS/RT-Thread)基于 C 语言;
- 其他语言:Python、Java、PHP 等高级语言的解释器 / 虚拟机,底层核心都是 C 语言实现;
- 行业领域:工业控制、汽车电子、航空航天、物联网,C 语言都是底层开发的标配。
2.3 TIOBE 2026 年 2 月 TOP10
TIOBE:https://www.tiobe.com/tiobe-index/
- Python:21.81%(-2.08%)
- C:11.05%(+1.22%)
- C++:8.55%(-2.82%)
- Java:8.12%(-2.54%)
- C#:6.83%(+2.71%)
- JavaScript:2.92%(-0.85%)
- Visual Basic:2.85%(+0.81%)
- R:2.19%(+1.14%)
- SQL:1.93%(-0.93%)
- Delphi/Object Pascal:1.88%(-0.29%)

三、开发环境搭建
C 语言开发环境的核心是「编译器 + 编辑器 / IDE」—— 不同场景(纯 C 学习 / 嵌入式开发)、不同系统(Windows/Linux/Mac)的选择差异很大。本文从新手视角出发,拆解 3 类主流环境的搭建步骤,兼顾 “易上手” 和 “适配嵌入式” 的需求。
3.1 先搞懂:开发环境的核心组成
新手不用纠结复杂概念,记住 2 个核心组件即可:
| 组件 | 作用 | 嵌入式常用工具 |
|---|---|---|
| 编译器 | 将 C 代码翻译成机器码(二进制),是开发环境的 “核心” | GCC、ARMCC(Keil)、Clang |
| 编辑器 / IDE | 编写代码、管理项目、调试程序(新手优先选集成化 IDE,避免手动配置编译器) | VS2022、Dev-C++、VS Code、Keil MDK |
3.2 IDE选择
方案 1:Dev-C++(极简,无需配置)
- 优势:轻量(<100M)、免安装 / 绿色版、集成 GCC 编译器,一键编译运行,无多余功能干扰;
- 搭建步骤:
- 下载:官网(https://www.bloodshed.net/devcpp.html)下载 Dev-C++ 5.11(选 64 位适配 Win10/11);
- 安装:双击安装包,一路 “Next”,安装路径选非中文 / 无空格(如
D:\Dev-C++,避免编译报错); - 配置(关键):
- 打开 Dev-C++ → 工具 → 编译选项 → 编译器路径:确认是
...\Dev-Cpp\MinGW64\bin\gcc.exe; - 编码设置:工具 → 编辑器选项 → 编码选 “UTF-8”(避免中文乱码);
- 可选:添加编译参数
-fexec-charset=GBK(解决控制台中文乱码);
- 打开 Dev-C++ → 工具 → 编译选项 → 编译器路径:确认是
- 验证:新建
hello.c,写 Hello World 代码,按 F11 “编译运行”,控制台输出内容即成功。
方案 2:VS2022(进阶,适配后续嵌入式开发)
- 优势:功能全、调试强,后续学嵌入式(如 STM32CubeIDE)可无缝衔接,支持大型项目管理;
- 搭建步骤:
- 下载:微软官网下载 VS2022 社区版(免费,https://visualstudio.microsoft.com/zh-hans/vs/);
- 安装:勾选「使用 C++ 的桌面开发」组件(必选,包含 C 语言编译器),点击 “安装”(约 8G,耐心等待);
- 创建项目:
- 打开 VS → 创建新项目 → 选 “空项目” → 命名(如 HelloC)→ 选择存储路径(非中文);
- 右键项目 → 添加 → 新建项 → 选 “C++ 文件 (.cpp)”,重命名为
hello.c(后缀必须为.c);
- 验证:写 Hello World 代码,按 F5 “调试运行”,控制台输出内容即成功。
3.3 VS2022安装
1、在浏览器输入VS2022,出现如下界面

2、点击Visual Studio 2022 IDE,进入官网,在官网界面找到下载 Visual Studio -> Communtion 2022

3、点击Community 2022意思是下载VS2022社区版,这里不是直接下载VS2022社区版,而是先下载VS2022的下载程序。
选择保存VS2022下载程序的位置,为了方便器件直接保存到桌面即可。

4、在桌面找到刚刚下载的VS2022的安装程序,点击运行

5、安装程序执行完之后,会进入下面的界面

6、不急着下载,我们先确认下载位置,一般不建议放在C盘,我这里放在D盘的一个文件夹中

7、找到工作负荷,因为是使用VS2022进行C语言学习,所以工作负荷中选择“使用C++的桌面开发”,因为C++是建立在C语言基础上的,所以C++的编译器是支持C语言的,不用担心。选择后,直接点击安装。

8、等待安装成功

四、第一个 C 程序
4.1 新建工程
1、在桌面新建一个文件夹用于保存创建的C语言项目

2、打开VS2022,选择创建新项目

3、选择创建新项目,然后点击下一步

4、第一个项目的名称为01_helloworld,保存位置在桌面新建的文件夹内,然后点击创建

5、创建成功后,会进入到开发界面

6、在开发界面的右侧有解决方案资源管理器,其中就有我们刚才创建的01_helloworld项目,右键点击源文件,选择添加->新建项,在添加新项中更改文件名为main.c文件

7、创建完成

4.2 编写代码
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}

4.3 编译代码
选择生成,然后选择生成解决方案就可以完成代码编译

编译成功后,在输出栏会看到如下现象,表明编译成功

4.4 运行代码
选择调试,然后选择开始执行(不调试),就可以执行当前代码

实验现象:会在调试控制台输出“Hello World!”字样

五、C语言基础
5.1 程序结构
// 头文件:引入标准输入输出库(printf函数需要这个库支持)
#include <stdio.h>
// 主函数:程序的唯一入口,所有C程序都从main()开始执行
int main()
{
// 输出语句:向控制台打印字符串,\n表示换行
printf("Hello World!\n嵌入式C语言入门第一步\n");
// 返回值:0表示程序正常结束,非0表示异常(嵌入式中可作为错误码)
return 0;
}
对于C语言来说,他是一种编程语言,在用户编写完代码后,并且通过编译器将其编译为了可执行程序,则该程序是可以运行起来的;在程序中可能会非常多的代码/函数,但是基本的C语言程序的主要结构是由以下几部分组成:
1、头文件:#include <stdio.h>,该语句的功能是在当前源代码中华包含stdio.h这个文件;
<1> #,标志当前该操作是一个预处理操作,只要操作前带#都是预处理操作
<2> include,本身是包含的意思,是属于C语言的指令,目的是在当前文件中包含include后面的文件
<3> stdio.h,是一个头文件,C语言标准的输入和输出头文件
std->standard,标准的
i->input,输入
o->output,输出
因为C语言本身只有32个关键字,9种控制语句,34个运算符;如果要通过C语言来实现输入和输出功,则必须要使用C库;C库也叫做CLib,是C语言提供的库函数;
<4> <>,表示按照何种方式来索引当前stdio.h文件;
因为stdio.h是C库的头文件,所以该文件在文件系统中是存在的;对于C库的头文件都是在/usr/include路径中
<>:<stdio.h>,指的是从系统路径中查找该文件的位置
"":"stdio.h",指的是先从当前工程路径中查找该文件的位置,如果没有再从系统路径中华查找
注意1:C库的所有的头文件都是保存在系统路径下的

注意2:头文件主要分两类
<1> C库提供的头文件:stdio.h string.h stdlib.h等,一般存放于系统路径下;< > “ ”包含;
<2> 用户自定义的头文件:一般存放于和当前main.c相同的路径下;“ ”包含;
总结:
C库提供的头文件包含方式:
<1> #include <stdio.h> //推荐使用<> -> #include <C库头文件>
<2> #include "stdio.h"
用户自定义的头文件包含方式:
<1> #include "main.h" -> #include "用户自定义头文件"
2、main()函数
C语言的程序是由非常多的函数组成,但是main()非常特殊的函数,一个C语言程序中必须而且有且只能由有一个main(),在C程序中main()是当前程序的入口;一个C程序执行从main{ 到main }结束;
/**************** 第1种写法 ****************/
int main(void) //推荐使用第一种写法
{
函数体;
}
/**************** 第2种写法 ****************/
void main(void)
{
函数体;
}
/**************** 第3种写法 ****************/
main(void) //main()默认的返回值类型就为int
{
函数体;
}
/**************** 第3种写法 ****************/
main()
{
函数体;
}
3、语句
在编写C程序时,一行一般可以写多条语句,但是为了方便查看,推荐一行只写一条语句;而且每条语句必须以分号结尾;
5.2 文件类型
C语言的代码文件主要有2种文件类型:
<1> 源文件:一般以.c为后缀名,比如main.c Func.c Array.c Led.c等;源文件一般存放函数的定义,具体业务逻辑的实现;
<2> 头文件:一般以.h为后缀名,比如main.h stdio.h Array.h string.h等;代码的实现一般不放头文件,头文件中存放的内容一般是函数声明、数据类型的定义、宏定义等;
5.3 注释方法
在C语言中添加注释,主要为了方便自己或者其他用户查看代码;在写函数或者是关键代码时,一定要记得添加注释;注释不属于代码本身,注释一般在预处理阶段就被删除了;
1、行注释:只能注释一行
//这是行注释
/*这也是行注释*/
2、块注释
/* 起点
* @brief Initializes the GPIOx peripheral according to the specified parameters in the GPIO_InitStruct.
* @param GPIOx: where x can be (A..K) to select the GPIO peripheral for STM32F405xx/407xx and STM32F415xx/417xx devices
* x can be (A..I) to select the GPIO peripheral for STM32F42xxx/43xxx devices.
* x can be (A, B, C, D and H) to select the GPIO peripheral for STM32F401xx devices.
* @param GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that contains
* the configuration information for the specified GPIO peripheral.
* @retval None
*/ 终点
六、输出函数
头文件:
#include <stdio.h>
函数原型:
int printf(const char *format, ...)
函数功能:
发送格式化数据format输出到标准输出设备(stdout)。
函数参数:
【1】format,字符串,表示需要输出到标准输出设备的数据;
【2】...,当前函数的参数可变;
函数返回:
成功,返回输出到标准输出设备的字符个数;
失败,返回-1;
函数说明:
<1> C语言中变量的定义:数据类型 变量名;
定义一个整型变量Num,保存178; int Num = 178;
定义一个浮点型变量Num,保存178.98; float Num = 178.98;
<2> 一个函数可能会有多个参数,也有可能没有;如果一个函数的参数没有,则直接用void表示;如果函数中存在了多个参数,则参数与参数之间需要用,进行隔离;
int main(void) -> ()中就是main函数的参数,void->main函数没有参数;
int Func(int Num) -> Func函数就有一个参数,参数是Num;
int Array(int A,int B) -> Array函数就有2个参数,参数1是A,参数2是B;
<3> printf()函数的第一个参数一定是一个字符串,表明输出的数据是什么;
字符串:"hello world"
字符:'A' 'B' 'c' '8'
使用方式1:直接使用printf函数输出“hello world”;函数参数只有1个;
使用方式2:使用printf函数输出1个数值;
使用方式3:使用printf函数同时输出多个数据;
#include <stdio.h>
//printf(参数1-字符串,参数2,参数3,......);
int main(void)
{
//1-输出:hello world
printf("------------ Demo1 -----------\n");
printf("hello world\n");
//注意:“”中的内容并非都会原样输出,除了转义字符和格式控制符其他部分原样输出
// \n就是转义字符---表示换行
printf("------------ Demo2 -----------\n");
//2-输出:198 Num=388
int Num=388; //定义了一个变量Num,Num的值为388
printf("%d\n",198); //%d:整型格式控制符-替换后198整型数据
printf("%c\n",'A'); //%c:字符型格式控制符-替换后'A'字符数据
printf("%f\n",3.1415);//%f:符点型格式控制符-替换后3.1415符点型数据
printf("Num=%d\n",Num);
printf("------------ Demo3 -----------\n");
//3-同时输出多个不同的数值:%d-188 %c-'B' %f-1.156 %s-"NanJing"
printf("[1]%d [2]%c [3]%f [4]%s\n",188,'B',1.156,"NanJing");
}
使用说明:
<1> printf()函数至少会有一个参数就是参数1,主要用于输出数据;
<2> printf()函数的参数1是待输出的内容,其他参数都会被参数1中的格式控制符替换;
%d-整型数值/整型变量
%c-字符数值/字符变量
%f-小数数值/浮点型变量
%s-字符串
<3> printf()函数的参数1中除了格式控制符和转义字符外,其他内容全部输出;
格式控制符匹配后面参数的数据;转义字符是具有特殊功能的字符,如换行;
七、输入函数
头文件:
#include <stdio.h>
函数原型:
int scanf(const char *format, ...)
函数功能:
从标准输入设备(stdin),按照format格式获取数据;
函数参数:
【1】format,字符串,表示要从标准输入设备获取的数据;
【2】...,当前函数的参数可变;
函数返回:
成功,该函数返回成功匹配和赋值的个数。
如果到达文件末尾或发生读错误,则返回 EOF。
使用方式1:从键盘获取数值给到变量Num1;
//1-向变量写入数值
int Num1; //定义了变量Num1,该变量保存整型值
printf("Input Num1:>");
//从标准输入设备获取%d类型的数据写如Num的空间中
scanf("Num1=%d",&Num1); //%d匹配0-9,+,-
printf("Num1=%d\n",Num1);
使用方式2:从键盘获取数值给到变量Num1和Num2;
//2-同时输入2个变量的值
int Num1,Num2;
printf("Input Num1,Num2:>");
scanf("Num1=%d Num2=%d",&Num1,&Num2);
printf("Num1=%d Num2=%d\n",Num1,Num2);
注意:在使用scanf()时,可能会存在的问题;存在的问题一般是换行符遗留问题;
解决方案就是在第一次输入完成后调用getchar()从输入缓冲区将换行提取出来;

scanf()输入流程:

八、缓冲区
8.1 概念
缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
为什么要引入缓冲区?
比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。
缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。
8.2 缓冲区类型
缓冲区分为三种类型:全缓冲、行缓冲和不带缓冲。
-
全缓冲 在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。一般磁盘文件是全缓冲的(缓冲区一般为4096个字节)。
-
行缓冲 在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是标准输入(stdin)和标准输出(stdout)。注意:换行符也被读入缓冲区。(缓冲区一般为1024个字节)
-
不带缓冲 也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
8.3 缓冲区特征
1、缓存特征
ANSI C( C89 )要求缓存具有下列特征: 当且仅当标准输入和标准输出并不涉及交互设备时,它们才是全缓存的。 标准出错决不会是全缓存的。
但是,这并没有告诉我们如果标准输入和输出涉及交互作用设备时,它们是不带缓存的还是行缓存的,以及标准输出是不带缓存的,还是行缓存的。
我们经常要用到标准输入输出流,而ANSI C对stdin、stdout和stderr的缓存特征没有强行的规定,以至于不同的系统可能有不同的stdin、stdout和stderr的缓存特征。目前主要的缓存特征是:stdin和stdout是行缓存;而stderr是无缓存的。
2、缓冲区的大小
如果我们没有自己设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲区的大小通常是4096个字节的大小,这和计算机中的分页机制有关,因为进程在计算机中分配内存使用的就是分页与分段的机制,并且每个页的大小是4096个字节,因此通常情况下缓冲区的大小会设置为4096个字节的大小。
8.4 输出行缓冲条件
1、缓冲区满了,缓冲区的数据会输出到显示屏
2、遇到\n
3、程序正常结束,缓冲区的数据在程序结束前输出到显示屏
4、调用fflush(),强制刷新缓冲区更多推荐

所有评论(0)