C语言-结构体


0.引入 

    建模: 建立模型

        为什么要建模?
            是为了理解事物而对事物进行的一种抽象, 是对事物的一种无歧义的书面描述 

                物理学家,数学家     --->  物理/数学公式 
                建筑工程师          --->  CAD制图

        那么 我们程序员建模应该是怎样的呢?
            解决某一个问题
                1)问题域
                    根据问题本身的相关因素,条件等,先把导致该问题的原因分析出来 
                    我们才能去解决问题 
                    C语言中 问题域具体体现在算法思想 

                2)解决方案域
                    对于具体解决问题的方式,方法相关的因素,条件等 
                    解决方法在数学上相当于公式 
                    C语言中 就相当于算法模型,语法,变量等 

        假设 
            用C语言对一个学生进行建模, 也就是 将学生 从现实世界中的事物 抽象到 计算机语言中的 数据类型 
            该如何去描述? 

                学生: 
                    姓名    -->     char name[32];
                    学号    -->     int ID;
                    性别    -->     char/int gender;
                    年龄    -->     int age;
                    身高    -->     int high;
                    班级    -->     int class;
                    成绩    -->     float score;
                    电话    -->     int tel[11];
                    地址    -->     char addr[128];
                    ...

                那么学生的属性有这么多, 而且每一个属性的数据类型都是不一样的
                怎么把事物的属性组合到一个数据类型中? 

        C语言允许程序员自定义 自己的类型的 
                结构体 
                联合体(共用体)
                枚举类型

    
