C自定义类型(结构体,联合体,枚举)详解

        在C语言中,数据类型可以分为内置类型(char short int long float double...)和自定义类型。内置类型是由编程语言本身定义的基本数据类型,而自定义类型是由程序员根据需要创建的数据类型。

        自定义类型:  结构体 ,联合体(共用体),枚举。

结构体:用于组合多个不同类型的数据项。结构体允许在一个单独的数据结构中存储不同类型的数据。每个数据项在结构体中称为一个成员(member),结构体的每个成员可以是不同类型的变量。

//结构体类型的定义

struct Stu    //struct: 结构体关键字  Stu: 结构体标签名(tag)    
{             //成员列表 在这不能初始化 因为是创造的类型 并未分配内存 类似于int flaot这种 在使用该结构体类型创建变量时,才会为这些成员分配内存空间。
    char name[20];
    char id[10];
    int age;
}s1,s2;      //变量列表  s1与s2是struct Stu类型的变量为全局变量  


int main()
{
    struct Stu s3;//s3为局部变量
    return 0;
}

匿名结构体:指在声明结构体变量时不给出结构体类型名称,直接定义结构体的成员。这种方式常用于临时的数据,无法在其他地方再次使用同样的结构体定义。只能用一次。

//匿名结构体

struct   //省略了结构体标签(tag)
{
    char name[20];
    char id[10];
    int age;
}s1;

结构体的自引用:指在结构体的定义中包含指向相同结构体类型的指针

#include <stdio.h>

// 定义一个包含自引用的结构体 Node
struct Node {
    int data;
    struct Node *next; // 指向下一个 Node 结构体的指针
};

int main() {
    // 声明结构体变量
    struct Node node1, node2, node3;

    // 设置节点数据
    node1.data = 10;
    node2.data = 20;
    node3.data = 30;

    // 建立节点之间的关系
    node1.next = &node2;
    node2.next = &node3;
    node3.next = NULL; // 最后一个节点的 next 指针通常设为 NULL

    // 遍历链表并输出数据
    struct Node *current = &node1;
    while (current != NULL) {
        printf("%d\n", current->data);
        current = current->next;
    }

    return 0;
}

typedef 对于结构体名的影响

typedef struct Node
{
    int data;
    struct Node* next;
} Node;

typedef 关键字被用来为结构体 Node 创建一个别名,可以在代码中使用 Node 代替 struct Node 来定义该结构体。在这里,Node 是结构体 Node 的别名,可以像使用其他数据类型一样使用它来声明变量,如Node Node1。

struct Node
{
    int data;
    struct Node* next;
}Node;

无typedef 关键字,Node表示定义了一个结构体变量 Node,即在定义结构体的同时也创建了一个名为 Node 的结构体变量。所以,Node 在这种情况下是一个具体的结构体变量,而不是一个类型别名。如果需要创建另一个 Node 结构体变量,可以使用 struct Node Node2。

结构体变量的定义和初始化:

struct Point
{
 int x;
 int y;
}p1; //声明类型的同时定义变量p1

struct Point p2; //定义结构体变量p2

//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};

struct Stu       
{
 char name[20];
 int age;      
};

struct Stu s = {"PTM", 20};//初始化

struct Node
{
 int data;
 struct Point p;
 struct Node* next; 
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化

struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

结构体内存对齐:

