结构体

        在各种业务上,现有的数据类型不足以解决问题,所以有时会将不同的数据类型组合成一个整体,称为结构体,而结构体类型需要由编写者自己声明,拿数组相比,可以理解为,数组是同类型元素的集合,而结构体是不同类元素的集合

结构体定义

        形式:struct 结构体名 {成员表列};        其中成员表列形式为:类型名 成员名,结构体名第一个字母最好大写

        struct是关键字,不可省略,而且末尾必须加上分号。接下来以学生的学号姓名成绩组成结构体为例

struct Student
{
    int id;
    char n[20];
    float sorce;
}s1,s2;

int main()
{
    struct Student s = {1, zhangsan,93.5};
}

        如此便定义了一个新的数据类型Student,有两种定义方式s1、s2(全局变量)和s(局部变量),而s进行了初始化,初始化时,花括号中内容的顺序必须和声明的顺序一致,如果只初始化一部分,那么其他部分都会变成0,而部分初始化时需要通过运算符具体列出,该用法只在GNU体系下,Windows下不行,需要注意的是,成员与成员之间用逗号间隔

        结构体之间是可以嵌套的,如下,在struct S中,s属于成员名,struct Date属于类型名,而对于嵌套结构体的初始化,无非就是在花括号中再添加花括号

struct Date
{
    int year;
    int month;
};

struct S
{
    int i;
    struct Date day;
}S1;

        那么如何确定出具体的成员呢,这需要用到几个运算符

结构体运算符

        (1)“.”结构体成员运算符,优先级一级,方向自左向右        例:s.id,表示在s这个结构体中id这个成员;S1.day.month,表示在S1结构体中的day结构体成员中的month这个成员

        (2)“->”指向结构体成员运算符,优先级一级,方向自左向右,这个运算符需要用到指针,还是以学生结构体为例

#include <stdio.h>


struct Date
{
    int year;
    int month;
    int day;
};

struct Student
{
    int id;
    char n[20];
    float sorce;
    struct Date birthday;

};

void printfstruct(struct Student *p)
{
    printf("%d %s %f %d-%d-%d", p -> id, p -> n, p -> sorce, p -> birthday . year, p -> birthday . month, p -> birthday . day);
}

int main()
{
   struct Student s = {1, "zhangsan", 96.5, {2000, 6, 21}};
   struct Student *p = &s;
   printfstruct(p);
}

        对于函数printfstruct(),为了防止值传递,所以采用了指针传递的形式 ,所以对于“->”运算符,应该用于指向结构体的指针,所以“.”和“->”的区别在于一个左侧是变量一个左侧是指针

结构体数组

        由于结构体属于一种数据类型,所以自然也会有数组,同样可以进行数组的操作

遍历

        与数组不同的是,结构体(不是结构体数组)是可以被整体赋值的,如下

逆序

按照分数高低排序(更改if()可以更换排序依据,所以可用回调函数)

结构体内存 

        结构体所占内存的字节数并不是简单的成员类型相加,例如以下结构体所占内存空间为8个字节、12个字节、24个字节​​​​​​由于结构体中变量的成员在内存中的顺序,与结构体声明成员时的顺序一致,即有序性,而且变量在内存存放时,会有这么一种现象,被称为内存对齐

        内存对齐:变量存在内存空间中的地址,必须能够整除sizeof(变量类型),例:int t ,t的内存空间地址可能是0x10004000,而绝对不可能是0x10004001,0x10004005,也就是说地址数值一定能够整除以4,同理short型能整除以2。这种方式会提高CPU的读写速率 

        于是我们便可以知道上述结构体的储存方式,可以根据内存空间位置是否能够整除来存放

第一种

0 1 2 3 4 5 6 7
c s s i i i i

        1不能被2整除,所以空过去

第二种

0 1 2 3 4 5 6 7 8 9 10 11

c

i i i i s s

         最大数据类型int是4个字节,所以10和11需要空出

第三种

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
c d d d d d d d d d i i i i

         规则:(1)默认按照cpu位数对齐:最终大小必须是8的整数倍

                    (2)在结构体成员中找到最长成员,最终按该成员长度对齐(不与1矛盾,例如第二种)

                    (3)按照结构体声明的顺序,依次将成员保存到结构体内存中,保存的偏移量/sizeof(成员)== 0

                    (4)数组的话是根据数组元素的基类型,而并非一整个数组占了多少个字节

