c语言个人笔记

linux嵌入式C语言

课程链接: [史上最强最细腻的linux嵌入式C语言学习教程李慧芹老师]


0. gcc与vim的使用

gcc

指令

-Wall:显示所有警告

gcc默认的警告包括真正的错误:error和 告警warning

执行过程

c源代码.c -> 预处理^(E)^ -> 编译^(S)^ -> 汇编^©^.o -> 链接 -> 可执行文件(默认a.out)

带#都在预处理阶段完成

vim

vim的配置

默认全局参数在/etc/vim/vimrc中,一般建议在家目录拷贝一份命名为.vimrc,以生成只对本用户生效的配置,不干扰其他用户

快捷键

ctrl + p 可以补全声明过的代码
shift + k跳转到函数MAN解释
vim * -p以vim打开当前目录下所有文件(gt )


1.基本概念

  • 算法:解决问题的方法(流程图,NS图,有限状态机FSM)
  • 程序:用某种语言实现算法
  • 进程
  • 防止写越界,防止内存泄漏,谁打开谁关闭,谁申请谁释放

编程注意事项

  • 建议常用头文件stdlib.h
  • main函数建议用exit(0)替换return 0
  • c语言中,如果一个函数没有找到其原型,编译器会 默认认为其返回int int * p= (int *) malloc(sizeof(int)),强转可能会掩盖问题^1^
  • printf()是有返回值的
  • 大段注释用#if 0#endif
c 复制代码
#if 0
被注释掉的内容
#endif

  • 注释尽量用英文,防止中文标点符号乱入混淆

2. 数据类型,运算符和表达式

  数据类型:(基本数据类型参考C语言数据类型)
  基本数据类型
  所占字节数
      存储区别
      不同类型的数据间进行转换 (隐式,显式->强制类型转换)
    特殊性:
    1.  布尔型bool
    2.  float类型
    3.  char型是否有符号
    4.  不同形式的值:0,'0','\0'
    5.  数据类型与后续代码中所使用的输入输出要相匹配

常规计数方式

  • 254 表示十进制
  • B1111110 表示二进制
  • 0376 表示八进制
  • 0xFE 表示十六进制

数据的存储

整型

整形的存储都是以补码 形式存储的

正数的补码是它本身 ,负数补码为其绝对值的补码取反+1

浮点型

浮点型的存储方式

表示为:0.314 * 10^1^

让整数部分为零,只在意精度部分(小数部分) 和 指数部分

如图float类型为例,0-22位表示小数部分 ,22-30位表示指数 ,31表示符号位

double类型多出的32位全部用于精度控制

精度问题
  • float只表示一个大概的范围,是一个近似值
  • 一个浮点型不可能绝对的等于一个整型

浮点型有位数限制,不可能无限精确

c 复制代码
float f=1;
int Fun(float f)
{
  if(f < 0) return 1;
  else if(if==0) return 0;
  else return -1;//结果返回-1
}

解决方法:

c 复制代码
fabs(f-0)<=1e-6 //等价于f==0 
char型

char型也以补码 的形式存储

c语言中默认char有无符号是不确定的

bool类型
  • 头文件 stdbool.h

数据类型转换

  • 小转大精度增加数据不丢失,大转小丢失数据且不会进行四舍五入
  • 注意%d是按照有符号整型打印的,可能会丢失数据
隐式转换

不同数据类型进行运算,默认向精度比较高的数据类型靠拢

c 复制代码
  int i;
  float f
  i + f -> res
  //int float -> float
显式转换(强制类型转换)

变量与常量

常量:在程序执行过程中值不会发生变化的
分类:整型常量,实型常量,学符常量,学符常量,标识常量
    整型常 :1,790,76,52                 
    实型常量:3.14,5.26   1.9999
    字符常量:由单引号引起来的单个的字符或转义字符,如'a','\n'
    字符串常量:由双引号引起来的一个或多个字符组成的序列,如:"a"
    标识常量:`#define 宏名 宏体` ,在程序执行过程中用宏体替换宏名,占用编译时间,不检查语法,只是单纯替换
变量:用来保存一些特定内容,并且在在程序执行过程中值随时可能变化的量

注意:

  • '\015':把该数当八进制看
  • '\x7f': 把该数当作十六进制看
  • "":空串,只有一个\0
  • \018 因为八进制里不可能出现8,所以表示\0和1和8
  1. 最基础用法#define PI 3.14
  2. 表达式 #define MAX(a,b) a>b?a:b

当传入MAX(i++,j++)时,比较时错误,大的那一项会自增两次,解决方法:用变量接收

解决:

c 复制代码
#define MAX(a,b) \
                ({typeof(a) A=a,B=b;((A)>(B)?(A):(B));})//非标准c
                 //用a的类型定义A,B,类似c++里的auto和decltype

