戒骄戒躁:C入门,51和基础硬件知识铺垫,STM32和RTOS深入,再分层技术路线由深入浅:先学linux应用开发-》linux底层固件或者驱动开发,进阶就是FPGA开发资源较少。还有就是硬件开发可以慢慢补充。
[TOC]
最近还在忙着跑实验写文章,只能抽着时间学学,快速过C语言基础的内容,此处整理C遗忘知识点。
第 1 章 常量
1.1 常量的概念
程序运行时,其值不能改变的量,即为常量。
1.2 常量的分类
字面量常量
标识符常量
1.3 标识符常量的定义方法
第一种方式 宏定义方式
#define 常量名 值
注意:不能加分号!!!
第二种方式 const 关键字方式
const 数据类型 常量名 = 值;
数据类型 const 常量名 = 值;
两种方式的区别
**宏定义纯文本性质:**宏定义只是简单的文本替换,如#define MAX 1000会被直接替换为1000,不关心数据类型
宏定义调试困难:因为预处理阶段就已替换,调试时无法追踪原始宏名

1. 类型检查
#define 方式不能进行类型检查
const 关键字方式可进行编译期类型检查
2. 执行时机不同
#define 方式在预处理阶段进行文本替换,预处理后即不存在内存实体
const 关键字方式,在程序执行的时候,在内存中分配空间
第 2 章 原码、反码、补码

