关于C语言提高的一些总结

发布时间:2023年12月18日

该文总结来自于观看韦东山《C语言提高》篇,为一些基础的记录于总结,不一定能帮到你,如果本文章有错误,欢迎指正。

1、变量和指针:一块芯片:芯片内部有cpu、RAM(内存)、flash(代码存储)、GPIO等外设
?? ?a、变量:变量能变,所以存储在内存中(可读可写)
?? ?b、指针:保存在内存中,指针变量保存的是指针的地址,,如int *a,指针a保存的是int*的地址,为4个字节(32位芯片),指针都为为4个字节、char* ?int* 结构体指针等都为4个字节,因为指保存的是地址

2、关键字
?? ?a、volatile:易怒的,volatile声明的变量不易被cpu优化,
?? ??? ?什么叫优化呢——如执行a=1;a=2,没有volatile时,cpu直接将a=2,跳过了a=1这个步骤,cpu不会读写内存RAM,从而速度更快,但是加了volatile时,a=1时,cpu读写一次内存,a=2时再读写一次,
?? ??? ?因此volatile适合不能被优化的数据,如GPIO数据的读写等,这些变量要使用这个关键字。
?? ??? ?访问硬件寄存器时常使用volatile。
?? ?b、const:声明不可变,即关键字声明的变常量数值不能改变,不能被赋值,这个常量将保存在flash中。
?? ?c、extern:声明外部变量可在这个文件中调用、当某个文件定义了变量后,其他文件要使用这个变量,则需要extern声明,该声明不可赋值。
?? ??? ?使用这个关键字时,a.c文件使用extern int b; b.c文件中有int b;那么编译器编译的时候会把a.c与b.c编译成a.o与b.o文件,
?? ??? ?a.o文件一直保存这哪里有int b这个象征,,接下来编译器直接把所有的.o文件变成hex\bin文件,然后extern最后是通过hex文件将不同c文件的变量链接到一起。
?? ?d、static:只允许本文件或本作用域内有效使用,如本文使用static件定义了一个函数或者变量,那么只允许本文件调用,局部函数使用static定义变量时。该变量就不用再次运行函数时就不用重新定义。该关键字防止多个文件的函数或变量命名重复。
?? ?e、typedef:类型定义,就是只能定义类型的,不能赋值 ?(就是将类型换个名字)
?? ??? ?如typedef int A:将int类型定义为A;
?? ??? ? typedef int *B:将int *类型定义为B,使用B b;相当于int *b;
?? ??? ? typedef struct person *c:将 typedef struct person *结构体指针类型定义为C
?? ??? ? typedef int void(*max)(int1,int2):将函数指针定义为max;如max f1相当于int void(*f1)(int1,int2);这就是函数指针


3、结构体 struct:在keil中生成的文件里面,.map文件可以查看变量内存地址
?? ?a、使用struct声明结构体时不会分配空间,只有用这个结构体声明成员才会分配
?? ?b、结构体的大小:int类型为4个字节,char类型为1个字节但是cpu为了提高效率,char为4个字节后面3个字节不写,若有两个char,这这两个联和在一起为2个字节,但是后续2个字节不写,因此两个char还是4个字节,
?? ? ? 如果定义一个数组a[100],那么所占空间为100个字节,因此一般使用chiar *替代数组。
?? ?c、int a;a=1;a='A';
?? ??? ?a=1——4个字节,取决于int
?? ??? ?a=‘A’——也是4个字节,取决于int,虽然A字符只占1个字节,后续3个字节都不写。
?? ?d、非结构变量(全局或者局部变量),定义char时占4个字节,
?? ??? ?char a;——独占4个字节
?? ??? ?char b;——独占4个字节
?? ?而结构体变量中,char a;char b;共同使用4个字节——第一个字节存放a,第二个存放b,3、4个字节空着

4、通过指针赋值
?? ?a、int a;a=123;定义a,在内存中分配一块4个字节的内存作为a的地址,地址里面存放123,那么这个过程是怎么样的呢??
?? ?谁写内存?答:CPU ? 怎么知道写123值?答:有指令 ? ?指令哪来?:CPU从Flash中取出指令
?? ?因此过程为:上电——CPU从Flash中读取指令——CPU执行指令——写内存变量a
?? ??? ?怎么写内存变量a呢?a、获得a的地址(程序写好时变量的地址已固定) b、获得数据(123) c、把数据写入这个地址
?? ?因此,int a=123;相当于int*p;p=&a;*p=123;