errno

 [C 库宏 - errno](https://www.runoob.com/cprogramming/c-macro-errno.html)

perror()

c 复制代码
void perror(const char *str)

在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和errno所对应的错误一起输出。

C 库函数 - perror()

定义

TYPE NAME =VALUE;

标识符:由字母,数字,下划线组成不能以数字开头的一个表示序列

要求写标识符尽量见名生义

数据类型:基本数据类型+构造类型

存储类型:auto static register extern(说明型)

  • auto:默认,自动分配空间,自动回收空间
  • register :建议型,寄存器类型(既少又快)

大小有限制,只能定义32位大小的数据类型,寄存器没有地址,所以一个寄存器类型的变量无法打印出地址查看或使用

  • static:静态型,自动初始化为0或空值,存放在静态区,生命周期和程序一致,并且其变量的值有继承性,只定义一次,防止变量/函数对外扩展,常用于声明变量和函数,如果想在外部进行调用,需要get函数
  • extern:说明型,意味不能改变被说明的变量的值
变量的生命周期和作用和范围
  • 全局变量作用与定义开始知道程序结束
  • 变量定义遵循就近原则
  • 全局变量使用要谨慎(errno要马上打印,否则可能不是预期值)

运算符和表达式

  • 表达式与语句的区别
  • 运算符部分:
    1. 每个运算符所需要的参与运算的操作数个数
    2. 结合性
    3. 优先级
    4. 运算符的特殊用法
    5. 位运算的重要意义
      tips:
  • %:左右必须为整型
c 复制代码
printf("1 divide 2 equals : %d",1/2);//结果为 0
运算符

1.自增自减运算符

赋值表达式返回值为所赋值的对象

c 复制代码
    char a;
    char ret=(a='\0');

    printf("a='\\0' 表达式的返回值是: %c\n",ret);//ret='\0'
  1. 逻辑与-短路特性
c 复制代码
int m=1,n=1,a=1,b=2,c=3,d=4;
(m=a>b)&& (n=c>d)

只有&&左边为真才会执行右边语句,如果左边为假,不执行右边语句

强制类型转换
  • 只是中间过程,不会让被强转的数赋值
位运算

3. 输入,输出专题

input & output -> I/O

  • 标准IO(系统调用IO)
  • 文件IO

1. 格式化的输入输出函数:scanf,printf

printf:

"printf(%[修饰符]格式字符",输出表项)~

  • %f 默认输出保留六位小数,不够补零
  • 注意数据溢出(加单位)
  • 注意加上\n,牵扯到缓冲区^2^,\n能刷新缓冲区
  • 返回值是打印个数+1
scanf:

scanf有返回值,把scanf放到循环内部一定要慎重,注意核验scanf返回值,否则可能无法接收到有效内容

  • %s 是比较危险的,因为不知道存储空间大小

2. 字符串输入输出函数:getchar,putchar

抑制符:例如两个连续换行输入,用getchar吃掉换行符

出错都返回EOF

3. 字符串输入输出函数:gets(危险),puts

gets

危险原因:

  • 不会进行溢出检查

标准库替代品--fgets()

治标不治本,不一定保证我们所输入的字符串都能完整输出,只是保证安全性

"方言"替代:GNC库提供 getline()

puts

输出自带换行符


4. 流程控制

C语言三大结构

  • 1 顺序,2 选择,3 循环
    1. 语句逐步执行
    2. 出现了一种以上情况
    3. 某个条件成立的情况下,重复执行某个动作
      简单结构与复杂结构:自然流程
      NS图,流程图 工具 - " Dia "

养成好习惯

  • 先理清结构

  • 慎用goto

    goto使用的是无条件的跳转,且不能跨函数调整,会影响代码的结构性

5.数组

  1. 定义:【存储类型】 数据类型 标识符[下标]
  2. 特点:在内存中连续存放

6.指针

数组名是地址常量,不能放在等号左边

c 复制代码
char str[]="hello";
str="world";//false

解决方式:strcpy(str,"world");

c 复制代码
char *str="hello";
strcpy(str,"hello");//false 因为字符指针指向了一个串常量,不允许修改
str="world";//true 可以让它指向另一块区域

const

指针常量:指向的值能修改,指针方向不可以修改

常量指针:指向的值不能修改,指针方向可以修改


7.函数

函数与一维数组

传参[]等价于*

函数与二维数组

  1. 传首地址相当于视作大数组
  2. 传参[]等价于*但是第二个[N]不能转换,因为它说明了数组列的大小

例如 :void print(int [][N],int row,int column);

或者 void print(int (*p)[N],int row,int column);