1.结构体  struct

    自定义的一种组合类型

    1)结构体的定义语法: 

        语法: 
                struct 结构体名 
                {
                    成员类型1  成员变量名1;
                    成员类型2  成员变量名2;
                    ...
                    成员类型n  成员变量名n;
                };

                结构体类型有多个成员组成, 且每一个成员都是有自己的类型 (C语言合法的数据类型都可以)
                当然不同的成员, 类型可以相同, 但是 成员变量名是不能相同的

                    "结构体名" : 符合C语言合法标识符的定义规则 

                    "成员类型1,2,...n" : C语言合法的数据类型都可以(基本类型,指针类型,构造类型)

                    "成员变量名1,2,...n" : 符合C语言合法标识符的定义规则 (成员变量名是不能相同的)

            例子: 
                定义一个学生这样的结构体类型 

                    struct student
                    {
                        char name[32];      //姓名
                        int ID;             //学号 
                        char gender;        //性别
                        float score;        //成绩
                        char addr[128];     //地址 
                        //...
                    };

                上面 我们就成功定义了一种新类型   struct student 
                    并且这个类型里面包含了 name, ID, gender, score等等一些成员变量 


            如果用这个类型去定义变量: 
                数据类型  变量名;       // 数据类型  变量名 = 初始值;

                    例子: 
                        定义一个结构体变量 

                        struct student  lisa;  
                            //用 struct student 数据类型 定义了一个变量 名为lisa 
                            // 变量lisa 里面包含了一个char []类型的name, int类型的ID, char类型的gender, float类型的score, ... 


        ☆☆☆
    2)结构体成员的内存布局

        (1)结构体类型所占的内存空间大小 是各个成员变量所占的空间大小之和 (可能会有填充) 
        (2)结构体内各个成员变量 是按照它们定义时 出现的次序 依次保存的 

            例子: 
                struct s1 
                {
                    int a;      // 4 
                    int b;      // 4 
                };
                sizeof( struct s1 ) --> 8 

                struct s2 
                {
                    char a;
                    int b;
                    short c;
                };
                sizeof( struct s2 ) --> 12 

                计算结构体大小时, 要考虑内存布局, 结构体在内存中是按单元存放的
                    每一个单元多大 取决于该结构体中最大的基本类型(和指针)的大小 

                    以int类型占4个字节作为倍数(单元长度), 因为a占用了一个字节, 剩余的3个字节 后面的成员b是存不下的
                    需要开辟一个新的单元存放b, 这个单元b又放满了
                    又重新开辟一个单元 存放c 
                    所以 总共开辟了3个单元,每个单元占4个字节, 占用的总字节数为 3*4 ==> 12 个字节 


            练习:
                1)
                    struct s3 
                    {
                        char a;
                        short c;
                        int b;
                    };
                    sizeof( struct s3 ) -->  2*4  -->  8 

                    struct s4 
                    {
                        char a;
                        short b;
                        short c;
                    };
                    sizeof( struct s4 ) -->  3*2  -->  6

                    struct s5 
                    {
                        char a;
                        char b[2];
                        char c[4];
                    };
                    sizeof( struct s5 ) --> 1+2+4  -->  7 

                2) 
                    struct s6 
                    {
                        int a;
                        char b;
                        double c;
                    };
                    sizeof( struct s6 ) -->     32位操作系统下  4*4 --> 16 
                                                64位操作系统下  2*8 --> 16

                    struct s7
                    {
                        char a;
                        int b;
                        char c;
                        double d;
                    };
                    sizeof( struct s7 ) -->     32位操作系统下 5*4 --> 20 
                                                64位操作系统下 3*8 --> 24 

        ☆☆☆
            结构体的字节对齐的规则: 
                CPU为了访问效率,一般会要求数据按照字节对齐来保存 

                    32位操作系统下, 最大是按照4字节对齐的, 意思就是 哪怕是结构体中最大的成员的长度超过了4个字节 
                            也是按照4字节为一个单元长度来算的 

                    64位操作系统下, 最大是按照8字节对齐的

                可以人为指定对齐方式 
                    32位的默认最大是4字节对齐, 64位的默认最大是8字节对齐 

                    也可以人为指定字节对齐:
                    在代码的最前面添加这个语句 
                        #pragma pack(n)         //n只能是2的幂值 1,2,4,8

    3) 结构体成员变量的访问 
        
        分量运算符(成员选择运算符)
            .
            -> 

        作用: 
            引用某一个成员

        语法: 
            结构体变量名.成员变量名 
            结构体指针->成员变量名 
            (*结构体指针).成员变量名 

            例子: 
                struct lisa;
                lisa.name 
                lisa.ID 
                lisa.score
                ...

        引用 结构体成员变量 和 引用普通变量 是一样的, 也是有左值和右值之分 

                lisa.ID  = 1001;        //左值 
                lisa.score = 88.88;     
                strcpy( lisa.name, "lisa" );
                ...

                int a = lisa.ID;        //右值      a --> 1001


        结构体指针变量的定义 
            struct student *p; 
            p = &lisa;

            struct student *q = malloc( sizeof(struct student) );   
            //struct student *q = malloc( sizeof( *q ) );   

            指针p和q 指向的都是 struct student类型的结构体变量 

            p->name 
            p->ID = 1002;
            
            q->name 
            q->ID 

            (*p).name 
            (*p).ID 

    
    4)定义并初始化结构体变量 

        结构体的初始化 使用{} 

            例子: 
                    struct date     //日期 
                    {
                        int year;
                        int month;
                        int day;
                    };

                    struct student
                    {
                        char name[32];
                        int ID;
                        float score;
                        struct date birthday;
                    };

            (1)按照定义时出现的次序, 依次初始化结构体的每一个成员, 用逗号隔开

                struct student lisa = { "lisa", 1002, 88.88, { 2008, 8, 8 } };

            (2)不按顺序来  
                    .成员变量名 = 值  

                struct student xuxu = { 
                                            .score = 60,
                                            .name = "xuxu",
                                            .birthday = { 2024, 1, 24 },
                                            .ID = 1003
                                        };

                printf("%s\t%d\t%.2f\t%d-%d-%d\n", xuxu.name, xuxu.ID, xuxu.score, xuxu.birthday.year, xuxu.birthday.month, xuxu.birthday.day );


            (3) 结构体数组的初始化 

                按照数组元素的顺序,依次进行初始化
                    struct student class[60] = {
                                                    { "zhangsan", 1003, 80, {2001,1,1} },
                                                    { "lisi", 1004, 90, {2002,2,2} },
                                                    .... 
                                                };

                不按顺序来 :  [下标] = {初始值} 
                    struct student class[60] = {
                                                    [2] = { "zhangsan", 1003, 80, {2001,1,1} },
                                                    [0] = { "lisi", 1004, 90, {2002,2,2} },
                                                    .... 
                                                };