?? ?b、结构体people.age=10;其中的“ . ”读为“的”
?? ? ? ? 指针p=&people;p->age=10;其中的“->”读为“指向的”
?? ?c、指针int *p;p=&people:——将结构体people地址值给指针p,保存在p的的地址中
?? ? ?struct people2 *pt;*pt=people2——将结构体people2地址中的全部值赋值给同为结构体的*pt;
?? ?若程序为pt=&people;*pt=people2;——则将结构体people的地址保存在pt中,然后,对这个地址的值操作,也就是将people2的值直接赋值给people。


5、在结构体里怎么使用指针
?? ?a、typedef struct student{char *name;
?? ??? ??? ??? ?int age;?
?? ??? ??? ??? ?void (*play)(void);?
?? ??? ??? ??? ?struct strdent classmate;
?? ??? ??? ??? ?}Student,*pstudent;
?? ??? ?a、char *name:为什么不使用数组呢char a[10]?答:数组使结构体所占的内存变大。因此使用指针。
?? ??? ?b、struct strdent classmate:可以这样定义吗?答:不可以,由于结构体classmate这样定义,那么就会形成递归函数,结构体大小无法确定,因此要使用指针。正确为:struct strdent *?? ?classmate;
?? ??? ?c、Student:重命名这个结构体为Student
?? ??? ?d、*pstudent:定义了这个结构体指针

?? ?b、在结构体里面如何定义函数变量,之后要使用函数的时候直接取址即可?
?? ??? ?a、如void *play(void):为函数返回值为void *(void* 空指针,可以返回任意类型数值),不可在结构体里面定义
?? ??? ?b、如void (*play)(void):为一个函数指针,与int *a类似也会在内存中分配4个字节,可在结构体里面定义,之后直接使用函数即可,如people.play=play1()(也可以是&paly1);(函数名字和取址函数是一样的)
?? ??? ?c、声明新的结构体变量时,直接传入函数即可如 ?Student LYS={25,dance(),someone}(其中的dance是函数,属于LYS专用)

?? ?c、有两款或几款产品,怎么兼容?
?? ??? ?a、如有LCD1、LCD2:
?? ??? ??? ? #defien LCD_CHOOSE ?

?? ??? ??? ? #ifdef?? ?LCD_CHOOSE ?
?? ??? ??? ? LCD1();?
?? ??? ??? ? #else?
?? ??? ??? ? LCD2();
?? ??? ??? ? #endif ??
?? ??? ? ?也就是说改变宏定义LCD_CHOOSE ?就可选择不同的产品,但是如果有几十或者上百个产品呢?

?? ??? ?b、也可以将读取硬件信息而判断运行哪个产品的函数,如写入EEPROM中写入了不同信息,有一个函数读取EEPROM信息,然后做判断,
?? ??? ? ? 优点,能根据硬件做反馈,每次更改硬件只需修改EEPROM里面的值即可,局限性:产品一多时,if判断过多。

?? ??? ?c、当产品多时应使用结构体如
?? ??? ??? ?typedrf struct LCD_C{
?? ??? ??? ??? ?int lcd;
?? ??? ??? ??? ?void (*LCD_Chose)(void);
?? ??? ??? ?}lcd_c,*p_lcd_c;
?? ??? ?lcd_c xxx_LCD[]={{0,LCD0();},{1,LCD1();}}有多少个产品直接添加到数组里面即可,然后从EEPROM读取LCD类型type(上面一步有介绍),
?? ??? ??? ?然后xxx_LCD[type].LCD_Chose();就可以选定型号。

?? ??? ?e、在程序中尽量少使用全局变量,一般使用结构体和函数返回值、指针的方式替代全局变量。


6、链表
?? ?struct person {
?? ??? ?int age;
?? ??? ?char *name;
?? ??? ?struct person *next;
?? ?};
?? ?注意:next代表的时struct person * ? ;也就是说next是指针。&a也是指针,*next是指针next所指向的东西。
?? ?person head;person a;person b;person c;(初始化,为了方便年龄名字都不写)
?? ?head->next=&a:含义为 ? 头部head 指向的 指针为 取址A,也就是说head的指针next 的4个字节中保存的是a的地址.
?? ?a->next=&b:含义为 ?a结构 指向的 指针 为取址b,也就是说a的指针next 的4个字节中保存的是b的地址.
?? ?b->next=&c:含义为 ?b结构 指向的 指针 为取址c,也就是说b的指针next 的4个字节中保存的是c的地址.
?? ?c->next=NULL;c含义为 c结构体 指向的 指针 为空,也就是说c的指针next 没有数据。

?? ??? ?
7、插入链表
#include "stm32f10x.h"
#include "./led/bsp_led.h"?
#include "./usart/bsp_usart.h"?


