C语言-结构体

0,引言

我们知道,如果需要保存一个整数,如:学号,定义一个int类型的变量

                  如果需要保存一个小数,如:成绩,定义一个 double类型的变量

                  如果需要保存一个字符串,如:姓名,定义一个字符数组 char name[50];

但是实际问题中,有些数据比较复杂,如:怎么保存一个“学生”数据呢?
    没办法用一个单一的数据去保存,因为学生有很多属性。
​
所以c语言中,允许程序员自定义“组合类型”     -》 构造类型
用来保存一些复杂的数据
    -》
        结构体
        共用体
        枚举

1,结构体

结构体是需要程序员自己去构造的一种类型

用 struct 关键字来构造

1.1 怎么构造/声明一个结构体类型
    struct 结构体名
    {
        成员1类型 成员1名字;
        成员2类型 成员2名字;
        .....
        成员n类型 成员n名字;
    };
​
    成员个数 >= 1即可
​
    上面的语法就是声明了一个新的类型,新类型的名字叫做 : struct 结构体名
    一般写在函数外面。如果有.h文件,就写在 .h文件里面
    如:
        struct test
        {
            int a;
            double b;
        };
    声明了一个结构体类型,叫做 struct test。
    声明:就是告诉编译器现在多了一个新的数据类型,-》 struct test
    意思是说:从此刻起,数据类型除了 int double char long ......这些基本类型之外,我们多了
            一个数据类型 -》 struct test
​
1.2 定义结构体类型的变量
    怎么定义?符合定义变量的基本语法:
    数据类型 变量名;
    struct test t;
    定义一个变量,名字叫做t,类型是 struct test ,变量t中包含了两个成员 a,b 
​
1.3 如何访问结构体变量中的成员
    使用 . 和 -> 这两个运算符
    (1) 结构体变量名.成员变量名
        t.a 
        t.b 
​
        t.a = 10;
        t.b = 4.7;
        printf("t.a=%d,t.b=%lf\n",t.a,t.b);
​
    练习:
        声明一个结构体类型 struct student  ,再定义一个结构体变量,然后给各个成员赋值
​
    (2) (*结构体指针).成员变量名
        struct student * p = &t;//定义指针变量,指向结构体变量t
        (*p).a 
        (*p).b
    (3) 结构体指针->成员变量名
        struct student * p = &t;//定义指针变量,指向结构体变量t
        p->a 
        p->b 
​
    具体见代码
​
1.4 结构体变量初始化 ,用 {}
    (1) 按照声明结构体类型时成员的顺序进行初始化,用逗号隔开
        struct test t = {15,1.8};
        struct student s = {1,85.5,"zhangsan"};
​
    (2) 不按顺序,指定初始化某个成员
        struct test t = {.b=2.5};
        struct student s = {.id=1,.name="zhangsan"};
​
    (3) 初始化结构体数组
        struct student s[3] = {{1,80,"zhangsan"},{2,85,"lisi"},{3,70,"wangwu"}};
    结构体变量之间的赋值,可以直接用 = 符号进行整体赋值
        struct student s1 ={1,80,"zhangsan"};
        struct student s2 ={2,85,"lisi"};
        s1 = s2;
练习题:
    定义一个 struct student数组并初始化,保存5个学生的信息,然后按照成绩进行排序(从小到大)
1.5 结构体各个成员在内存中的布局    -》字节对齐
    最基本原则:各个成员按顺序存储
    strcut test
    {
        int a;
        double b;
    };
​
    struct test t;
    printf("%lld\n",sizeof(t));//16 为什么???
​
    因为构造类型存在字节对齐的问题。
    字节对齐是指在内存中存储各种类型数据时,为了优化内存存储和访问效率,
    需要把特定的数据类型的地址对齐到某个固定的边界上,通常是该数据类型大小的整数倍。
    具体规则(以64位机器为例):
    1,基本对齐原则
        数据类型的起始地址必须是该数据类型大小的整数倍。
        例如:
            一个int类型的数据,地址必须是4的整数倍
            一个double类型的数据,地址必须是8的整数倍
            ....