#include <stdio.h>

struct date     //日期 
{
    int year;
    int month;
    int day;
};

struct student	//学生
{
    char name[32];
    int ID;
    float score;
    struct date birthday;
};

int main()
{
	// (1)按照定义时出现的次序, 依次初始化结构体的每一个成员, 用逗号隔开
    struct student lisa = { "lisa", 1002, 88.88, { 2008, 8, 8 } };

	//不按顺序来 .成员变量名 = 值  
	struct student xuxu = { 
            .score = 60,
            .name = "xuxu",
            .birthday = { 2024, 1, 24 },
            .ID = 1003
        };

	printf("%s\t%d\t%.2f\t%d-%d-%d\n", xuxu.name, xuxu.ID, xuxu.score,
                                       xuxu.birthday.year, xuxu.birthday.month, 
                                       xuxu.birthday.day );


	struct student class[2] = {
           { "zhangsan", 1003, 80, {2001,1,1} },
           { "lisi", 1004, 90, {2002,2,2} },
        };
}

            练习: 
                定义一个结构体类型, 用来描述一个学生的基本信息(姓名,学号,成绩,...)
                再使用该类型定义一个结构体数组,用来描述咱班3位同学, 并且从键盘上获取这3个学生的信息
                最后打印输出 

#include <stdio.h>

struct student 
{
    char name[32];
    int ID;
    float score;
    //...
};

int main()
{
    struct student stu[3];

    int i;
    for( i=0; i<3; i++ )
    {
        scanf("%s%d%f", stu[i].name, &stu[i].ID, &stu[i].score );
    }

	putchar('\n');
	
    for( i=0; i<3; i++ )
    {
        printf("%s\t%d\t%.2f\n", stu[i].name, stu[i].ID, stu[i].score );
    }
}


    5)柔性数组 

        作用: 
            为了满足 需要改变长度的结构体 
            方便管理内存缓冲区,减少内存的碎片化 

        用法: 
            在结构体的最后,声明一个长度为0的数组
            就可以使得 这个结构体是可变长的
            对于编译器来说, 此时长度为0的数组 是不占用空间的 

        例子: 

#include <stdio.h>
#include <stdlib.h>

struct test 
{
    int a;
    int b;
    char data[0];   //柔性数组 
};

int main()
{
    printf("%ld\n", sizeof( struct test ) );	// 8

	struct test *p = malloc( sizeof( struct test ) + 10 );

	int i;
	for( i=0; i<10; i++ )
	{
		scanf("%c", p->data + i );
		getchar();
	}

	for( i=0; i<10; i++ )
	{
		printf("%c ", p->data[i] );
	}
	putchar('\n');

	free(p);
}

2. 联合体 (共用体)  union 

    1) 
        语法: 
            union 共用体名 
            {
                成员类型1  成员变量名1;
                成员类型2  成员变量名2 
                ...
                成员类型n  成员变量名n;
            };

            共用体 和 结构体 的区别: 
                结构体所占的空间大小 是各个成员变量所占的空间大小之和 
                共用体所占的空间大小 是各个成员变量中 所占的空间大小 最大的那个 (也有字节对齐)

                共用体的存储空间是各个成员之间共用的
                同一个时刻 只能有一个成员变量, 为了节省空间才提出来共用体 

        例子: 

#include <stdio.h>

union test1 
{
    short a;
    char b;
    int c;
    char d;
};
//sizeof( union test1 ) -->  4 