共用体(联合体)

        将不同类型变量存到同一段内存空间中,所占字节数不同,但是从同一地址开始存放,该类结构被称为共用体。共用体中所有元素共用同一段内存

        形式:union 共用体名 {成员表列} 变量表列;        其中分号必须加,变量表列可以单独拿出来重新定义,可以参考结构体

        例:

#include <stdio.h>
union Demo   
{
    int i;
    short s;
    char c;
}a,b;

int main()
{
    union Demo d;
    d.i = 10;
    d.s = 100;
    d.c = 1;
printf("%d\n", d.i);
}

        会输出1,因为三个变量共用一段内存空间,三者首字节地址完全相同,相当于后者将前者覆盖了。内存也是只是最大的类型所占的空间,所以Demo占用4个字节

        通过这种性质可以来判断计算机是大端存储还是小端存储

#include <stdio.h>

union Demo
{
    int i;
    char c;
};

int main()
{
   union Demo d;
   d.i = 0x11223344;
   d.c = 1;
   printf("%x", d.i);
}

        如果是小端存储,则是0x11223301,如果是大端存储,则是0x01223344

        共用体可以作为函数参数传递,而且大部分也是通过指针传参

枚举类型

        如果一个变量只有几种可能的值,便可定义为枚举类型。“枚举”就是把变量的值一一列出来,变量的值仅限于列出来的值

        形式:enum 变量名{变量值,变量值.....};

        例:

        该类型与整型相兼容,默认情况下,常量在花括号中位置是几,对应的整型数字就是几,当然也可以进行如下赋值操作。一般来说,枚举类型和switch-case类型相运用

        输出为1,如果w = Wes的话,输出则为9,它会根据上一个值的默认值加一

typedef

        将已有数据类型的类型名更换

        形式:typedef 数据类型 类型名

        例:

        INT就是int改了名,所以完全可以定义一个INT类型,但是int也可以照常使用。在没加typedef之前,类型名就是变量名,而去掉变量名就是该变量的数据类型,加上typedef之后,变量名改为类型名,所以a的类型是一个长度为40的整型数组,输出40

        typedef可以用于结构体改名,如下,在这里数据类型和类型名并不冲突,因为该类型是struct Demo类型,却不是Demo类型,而且经过typedef后,在主函数定义该结构体时,便可直接Demo,而不用加上struct

        typedef不止可以改一个名字,以下图为例,要判断p的类型,可以从没有typedef时后PDEMO是什么类型,是指针类型,但是p之前不用加*,是因为改名后,PDEMO就表示指针类型,而改名前需要*来表示PDEMQO为指针类型

        typedef更改的名字可以再次进行更改,以下图为例,会输出80。从头来看,第一行将指向函数的指针改名为pfn,第二行将pfn的数组改为ARRAY,所以此时ARRAY表示的是这样的数据类型,即含有十个函数指针的数组,由于指针数据类型占8个字节,所以a作为数组占了80个字节

位运算

        除了~之外,均为双目运算符,运算只能是整型或与整型兼容的数据类型,运算方式是将参数化为二进制,然后进行运算

        &:指定位清零,如果两个对应的二进制位都为1,则结果为1,否则为0

        |:指定为置一,如果两个对应的二进制位都为0,则结果为0,否则为1

        ^:指定位翻转,如果两个对应的二进制位相同,则结果为0,不同则为1

        ~:每个二进制位取反,1变为0,0变为1

        <<:删去最左侧的n个数,在最右边补0(与1使用可以确定第几位为1的二进制数)

                该运算符与其他运算符使用会有简便效果,例如取第二位和第五位为1的数,便可以写为(1 << 5) | (1 << 2)

        >>:对于无符号数,删去右侧n个数,在左边补0,对于有符号数,若符号位为0,则同样,若为1,则根据计算机系统,左边补0为“逻辑右移”,补1为“算数右移”

        例:

运算数 1111 0010 1110 0010 1101 1111 1101 0010 1100 1111 1101 1001
运算符 & | ^ ~ << >>
运算数 1001 1011 1001 0111 1001 0001 2 2
结果 1001 0010 1111 0111 0100 1110 0010 1101 0011 1100 0011 0110

        运算符可以与赋值运算符=一起使用,营造出类似于自增自减的效果,如果长度不同,系统会按右侧对齐,正数和无符号数左侧添0,负数则添1 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