草庐IT

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

不能再留遗憾了 2023-05-14 原文

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

目录

什么是结构体呢?

结构体的自引用

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

位段 

什么是位段

 枚举

枚举的优点

总结


什么是结构体呢?

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

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

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

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

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

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

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

结构体的自引用

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

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

 这样很显然是不能达到我们的目的的,因为这里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?我们来看看。

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

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

  

 

 

 

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

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

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

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

位段 

什么是位段

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

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

1.位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
2. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
 

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

一共开辟的是3个字节。

 

 枚举

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

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;

总结

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

有关自定义数据类型(结构体、枚举)的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  5. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  6. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  7. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  8. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  9. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  10. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

随机推荐