        在 C 语言中,结构体内存对齐(Memory Alignment)是指编译器为结构体成员分配内存时,为了提高访问效率,会使结构体成员按照一定规则对齐在内存中。内存对齐的规则因编译器和平台而异,通常受到结构体成员的数据类型和编译器的设定影响。以下是一些内存对齐的一般规则:

默认对齐规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8。

3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
大多数编译器会使用“最严格对齐原则”,即结构体成员的偏移量应该是成员大小的整数倍,且结构体的大小应该是成员中最大成员大小的整数倍。
结构体成员对齐:
基本数据类型通常按其大小对齐(如 char 一般按1字节对齐,int 通常按4字节对齐)。
结构体成员的对齐通常受到平台的影响,比如 32 位平台一般按4字节对齐,64 位平台一般按8字节对齐。
指定内存对齐:
可以使用 #pragma pack(n) 指令(n 为对齐字节数)来改变默认对齐规则,但这种做法可能会增加内存访问的开销。
结构体大小计算:
结构体的大小通常是其成员大小的总和,但因为对齐规则的存在,结构体的大小可能会大于成员大小的总和。
内存对齐优化:
内存对齐可以提高访问效率,但也可能会引起内存浪费,特别是在结构体的成员排列上。
总的来说,内存对齐是为了提高程序的性能和效率,但也需要注意可能带来的内存浪费问题。在实际编程中,可以通过合理设计结构体成员的排列顺序和使用 #pragma pack 等方式来优化内存对齐。

结构体的内存对齐是用空间换时间,将结构体成员按照大小递减的顺序排列,让占用空间小的成员尽量集中在一起。

#include <stdio.h>
#include <stddef.h>
#pragma pack()//恢复到默认对齐
//x64

struct S1   //0(a) 1 2 3 4(b) 5 6 7 8(c) 9 10 11 12 13 14 15
{
    char a;
    int b;
    double c;
};

struct S2   //0(b) 1 2 3 4 5 6 7 8(c) 9 10 11 12 13 14 15 16(a) 17 18 19 20 21 22 23 24
{
    int b;
    double c;
    char a;
};

#pragma pack(4)//更改默认对齐为4字节

struct S3  //0(b) 1 2 3 4(c) 5 6 7 8 9 10 11 12(a) 13 14 15 
{
    int b;
    double c;
    char a; 
};

int main()
{
   
    printf("%zd\n", sizeof(struct S1));//16
    printf("%zd\n", sizeof(struct S2));//24
    printf("%zd\n", sizeof(struct S3));//16

    printf("\n");

    printf("%zd\n", offsetof(struct S1, a));//0
    printf("%zd\n", offsetof(struct S1, b));//4
    printf("%zd\n", offsetof(struct S1, c));//8

    printf("\n");

    printf("%zd\n", offsetof(struct S2, a));//16
    printf("%zd\n", offsetof(struct S2, b));//0
    printf("%zd\n", offsetof(struct S2, c));//8

    printf("\n");

    printf("%zd\n", offsetof(struct S3, a));//12
    printf("%zd\n", offsetof(struct S3, b));//0
    printf("%zd\n", offsetof(struct S3, c));//4

    return 0;

}

结构体传参:结构体传参的时候,最好传结构体的地址。

#include <stdio.h>

struct S
{
    char arr[20];
    int n[3];
    int num;
};

void print1(struct S s)
{
    int i = 3;
    printf("%s\n",s.arr);

    while (i--)
    {
        printf("%d ", s.n[i]);
    }

    printf("%d \n", s.num);

}

void print2(const struct S* s)
{
    int i = 3;
    printf("%s\n", (*s).arr);

    while (i--)
    {
        printf("%d ", s->n[i]);
    }

    printf("%d ", s->num);

}

int main()
{
   
    struct S s = { "hello world",{1,2,3},-10 };

    print1(s);  //传值调用  在传值方式下,函数会复制整个结构体的内容,函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销,会导致性能的
下降。

    print2(&s); //传址调用  通过传递结构体指针的方式,可以减少复制结构体的开销。

    return 0;

}

位段:位段(Bit Fields)是 C 语言中一种特殊的结构体成员,用于在结构体中按位对数据进行存储。通过位段,可以将一个整数类型的数据按照指定的位数拆分成多个字段,从而节省内存空间。

struct {
    type member_name : width;
} variable_name;
  • type:表示位段的数据类型,可以是 intchar 等整数类型。
  • member_name:位段成员的名称。
  • width:指定该位段的位宽,即占用的位数。

1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

位段的跨平台问题 :

1. int 位段被当成有符号数还是无符号数是不确定的。

2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。

总结: 跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

枚举: 用于存储一组固定的常量值,把可能的取值一 一列举出来。

#include <stdio.h>

enum Day //enum: 枚举关键字  Day: 枚举标签
{
    Mon,//枚举常量  这些取值都是有值的,默认从0开始,一次递增1。
    Tues,
    Wed=5,//赋初值从5开始,一次递增1。
    Thur,
    Fri,
    Sat,
    Sun
};

int main()
{

    enum Day d = Fri;//声明一个名为d的枚举类型变量,并将其赋值为Fri。
    d = Mon;
    printf("%d\n", Mon);//0
    printf("%d\n", Tues);//1
    printf("%d\n", Wed);//5
    printf("%d\n", Thur);//6
    printf("%d\n", Fri);//7

    return 0;

}

联合体:允许在相同的内存位置存储不同的数据类型。在联合体中,所有成员共享相同的内存空间又叫共用体,因此联合体的大小等于最大的成员大小。

#include <stdio.h>

union Un //union: 联合体关键字   Un: 联合体标签
{
    int a;//4
    char b;//2
};

int main()
{
    union Un u;//创建一个联合体变量
    printf("%d\n",sizeof(u));//4

    printf("%p\n", &u);
    printf("%p\n", &(u.a));
    printf("%p\n", &(u.b));

    u.a = 0x12345678;
    u.b = 0x11;
    printf("%#x\n", u.a);//0x12345611

    return 0;
}
#include <stdio.h>

int check_sys()
{   
    int a = 1;
    return *(char*)&a;
}

int check_sys2()
{
    union //匿名联合体  只使用一次
    {
        char c;
        int i;
    }u;
    u.i = 1;
    return u.c;
}
//判断存储模式(大端模式/小端模式)
int main()
{
    //int a = 1;//0x 00 00 00 01
    //低地址----------高地址
    //01 00 00 00 --- 小端存储
    //00 00 00 01 --- 大端存储

    if (check_sys2())
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    return 0;
}

联合体大小的计算:

联合体的大小至少是最大成员的大小。 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

#include <stdio.h>

union Un
{
    char arr[9];//9
    long long i;//8
}u;

int main()
{
    printf("%d\n",sizeof(long));//4
    printf("%d\n", sizeof(long long));//8
    printf("%d\n", sizeof(u));//16
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值