union test2 
{
    char a[10];		// 10 	最大的成员 
    int c;			// 4	最大的对齐方式(基本类型和指针)
    short d;		// 2
    char e[6];		// 6 
};
//sizeof( union test2 ) --> 12  (也有字节对齐 10 + 2填充 )


int main()
{
	printf("%ld\n", sizeof(union test1) );

	printf("%ld\n", sizeof(union test2) );
}


    ☆☆☆ 
    2)大端模式 和 小端模式 

        CPU内部的寄存器按bit位来储存信息的, 但是内部寄存器的数量是有限的 
            所以 我们经常把寄存器的数据存储到内存中 

        但是 内存它不是按bit位来存储的, 内存是按 字节编号(地址)来寻址的 
            如果我们要把数据保存到内存中, 那么就需要知道 计算机的两种存储模式


            (1)大端模式     参考图示
                是指 数据的高字节(高位) 保存 在内存中的低地址中 
                而数据的低字节(低位) 保存 在内存中的高地址中

                    这种存储方式 有点类似于把数据作为字符串处理
                    地址从低到高增加, 数据从高位到低位 
                    和"阅读习惯"是一样的 (从左至右)

            (2)小端模式 
                是指 数据的高字节(高位) 保存 在内存中的高地址中 
                而数据的低字节(低位) 保存 在内存中的低地址中 

                    这种存储方式 将地址的高低和数据的权位有效的结合起来
                    高地址的部分 权值高, 低地址的部分 权值低 
                    和"阅读习惯"是相反的 (从右至左)

            练习: 
                写一个代码 验证自己的电脑是大端模式 还是 小端模式 ? 

#include <stdio.h>

//写一个代码 验证自己的电脑是大端模式 还是 小端模式 ? 
union test 
{
    char a;
    int b;
};

int main()
{
    union test  t;
    t.b = 1;

    if( t.a )
    {
        printf("小端模式\n");
    }
    else 
    {
        printf("大端模式\n");
    }
}


3. 枚举类型 enum  

    把该类型变量的所有可能的值 都列举出来
    所以 枚举类型一定是可以列举的值, 也就是说 是一个整数值 
        
        当一个变量的值 可能有几种值的时候, 可以将其定义成 枚举类型

            int weekday;        // 星期一 ~ 星期天 

    语法: 
            enum 枚举类型名 
            {
                枚举元素的列表
            };

        例子: 

#include <stdio.h>

enum weekday 
{
        sun, mon=2, tus, wed, thu, fri=7, sat 
    //   0    2      3    4    5     7     8
};

int main()
{
	enum weekday day = mon;
	printf("day = %d\n", day );

	enum weekday s = sat;
	printf("s = %d\n", s );
}


        枚举变量的初始化: 
                枚举变量的赋值 只能是枚举元素之一 

                    enum weekday  date = mon;       // OK

                    day = 1;        // error 

        
        枚举元素 所代表的整型常量, 从左至右依次代表 0,1,2,3,4,5,6, .... 

            也可以在定义时 指定枚举元素的值, 
                后面未指定的元素, 又依次从左至右顺序+1  (数值是可以重复的)

                例如: 
                    enum weekday 
                    {
                            sun, mon=2, tus, wed, thu, fri=7, sat 
                        //   0    2      3    4    5     7     8
                    };

