zoukankan      html  css  js  c++  java
  • 基础C语言知识串串香10☞数组&字符串&结构体&联合体&枚举

    五、数组&字符串&结构体&联合体&枚举

    5.1、c语言中定义一个字符串:char a[6]={'l','i','n','u','x',''};''的字符编码为0就是NULL;也就是说内存中遇到0,翻译成字符是就是'',或是NULL;

    char a[6]="linux";//定义字符数组a,并对其赋初值
    char *p="linux";//定义字符指针p,并将字符串linux的地址赋给它。
    

    5.2、sizeof(a)=6是运算符,其意思是所占空间大小,包括字符串后面的‘',strlen(a)=5是一个函数,其意思是字符串的长度。strlen(p);其中p只有是字符指针变量才有意义,它的形参是数组变量是没有意义的,因为strlen是根据什么时候遇到'',才结束测试字符串的长度,就算数组不初始化也是有长度的。

    char *p="linux"; sizeof(p)永远等于4,因为p是指针变量,存的是地址。

    所以总结:sizeof()是拿来测数组的大小,strlen()是拿来测试字符串的长度。

    5.3、结构体用.或者是->访问内部变量,其实质是用的指针访问。

    struct student{
    int a;
    double b;
    char c;
    }s1;
    

    s1.a=12;实质就是int *p=(int *)&s1;*p=12;首先a是int型,所以是强制类型int*,其次是就是算地址,然后强制类型,地址应该是int型然后加减,不然的话,系统s1.b=12.2;实质就是double *p=(double *)((int)&s1+4),*p=12.2;不知道是以int型加减还是以float型加减,还是以char型加减,所以应当(int)&s1;而且因为地址是s1.c=c;实质就是char *p=(char*)((int)&s1+12); *p=c;4字节的,所以必须是int型。

    5.4、对齐方式:

    (1)猜测如果是32位系统,那么编译器默认是4字节对齐,64位系统,那么编译器默认是8字节对齐,因为32位或64位一次性访问效率是最高的。

    (2)结构体对齐

    1. 结构体首地址对齐(编译器自身帮我们保证,会给它分配一个对齐的地址,因为结构体自身已经对齐了,那么第一个变量也就自然对齐,所以我们才可以想象成第一个变量从0地址存放);

    2. 结构体内部的各个变量要对齐。

    3. 整个结构体要对齐,因为定义结构体变量s1时,再定义变量s2时,如果s1没有对齐,就坑了s2,所以也要保证整个结构体对齐。

    无论是按照几字节对齐,我们都可以联想到内存实际的安排。1字节对齐那么不管int float double类型,在每4个格子的内存挨着存放。2字节对齐,也是一样的想法,举一个列子,如果第一个变量是char型,第二个变量是int型,那么0地址存放char型,1地址空着,2地址存放int型地址部分,3地址存放int型地址部分,然后上排最右4、5地址存放int型高址部分。4字节对齐,如果第一个变量是char型,第二个变量是int型,那么0地址存放char型,1,2,3地址空着,从4地址开始存放int,最后给变量分配完内存空间后,必须要保证整个结构体对齐,下一个结构体的存放起始地址是n字节对齐整数倍,如是4字节对齐,那么最后short算成4字节以保证整个结构体对齐。
    整个结构体对齐,如2字节对齐(2的整数倍),只要是0、2、4地址就行了,如果是4字节对齐(4的整数倍),就必须是0、4地址。8字节对齐(8的整数倍)

    (3)猜测4字节/8字节其实是针对int型/double型的,比如0地址是char型,那么4字节对齐,int型、float型就必须从4地址开始存放,那么8字节对齐,int型就必须从4地址存放,double型就必须从8地址开始存放.小于几字节对齐的那些,如char型和short型只要能按照规则存放就行了。

    (4)对齐命令:

    1. 需要#pragma pack(n)开头,以#pragma pack()结尾,定义一个区间,这个区间内的对齐参数就是n。(不建议使用)

    如:s1占5个字节,s2占8字节(默认)

    #pragma pack(1)
    struct stu1
    {
        char c;
        int a;
    }s1;
    #pragma pack()
    
    struct stu2
    {
    char c;
    int a;
    }s2;
    
    1. gcc推荐的对齐指令
      __attribute__((packed))
      __attribute__((aligned(n)))
      在VC中就不行,没有定义这个命令

    (1)__attribute__((packed))使用时直接放在要进行内存对齐的类型定义的后面,然后它起作用的范围只有加了这个东西的这一个类型。packed的作用就是取消对齐访问。

    (2)__attribute__((aligned(n)))使用时直接放在要进行内存对齐的类型定义的后面,然后它起作用的范围只有加了这个东西的这一个类型。它的作用是让整个结构体变量整体进行n字节对齐(注意是结构体变量整体n字节对齐,而不是结构体内各元素也要n字节对齐,内部元素按照默认对齐方式)

    例子:

    struct mystruct11
    { //元素1字节对齐,结构体4字节对齐
        int a;//4
        char b;//1
         short c;//2
    }__attribute__((packed));
    
    typedef struct mystruct111
    {//1字节对齐4字节对齐2字节对齐
    int a;//444
    char b;//12(1+1)2
    short c;//222
    short d;//24(2+2)2*
    }__attribute__((aligned(1024)))My111;
    

    5.5、offsetof宏
    #define offsetof(TYPE,MEMBER) ((int)&((TYPE*)0)->MEMBER)

    (1) offsetof宏的作用是:用宏来计算结构体中某个元素和结构体首地址的偏移量(其实质是通过编译器来帮我们计算)。

    (2)offsetof宏的原理:我们虚拟一个type类型结构体变量,然后用type.member的方式来访问那个member元素,继而得到member相对于整个变量首地址的偏移量。

    (3)学习思路:第一步先学会用offsetof宏,第二步再去理解这个宏的实现原理。

    (TYPE*)0这是一个强制类型转换,把0地址强制类型转换成一个指针,这个指针指向一个TYPE类型的结构体变量。(实际上这个结构体变量可能不存在,但是只要我不去解引用这个指针就不会出错)。

    ((TYPE)0)->MEMBER,(TYPE)0是一个TYPE类型结构体变量的指针,通过指针指针来访问这个结构体变量的member元素,然后对这个元素取地址,又因为改地址是从0地址开始算的,所以这个地址就是相对起始地址的偏移量。

    5.6、container_of宏:

    #define container_of(ptr,type,member) ({
    const typeof(((type*)0)->member)*__mptr=(ptr);
    (type*)((char*)__mptr-offsetof(type,member));})
    

    这里有两条语句,用{},表示提示编译器本行因为屏幕不够,链接下一行。用#(也就是宏定义)时,如果本行不够要用提示编译器接着是下一行的,必须要用,猜测因为宏定义一行就算结束了。

    (1)作用:知道一个结构体中某个元素的指针,反推这个结构体变量的指针。有了container_of宏,我们可以从一个元素的指针得到整个结构体变量的指针,继而得到结构体中其他元素的指针。

    (2)typeof关键字的作用是:typepof(a)时由变量a得到a的类型,typeof就是由变量名得到变量数据类型的。

    (3)这个宏的工作原理:先用typeof得到member元素的类型定义成一个指针,然后用这个指针减去该元素相对于整个结构体变量的偏移量(偏移量用offsetof宏得到的),减去之后得到的就是整个结构体变量的首地址了,再把这个地址强制类型转换为type*即可。

    5.7、p是一个地址(int)p+6(char *)+6;效果是一样的,第一种是将地址p当作int型加减,第二种是将地址p做为char *指针,他每次加减都是一字节一字节相加减的,如果是(int *)p+6,那么他每次加减都是按照4字节一跳。就相当于加了+4*6;

    5.8小端模式:变量的高地址存放在高地址,低地址存放在低地址;通信模式也要分大小端,先发送/接受的是高地址还是低地址,大端模式:变量的高地址存放在低地址,低地址存放在高地址;

    测试:有用共用体union和指针方式来测试,基本思路是让int a=1;看低地址char是0还是1;变量都是从地址开始存放,只是变量的高地址和低地址先存放谁不确定。

    不能用位与来测,因为存放和读取都是按照某一个方式来的,结果永远都是一样的。int a=1;char b=(char)a;这种方式不可以测试,因为不管大小端,它都以变量a的低地址部分赋给b;

    union stu{
    int a;
    int ce()
    {
    int a=1;
    int b=*((char*)&a);
    return b;
    }
    char b;
    }
    
    int ce()
    {
    union stus;
    s.a=1;
    return s.b;
    }
    

    5.9、枚举类型(int型):这样写默认从第一个常量开始是0,1,2,3,4.........也可以自己赋值,但是每一个值是不一样的,否则逻辑上出错。

    enum week{
    sunday,sunday=1,
    moday,moday=5,
    tuseday,然后其他常量以此递增。
    wenzeday,
    friday,
    saterday,
    }today;
    today=sunday;
    
    *//错误1,枚举类型重名,编译时报错:error:conflicting types for ‘DAY’
    typedef enum workday
    {
    MON,//MON=1;
    TUE,
    WEN,
    THU,
    FRI,
    }DAY;
    
    typedefenumweekend
    {
    SAT,
    SUN,
    }DAY;
    
    
    //错误2,枚举成员重名,编译时报错:redeclaration of enumer at or ‘MON’
    typedef enum workday
    {
    MON,//MON=1;
    TUE,
    WEN,
    THU,
    FRI,
    }workday;
    
    typedef enum weekend
    {
    MON,
    SAT,
    SUN,
    }weekend;
    
    

    往期文章列表:****往期热文:
    基础C语言知识串串香(1)

    基础C语言知识串串香(2)

    基础C语言知识串串香(3)

    基础C语言知识串串香(4)

    基础C语言知识串串香(5)

    基础C语言知识串串香(6)

    基础C语言知识串串香(7)

    基础C语言知识串串香(8)

    基础C语言知识串串香(9)


    ===========我是华丽的分割线===========


    更多知识:
    点击关注专题:嵌入式Linux&ARM

    或浏览器打开:https://www.jianshu.com/c/42d33cadb1c1

    或扫描二维码:

    6217760-e6bba06e005d8fe7.jpg

  • 相关阅读:
    软件开发的升级打怪攻略:从新手到高级工程师
    Java实现递归将嵌套Map里的字段名由驼峰转为下划线
    生活是什么
    批量下载网站图片的Python实用小工具
    工作的方法
    工作的心境
    LODOP直接导出图片不弹框
    LODOP打印table超宽用省略号带'-'的内容换行问题
    LODOP打印table表格宽度固定-超宽隐藏
    如何领购和作废电子发票流程
  • 原文地址:https://www.cnblogs.com/leon1124/p/14039750.html
Copyright © 2011-2022 走看看