1.IDE开发工具:
1.1.常用开发工具很多,本次学习用的是Visual Studio,官网直接下载,免费使用,下载安装方便。
2.C语言概述
2.1.概述:
通俗来说就是一种机器语言,人与机器交互的工具。C语言通过编译,生成机器可以读懂的语言,通过执行相应的命令,从而实现我们想要的功能。
2.2.C语言程序结构
程序是由一个或者多个函数组成
必须以main() 函数为入口,执行向对应的代码指令。程序开头可以引入C语言自带的包,或者开发者自己编写好的包,引入后可以直接调用包里的方法。
函数包含:声明(函数名称、返回类型、参数形式)和函数体(执行内容)
2.3计算机基础:
1.位、字节、字长的定义
1位 :1bit ,1kb=1024bit (2的10次方 1024)
1字节 = 8bit。
cpu一次并行处理的位数:字长。
比如64位计算机,就是一次可以并行处理16位
2.西文编码:从ASCII码到字符显示
ASCII码:就是用二进制编码表示字符(7位或者8位),实现了字符以二进制形式存在到计算机内存中。字符编码解决了字符的存储问题,但是没有解决字符的显示问题,需要有字符的字型数据。
字形数据:点阵数据(8*8点阵)、矢量数据(笔画)
3.汉字字符编码
西文字符,用一个字节进行编码。
国标码:GB23120-80 1980年颁发 (4位16进制数表示) 6千多汉字
字形码:点阵数据(16*16点阵)、矢量数据(笔画)
现在计算都是用
GBK编码:对GB23120-80进行了补充,2001年颁布了GB18030 可收录2.6万个汉字。
4.计算机的硬件系统
组成:输入设备(键盘、鼠标 等)+处理设备/单片机(存储器、运算器、控制器)+显示设备(显示器、打印机、投影仪等)
存储器:内存储器(ROM只读存储器 RAM随机存储器)、外存储器
RAM:SRAM高速缓存、 DRAM内存条
外存储器:硬盘、U盘、光盘、软盘
显卡:数据信号转换成模拟信号。
3.C语言-数据和C
3.1.示例程序:
#include <stdio.h> //引入包 //主函数 返回int类型 参数类型 为空 void butler(void); void getyour_val(void); int main(void) { int num; //定义变量 参数类型 变量名称 num = 1; //变量赋值 printf("调用了stdio包里的打印函数 %d \n" , num); //打印函数 %d 为占位符,用后面参数代替 getyour_val(); return 0; //函数返回值 } void getyour_val() { float weigth; float val_n; printf("请按kg为单位,输入您的体重 \n"); scanf_s("%f", &weigth); //接收输入参数 val_n = weigth * 980.4*1000; printf("与你体重相同黄金价格是:%f 元\n", val_n); }执行效果:
3.2.变量与常量
程序要完成各项任务,就需要使用数据:即承载信息的数字和字符。
常量:有些数据类型使用之前就设定好了,整个程序执行过程中没有变化。
变量:有些数据类型在程序执行期间可能会被赋值或者改变,这些称为变量。
3.3.数据类型和关键字
| 关键字 | 说明 | 典型占用 | |
| 基本数据类型 | int | 整型 | 通常 4 字节 |
| char | 字符型(本质是小整数) | 1 字节 | |
| float | 单精度浮点型 | 4 字节 | |
| double | 双精度浮点型 | 8 字节 | |
| 类型修饰符 不单独使用 | short | 短整型(如 short int) | |
| long | 长整型(如 long int、long double) | ||
| signed | 有符号(默认即是有符号) | ||
| unsigned | 无符号(只表示非负数) | ||
| 常见组合示例:unsigned int、long long、unsigned char。 | |||
| 构造类型(派生类型) | struct | 结构体,成员内存连续分配 | |
| union | 联合体/共用体,成员共享同一段内存 | ||
| enum | 枚举类型,定义命名的整数常量集合 | ||
| 类型定义与特殊类型 | typedef | 为已有类型起别名(如 typedef unsigned int uint;) | |
| void | 空类型,用于无返回值函数、无参数列表、void* 通用指针 | ||
| C99 / C11 标准新增的类型 | _Bool | C99 | 布尔类型(需包含 <stdbool.h> 后可用 bool) |
| _Complex | C99 | 复数类型 | |
| _Imaginary | C99 | 虚数类型 | |
| _Atomic | C11 | 原子类型(用于并发编程) | |
3.3.1 位、字节、字
位:最小存储单位:0/1
字节:1个字节为8位 256种情况
字:设计计算机给定的自然存储单位。
8位计算机:一次可以处理8个位的数据,也叫8字计算机。
64位计算机:一次可以处理64个位的数据,也叫64字计算机。
3.3.2整数和浮点数
整数:直接按二进制存储
浮点数:转成科学计数法,以二进制的形式,分开存在符号位、小数位和指数位
ps:浮点数因为存储方式的原因,在运算过程中可能会损失部分精度。
3.4.基本数据类型
【代码示例】
#include <stdio.h> #include <stdint.h> int main(void) { // 标准类型大小(32位MCU上) printf("char: %lu 字节\n", sizeof(char)); // 1 printf("short: %lu 字节\n", sizeof(short)); // 2 printf("int: %lu 字节\n", sizeof(int)); // 4 printf("long: %lu 字节\n", sizeof(long)); // 4 printf("long long: %lu 字节\n", sizeof(long long));// 8 printf("float: %lu 字节\n", sizeof(float)); // 4 printf("double: %lu 字节\n", sizeof(double)); // 8 // 嵌入式首选:固定宽度类型(stdint.h) int8_t a = -128; // 精确 8位 有符号 uint8_t b = 0xFF; // 精确 8位 无符号 int16_t c = 1000; // 精确 16位 uint32_t d = 0xDEADBEEF;// 精确 32位 return 0; }【代码讲解】
嵌入式开发中,int 在不同平台可能不同(8位单片机是16位,32位MCU是32位)。使用 <stdint.h> 中的固定宽度类型(int8_t/uint16_t等)可以消除移植隐患。
sizeof 是编译期运算符,返回类型或变量的字节数。常用来计算数组长度、结构体大小等。
char 在任何平台都是 1 字节,是 C 语言最小的寻址单位。
3.5.有符号和无符号混用陷阱
#include <stdio.h> #include <stdint.h> int main(void) { int32_t a = -1; uint32_t b = 2; // 危险!有符号会被隐式转换为无符号 if (a < b) { printf("-1 < 2\n"); } else { printf("-1 >= 2 (无符号比较结果!)\n"); } // 输出:-1 >= 2 (无符号比较结果!) // 因为 -1 转为 uint32_t 变成 0xFFFFFFFF // 正确写法:统一类型 if (a < (int32_t)b) { printf("正确:-1 < 2\n"); } return 0; }【代码讲解】
当 有符号 和 无符号 类型在同一个表达式中比较或运算时,有符号数会被隐式转换为无符号数。
-1 转为 uint32_t 后变成 0xFFFFFFFF(4294967295),远大于 2,导致逻辑错误。
这是嵌入式开发中极常见的 bug 来源,尤其在数组索引、循环条件中。
因此尽量用相同类型进行比较,或者无符号转成有符号进行比较
3.5.1.for循环的简单使用
#include <stdio.h> #include <stdint.h> int main(void) { int i = 10; //uint8_t i = 10; for ( i ; i >= 0; i--) { printf("输出数据i:%d \n", i); } return 0; }正常通过for循环,依次输出 10,9......0
如果i设置成uint8_t类型,再循环到0之后 会把-1被隐式转换为无符号数,下溢变成255,永远都是>=0,程序会进入死循环。
3.6.整数溢出与环绕
int main(void) { uint8_t i = 255; i = i+1; printf("输出上溢数据uint8_t 255+1:%d \n", i); //输出为 0 uint8_t ix = 0; ix = ix - 1; //没有下溢,因为-1不能赋值给无符号变量 printf("输出下溢数据uint8_t 0-1:%d \n", ix); //输出为 255 int8_t i2 = 127; i2 = i2 + 1; printf("输出上溢数据int8_t 127+1:%d \n", i2); //输出为 -128 int8_t i3 = -128; i3 = i3 - 1; printf("输出下溢数据int8_t -128-1:%d \n", i3); //输出为 127 // 嵌入式中的实际应用:计数器环绕 uint32_t timer = 0xFFFFFFFF; timer++; // 变成 0,定时器常用此特性 printf("输出上溢数据uint32_t timer :%d \n", timer); //输出为 0 return 0; }4.指针与地址操作
指针(Pointer) 本质上是一个变量,但它存储的不是普通数据,而是另一个变量的内存地址。
指针是 C 语言的灵魂,也是嵌入式开发的核心。寄存器访问、DMA 配置、内存映射、回调函数都离不开指针。
4.1.指针基础与取地址
int main(void) { uint32_t var = 120; uint32_t *p = &var; // p 指向 var 的地址 printf("var 的值: %d\n", var); printf("var 的地址: %p\n", (void*)&var); printf("p 指向的值: %d\n", *p); *p = 12; // 通过指针修改值 printf("修改后 var: %d\n", var); // 指针运算 uint16_t arr[4] = { 0, 1, 2, 3 }; uint16_t *q = arr; q++; // 指针存数组,指向的是arr[0]地址,p++后 就指向 arr[1] printf("arr[1] = %d\n", *q); printf("arr[1]地址 = %p\n", &*q); return 0; }1.指针定义: 类型 *指针名; 例: uint16_t *q
2.指针赋值: *指针名 = 内容(常量、地址、变量、数组 等);
例:*p=12;*p = &var;*p = var
3.获取指针:*指针名 或者 指针名,例:*p(指针存的内容)或p(内容存的地址)
4.2.指针与数组的关系
数组名 buf 在表达式中会退化为指向首元素的指针;
数组赋值给指针后(p= buf; // 不用 &,buf 本身就是地址),两者可以看成是同一个数组指针。
我们可以获取数组内容,也可以或者到每个数组成员的地址。
#include <stdio.h> #include <stdint.h> #include <stdlib.h> int main(void) { uint16_t buf[11] = { 0,1,2,3,4,5,6,7,8,9,10}; // 数组名在大多数语境下退化为指针 uint16_t *p; p = buf; // 不用 &,buf 本身就是地址 printf("sizeof(buf) %zu \n", sizeof(buf)); //22 数组所有元素的字节 printf("sizeof(p) %zu \n", sizeof(p)); //8 指针字节 64位系统 uint16_t指针 8字节,32位系统则是4 printf("sizeof(p) %zu \n", sizeof(p[0])); //2 元素字节 int n = sizeof(buf) / sizeof(buf[0]); printf("arr_len(buf) %d \n", n); // 11 元素个数 // 以下三种方式完全等价 printf("%d\n", buf[3]); // 下标访问 printf("%d\n", *(buf + 3)); // 指针偏移+解引用 printf("%d\n", 3[buf]); // 等价写法(少用) p[5] = 99; // 通过指针下标赋值 printf("buf[7] %d\n", buf[7]); printf("通过指针下标赋值后的结果 buf[5] %d\n", buf[5]); //获取地址 printf("p %p\n", p); printf("&p[0] %p\n", &p[0]); printf("buf %p\n", buf); printf("&buf[0] %p\n", &buf[0]); return 0; }