​
    2,填充字节原则
        为了满足基本对齐原则,在相邻的成员之间可能需要填充若干个字节,使得后续数据类型能够满足对齐规则
        这些填充的数据没有意义
​
    3,结构体整体对齐规则
        对于结构体等构造类型,其对齐方式必须满足最基本类型成员对齐方式的要求。
        并且该结构体的起始地址是最大基本类型成员大小的整数倍,
        该结构体整体大小也是最大基本类型成员大小的整数倍
​
        例如:如果一个结构体中有3个成员,分别是 int ,double ,char类型,那么该结构体
        的起始地址肯定是 8的整数倍,并且结构体的整体大小也是8的整数倍
​
        如果一个结构体中有3个成员,分别是 int ,double ,char [10]类型,那么该结构体
        的起始地址肯定是 8的整数倍(char [10]不是基本类型,当作10个单独的char来看待),
        并且结构体的整体大小也是8的整数倍
        struct test1
        {
            char a;
            short b;
            int c;
        };
        sizeof(struct test1) -> 8
​
        内存布局(*代表填充)  -* -- ----
​
        struct test2
        {
            short b;
            int c;
            char a;
        };
        sizeof(struct test2) -> 12
        内存布局(*代表填充) --** ---- -***
​
        struct test3
        {
            short b;
            char d;
            int c;
            char a;
            char e;
        };
​
        sizeof(struct test3)  -> 12
​
        内存布局(*代表填充) -- -* ---- - -**
​
    struct student
    {
        int id;
        double score;
        char name[20];
    };
​
    sizeof(struct student) -> 40
​
    ---- **** -------- 20--- ****
    struct student
    {
        char name[20];
        int id;
        double score;
    };
​
    sizeof(struct student) -> 32
    20--- ---- --------
    struct test4
    {
        double data;
        char s[10];
        struct student stu;
    };

    sizeof(struct test4) -> 

2,共用体(联合体)

也是构造类型, 用关键字 union 声明

声明语法:

union 共用体名字

{

        成员1类型 成员1名字;

        成员2类型 成员2名字;

         ....

        成员n类型 成员n名字;

};

访问共用体成员的方法和结构体完全一样
和结构体的区别在于:
    结构体各个成员都有独立的内存空间,互不影响
    共用体各个成员公用一块内存,不管有几个成员,都是共用一块内存
        公用一块内存怎么理解?各个成员的首地址相同
    
    union test
    {
        char a; //1字节
        int b;  //4字节
        double c;//8字节
    };
    三个成员a,b,c首地址相同

    union test t;//定义共用体变量
    printf("%p\n%p\n%p\n%p\n",&t,&t.a,&t.b,&t.c);//打印的四个地址的值完全一样

    共用体总共占几个字节,由最大的那个成员决定。

    改变任何一个成员的值,其它的成员都会改变。

大小端模式
    内存中是以字节为单位存储数据的,如果一个数据超过1字节,就会占用多个内存单元,
    这多个内存单元是按照什么顺序来存储这个数据的呢?
    不同的机器有不同的存储顺序,有两种:被称之为大端模式和小端模式

    int b = 0x41424344;//占4字节
    这四个字节的数据分别为 0x41 0x42 0x43 0x44

    小端模式:
        低地址 存 低字节数据
        高地址 存 高字节数据

    大端模式:
        低地址 存 高字节数据
        高地址 存 低字节数据

    具体分析见图

练习:
    int a = 0x12345678;
    char * p = (char *)&a;
    printf("%d\n",*p);

    请问在大端或小端模式下,输出结果是什么?
    大端: 18
    小端: 120

练习:
    写一个程序,测试你的电脑是大端还是小端?
    int a = 0x12345678;
    char * p = (char *)&a;
    //printf("%d\n",*p);   
    if(*p == 0x12)
    {
        大端 
    } 
    else
    {
        小端
    }

    经过测试,我们的电脑是小端模式