c 复制代码
#include <stdlib.h>
#include <stdio.h>
#define COLUMN 4
//int *a[COLUMN]是错误的,因为这样表示的是int **a
void print_arr(int (*a)[COLUMN],int R,int C)//表示指向数量为4的数组的指针
{
    for (size_t i = 0; i < R; i++)
    {
        for (size_t j = 0; j < C; j++)
        {
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }
}
int main()
{
    int arr[][COLUMN]={1,2,3,4,5,6,7,8,9,10,11,12};
    print_arr(arr,3,COLUMN);
    exit(0);
}

函数与字符数组


7.结构体

内存/地址 对齐

-如果要求不让编译器对齐 ,结构体尾部加上宏:__attribute__(packed)

结构体传形参开销大,一般传指针

共用体

  • 变量不能共存
  • 共用一块空间
  • 空间占用以最大为准
位域

大小端

"大小端"是指计算机系统中对多字节数据类型(如16位、32位或64位整数)存储时字节顺序的一种约定。主要分为两种不同的字节顺序格式:

  • 小端模式 (Little Endian):---x86
    在小端模式中,低序字节(即最低有效字节,LSB)被存放在内存中的低地址处,而高序字节(即最高有效字节,MSB)则被存放在内存中的高地址处。
    例如,对于一个16位的值0x1234,在小端模式下会被存储为:
    内存地址低字节:0x34
    内存地址高字节:0x12
  • 大端模式 (Big Endian):---51单片机
    在大端模式中,高序字节被存放在内存中的低地址处,而低序字节则被存放在内存中的高地址处。
    例如,对于同一个16位的值0x1234,在大端模式下会被存储为:
    内存地址低字节:0x12
    内存地址高字节:0x34

嵌套

  • 结构体共用体一种用法:把32位分成高位低位
c 复制代码
union
{
  struct
  {
    uint16_t i;
    uint16_t j;
  }x;

  uint32_t y;//无符号32位整型
}a;



a.y=0x11223344;//32位整型

printf("%x\n",a.x.i+a.x.j);//打印4466

因为共用体共用一块连续空间,32位占用的空间正好分配给两个十六位整型

枚举

常当作宏使用,不会在预处理阶段被替换


8. 动态内存管理

原则:谁申请谁释放

free只是不把指针分配给那块空间,不是把空间清空

如果free后再次对原指针赋值,会造成野指针,十分危险

所以一般在free后立刻把原指针置空

void*

windows环境下:void* 必须转成接收类型 才能接受

linux环境下:void*可以直接接收


指针作函数参数

指针传参证明
  • 指针传参,传的是形参
c 复制代码
void Func(int * p)
{
    printf("passing_p`s adress is :%p\n",&p);
    p=(int *)malloc(sizeof(int));
    *p=6;
}


int main()
{
    int *p;

    Func(p);
    if(p==NULL)
    {   
        
        printf("p为空指针,赋值错误\n");
        return 0;
    }
    printf("p`s adress is :%p\n",&p);
    free(p);
    return 0;
}

结果:passing_ps adress is :0061FF00 ps adress is :0061FF1C

二者地址不同,非同一指针

结果: 段错误^3^

原因:main函数里的p没有分配内存,证明函数里对p的操作没有影响到p本身

c 复制代码
void Func(int * p)
{
    printf("passing_p`s adress is :%p\n",p);
    *p=6;
}
int main()
{
    int *p;
    Func(p);
    if(p==NULL)
    {   
        printf("p为空指针,赋值错误\n");
        return 0;
    }
    printf("p`s adress is :%p\n",p);
    free(p);
    return 0;
}

结果:passing_p adress is :00213000 ; p adress is :00213000

二者指向地址一样,为同一数据


总结:形参都是值传递。但是这个值如果是指针的话,是可以改变指针指向内容的值,即实参的值。这个要弄清两个概念:指针和指针指向的数据。

解决方法:
  1. 传指针的地址

    结果:

    成功

  2. 返回值返回


9.重定义 typedefine

  • 和宏的区别
c 复制代码
#define int* P
typedef int* p;
P a1;//a1是int *
p a2;//a2是int *
//-----------------
P b1,b2;//b1是int *,b2是int
p c1,c2;//C1,c2都是int *

10.Makefile

工程管理器

Makefile的基本格式

bash 复制代码
target1 ...: prerequisites ...
	command   # 注意在命令前面需要添加一个Tab键
        ...
target2 ...: prerequisites ...
	command
        ...

  1. Linux环境下malloc本身返回的是void*,可以用任意类型指针接受,本身就不需要强转 ↩︎

  2. 缓冲区是一种存储数据的临时区域,通常用于暂时保存输入输出数据、网络数据或其他类型的数据,直到这些数据可以被处理或传输。 ↩︎

  3. 访问不存在的内存地址、访问系统保护的内存地址和访问只读的内存地址 ↩︎

相关推荐
青い月の魔女1 小时前
数据结构初阶---二叉树
c语言·数据结构·笔记·学习·算法
qq_589568101 小时前
node.js web框架koa的使用
笔记·信息可视化·echarts
最后一个bug2 小时前
STM32MP1linux根文件系统目录作用
linux·c语言·arm开发·单片机·嵌入式硬件
FeboReigns2 小时前
C++简明教程(4)(Hello World)
c语言·c++
FeboReigns2 小时前
C++简明教程(10)(初识类)
c语言·开发语言·c++
stm 学习ing2 小时前
HDLBits训练6
经验分享·笔记·fpga开发·fpga·eda·verilog hdl·vhdl
stm 学习ing3 小时前
HDLBits训练4
经验分享·笔记·fpga开发·课程设计·fpga·eda·verilog hdl
小猿_003 小时前
C语言实现顺序表详解
c语言·开发语言
炸毛的飞鼠3 小时前
汇编语言学习
笔记·学习
风无雨3 小时前
react杂乱笔记(一)
前端·笔记·react.js