首页 > 其他分享 >C自定义类型(结构体,联合体,枚举)详解

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

时间:2024-08-04 14:25:11浏览次数:15  
标签:Node struct 自定义 int 枚举 详解 printf 对齐 结构

        在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;
}

标签:Node,struct,自定义,int,枚举,详解,printf,对齐,结构
From: https://blog.csdn.net/qq_35621280/article/details/140777659

相关文章

  • linux下时间时区详解
    首先我们要明白,“时间”和“时区”是两个东西。时间是指从某个时间点开始到另一个时间点经过的“长度”,是“纵向”距离,一般在linux系统内有两个主要的时间,一是始于1970年(unix元年)至今的距离,二是系统启动后至今的距离。前者一般是由不断电的硬件维护(RTC)或者其他专门服务器......
  • 【C++核心篇】—— C++面向对象编程:封装相关语法使用和注意事项详解(全网最详细!!!)
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、封装(类)1.封装的使用(类和对象)2.对象的初始化和清理2.1构造函数2.2析构函数2.3构造函数的分类及调用3.深拷贝与浅拷贝4.C++对象模型和this指针5.友元6.运算符重载前言在本篇......
  • Java 文件 I/O流详解
    文件文件操作是Java开发中一个重要的组成部分,它允许开发者对文件进行读取,写入,创建,删除和修改等操作,文件操作的主要通过java.io包中的类来实现的,其中的File类更是文件操作的核心类File类的常用方法创建文件或目录文件创建使用createNewFile();可以创建一个新的空文......
  • 【leetcode详解】另一棵树的子树 (C++递归:思路精析&& 过程反思)
    思路详解:总体框架:对root树进行先序遍历,如果当前结点(记为cur)的值和subRoot的根节点值相等时,就开始判断 以cur为根节点的树和子树是否结构一样?如何判断两棵树是否结构完全相同?分析:一提到“树”结构,很容易想到在(先/中/后序)遍历上做文章,请教了AI后笔者得知,如果两棵树......
  • 枚举
    usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;namespaceMR.View{classProgram{enumenXingQi{星期一=1,星期二=2,星期三=3,星期四=4,......
  • wkt格式文件详解(包含应用示例)
    还是大剑师兰特:曾是美国某知名大学计算机专业研究生,现为航空航海领域高级前端工程师;CSDN知名博主,GIS领域优质创作者,深耕openlayers、leaflet、mapbox、cesium,canvas,webgl,echarts等技术开发,欢迎加底部微信(gis-dajianshi),一起交流。No.内容链接1Openlayers【入门教程】-......
  • WPF【无限滚动图片浏览】自定义控件
    自定义控件自定义控件是我比较陌生的一个主题。我好久没练习过wpf了,需要巩固记忆。我想了一会儿,打开动漫之家,忽然觉得这个看漫画的图片浏览控件有意思。于是特地花了一天做了这个图片控件。我原本以为很容易,但实际上并不简单。这个图片浏览控件比我想象中要难许多,有技术上的难题......
  • UML类图 详解
    总目录前言作为一个程序员,我们经常会使用UML来绘制各种图(UML中定义了用例图、类图、时序图、协作图等九种),类图就是其中常用图之一。设计模式中经常会用到的是类图,本文主要是学习UML类图相关资料后的汇总笔记,也是作为设计模式系列文章中“前菜”。一、基本介绍1.什么是......
  • C++自定义接口类设计器之模板代码生成四
    关键代码QStringListmultis=templateStr.split('\n');boolstartConfig=false;boolstartVar=false;boolstartTemplate=false;for(constauto&line:multis){if(startConfig){if(line.trimmed().st......
  • 【C语言】字符函数和字符串函数详解
    ......