0 简介
在C语言中,相同类型的数据元素若想放在一起,可以采用数组,那不同类型的元素呢?
有的计算机语言是不会care
这个问题的,比方说python
,毕竟python
本来就是所谓的弱数据类型的语言;C
语言虽然出生较早,但历尽千帆,仍能常年在计算机语言排行榜中名列前茅,其优势之一便是对数据类型和内存的精准把控。而结构和联合,正是这种思想的完美诠释与实践。
在部分的教材和资料中,结构也称为结构体
,联合也称为联合体
,共同体
,共用体
。
正所谓“一花独放不是春,百花齐放春满园”,因其可以容纳多种类型的数据,在实际开发中,结构和联合经常合作发力,为项目开发提供了很大的便利。
本篇内容概览:
1 结构基础知识
聚合数据类型能够同时存储超过一个的单独数据。C提供了两种类型聚合数据类型,数组
和结构
。结构是一些值的集合,这些值称为它的成员。
数组可以元素可以通过下标访问,这只是因为数组的元素长度相同。但是在结构中情况并非如此。由于一个结构的成员可能长度不同,所以不能使用下标来访问他们。
1.1 结构声明
结构体的声明形式如下:
struct tag {member-list} variable-list;
除了结构标签的不完整声明,所有可选部分不能全部省略,至少要出现两个。这句话有点费解 ,只需要看几个例子便可以明白,在实际开发中,结构体常见的声明方式只有两种,将在下面进行说明。
struct { int a; char b; float c; }x;
这里声明了一个名叫x的变量,包含三个成员,声明没有什么错误,但是因为拓展性不强,在实际开发中并不常用,较为常用的是以下两种。
struct SIMPLE{ int a; char b; float c; };
这种有了标签,就可以避免重复造轮子,有点像C++中创建的对象。对于具有相同属性的事物,只需要创建一次即可,在具体的使用中,可根据自己的需要进行变量的创建。
还有一种更加方便的方法,也就是采用typedef
创建一种新的类型。
typedef struct { int a; char b; float c; }Simple;
这种方法看起来在定义的时候稍微复杂了一点点,但是在使用的时候方便了一些,举个例子:
#include <stdio.h> struct SIMPLE{ int a; char b; float c; }; typedef struct { int a; char b; float c; }Simple; int main() { //定义结构体变量 struct SIMPLE s1; Simple s2; //给结构体变量赋值 s1.a = 10; s1.b = '?'; s1.c = 3.1415; s2.a = 20; s2.b = '!'; s2.c = 3.1415; //打印输出 printf("s1.a = %d\n",s1.a); printf("s1.b = %c\n",s1.b); printf("s1.c = %f\n",s1.c); printf("s2.a = %d\n",s2.a); printf("s2.b = %c\n",s2.b); printf("s2.c = %f\n",s2.c); return 0; }
运行,打印输出:
可以看到如果采用了typedef
关键字来声明结构体,则在定义结构体变量的时候就可以少写一个struct
关键字,看似不会简洁太多,但是在大型项目开发中,会省去更多的时间。
1.2 结构成员
到目前为止的例子里,我只使用了简单类型的结构成员。但可以在一个结构外部声明的任何变看都可以作为结构的成员。尤其是,结构成员可以是标量、数组、指针甚至是其他结构(标量一般指的是整型或者浮点型的数据)。
比如:
struct COMPLEX( float f; int a[20]; long *lp; struct SIMPLE s; struct SIMPLE sa[10]; struct SIMPLE *sp; };
1.3 结构成员的直接访问
结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数,左操作数就是结构变量的名字,右操作数就是需要访问的成员的名字。这个表达式的结果就是指定的成员。例如,考虑下面这个声明:
struct COMPLEX comp;
比方说,表达式
((comp.sa)[4]).c
下标引用和点操作符具有相同的优先级,他们的结合性都是从左向右,所以可以省略所有的括号。变成了下面的表达式
comp.sa[4].c
此二者等效。
所以在访问嵌套的数据类型的时候,与普通的数据元素访问没有什么区别。只需要一层一层访问即可。
1.4 结构成员的间接访问
当我们拥有一个指向结构的指针时,该如何访问这个结构的成员呢?首先就是对指针执行间接访问操作。然后使用点操作符来访问它的成员。但是点操作符的优先级高于间接访问操作符,所以在访问结构的成员的时候就出现了下面的情况。
先定义一个指向结构体的指针:
struct COMPLEX *CP;
然后用访问其元素:
(*cp).f
这样的操作符显然过于繁琐,于是在C语言中就出现了->
操作符,结构体指针可用来访问结构体成员,如下:
cp->f cp->a cp->s
表达式1
访问一个浮点数成员,表达式2
访问一个数组名,表达式3
访问一个结构。
1.5 结构的自引用
结构体中包含一个同类型的结构体是否合法呢?答案是否定的。因为这样可以无穷无尽地包含下去,导致程序无法执行。比如:
struct SELF_REF1 { int a; struct SELF_REF1 b; int c; };
但是结构体中包含指向同类型结构体的指针是合法的,比如:
struct SELF_REF2 { int a; struct SELF_REF2 *b; int c; };
因为我们仅仅是多了一个指向同类型结构体的指针,不会出现层层包含,无穷无尽的情况。在实际开发中,经常用来实现一些数据结构,比方说链表和树,每个节点都可能指向相同类型的下一个节点(有时不止一个)。
1.6 不完整的声明
通常情况下,我们的声明都是完整的,但若是有两个相互包含的结构体,究竟应该先定义哪一个呢?
这个时候,我们的不完整声明就派上用场了,如下所示:
struct B; struct A { struct B *partner; }; struct B { struct A *partner; };
虽然声明了结构体B,但是并未完全声明,因为结构体成员未给出,主要是没法给出,若是给出A
,但A
也尚未定义。于是就有了这样的处理方法。
如此一来,就形成了“你中有我,我中有你”的两个结构体。如下图所示:
1.7 结构的初始化
结构体的初始化方式和数组的初始化很类似。在一对花括号内用逗号分隔,然后再分别对各个成员进行初始化(赋值)即可。如果初始列表的值不够,剩余的结构成员将使用缺省值进行初始化。举个例子:
struct INIT_EX { int a; short b[10]; Simple c; }x = { 10,{1,2,3,4,5},{10,'x', 3.14} };
可以看到成员中有个数组b
,我们仅初始化了起始的5
个元素,其余元素则会采用缺省值进行初始化,一般情况下初始化为0
。如下图所示: