首页 > 其他分享 >C语言自定义数据类型-结构体

C语言自定义数据类型-结构体

时间:2023-11-25 16:32:20浏览次数:37  
标签:struct 自定义 int 数据类型 C语言 char 对齐 字节 结构


在讨论自定义数据类型之前,我们不妨先回忆一下C语言的内置类型。例如字符型的char,整型中的int short long 以及浮点型的 float double,这些都会C语言本身提供的数据类型,但仅仅有这些,是不足以满足我们的开发的。那么也就意味着需要一些复杂类型来帮助我们实现对复杂对象的操作,例如结构体,枚举,联合体等。

结构体

本章主要讨论结构体。将由以下几个部分组成

结构体类型的声明

结构的基础

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。(那么看到了集合我们又会想到数组,回忆一下数组,数组指的是相同类型的一个集合。这是他们的区别。)


结构的声明

struct tag{
  
member-list;//成员列表
  
}variable-list;//变量列表

例如描述一个学生

生命一个结构体类型同时给出学生的属性(成员变量)

struct Student {
    char name[20];//姓名
    int age;      //年龄
    char sex[5];  //性别
    char id[20];  //学号
};//注意分号不能少

可以由以下几个成员组成,char类型数组描述姓名,int类型变量描述年龄,char类型变量描述性别,以及最后的char类型表示学号,当然还可以自己添加更多的信息比如成绩 手机号家庭地址等等。

如何使用呢?用的时候我们需要创建结构体变量s1,s2来使用

int main(){
    
    //创建结构体变量
    struct  Student s1;
    struct  Student s2;
    
}

除此之外还有匿名结构体类型

struct {
    int a;
    char b;
    float c;
}x;

我们可以看到 struct后面是没有名字的,但是在大阔号的后面有个x我们创建的时候就要用到这个x来创建变量。后面这个x就是 变量列表。我们在是用的时候直接使用x.成员变量即可。

同时我们也可以用一个指针变量来代表这个结构体。

struct {
    int a;
    char b;
    float c;
}*xa;

当然这里需要注意的是,x和*xa都是结构体,同时里面的成员变量 也相同,那我们可不可以将x的地址给xa也就是xa这个指针指向x得地址呢?

xa=&x;

这样是会出警告的,这就是需要注意的地方,即便是同样的结构体成员变量也相同,但是在编译的时候,系统会认为这两个结构体是两个不一样的类型。这是一种非法操作。

结构体重命名

这里要用到typedef这个关键字

typedef struct Student{//这里的Student是不能省略的
	  char name[20];//姓名
    int age;      //年龄
    char sex[5];  //性别
    char id[20];  //学号
}Student;
int main(){
	//我们在声明变量得时候可以这样
    struct Student s1;
    //或者直接使用重命名后的Student进行定义
    Student s2;
    
}

这里需要注意 使用typedef的时候 后面的Student是不能省略的。

结构的自引用

在结构体中包含一个类型为该结构本身的成员是否可以呢?

这里我们参考数据结构中的链表,定义一个结构体类型Node,思考一下这样定义可以吗?

struct Node{
    int data;
    struct Node n;
};

如果可以的话,那么这个结构体Node他占多少个字节能算出来吗?

其实这里是会报错的。原因就是结构体不能自己里面包含自己这个变量,你是没办法计算出来这个结构体到底有多大的。所以这种写法是错误的。

那么到底应该怎么写?

struct Node{
    int data;//数据域
    struct Node* next;//指针域
};

应该放的是下一个结构体的指针,这样的话每个结构体的大小是能够算出来的。整型的四个字节以及地址的4/8给字节。

 

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

有了结构体类型,那如何定义变量呢?其实也很简单。

struct AA{
    char c;
    int a;
    double d;
    char arr[20];
};
int main(){
    struct  AA a={'c',100,3.14,"hello 51cto"};
    printf("%c,%d,%lf,%s",a.c,a.a,a.d,a.arr);

    return 0;
}

