自定义数据类型(结构体、枚举)

简介: 各位朋友,大家好。欢迎大家来到我的博客,我今天将要为大家分享的是自定义数据类型中的结构体和位段方面的知识。那么,话不多说,接下来就来看看我的分享吧。

什么是结构体呢?


我们都知道数组,我们可以在数组中存放相同数据类型的数据,比如说整型数组里存放的都是整形,字符数组里存放的都是字符,但是,当我们想要存放不同数据类型的时候,比如说一个学生的姓名,年龄,性别,电话号码,我们该怎么做呢?这时候就体现到自定义数据类型:结构体的作用了。我们可以在结构体中存放我们想要存放的数据类型,这就极大的方便了我们的日常使用。


那么我们先来通过一个简单代码,来看看结构体是怎样定义和使用的吧。

#include<stdio.h>
struct Stu
{
  char name[20];
  int age;
  char sex[10];
  char number[11];
}S;
int main()
{
  S = { "张三",18,"female","12345678901" };
  return 0;
}

63.png


struct是关键字,而struct tag则是结构体类型 ,member-list是成员变量,variable-list是一个结构体变量,它属于全局变量,当我们初始化的时候我们可以像上面那样按顺序初始化,也可以像这样不需要严格的按顺序来初始化。

#include<stdio.h>
struct Stu
{
  char name[20];
  int age;
  char sex[10];
  char number[11];
};
int main()
{
//通过.来访问结构体成员变量
  struct Stu S = { .age = 18,.sex = "female",.name = "张三",.number = "12345678901" };
  return 0;
}

并且这里得注意,当我们在给结构体成员变量赋值的时候,前面必须得加上结构体数据类型,否则会报错。就像这样:

#include<stdio.h>
struct Stu
{
  char name[20];
  int age;
  char sex[10];
  char number[11];
} S;
int main()
{
   S = { .age = 18,.sex = "female",.name = "张三",.number = "12345678901" };
  return 0;
}

64.png


在声明结构体类型的时候,还有一种特殊的声明:不完全声明,也叫匿名结构体

struct
{
  char name[20];
  int age;
  char sex[10];
  char number[11];
} S;

这个结构体因为省略了tag,所以我们不知道他的数据类型,所以如果我们要在使用的时候,就需要在定义的时候就初始化,否则我们就找不到这个结构体了。

#include<stdio.h>
struct
{
  int a;
  int b;
}x;
struct
{
  int c;
  int d;
} *p;
int main()
{
  p = &x;
}

65.png


这个代码就是典型的匿名结构体不知道结构体类型的错误。


结构体的自引用


当我们知道了结构体之后,那么结构体有什么作用呢?结构体通常被使用在链表当中,链表中分为数据域跟指针域,数据域中用来数据,指针域中用来存放地址。所以这就需要我们使用结构体来存放不同类型的变量。但是我们这样写可以吗?

struct ListNode
{
  int data;
  struct ListNode next;
};

66.png


这样很显然是不能达到我们的目的的,因为这里struct ListNode并没有定义结束,我们在这里使用的时候可能会报错。正确的定义方法应该是这样的:

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

我们定义一个结构体指针,这个结构体指针用来存放另一个结构体的地址。

并且我们知道,在函数传参的时候,我们可以传值,也可以传址。那么当我们的参数是结构体的时候我们是传值好一些还是传地址好一些呢?

#include<stdio.h>
struct S
{
  int data[1000];
  int num;
};
void Print1(struct S s)
{
  printf("%d\n", s.num);
}
void Print2(struct S* s)
{
  printf("%d\n", s->num);
}
int main()
{
  struct S s = { {1,2,3,4,5},10 };
  Print1(s);
  Print2(&s);
  return 0;
}

是Print1好一些还是Print2好一些呢?我们都知道当我们传入的是值时,其实传入的是一份参数的临时拷贝,拷贝就当然需要额外的消耗内存了,如果结构体的占用内存小一点还好,如果内存很大,那么代码的速度就会减慢,而我们传址,传入的是地址,用指针变量来接收,指针变量最多也就是8个字节,这样就极大的节省了空间,但是又有人会问了,如果传入的结构体的指针,我们在函数中可能会修改结构体里面的内容,这样不就显得不安全了吗?没错,这个顾虑是对的,但是我们可以通过适当的修改来解决这个忧患,那就是加上const修饰。

void Print2(const struct S* s)
{
  printf("%d\n", s->num);
}

说到结构体占用的内存,我们如果想知道结构体的大小该怎么办呢


计算结构体的大小(对齐数)

#include<stdio.h>
struct S1
{
    char c1;
    int i;
    char c2;
};
int main()
{
    printf("%d\n",sizeof(struct S1));
    return 0;
}


结果会是什么呢?6?我们来看看。


67.png

12,为什么这个结构体的大小是12呢?


