戒骄戒躁: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=2+(−2)

    • 正确性验证:

      • 2_{补码}=00000010
      • -2_{补码}=11111110
      • 相加得00000000(溢出位舍弃,结果正确
  • 硬件设计:

    • 算术逻辑单元(ALU)仅需加法器
    • 乘法/除法可通过加法迭代实现


特殊值:1000表示-8,既保证0的唯一性又节省硬件资源

排列规律:

  • 全1组合:有符号时为-1,无符号时为最大正数(4位时为15)
  • 负数补码值必然大于正数补码值(因占用高位组合)

2、存储角度分析

  • 表示范围优化:
    • 4位二进制示例:
      • 无符号数:0~15
      • 有符号补码:-8~7
  • 编码效率:
    • 补码消除+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 *