C语言自定义数据类型-结构体_结构体


如果结构体中嵌套了个结构体的话那么初始化应该怎么初始化呢?

 

struct T{
    double weight;
    short age;
};
struct S{
    char c;
    struct T st;
    int a;
    double d;
    char arr[20];
};
int main(){
//    struct  AA a={'c',100,3.14,"hello 51cto"};
//    printf("%c,%d,%lf,%s",a.c,a.a,a.d,a.arr);

    struct S s={'c',{56.5,19},100,3.14,"hello 51cto"};
    printf("%lf\n",s.st.weight);
    return 0;
}

可以看到结构体S中嵌套了一个结构体T,在对S进行初始化的时候我们需要对T也进行初始化,其实只需要在其中加入大阔号{}然后在里面使用同样的初始化方式初始化即可。

然后输出的时候找到声明的变量 s.st.weight 或者 s.st.age。


结构体内存对齐

内存对齐我们先看一下如何计算结构体所占的字节数,也就是说我们创建了一个结构体他是在内存中占据了多大的空间,这个怎么计算。

struct S1{
    char c1;
    int a;
    char c2;
};
struct S2{
    char c1;
    char c2;
    int a;
};
int main(){
    struct  S1 s1={0};
    printf("%d\n",sizeof (s1));
    struct  S2 s2={0};
    printf("%d\n",sizeof (s2));
    return 0;
}

大家可以自己算一下这两个结构体大小是否一致,以及大小为多少?

这个代码输出结果是:

C语言自定义数据类型-结构体_结构体_02

s1 的内存空间

C语言自定义数据类型-结构体_结构体_03

s2的内存空间

C语言自定义数据类型-结构体_结构体_04

为什么会出现这种情况呢?

答案是因为有结构体的内存对齐。首先我们先讨论结构体的对齐规则:

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

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数=编译器默认的一个对齐数与该成员大小的较小值。vs中的默认值为8 ,gcc没有默认对齐数(成员大小就是对齐数)当然我们可以自己修改默认对齐数语法为:

#pragma pack(4)

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

4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有的最大对齐数(含嵌套结构体的对齐数)的整数倍。

大家可以认真思考一下上面的四个规则。然后我们分析一下为什么是这样。给出内存示意图

首先对于s1:

C语言自定义数据类型-结构体_结构体的内存对其_05

根据上面的规则 结构体的首地址开始计算,

第一个类型因为第一个类型为char 占据1个字节 相对于8来说1比较小所以直接找1的倍数存即可那么就从第一个位置存c1。第一个绿色格子

第二个成员变量类型为int 占据4个字节。相对于8来说4比较小,那么就往下找这片空间4的倍数,往下数三个格。拿出4个格子橙色部分来存储

然后第三个成员变量char类型 占据1个字节,相对于8来说1比较小,所以找1的整数倍直接在橙色下面找第一个格存即可。第二个绿色格子

到此为止我们已经找到了内存空间的分布与存储了,但是有个问题,这里加起来也就才占据9个字节。怎么算出来是12呢?

那么这就要用到第三个规则了。结构体的总大小也要对齐最大对齐数的整数倍,s1的最大对齐数是4,所以最后也要是4的整数倍,距离9最近的整数倍是12,所以最后存储就是12。

那也许有人会问绿色和橙色部分中间那些空间怎么办呢?那些空间依照规则实际上就浪费了。

那依照这种方式大家可以自行分析第二个为什么是8了。

接下来为了让大家理解最后一个规则,我在给出一个例子,也就是结构体嵌套结构体。

struct S3{
    double d;
    char c;
    int i;
};

struct S4{

    char c1;
    struct  S3 s3;
    double d;

};
int main(){
    struct  S3 s3={0};
    printf("%d\n",sizeof (s3));//16
    struct  S4 s4={0};
    printf("%d\n",sizeof (s4));//32


    return 0;
}

 最后的结果是