4. typedef 

    typedef 用来声明一个新的类型, 来替换一个已有的类型, 相当于取一个别名 
            为了方便移植  

            语法: 
                typedef 旧的类型名 新的类型名;
                typedef 类型 (新类型名) [size];

        例子: 
            1) 
                typedef unsigned char u8_t; 
                u8_t a;  <==> unsigned char a;

                typedef int DATA;

                    DATA b; <==> int b;
                    DATA c;
                    DATA ...;

            2) 
                typedef struct student  STU;
                STU lisa;   <==>  struct student lisa;

                也可以写成如下形式: 

                        typedef struct student 
                        {
                            char name[32];
                            int ID;
                            float score;
                            //...
                        } STU;
                        意味着 我们在定义这个struct student结构体类型的时候,同时取一个别名STU 
                            此时 两者是等价的   STU <==>  struct student 

                ===========================================================
                    注意: 如果没有typedef的话,那么此时stu是一个普通结构体变量名 

                        struct student 
                        {
                            char name[32];
                            int ID;
                            float score;
                            //...
                        } stu;


            3) typedef 类型 (新类型名) [size]; 

                int b[10];
                typeof(b)  -->  int [10]

                typedef int (INT10) [10];
                INT10 c;    <==>  int c[10];


            4) 
                void (*p) (int, int);   
                            //定义了一个函数指针p 
                            //指向了一个 返回值为void,且带有两个int类型参数 的函数 
                    
                    想要定义一个和指针p 类型一样的变量 q ,该如何定义呢? 

                    typeof(p)  -->  void (int, int ) * 

                        typeof(p) q;
                        --> void (int, int) * q;
                        --> void (*q) (int, int);


                typedef void (int, int ) *  FUNC_TYPE
                --> 
                    typedef void (*FUNC_TYPE) (int, int);  

                            //定义了一个新的类型, 类型名为 FUNC_TYPE , 这个类型是一个函数指针 
                            //指向了一个 返回值为void,且带有两个int类型参数 的函数 

                
    
练习:
        写一个函数, 实现两个超大数的相加
            1478575895732457623895472389
                57235723057209580958405

#include <stdio.h>
#include <string.h>

//字符转换成数字
void char_to_int( char *s )
{
    int i;
    int n = strlen(s);
    for( i=0; i<n; i++ )
    {
        s[i] = s[i] - '0';
    }
}

//数字转换成字符串
void int_to_char( char *s, int n )
{
    int i;
    for( i=0; i<n; i++ )
    {
        s[i] = s[i] + '0';
    }
}

void add_biggest( char *num1, char *num2, char *sum )
{
    char *p1, *p2;      //定义两个指针, 分别指向要处理的数据 

    p1 = num1 + strlen(num1);       //首先 指向个位
    p2 = num2 + strlen(num2);

    int i;
    i = strlen(num1)>strlen(num2) ? strlen(num1) : strlen(num2) ;
    int temp = i;

    //把字符转换成数字
    char_to_int( num1 );
    char_to_int( num2 );

    while( p1 != num1 && p2 != num2 )  //当有一个数加完 就结束循环
    {
        sum[i] = sum[i] + (*p1 + *p2);
        if( sum[i] >= 10 )  //进位
        {
            sum[i] = sum[i] % 10;
            sum[i-1] = sum[i-1] + 1;    //进位 
        }
        p1--;
        p2--;
        i--;
    }

    if( p1 == num1 && p2 != num2 )  //num2还有剩余 
    {
        while( p2 != num2 )
        {
            sum[i] = sum[i] + *p2;
            if( sum[i] >= 10 )  //进位
            {
                sum[i] = sum[i] % 10;
                sum[i-1] = sum[i-1] + 1;    //进位 
            }
            p2--;
            i--;
        }
    }
    else if( p1 != num1 && p2 == num2 )  //num1还有剩余 
    {
        while( p1 != num1 )
        {
            sum[i] = sum[i] + *p1;
            if( sum[i] >= 10 )  //进位
            {
                sum[i] = sum[i] % 10;
                sum[i-1] = sum[i-1] + 1;    //进位 
            }
            p1--;
            i--;
        }
    }

    //再将结果 转换回来 
    int_to_char( sum, temp );

}

int main()
{
    char num1[128] = {58};
    char num2[128] = {58};
    char sum[129] = {58};        //空出sum[0], 留作进位

    scanf("%s%s", num1+1, num2+1 );

    //相加 
    add_biggest( num1, num2, sum );

    sum[0] = sum[0] - 58;
    if( sum[0] == '0' )
    {
        //没有进位
        printf(" %s\n", sum+1 );
    }
    else 
    {
        //有进位的
        printf("%s\n", sum );
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值