C语言学习第八天-结构体

结构体

C语言结构体从本质上讲是一种自定义的数据类型,只不过这种数据类型比较复杂,是由int、char、float等基本类型组成的。

在实际开发中,我们可以将一组类型不同的、但是用来描述同一件事物的变量放到结构体中。

在C语言中,可以使用结构体来存放一组不同类型的数据。

结构体的定义形式为:

struct 结构体名 {

结构体所包含的变量或数组

}

结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。

注意大括号后的分号;不能少,这是一条完整的语句。

结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据。

像int、float、char等是由C语言本身提供的数据类型,不能再进行拆分,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它成为复杂数据类型或构造数据类型。

结构体变量

既然结构体是一种数据类型,那么就可以用它来定义变量。可以定义结构体后再定义结构体变量,也可以在定义结构体的同时定义结构体变量。

成员的获取和赋值

结构体和数组类似,也是一组数据的集合,整体使用没有太大的意义。数组使用下标[]获取单个元素,结构体使用点号.获取单个成员。

获取结构体成员的一般格式为:

结构体变量名.成员名;

整体赋值仅限于定义结构体变量的时候,在使用过程中只能对成员逐一赋值,这和数组的赋值非常类似。

需要注意的是,结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储。

结构体数组

结构体数组是指数组中的每个元素都是一个结构体。在实际应用中,C语言结构体数组常被用来表示一个拥有相同数据结构的群体,比如一个班的学生、一个车间的职工等。

结构体指针

结构体变量名或数组名不同,数组名在表达式中会被转换为数组指针,而结构体变量名不会,无论在任何表达式中它表示的都是整个集合本身,要想取得结构体变量的地址,必须在前面加&。

获取结构体成员

通过结构体指针可以获取结构体成员,一般形式为:

(*pointer).memberName或者pointer->memberName

第一种写法中,.的优先级高于*,(*pointer)两边的括号不能少。第二种写法中,->是一个新的运算符,习惯称它为“箭头”,有了它,可以通过结构体指针直接取得结构体成员;这也是->在C语言中的唯一用途。

结构体指针作为函数参数

结构体变量名代表的是整个集合本身,作为函数参数时传递的整个集合,也就是所有成员,而不是像数组一样被编译器转换成一个指针。如果结构体成员较多,尤其是成员为数组时,传送的时间和空间开销会很大,影响程序的运行效率。所以最好的办法就是使用结构体指针,这时由实参传向形参的只是一个地址,非常迅速。

枚举类型

C语言提供了一种枚举类型,能够列出所有可能的取值,并给它们取一个名字。

枚举类型的定义形式为:

enum typeName{ valueName1, valueName2, valueName3, ... };

enum是一个新的关键字,专门用来定义枚举类型,这也是它在C语言中的唯一用途;typeName是枚举类型的名字;valueName1, valueName2, valueName3, ...是每个值对应的名字的列表。注意最后的;不能少。

枚举和宏其实非常类似:宏在预处理阶段将名字替换成相应的值,枚举在编译阶段将名字替换成对应的值。我们可以将枚举理解为编译阶段的宏。

共用体

在C语言中,还有另外一种和结构体非常类似的语法,叫做共用体。

共用体定义格式:

union 共用体名

{

        成员列表

};

共用体有时也被称为联合或者联合体。

结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。

结构体占用的内存大于等于所有成员的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。

共用体也是一种自定义类型,可以通过它来创建变量。可以先定义共用体,再创建变量,也可以在定义共用体的同时创建变量。

大端小端

大端和小端是指数据在内存中的存储模式,它由 CPU 决定。

大端模式(Big-endian)是指将数据的低位(比如 1234 中的 34 就是低位)放在内存的高地址上,而数据的高位(比如 1234 中的 12 就是高位)放在内存的低地址上。这种存储模式有点儿类似于把数据当作字符串顺序处理,地址由小到大增加,而数据从高位往低位存放。

小端模式(Little-endian)是指将数据的低位放在内存的低地址上,而数据的高位放在内存的高地址上。这种存储模式将地址的高低和数据的大小结合起来,高地址存放数值较大的部分,低地址存放数值较小的部分,这和我们的思维习惯是一致,比较容易理解。

位域

有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。

在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。

:后面的数字用来限定成员变量占用的位数。C语言标准规定,位域的宽度不能超过它所依附的数据类型的长度。通俗地讲,成员变量都是有类型的,这个类型限制了成员变量的最大长度,:后面的数字不能超过这个长度。

C语言标准还规定,只有有限的几种数据类型可以用于位域。在 ANSI C 中,这几种数据类型是 int、signed int 和 unsigned int(int 默认就是 signed int);到了 C99,_Bool 也被支持了。

位域的存储

位域的具体存储规则如下:

1. 当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。

2. 当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC会压缩存储,而 VC/VS 不会。

3. 如果成员之间穿插着非位域成员,那么不会进行压缩。

无名位域

位域成员可以没有名称,只给出数据类型和位宽。

无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。

位运算

所谓位运算,就是对一个比特(Bit)位进行操作。

运算符&|^~<<>>
说明按位与按位或按位异或取反左移右移

按位与运算(&)

一个比特(Bit)位只有 0 和 1 两个取值,只有参与&运算的两个位都为 1 时,结果才为 1,否则为 0。例如1&1为 1,0&0为 0,1&0也为 0,这和逻辑运算符&&非常类似。

再强调一遍,&是根据内存中的二进制位进行运算的,而不是数据的二进制形式;其他位运算符也一样。按位与运算通常用来对某些位清 0,或者保留某些位。

按位或运算(|)

参与|运算的两个二进制位有一个为 1 时,结果就为 1,两个都为 0 时结果才为 0。例如1|1为1,0|0为0,1|0为1,这和逻辑运算中的||非常类似。

按位或运算可以用来将某些位置 1,或者保留某些位。

按位异或运算(^)

参与^运算两个二进制位不同时,结果为 1,相同时结果为 0。例如0^1为1,0^0为0,1^1为0。

按位异或运算可以用来将某些二进制位反转。

取反运算(~)

取反运算符~为单目运算符,右结合性,作用是对参与运算的二进制位取反。例如~1为0,~0为1,这和逻辑运算中的!非常类似。

左移运算(<<)

左移运算符<<用来把操作数的各个二进制位全部左移若干位,高位丢弃,低位补0。

如果数据较小,被丢弃的高位不包含 1,那么左移 n 位相当于乘以 2 的 n 次方。

右移运算(>>)

右移运算符>>用来把操作数的各个二进制位全部右移若干位,低位丢弃,高位补 0 或 1。如果数据的最高位是 0,那么就补 0;如果最高位是 1,那么就补 1。

如果被丢弃的低位不包含 1,那么右移 n 位相当于除以 2 的 n 次方(但被移除的位中经常会包含 1)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值