C语言自定义数据类型-结构体_结构体的内存对其_06

我们先算s3, 首先double类型占据8个字节 它又是第一个成员变量那么从第一个地址处开始存储。占据了8个字节。第二个成员变量为char 1个字节,那就继续往后存储,现在总共9个字节了。但是第三个成员变量int类型 占据4个字节,它需要找到4的倍数,离9最近的4的倍数是12所以从12这个位置开始存,占据4个字节,所以总共是16个字节,又因为16是8的整数所以这个s3就占据16个字节。

然后再来计算s4,char 类型占据一个字节从第一个位置开始存储,紧接着要存储s3,根据规则4,s3自己的最大对齐数为double类型的8个字节。所以这个s3的最对齐数为8所以我们要往下数7个字节,然后再存储s3 存16个字节那目前就占据了内存的24个字节了中间有7个字节是浪费的。最后存储double类型,8个字节 ,24也是8的倍数所以从24往后存即可,存到32个字节。根据规则3,结构体的大小要是最大对齐数的倍数,32也是8的倍数,所以不用往后再加字节数了。所以最终的大小就是32。


计算偏移量的宏offsetof

struct S3{
    double d;
    char c;
    int i;
};

struct S4{

    char c1;
    struct  S3 s3;
    double d;

};
int main(){
    struct  S3 s3={0};

    printf("%d\n",offsetof(struct S4,c1)) ;//0
    printf("%d\n",offsetof(struct S4,s3)) ;//8
    printf("%d\n",offsetof(struct S4,d)) ;//24
}

这里有个宏是可以直接算出来结构体中成员变量的偏移量的,这个大家要会用。


但是我们这里会发现有了这个内存对齐后,会有空间的浪费,那么为什么还需要内存对齐呢?

关于这个问题,官方是没有给出具体的原因的。但是根据大部分资料的原因有两个。

1.平台原因:不是所有硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取的某些特定类型的数据,否则抛出硬件异常。

2.性能原因:数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总的来说内存对齐,算是拿空间换取时间的做法。



结构体传参

我们之前使用结构体都是直接在main函数中对齐进行赋值然后输出,例如

struct S{
    int a;
    char c;
    double d;
};
int main(){

    struct S s;
    s.a=100;
    s.c='b';
    s.d=35,6;
    printf("%d",s.a);
    return 0;
}

算是一种使用,但实际上结构体并不是这样用的。那我们假设写一个初始化结构体的函数如下init

struct S{
    int a;
    char c;
    double d;
};
void init(struct S tmp){
    tmp.a=102;
    tmp.d=3.14;
    tmp.c='aa';
}
int main(){

    struct S s={0};
    init(s);

    //    s.a=100;
//    s.c='b';
//    s.d=35,6;
    printf("%d",s.a);
    return 0;
}

能不能实现对结构体的赋值呢?最后的输出结果是否是102呢?

答案是

C语言自定义数据类型-结构体_结构体的内存对其_07

之所以这样的原因是结构体tmp是另外一个新生成的结构体,我们虽然修改了tmp但是并没有影响原本的结构体s所以这里需要传递地址。怎么修改呢?看下面

struct S{
    int a;
    char c;
    double d;
};
void init(struct S* tmp){
    tmp->a=102;
    tmp->d=3.14;
    tmp->c='aa';
}
int main(){

    struct S s={0};
    init(&s);

    //    s.a=100;
//    s.c='b';
//    s.d=35,6;
    printf("%d",s.a);
    return 0;
}

输出结果:

C语言自定义数据类型-结构体_结构体_08

这样就实现了初始化原本的那个结构体s,所以结构体传参数,要传递其地址。

除了这个原因以外,结构体所占据的空间比较大,如果不传递地址,在时间和空间的开销比较大,所以结构传递参数尽可能的传递地址。





标签:struct,自定义,int,数据类型,C语言,char,对齐,字节,结构
From: https://blog.51cto.com/u_16160587/8561396