机器数:机器真实存储的二进制表示
形式值:不考虑符号位
真值:把最高位当符号位
1.原码
表示规则:与机器数真值表示相同,第一位表示符号,其余位表示数值
正数规则:直接对应二进制数(如+1原码为00000001)
负数规则:绝对值对应二进制数且最高位变1(如-1原码为10000001)
零的表示:原码仍为0
位数约定:需预先确定使用的二进制位数(常用8/16/32/64位)
2.反码
正数规则:与原码相同(+1反码仍为00000001)
零的表示:反码仍为0
负数规则:
符号位保持不变
其余各位取反(如-1原码10000001→反码11111110)
设计目的:为补码计算服务,本身无独立应用价值
3.补码
正数规则:与原码、反码相同(三码合一)
零的表示:补码仍为0
负数规则:在反码基础上加1(如-1反码11111110→补码11111111)
核心特性:计算机内部实际存储整数的方式(特别是负数)
表示范围:以8位为例,有符号数范围为-128到127
补码计算器:https://www.lddgo.net/convert/number-binary-code
2.1 计算机为什么使用补码
1、计算角度分析
-
核心问题:原码运算会导致符号位干扰
- 例:2+(-2)用8位原码计算:
- 2(原码)=00000010
- -2(原码)=10000010
- 相加得10000100(对应十进制-4,错误结果)
- 例:2+(-2)用8位原码计算:
-
补码优势:
-
统一加减法运算:减法转为加负数(
2−2=2+(−2)
-
正确性验证:
- 2_{补码}=00000010
- -2_{补码}=11111110
- 相加得00000000(溢出位舍弃,结果正确)
-
-
硬件设计:
- 算术逻辑单元(ALU)仅需加法器
- 乘法/除法可通过加法迭代实现

特殊值:1000表示-8,既保证0的唯一性又节省硬件资源
排列规律:
- 全1组合:有符号时为-1,无符号时为最大正数(4位时为15)
- 负数补码值必然大于正数补码值(因占用高位组合)
2、存储角度分析
- 表示范围优化:
- 4位二进制示例:
- 无符号数:0~15
- 有符号补码:-8~7
- 4位二进制示例:
- 编码效率:
- 补码消除+0/-0歧义(原码中0000和1000都表示0)
- 连续编码:负数补码比原码大1(如-1补码为1111,原码为1001)
浮点数存储原理
根据IEEE754标准,浮点数在计算机内部分成符号S、指数E、尾数M三部分,分别以二进制的形式进行存储,其中S取0或者1(0表示正数,1表示负数),M要求大于等于1且小于10。如数字 120.45 用科学计数法表示是 1.2045*10^2,所以,S=0,E=2,M=1.2045。
float 类型是32位浮点数,最高的1位是符号位S,接着用8位表示指数E,剩下的23位表示尾数M。

double 类型是64位浮点数,最高的1位是符号位S,接着用11位表示指数E,剩下的52位表示尾数M。
第 3 章 数据类型和运算符
3.1 获取数据的存储大小
使用sizeof 可以获取数据类型或变量、字面量的存储大小,单位是字节。sizeof返回一个size_t类型的无符号整数值,格式占位符是 %zu。
#include <stdio.h>
int main()
{
// 计算数据类型的大小, 必须使用括号将数据类型关键字包裹起来
printf("char:%zu \n", sizeof(char)); // char:1
printf("short:%zu \n", sizeof(short)); // short:2
printf("int:%zu \n", sizeof(int)); // int:4
printf("long:%zu \n", sizeof(long)); // long:4
printf("long long:%zu \n", sizeof(long long)); // long long:8
printf("float:%zu \n", sizeof(float)); // float:4
printf("double:%zu \n", sizeof(double)); // double:8
printf("long double:%zu \n", sizeof(long double)); // long double:16
printf("\n");
// 计算字面量数据的大小,字面量可以省略括号
printf("%zu \n", sizeof('a')); // 4,在 C 语言中,字符常量(如 'a')的类型是 int,而不是 char
printf("%zu \n", sizeof(431)); // 4
printf("%zu \n", sizeof 4.31); // 8
printf("\n");
// 计算变量的大小,变量可以省略括号
char a = 'A';
int b = 90;
long long c = 100;
double d = 10.8;
printf("a: %zu \n", sizeof(a)); // a: 1
printf("b: %zu \n", sizeof b); // b: 4
printf("c: %zu \n", sizeof(c)); // c: 8
printf("d: %zu \n", sizeof(d)); // d: 8
return 0;
}
3.2 数据类型转换
自动类型转换(隐式转换)
1、转换规则
(1)不同类型整数进行运算,窄类型整数自动转换为宽类型整数,有符号整数转换为无符号整数。
(2)不同类型浮点数进行运算,精度小的类型自动转换为精度大的类型。
(3)整数与浮点数进行运算,整数自动转换为浮点数。
2、赋值过程中的自动类型转换
在赋值运算中,赋值号两边量的数据类型不同时,等号右边的类型将转换为左边的类型。
如果窄类型赋值给宽类型,不会造成精度损失;如果宽类型赋值给窄类型,会造成精度损失。
#include <stdio.h>
int main()
{
// 赋值 窄类型赋值给宽类型
int a1 = 10;
double a2 = a1;
printf("%f \n", a2);
// 赋值 宽类型赋值给窄类型
double b1 = 1.2;
int b2 = b1;
printf("%d", b2);
return 0;
}
输出结果:
10.000000
1
3.3 强制类型转换(显式转换)
1)转换格式
(类型名)变量、常量或表达式
3.4 数据类型转换
1、取模(取余)
a % b 的结果 符号永远与被除数(a)一致
#include <stdio.h>
int main()
{
int res1 = 10 % 3;
printf("%d\n", res1);
int res2 = -10 % 3;
printf("%d\n", res2);
int res3 = 10 % -3;
printf("%d\n", res3);
int res4 = -10 % -3;
printf("%d\n", res4);
return 0;
}
输出结果:
1
-1
1
-1
注意:
(1)取模运算符的操作数必须是整数。
(2)运算结果的符号与被模数也就是第一个操作数相同。
2、自增和自减
自增、自减运算符在前在后,对于表达式的值是不同的。
如果运算符在前,表达式的值是操作数自增、自减之后的值;如果运算符在后,表达式的值是操作数自增、自减之前的值。
#include <stdio.h>
int main()
{
int i1 = 10, i2 = 20;
int i = i1++;
printf("%d\n", i); // 10
printf("%d\n", i1); // 11
i = ++i1;
printf("%d\n", i); // 12
printf("%d\n", i1); // 12
i = i2--;
printf("%d\n", i); // 20
printf("%d\n", i2); // 19
i = --i2;
printf("%d\n", i); // 18
printf("%d\n", i2); // 18
return 0;
}
3.5 逻辑运算符
1)逻辑与 &&
(1)如果两个操作数都为真(非零),那么表达式的值为真,否则为假。
(2)如果第一个操作数为假,第二个操作数没有计算的必要了,这种现象称为短路现象。
2)逻辑或 ||
(1)只要有一个操作数为真,表达式的值就为真;两个操作数都为假,表达式的值为假。
(2)如果第一个操作数为真,第二个操作数没有计算的必要了,这种现象称为短路现象。
3.6 位运算符
| 运算符 | 描述 | 操作数个数 | 副作用 |
|---|---|---|---|
| & | 按位与 | 2 | 无 |
| | | 按位或 | 2 | 无 |
| ^ | 按位异或 | 2 | 无 |
| ~ | 按位取反 | 1 | 无 |
| << | 按位左移 | 2 | 无 |
| >> | 按位右移 | 2 | 无 |
注意:操作数进行位运算的时候,以它的补码形式进行运算。
1)按位与、按位或、按位异或
#include <stdio.h>
int main()
{
int a = 17;
int b = -12;
printf("a&b=%d\n", a & b); // a&b=16
printf("a|b=%d\n", a | b); // a|b=-11
printf("a^b=%d\n", a ^ b); // a^b=-27
return 0;
}
输出结果:
a&b=16
a|b=-11
a^b=-27

2)按位取反
#include <stdio.h>
int main()
{
int a = 17;
int b = -12;
// 按位非
printf("~a=%d\n", ~a);
printf("~b=%d\n", ~b);
return 0;
}
输出结果:
~a=-18
~b=11