这里我们就需要知道结构体的对齐数了,在结构体中每个地址都有一个跟0地址处的偏移量,每个数据存储的时候都必须存放在该数据类型跟编译器的默认对齐数相比,较小的数字的整数倍,我们平时使用的vs默认的对齐数是8个字节,而gcc则没有默认对齐数。不仅如此,结构体的第一个数据必须得从0偏移处开始存放,在计算出所有的占用内存后,内存的大小还必须是每个成员变量的整数倍数。知道了这些后我们来看看上面这个12是怎么来的吧。


68.png


image.png


那么再来一个题,小试一手吧。


#include<stdio.h>
struct S1
{
    char c1;
    int i;
    char c2;
};
struct S2
{
    int a;
    char b;
    struct S1;
};
int main()
{
    printf("%d\n", sizeof(struct S2));
    return 0;
}

70.png

这个原理跟上面那个一样的,只是一个结构体中多了一个结构体,我们可以把里面的结构体拆开来看。

我们再来了解下面一个知识:位段。


位段  


什么是位段


1.位段的成员得是整形家族

2.位段的成员名后边有一个冒号和一个数字。

例如:

struct A
{
    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;
};

A是一个位段类型,那么A的大小是多少呢?

#include<stdio.h>
struct A
{
  char _a : 2;
  char _b : 5;
  char _c : 6;
  char _d : 5;
};
int main()
{
  printf("%d\n", sizeof(struct A));
  return 0;
}

image.png


我们来看看位段在内存中是怎样分配内存的吧。

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

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


72.png


char类型,先分配8个比特位,并且从右向左使用,如果剩下的不够使用,就另外再开辟。


73.png


一共开辟的是3个字节。


image.png

枚举


枚举顾名思义就是列举,可以把可能的值都列举出来。

enum Day//星期
{
    Mon,
    Tues,    
    Wed,
    Thur,    
    Fri,
    Sat,
    Sun
};

这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。


枚举的优点

我们可以使用 #define 定义常量,为什么非要使用枚举?

枚举的优点:

1. 增加代码的可读性和可维护性

2. 和#define定义的标识符比较枚举有类型检查,更加严谨。

3. 防止了命名污染(封装)

4. 便于调试

5. 使用方便,一次可以定义多个常量

enum Color//颜色
{
    RED=1,
    GREEN=2,
    BLUE=4
};
enum Color clr = GREEN;

总结

这些就是我学到的关于自定义数据类类型的知识,欢迎大家来点评,记得点赞哦!

相关文章
|
SQL 关系型数据库 MySQL
彻底搞懂 MySQL 事务的隔离级别
MySQL的事务隔离级别一共有四个,分别是读未提交、读已提交、可重复读以及可串行化。
68248 12
彻底搞懂 MySQL 事务的隔离级别
|
缓存
uniapp清理app缓存
uniapp清理app缓存
334 0
|
存储 算法 NoSQL
[Eigen中文文档] 稀疏矩阵操作
在许多应用中(例如,有限元方法),通常要处理非常大的矩阵,其中只有少数系数不为零。在这种情况下,可以通过使用仅存储非零系数的特殊表示来减少内存消耗并提高性能。这样的矩阵称为稀疏矩阵。
835 0
|
Linux 数据处理
Linux中的numfmt命令:数字格式化的强大工具
**numfmt命令在Linux中用于数字格式化,如转换进制、添加千位分隔符、处理字节单位。它支持从文件读取数字并能自定义分隔符、小数位数。例如:`numfmt 12345` 输出12,345(十进制),`numfmt -b 255` 输出11111111(二进制),`numfmt --to=iec 1000000` 输出976.6K(字节单位)。使用时注意选项组合及单位标准。**
|
存储 API 数据安全/隐私保护
邮箱收不到验证码邮件是什么原因
在互联网应用中,未收到验证码邮件常令人困扰。原因包括:邮件误标为垃圾、邮箱设置不当、发件服务器故障、邮箱地址输入错误,及ISP拦截。解决策略有检查垃圾邮件、清理邮箱、修正设置、确认邮箱地址无误、联系服务提供商与ISP,或尝试其他邮箱服务。使用AOKSend等可靠邮件服务可提升送达率,其优势在于高送达率、实时监测与易集成性,确保验证码邮件及时准确到达,改善用户体验。
|
JavaScript 前端开发 Java
Vue CLI脚手架安装、搭建、配置 和 CLI项目分析
Vue CLI脚手架搭建和分析 详解。
486 0
|
JavaScript 定位技术 API
antdesign框架如何使用高德地图(搜索)
antdesign框架如何使用高德地图(搜索)
453 0
|
人工智能 固态存储 关系型数据库
阿里云国际短信费用价格表
阿里云国际短信费用价格表,​​阿里云国际短信费用价格表,印度短信0.216元一条、中国香港短信0.33元一条、美国短信0.053元一条、日本短信0.514元一条、俄罗斯短信1.02元一条、印尼短信1.01元一条、意大利短信0.565元一条、伊朗短信0.592元一条,阿里云国际短信支持东南亚、欧洲、非洲、美洲等国家和地区
1170 0
|
弹性计算 运维 安全
SaaS模式
SaaS模式
572 0
|
Swift 图形学 数据安全/隐私保护
Swift 各版本
介绍Swift各个历史版本
463 0
Swift 各版本