相关文章

  • Python:数据类型与操作,变量与函数
    数据类型1.整数Integer(int)2.浮点数Float(默认双精度)3.布尔值Boolean(bool)4.类型Typeprint(type(2))<class'int'>`print(type(2<2.2))<class'bool'>print(type(type(2)))<class'type'>常数1.python内置常量Ture,用于表示布尔真False......
  • 一起来学C语言吧 - 1
    C语言编辑器我用的是CLion,有点狮子、勇猛的人的意思-lion。也是这个杀手不太冷的lion-里昂。希望这个专栏能像伴随着你的狮子和这个不太冷的杀手一样,给你带来帮助和底气、勇气。先看个简单的吧:#include<stdio.h>//输出intmain(){printf("23+43=%d\n",23+43);re......
  • Hive学习路线-自定义函数
    九、自定义函数1.查看系统提供的函数列表showfunctions;2.查看具体某一个函数的描述信息descfunction[extended]函数名称;3.自定义函数Userdefinedfunction/UDF3.1创建一个java项目,导入hive的libs3.2创建类,继承org.apache.hadoop.hive......
  • C语言小案例(考试必备)
    1.学习C语言如何运用指针函数求解一个英语句子中的单词个数。#include<stdio.h>#include<ctype.h>intNumber(char*s){intcount=0;while(*s!='\0'){while(*s=='')++s;if(*s!='\0'){++count;......
  • C语言:爱心代码(表白必备,简单易懂,亲测有效)
       哈喽!学期已经过了大半了,那么我们今天就来用所学的知识编写一个代码,浪漫一下吧!今天我们要编写的是C语言的爱心代码。好了,话不多说,开始我们今天的学习吧~#include<stdio.h>#include<windows.h>intmain(void){floatx,y,a;for(y=1.5;y>-1.5;y-=0.1){ for(x=-1.5;x<1......
  • el-table 字段自定义排序
    我在element-ui中使用el-table排序,默认开启就是el-table-column上加个sortable即可,但是后端返回的数据中含有中文列如tableData中有个字段count对应值是类似 13,6,2,3,4,5,10以上,7,含有中文‘以上’两个字,这个时候自带的排序已经无法满足我的要求,所以需要增加该列的自定义排......
  • 爱芯元智AX650N部署yolov8s 自定义模型
    爱芯元智AX650N部署yolov8s自定义模型本博客将向你展示零基础一步步的部署好自己的yolov8s模型(博主展示的是自己训练的手写数字识别模型),本博客教你从训练模型到转化成利于Pulsar2工具量化部署到开发板上训练自己的YOLOv8s模型准备自定义数据集数据集结构可以不像下面一样,......
  • C语言【自定义数据类型、typedef、动态内存分配】
    C语言【自定义数据类型、typedef、动态内存分配】一、自定义数据类型。​ 关于下面讲到的所有自定义数据类型(enum、struct、union),有一点要说的是:定义类型不是声明变量,做这步操作时不分配内存,也不能在定义类型时赋值(枚举那个不是赋值,是做一个限定,赋值时赋限定之外的值也不报错。)......
  • C语言学习总集篇(分支与循环篇)——从不会到会的过程
    大家好,经过前段时间的学习,我相信大家对C语言的相关知识点有了一个初步的认识了,接下来我会将前面所学的内容进行一个梳理、汇总成一个总集篇。今天是这个篇章的第一篇——分支与循环语句,今天我将用这一篇的内容讲完分支与循环语句的相关内容。一、什么是C语言?C语言是一门 结构化 ......
  • c语言中的指针用法
    1、指向函数的指针在C语言中,函数名实际上是一个指向函数的指针,所以你可以直接使用函数名add来初始化函数指针,而不需要使用&add。在这种情况下,add和&add是等价的。这是因为在C语言中,函数名是函数的入口地址的别名。当你使用函数名时,你实际上获取的是函数的入口地址。这就是为什么......