3)按位左移、按位右移
#include <stdio.h>
int main()
{
int a = 17;
int b = -12;
// 按位左移
printf("a<<2=%d\n", a << 2); // a<<2=68
printf("b<<2=%d\n", b << 2); // b<<2=-48
// 按位右移
printf("a>>3=%d\n", a >> 3); // a>>3=2
printf("b>>3=%d\n", b >> 3); // b>>3=-2
return 0;
}

3.7 三元运算符
1)基本语法
条件表达式? 表达式1: 表达式2;
2)表达式最终取值
(1)如果条件表达式为非0(真),整个表达式的值是表达式1;
(2)如果条件表达式为0(假),整个表达式的值是表达式2;
#include <stdio.h>
int main()
{
int a = 10;
int b = 99;
int res = a > b ? a++ : b--;
int n1 = a > b ? 1.1 : 1.2;
printf("a=%d \n", a);
printf("b=%d \n", b);
printf("res=%d \n", res);
return 0;
}
输出结果:
a=10
b=98
res=99
计算三个数的最大值:
int max=(a>b? a:b)>C? (a>b? a:b):c
第 4 章 程序控制语句
4.1 多向分支switch
switch (表达式)
{
case 常量值1:
语句块1;
break;
case 常量值2:
语句块2;
break;
case 常量值n:
语句块n;
break;
default:
语句块n + 1;
}
说明:
(1)switch后面表达式的值必须是一个整型(char、short, int, long等)或枚举类型。
(2)case后面的值必须是常量,而不能是变量。
(3)default是可选的,当没有匹配的case时,执行default。
(4)break语句可以使程序跳出switch语句块,如果没有break,会执行下一个case 语句块,直到遇到break或者执行到switch结尾,这个现象称为穿透。
//编写程序,输入月份,输出该月份有多少天。
//说明:1月、3月、5月、7月、8月、10月、12月有31天,4月、6月、9月、11月有30天,2月有28 天或 29天。
#include <stdio.h>
int main()
{
// 定义变量记录月份
int month;
printf("请输入月份:");
scanf("%d", &month);
// 进行 switch 判断
switch (month)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
printf("%d 月有 31 天!", month);
break;
case 4:
case 6:
case 9:
case 11:
printf("%d 月有 30 天!", month);
break;
case 2:
printf("%d 月有 28 天或 29 天!", month);
default:
printf("请输入正确的月份!");
}
return 0;
}
4.2 跳转控制语句
1、 break
break语句用于终止某个语句块的执行,用在switch语句或者循环语句中。
2、goto 语句
goto语句是一种跳转语句,它允许程序控制流程在代码中跳转到带有标签(label)的语句处,标签(label)的名称可以自行设置,需要满足标识符规范。
注意,我们在开发中不建议使用goto语句,但我们需要掌握 goto 语句的执行流程,以能够看懂其他开发者的代码中可能出现的 goto 语句。
1)基本语法
//goto 标签名 // 跳转到指定的标签(label)处
//...
//标签名: // 定义一个标签(label)
//
//语句;
#include <stdio.h>
int main()
{
printf("start \n");
goto label1; // label1是标签名
printf("ok1 \n");
printf("ok2 \n");
label1:
printf("ok3 \n");
printf("ok4 \n");
return 0;
}
输出结果:
start
ok3
ok4
注意:goto 后面如果引用了没有定义的标签,编译器会报错!
附录
常用格式占位符速查表
(1)整数
| 格式占位符 | 含义 | 对应类型 |
|---|---|---|
| %d | 十进制有符号整数 | int |
| %u | 十进制无符号整数 | unsigned int |
| %hd | 十进制有符号整数 | short |
| %hu | 十进制无符号整数 | unsigned short |
| %ld | 十进制有符号整数 | long |
| %lu | 十进制无符号整数 | unsigned long |
| %lld | 十进制有符号整数 | long long |
| %llu | 十进制无符号整数 | unsigned long long |
| %x**、%X** | 十六进制无符号整数 | unsigned int |
| %#x | 显示前缀 0x 的十六进制整数,用于输出。 | unsigned int |
| %#X | 显示前缀 0X 的十六进制整数,用于输出。 | unsigned int |
| %zu | 输出数据的字节长度 | size_t |
(2)浮点
| 格式占位符 | 含义 | 对应类型 |
|---|---|---|
| %f | 浮点数的常规表示 | float、double |
| %lf | 浮点数的常规表示 | double |
| %Lf | 浮点数的常规表示 | long double |
| %e | 浮点数的科学计数法表示 | double、float |
| %le | 浮点数的科学计数法表示 | double |
| %Le | 浮点数的科学计数法表示 | long double |
(3)字符和字符串
| 格式占位符 | 含义 | 对应类型 |
|---|---|---|
| %c | 字符 | char |
| %s | 字符串 | char *、char[] |
(4)其他
| 格式占位符 | 含义 | 对应类型 |
|---|---|---|
| %p | 输出地址,通常以十六进制表示 | void * |