练习:分析大小端模式下 以下代码的输出结果
    union test
    {
        int a;
        short b;
    };

    void test3()
    {
        union test t;
        t.a = 0;
        t.b = 0x1234;
        printf("t.a=0x%x,t.b=0x%x\n",t.a,t.b);
    }

    大端模式: 输出 t.a=0x12340000  t.b=0x1234
    小端模式:输出 t.a=0x1234  t.b=0x1234

    void test4()
    {
        union test t;
        t.b = 0;
        t.a = 0x12345678;
        printf("t.a=0x%x,t.b=0x%x\n",t.a,t.b);
    }

    大端模式: 输出 t.a=0x12345678  t.b=0x1234
    小端模式:输出 t.a=0x12345678  t.b=0x5678
共用体适用于那种多个成员不同时使用的情况

3,枚举

把所有可能的值都列举出来

有些情况,一个变量的值是有一定的范围

int direction;//保存方向,约定 上 1  下 2 左 3 右 4
direction = 10;//万一给了一个其它的值怎么办
direction = 2;//就是是约定的值,也需要去找对应的注释才知道 2代表这哪个方向

enum dire //声明一个方向的枚举类型,并且把所有可能的值都列举出来
{
    UP,
    DOWN,
    LEFT,
    RIGTH
};

定义一个这样的变量 :
enum dire d;
d = UP;
d = DOWN;//这样可以提高代码的可读性

枚举{}里面列举出来的值本质其实就是一个整数:
如果没有特殊说明,默认是从0开始,依次递增1
enum dire //声明一个方向的枚举类型,并且把所有可能的值都列举出来
{
    UP,  //0
    DOWN,//1
    LEFT,//2
    RIGTH//3
};

如果有特殊说明,那就按照说明来处理
enum dire //声明一个方向的枚举类型,并且把所有可能的值都列举出来
{
    UP = 5,  //5
    DOWN,//6
    LEFT,//7
    RIGTH//8
};

enum dire //声明一个方向的枚举类型,并且把所有可能的值都列举出来
{
    UP,  //0
    DOWN,//1
    LEFT = 5,//5
    RIGTH//6
};

4, typedef

typedef用来给一个已有类型取一个新的名字

比如: typedef int xxx;//那么xxx就是int的另一个名字

xxx a = 10;//定义了一个xxx/int类型的变量

当然了,取一个新的名字要有意义,上面这样随便取没有必要
    typedef unsigend int uint;//给unsigend int 取了一个新的名字 uint 
    typedef unsigend char uchar;//给unsigend char 取了一个新的名字 uchar 

    uint a = 100;
    uint b=10,c=20;
    uint *p,*q;
    uchar b = 65;
    ....
struct student//声明类型 
{
    int id;
    double score;
    char name[20];
};
typedef struct student Student;//取另一个新的名字
struct student s;
Student s1;//定义了一个结构体变量

typedef struct student
{
    int id;
    double score;
    char name[20];
}Student; //声明类型并取一个名字
for(i=0;i<3;i++)
    strcpy(&x[i][0],ch);
    abc\0
    abc\0
    abc\0
​
    for(i=0;i<3;i++)
        printf("%s",&x[i][i]);
​
        abcbcc
​
int a[12] = {0},*p[3],**pp,i;//p 是 指针数组  pp是二级指针
for(i=0;i<3;i++)
    p[i] = &a[i*4];
​
p[0] = &a[0];
p[1] = &a[4];
p[2] = &a[8];
​
pp = p;
​
*(*(p+3)+1) <-> *(*(&p[0]+3)+1) <-> *(p[3]+1) <-> 
*(*(p+2)+2) <->  .... *(p[2]+2) <-> *(&a[8] + 2) <-> *(&a[0]) <-> a[10]
​
pp[0][1] <-> *(*(pp+0)+1) <-> *(p[0]+1) <-> *(&a[0]+1) <-> a[1]
​
char b1[] = "abcdefg";
char pb = b1 + 3;
while(--pb>=b1)
    strcpy(b2,pb);//abcdefg
int n = 2,*p = &n,*q = p;
->
int n = 2;
int * p = &n;
int * q = p;
void (*func_t) (int ,float);
定义了一个函数指针变量,名字 func_t ,指向的函数无返回值,有两个参数,分别是 int和 float类型
typedef void (*func_t) (int ,float);
声明了一个类型 func_t ,这个类型是函数指针类型,指向的函数无返回值,有两个参数,分别是 int和 float类型
​
func_t p;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值