1.函数会被分配一个内存块,该内存块首地址就是函数指针
2.用#define定义的常量叫做符号常量,如#define PI 3.14.16,符号常量一般用大写来表示,符号常量不占内存,在预编译阶段该符号被替换成数字。
3.const int a=0;该变量为常变量,常变量会占内存,但是不可修改。
4.循环中i++,if(i%30==0) 如果i为30或者30的倍数,进入
5.
volatile 类型:
volatile int sensorValue;
6.宏一个类似函数操作
![](https://img-blog.csdnimg.cn/direct/1ab8650f1cac4358be3b8197e20170e8.png)
在宏操作中,
\
是续行符号,它表示代码在下一行继续。
反斜杠用于将宏展开的代码连接成一个整体。
用do while的原因:列子:
若
![](https://img-blog.csdnimg.cn/direct/8fa9b01c271e4d26abcca50fd20a5a42.png)
![](https://img-blog.csdnimg.cn/direct/94857b7545934c7783d5c8359243d8d9.png)
![](https://img-blog.csdnimg.cn/direct/4a8ceff33cad441bac0f87f65ab4388c.png)
![](https://img-blog.csdnimg.cn/direct/796231bf1c074a85bb92987c3f0e0b07.png)
总之,
do { ... } while (0)
这种方式只是为了确保宏展开后的代码在逻辑上是一个块,以避免潜在的问题。
那么,为什么不直接定义函数?
宏展开后的代码直接替换,省去了函数调用的开销,因此在某些情况下可以提高效率。但宏也有一些限制,例如不会进行类型检查,可能导致意外的行为。此外,由于宏是在预处理阶段展开的,因此可能会导致代码大小增加,以及一些调试上的困难。
7.面向对象(目前只是了解表皮)
简称OOP,面向对象,
"对象" 指的是具体的事物,比如汽车,书籍,将对象的功能模块化,对象拥有这些功能属性,
提高代码的可读性和可维护性,同时也更符合现实世界的思维方式。
面向对象编程的特点包括:
封装(Encapsulation):
封装指的是将数据和操作数据的方法封装在一起,形成一个独立的对象。对象的内部实现对外部是隐藏的,外部只能通过对象提供的接口来访问和操作数据。
继承(Inheritance):
继承允许一个类(子类)继承另一个类(父类)的属性。
比如有个动物属性,胎生,体温温度,这是属性,哺乳动物类拥有该属性,对于狗这个类,哺乳动物类属于狗这个类的父类,哺乳动物是狗的父类,狗继承哺乳动物类的属性。
多态(Polymorphism):
多态性允许不同的对象对相同的消息做出不同的响应。通过多态,可以以统一的方式使用不同类的对象,从而实现更加灵活的代码设计。比如说有一个求面积的函数,该函数要可以求三角形,正方形,圆形各个几何图形的面积,不能限于一个几何图形。
(相关的虚函数还不会)
8.malloc是在堆中申请内存,是全局变量
9.while和for都是循环,使用不同场景
while (current->next != NULL) 在这里,我循环等到条件满足才退出,不需要管循环多少次,使用while比较好
10.
双指针
在函数内部修改函数外部指针的指向,可以使用双指针
![](https://img-blog.csdnimg.cn/direct/e2fceaadcdaa4cfeb2be9bb6cb4a9b20.png)
booktypedef *current=&book1
booktypedef **first=¤t
first相当于¤t
*first相当于current
**first相当于*current
11.头文件中使用 #ifndef __KEY_H #define __KEY_H的作用
使用__是告诉编译器是内部指令或者是预定义宏,编译器不会认为是实际文件名(不要也可以)。
使用大写是一种规范,keil编译器在宏这里不区分大小写(小写也可以)
注意,如果宏用小写key.h,这样会报错,因为
点号是文件名中的特殊字符,不应该出现在宏定义中。点号通常用于分隔文件路径或扩展名,而不应该用于宏定义。用key_h就好了。
![](https://img-blog.csdnimg.cn/direct/6a7b71569ba44829b5bb60f0e5e93dce.png)
出现这个情况的原因:
第二条,如果keil中不加头文件的哪个操作,那么就找不到key.h文件,报错,加了操作就不报错。
第一条,宏加入文件路径,就算不做加头文件的操作,也不会报错
12.在c语言中h的使用
每个c文件都是分别单个编译,如果有源文件中编译器不知道的变量,就需要引用头文件告诉编译器,比如应该在头文件中定义结构体,如果只是声明结构体,编译器不知道结构体的内存分配。(所有c中都要有这个思想,即要让编译器清楚变量的内存占用情况)
13.static修饰函数
当将
static
关键字用于函数时,它将限制函数的作用域仅限于定义该函数的源文件中。这意味着该函数无法在其他源文件中访问,即使它是在同一程序中定义的。这有助于隐藏函数的实现细节,以防止其他源文件意外地使用或修改它。
static
用于变量时,主要用于保持变量的状态
14.
代码分块管理技巧
对于大小不一样的内存块,比如函数,不同菜单显示函数大小不一样,我们可以将函数的地址传入结构体,因为不管函数怎么变,地址都不会变化。
15.结构体使用技巧
test1.data=6通过这个可知,结构体是对成员的值读写操作,如果成员是指针,那么就只能修改指针指向哪个地址,比如
test1.pc=&a,如果需要对指针成员指向的变量操作呢?方法一:
*(test1.pc)=2(这样居然可以用!);方法二:
struct test *struct_pc=&test1//结构体指针指向结构体,然后
*(struct->pc)=2,这两个方法原理都是*(地址)就可以操作指向的变量了
16.
(*struct_pc).data=1 和 *(struct_pc).data=1 和 *
struct_pc.data=1
的区别
这里注意就是.的优先级大于*,但是没有()优先级高,
*(struct_pc).data 和
*
struct_pc.data=1等价,都相当于
* (
struct_pc.data)=1,这是错误的,*应该解引用的是地址,
(*struct_pc).data=1这样就是正确的。
17.函数指针操作
这样定义void (*hanshu_pc)(); 可以直接这样使用hanshu_pc(),也可以这样使用(*hanshu_pc)()
18.对于地址的操作
![](https://img-blog.csdnimg.cn/direct/d01e55143eef40a78a42f0460ad4ca3e.png)
19 函数的堆栈会保存函数的局部变量,再函数中调用其他函数的时候,会在该函数堆栈中保存当前的现场包括内核寄存器值等。
20.队列(Queue)和列表(List)的相同点和不同点
相同点:
数据存储:队列和列表都用于存储一组数据元素,可以是任何类型的数据,如整数、字符串、对象等。
有序性:两者都是有序数据结构,元素按照它们被插入的顺序存储,并可以按照它们的位置访问。
不同点:
队列:队列是一种特殊的数据结构,遵循先进先出(FIFO)的原则。这意味着首先添加到队列的元素将首先被取出。主要支持两种操作:入队(enqueue)和出队(dequeue),队列通常用于需要按顺序处理数据的场景,入队和出队操作,这两个操作的复杂度通常是O(1),即常数时间,一般是线性存储结构。
列表:列表是一种通用数据结构,支持各种操作,包括在任何位置插入、删除、访问元素等,列表用于各种数据存储和操作需求,不仅支持有序存储,还支持随机访问、插入和删除元素。列表更通用,随机访问(通过索引)的复杂度通常为O(1),但插入和删除操作可能涉及线性时间O(n),其中n是列表的长度。
21.这个结果是4
![](https://img-blog.csdnimg.cn/direct/9a9bbc22e6e549b5968089daa75ccba0.png)
22.结果返回7
![](https://img-blog.csdnimg.cn/direct/8ea67e74a7d44736a437b8e95846909f.png)
结果返回20
![](https://img-blog.csdnimg.cn/direct/1e967d1cfa2e4858ba284d797a4c6e87.png)
22.数组地址在传入函数后,会退化
![](https://img-blog.csdnimg.cn/direct/a4fb2c2a7bbc4bcfb70fbd8300f2752f.png)
在主函数中使用 sizeof(num) 返回数组的大小,因为 num 是一个数组,编译器可以知道它的大小。然而,一旦数组传递给函数时,它就退化为一个指针,失去了关于数组大小的信息,因此,你会失去数组的大小信息。这是 C 语言的语法和语义规定,它使得传递数组的方式更加高效。
23.头文件的使用
场景:有个头文件叫oled.h,在该h文件中写#ifndef _UUU_H #define _UUU_H .......... #endif,然后,我在源文件main.c中写 #include “oled.h”
分析这个场景:首先,main.c中,需要引用的是oled.h(文件的真实名字),而不是_UUU_H,当程序运行到main中#include “oled.h”这句后,进入oled.h文件,执行#ifndef _UUU_H,发现没有define过_UUU_H,然后就进入接着执行#define _UUU_H及以下的宏和声明、定义,所以_UUU_H只是防止重复定义的标志而已。
24.使用scanf函数注意的
scanf(" %c", &sentence[i]);
添加了一个空格在 " %c" 中。这是为了吞噬任何之前的换行符或空格,以确保 scanf 从下一个非空白字符开始读取!!!!!!(之前就是这个问题导致输入不太合适)
25.C 标准库提供了一些有用的字符串处理函数。以下是一些
常用的 C 标准库字符串函数:
strcpy:将一个字符串复制到另一个字符串。
char dest[100];
char source[] = "Hello, World!";
strcpy(dest, source);
strcat:将一个字符串追加到另一个字符串。
char str1[100] = "Hello, ";
char str2[] = "World!";
strcat(str1, str2);
strlen:返回字符串的长度。
char str[] = "Hello";
int length = strlen(str); // length 等于 5
strchr:在字符串中查找指定字符的第一次出现。
char str[] = "Hello, World!";
char *ptr = strchr(str, 'W'); // ptr 指向 'W'
strstr:在字符串中查找指定子字符串的第一次出现。
char str[] = "The quick brown fox";
char *ptr = strstr(str, "brown"); // ptr 指向 "brown fox"
sprintf:将格式化的数据写入字符串。
char buffer[100];
int value = 42;
sprintf(buffer, "The answer is %d.", value);
26.C程序的编译过程,通常包括以下四个阶段:
预处理(Preprocessing): 预处理阶段的主要任务是处理源代码文件中的预处理指令,例如#include、#define等,并展开宏、删除注释、处理条件编译等。预处理器生成一个经过预处理的中间文件。
编译(Compiling): 编译器接收预处理阶段生成的中间文件,将其翻译成汇编语言或直接生成目标文件。这个阶段包括词法分析、语法分析、语义分析和代码生成。
汇编(Assembling): 汇编器将编译器生成的汇编代码转化为机器代码,生成一个或多个目标文件,通常具有扩展名为.o或.obj。这些目标文件包含可执行代码的二进制表示。
链接(Linking): 链接器接收一个或多个目标文件,以及可能的库文件,解决符号引用,将它们合并为一个可执行文件。这个可执行文件通常没有文件扩展名,准备好后可以运行。
它们一起构建了可执行文件。
28.字节对齐是一种内存布局的概念,它确保多字节数据类型的变量存储在内存中的地址是其大小的整数倍。这样做有助于提高内存访问的效率,因为处理器通常可以更快地访问按对齐边界存储的数据。例如,对于一个4字节整数(int),它的地址应该是4的倍数;对于一个8字节双精度浮点数(double),它的地址应该是8的倍数。如果某个数据类型的地址不满足这个要求,就需要在内存中插入一些填充字节,以使其对齐到正确的边界。
29.malloc 函数的基本用法:
首先,确保你的程序包含了 <stdlib.h> 头文件。
malloc 的函数原型如下:
void *malloc(size_t size);
size 参数表示要分配的内存字节数。
malloc 返回一个指向分配内存块的指针,或者如果分配失败则返回 NULL
例:
intArray = (int *)malloc(5 * sizeof(int)); // 分配5个整型变量的内存
30.c语言volatile作用和用法:
它用于告诉编译器不要对某个变量进行优化,因为该变量可能会在程序的控制之外改变。
volatile 变量通常用于以下情况:
硬件寄存器或外设:当你需要与硬件设备(例如微控制器、传感器或外部接口)通信时,相关的寄存器变量通常会被声明为volatile。这是因为这些变量的值可能在程序的正常流程之外发生变化,而编译器可能会对它们进行优化。
防止编译器优化:有时,编译器可能会根据代码的逻辑进行一些优化,例如删除似乎没有用的变量访问。使用 volatile 可以防止这种类型的优化。
31.头文件的两种包含方式的区别
<>包含方式会默认在标准库路径下寻找 “”包含方式会默认在程序的相对路寻找,如果找不到就会在默认标准库路径下寻找。
32.
将整数转换为
void*
指针是为了将整数值视为一个内存地址。
33.双指针
![](https://img-blog.csdnimg.cn/direct/5918d7f41e504d4c81ce541a7a5a11ba.png)
![](https://img-blog.csdnimg.cn/direct/cd45a290f11a4d7488d23bb5015d32a7.png)
int **ret=(int**)malloc(sizeof(int*)*2);//双指针指向一个可以存(8个字节)两个地址内存的干净地址(任意大小)
ret[0]=(int*)malloc(sizeof(int)*nums1Size);//通过偏移访问第一块,赋值有nums1Size个字节的干净内存块的首地址(解引用一次)
ret[1]=(int*)malloc(sizeof(int)*nums2Size);//通过偏移访问第二块,赋值有nums1Size个字节的干净内存块的首地址
ret[0][flag1++]=i-1000;//相当于**(解引用两次)
int** returnColumnSizes//双指针
*returnColumnSizes=(int*)malloc(sizeof(int)*2);//双指针指向一个有两字节的干净地址,解引用一次
(*returnColumnSizes)[0]=0;//解引用两次
(*returnColumnSizes)[1]=0;
函数在运行的时候,cpu寄存器会保存哪些信息?
程序计数器(Program Counter,PC):PC 寄存器保存着当前正在执行的指令的地址。它跟踪程序的执行位置,以便在执行下一条指令时能够正确定位。
栈指针(Stack Pointer,SP):栈指针寄存器指示当前堆栈的顶部地址。它用于管理函数调用时的堆栈操作,包括参数传递和局部变量存储。
通用寄存器(General-Purpose Registers):这些寄存器用于存储临时数据、函数参数、局部变量和中间计算结果。具体数量和用途会因处理器架构而异,通常有命名为 R0、R1、R2 等的通用寄存器。
条件码寄存器(Flags Register):条件码寄存器包含标志位,用于跟踪运算结果的状态,如零标志(Z)、溢出标志(O/V)、进位标志(C)、符号标志(N)等。
链接寄存器(Link Register):链接寄存器用于保存函数的返回地址,即要返回到哪个地址执行下一条指令。有时它也称为返回地址寄存器(Return Address Register,RAR)。
其他特殊寄存器:具体的处理器架构和操作系统可能还包括其他特殊用途的寄存器,如基址寄存器、帧指针寄存器等。
inline 关键字可以将函数的代码嵌入到调用它的地方,而不是通过函数调用的方式执行。这可以提高程序的执行效率,尤其是对于一些短小的函数。
对于小型、简单的函数,
使用 inline 可能是合适的,因为插入这些函数的代码不会导致很大的额外开销,并且可以提高程序的性能。
对于较大或者复杂的函数,inline 可能会导致代码膨胀,增加可执行文件的大小。此外,如果这些函数在多个地方被调用,可能会导致代码重复,影响缓存性能。在这种情况下,选择性地将一些频繁调用的小函数声明为 inline 可能更为合适。
切换用户
![](https://img-blog.csdnimg.cn/direct/42d32c7a5c414418b99b331483638c37.png)
![](https://img-blog.csdnimg.cn/direct/90d368eee6a847c195870b4d218c254f.png)
![](https://img-blog.csdnimg.cn/direct/8456cb49aeae431e9609baed24888498.png)
普通用户要使用sudo添加管理员权限执行(在日常情况下,普通用户是不能随便切换账户的,现在是开发情况,可以随意用sudo)
这两种结构体初始化的区别:
第一种方法只对数组第一个成员赋值。
第二种精确地控制每个成员的初始化,提高了代码的可读性和维护性,如果结构体中新增成员或者调整成员顺序,第二种方式(使用命名初始化)可以更加灵活地适应这些变化,因为你显式指定了每个成员的初始化值。而第一种方式则需要更加小心,以确保初始化列表中的值与结构体成员的顺序和类型匹配。
struct test_project now_device = {0,0,get_down_loadflag, down_load_method} ;
struct test_project my_project = {
.revice_uart0 = {0},
.uart0_pc = 0,
.get_down_loadflag = NULL,
.down_load_method = NULL,
.up_load_method = NULL,
};
#include "stdio.h":
#include "stdio.h"和#include <stdio.h>的区别
#include "stdio.h":
双引号表示编译器首先在当前文件的目录中搜索指定的头文件。如果没有找到,再去标准库的包含路径中搜索。
通常用于包含项目中的本地或自定义头文件。
#include <stdio.h>:
尖括号表示编译器只在标准库的包含路径中搜索指定的头文件。
通常用于包含系统或标准库头文件。