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

该文总结来自于观看韦东山《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、局部变量:每次定义都会单独赋值,不能像全局变量一大块共同赋值 

        

    

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值