• 【C语言自由】知识点备忘录


    嵌入式C语言自由!!!

    优先级

    • 有时候会不可避免的在一行使用多种运算符,分开写和加小括号不一定总是最合适的,此时还是要掏出八股秘籍------优先级表。。:
      在这里插入图片描述
    • 强制转换优先级等同小括号,向右结合

    关键字

    • volatile
      易变值,防止编译器优化使用原值的临时备份,每次使用必须要重读原值。临时备份可能被放在工作寄存器中临时使用,而原值可能被中断修改,通常与中断相关变量使用

    • switch

      • case可以为整型负数,不能为浮点数
      • 如果没有break将接着往下执行,即使case值不符合(c#不会这样),方便多个case执行一个代码块
    • struct

      • 结构的外部引用extern:如果使用typedef定义则不需要声明struct,直接声明新类型(c++允许使用结构类型时省略struct关键字,即使不用typedef声明)
      • 结构内包含指向自己的指针:如果使用typedef定义则需要在结构指针前再次添加struct声明,需要说明的时,包含指向自己的指针是C的一个未声明先使用的特例,仅此一例(此时该结构未定义)
      • c99允许结构包含不定长度的数组
    • enum
      可以用枚举代替的魔法数尽量用枚举代替,这不光有利于代码的可读性和移植性,还将在调试时带来可见的好处

    数组

    • []结合方向:从左向右,如[2][3]表示两个元素,每个里面又包含三个子元素
    • 数组下标不仅允许上溢出,还允许下溢出,分别指向数组内存的后面和前面,这里充分展示了一个C设计理念,那就是用户清楚的知道自己在干什么

    指针

    • 指针自增自减的单位是指向数据类型的字节数,也就是指向后一个或前一个同类型内存块,加减一个常数也是一样,但同类型指针不能进行一般数值运算,可相减,结果为单位指针指向类型内存块的个数
    • 和变量不太一样,指针在引用前必须初始化,否则其指向的地址未知,这是致命的。比如,stm32开发,强行引用将会导致硬件级总线访问错误引发系统崩溃,
    • 关于一般变量的使用,初始化就是要在使用前赋值,默认其地址由编译器自动分配(可以通过编译器指定)。多级指针变得比较复杂。
      • 在函数参数传入时,一般来讲,修改变量需要传入一级指针,修改指针需要传二级指针,也就是说,二级指针是修改指针的指针
      • 指针是特殊的变量,其使用方式其实是有点奇怪的:
        • 指针的引用修改的并不是该指针的值,该指针自己的地址和其指向的地址都没有发生改变
        • 指针存在一个级别和一个基本类型,都匹配才能正常赋值
        • 指针的引用修改的是其保存的地址指向的变量,该变量可以是普通变量或者是其他指针
        • 如果指针是指向其他指针的话,情况变得更加奇怪,也更加危险。比如一级指针能指向二级指针,三级指针也能指向一级指针,编译器只给出类型不一样的警告,类型转换后甚至是允许的使用方法,比如下面的代码不会有任何错误和警告(使用GCCv11.3)。
    char ip[] = "hello";
    
    int main(void)
    {
    	char** a;
    	char* c;
    	char* d;
    	char*** g;
    
    	c = ip; 			 //一级指针初始化
    	a = &c;				 //二级指针初始化
    	d = (char*)&a;       //一级指向二级
    	g = (char***)&*d;    //三级指向一级
    
    	printf("%s\r\n",*a);
    	printf("%s\r\n",**g);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 指针的声明
      • 指针的声明对*号的位置并不敏感
      • 需要注意的是单行声明多个变量,如果想声明一个类型的多个变量,则每个变量前都要加除了基本变量类型外的其他符号
    //以下定义等效
    char* a;
    char *a;
    char * a;
    char*a;
    //单行声明多个指针变量
    char *a,*b;
    char **a,**b;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • const
      • 当指针声明中有const, 则const修饰的是其向左结合的类型
      • 如果const左边为基本数据类型,则在一条语句内声明多个数据时该const对所有参数都生效,结合方式依旧向左
      • 对多个const的情况也一样,比如
    //声明只读的二级指针pp,其指向的一级指针和其一级指针指向的内容均为只读
    //声明一个可读写的二级指针cc,其指向的一级指针可读写,其指向的一级指针指向的内容只读
    //ss为任意的合法值初始化只读内容
    char const*const*const pp=ss, **cc=ss;
    
    • 1
    • 2
    • 3
    • 4
    • 特别的
      • 对一些arm核,比如cortex-m3,double类型和对应指针的访问要求四字节对齐,否则将产生硬件错误,在使用按字节对齐结构时容易发生

    强制转换

    • 强制转换通常配合指针使用,但在对非指针变量强制转换需要仔细考虑是否使用得当。在强转后数据结构变大的情况下,吞并数据需要注意一些问题,而在强转后数据结构变小的情况下,舍弃数据则不容易出问题。
      • 指针强转,内存向高位结合(向右)
      • 普通变量强转,内存向低位结合(向左)
      • 所以在涉及到接收字节序的时候需要特别注意。接收字节序增长顺序基本都是内存向高,所以比较适合使用指针强转。下面是一个例子,假设接收缓冲为ii,如果只用变量强转,解析一个uint16_t类型,则需要使用接收顺序的后一个字节;如果使用指针强转,则需要使用接收顺序的前一个字节,我感觉使用指针更符合常规思维方式。
    uint8_t ii[] = {0x00,0x00,0x02,0x00};
    int main() {
    	printf("%d\r\n",(uint16_t)ii[1]);  //向左结合
    	printf("%d\r\n",*(uint16_t*)&ii[1]);  //向左结合
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    • 因为不同类型强转的结合方向不同,所以需要特别注意大小端的问题。

    工程结构

    • 头文件可以相互包含(正点原子式编程),缺点是改动一个被包含的头文件将导致所有相关源文件的重新编译,这在一些老电脑上是很难以忍受的(make支持多线程编译还挺好)
    • 头文件中包含定义时不允许相互包含

    声明

    允许重复声明,不建议这样做,应该没有任何好处

    编码

    • ASCII和gb2312编码的识别:从起始位置开始,第一个字节第一位为1则用gb2312解释,为0则用ASCII解释

    • GBK兼容gb2312,是gb2312字符集的扩充

    库函数拾遗

    printf

    • %.*s*表示可用一个额外参数指定最大输出宽度,如3,hello表示hello仅输出3位,*也可直接用数值代替
    • %f,double类型不需要使用%lf,但默认输出会损失精度,需要使用格式输出参数指定小数位数
    • PC端编程printf("\033[2J")这个可以清空终端内容类似system("clear")
    • printf 家族其他成员:
      • sprintf() //输出到缓冲
      • snprintf()
      • vprintf() //变参函数参数列表处理,printf底层调用的接口
      • vsprintf()
      • vsnprintf()
      • fprintf() //输出到文件
      • vfprintf()

    scanf

    使用方式和printf很想,因为要给数据赋值,所以传参的时候传地址

    • scanf()
    • sscanf() //输入到缓冲

    字符串

    • 字符串的换行,当一个字符串太长的时候,在字符串""内部添加\换行符是不行的,可以把一个""换成两个,""\""这样就可以换行了,两个字符串也会自动拼接

    对齐

    • c支持使用编译器指令进行字节对齐,gcc和keil这些编译器的对齐指令也都不一样,互不兼容,功能强大,内容也比较多,用什么百什么即可。。。主要用于数据传输
      • MDK v6,放#pragma pack(push, 1)#pragma pack(pop)中间即可,这个例子是按一个字节对齐
      • MDK v5, 可以使用__packed,v6已弃用
    • 大小端转换可以配合union,比如浮点转换

    预设值

    • c预设了一些格式如__xxxx__的宏,包含了行号、函数名、日期、编译时间等信息,会方便一些调试信息的打印
      • __LINE__
      • __fun__
      • __TIME__
  • 相关阅读:
    #案例:演示鼠标api!#演示:鼠标单击#演示:鼠标双击#演示:鼠标拖拽#演示:鼠标悬停!
    第一章 C语言知识点(程序)
    内存函数
    面试考频最高(没有之一)——“谈谈进程和线程的区别?”我来教你如何回答~
    小米球ngrok内网穿透
    设计模式-行为型模式-观察者模式
    【yolov系列:yolov7改进添加SIAM注意力机制】
    python在线及离线安装库
    热重分析(TGA)真空试验中的真空度精密控制解决方案
    SpringCloud_第1章_入门到精通()
  • 原文地址:https://blog.csdn.net/a1058191679/article/details/121500908