/*
?*char *name ?4个字节
?*int age 4个字节
?*void (*people)(void) ?是变量,4个字节
?*struct person *next 是指针变量,函数指针 ?4个字节
?*Person是结构体的重命名
?* pperson是结构体Person结构体的指针的变量
?*/
typedef struct person{
?? ?char *name;
?? ?struct person *next;
}Person,*pperson;

pperson head=NULL;?

Person A={"a1",NULL};
Person B={"b2",NULL};
Person C={"c3",NULL};
Person D={"d4",NULL};

void Add_person(pperson people)
{
?? ?pperson last;
?? ?if(head==NULL)
?? ?{
?? ??? ?head=people;
?? ??? ?people->next=NULL;
?? ?}
?? ?else
?? ?{
?? ??? ?last=head;
?? ??? ?while(last->next!=NULL)
?? ??? ?{
?? ??? ??? ?last=last->next;
?? ??? ?}?? ?
?? ??? ?last->next=people;
?? ??? ?people->next=NULL;
?? ?}?? ??? ?
}


int main(void)
{
?? ?USART_Config();
?? ?
?? ?Add_person(&A);
?? ?Add_person(&B);
?? ?Add_person(&C);
?? ?Add_person(&D);
?? ?
?? ?
?? ?while(head)
?? ?{
?? ??? ?printf("%s\r\n",head->name);
?? ??? ?head=head->next;
?? ?}
?? ?printf("end\r\n");
?? ?while(1);
}


8、删除链表函数
void DelItemFromlist(pperson p)
{
?? ?pperson left;
?? ?if(head==p)
?? ??? ?head=head->next;
?? ?else{
?? ??? ?left=head;
?? ??? ?while(left->next!=p&&left)
?? ??? ?{
?? ??? ??? ?left=left->next;
?? ??? ?}
?? ??? ?if(left==NULL)
?? ??? ?{
?? ??? ??? ?printf("not fina data!");
?? ??? ??? ?return;
?? ??? ?}
?? ??? ?else
?? ??? ?{
?? ??? ??? ?left->next=p->next;
?? ??? ?}
?? ?}
}


9、ARM简单指令
?? ?int a;
?? ?int b;
?? ?a=a+b;
a、所有计算都是在cpu中操作的
b、cpu和内存通过读写指令联系
c、所以a=a+b过程为,1、读取a值,2、读取b值,3、cpu计算a+b,4、cpu写入值进内存a地址中
d、那么CPU从内存读取的值保存到哪里?
?? ?a、LDR(load):LDR R0,[a] ?cpu读取a地址的值保存在R0寄存器中
?? ?b、LDR(load):LDR R1,[b] ?cpu读取b地址的值保存在R1寄存器中?? ?
?? ?c、ADD R0,R0,R1?? ?CPU内部相加计算
?? ?d、STR(store) :STR R0,[a] cpu将寄存器R0的值保存在内存a地址上

10、全局变量
?? ?a、芯片断电后,内存RAM于cpu寄存器数据全部丢失,所有数据和代码保存在flash中
?? ?b、上电时,代码运行,全局变量初值从flash中读取
?? ?c、怎么赋值呢:所有全局变量初始值在flash保存在一块连续的地址上,上电时,只需将全局变量这一块的地址全部copy到RAM中。
?? ?d、初始值为0或没有初始值的全局变量如何在上电时如何初始化?答:将这些数据也放到一块,然后使用一个类似于清零的函数将这一块全部清零,然后保存到RAM 中
?? ?
11、局部变量和栈

int main(void)
{
?? ?A();

?? ?B();
?? ?
}
?? ?a、程序运行到A()函数时,汇编为 BL A
?? ??? ?a、先记录返回地址(汇编中为,将B函数和的返回地址赋值给LR(R14寄存器))
?? ??? ?b、再执行A()函数
void A(void )
{
?? ?C();
?? ?D();
}
?? ?b、程序运行到C()函数时,汇编为 BL C
?? ??? ?a、先记录返回地址(汇编中为,将D函数和的返回地址赋值给LR(R14寄存器))
?? ??? ?b、再执行A()函数
?? ?c、这个时候LR寄存器的值为函数C的返回地址D,覆盖了函数A的返回地址
?? ?d、于是程序在函数运行的第一步为将LR保存到栈里
?? ?e、栈:一块空闲的内存,?? ?
?? ??? ?a、在内存RAM中,有一块ZI段保存初始值为0或者没有初始值的内存
?? ??? ?b、在内存RAM中,还有一块内存用来保存数据
?? ??? ?c、那么,内存RAM中还有一些没有使用的内存空间就作为栈。

12、局部变量:每次定义都会单独赋值,不能像全局变量一大块共同赋值?

?? ??? ?

?? ?

文章来源:https://blog.csdn.net/qq_44941069/article/details/135061980
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。