C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
在实际的编程过程中,我们往往还需要一组类型不同的数据,例如对于学生信息登记表,姓名为字符串,学号为整数,因为数据类型不同,显然不能用一个数组来存放这些信息。这就需要使用到结构体来存储信息。
结构体
定义结构体
为了定义结构,必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
struct stu{
char *name; //姓名
int num; //学号
};
stu是结构体名,它包含两个成员,分别是 name、num。结构体成员的定义方式与变量和数组的定义方式相同,但是结构体内部变量不能初始化。
注意大括号后面的分号“ ; ”不能少,这是一条完整的语句。
结构体也是一种数据类型,只是它由程序员自己定义,可以包含多个其他类型的数据。包含的数据类型可以是任意一种C的数据类型,甚至可以是其他结构体!
定义结构体变量
既然结构体也是一种数据类型,那么我们就可以定义它:
struct stu stu1;
这样就定义了一个结构体变量stu1,为stu类型,具有两个成员组成。struct stu起到的作用相当于一般声明中的int或float。
例如,也可以定义两个struct stu类型的变量,或者是指向struct stu类型结构体的指针:
struct stu stu1, stu2, *stu3;
也可以在定义结构体的同时定义结构体变量(将变量名放在结构体定义的最后即可):
struct stu{
char *name; //姓名
int num; //学号
} stu1, stu2;
如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名:
struct{ //没有写 stu
char *name; //姓名
int num; //学号
} stu1, stu2;
这样定义虽然省去了结构体名,但是若后序需要使用到结构体的话将无法定义。
理论上讲结构体的各个成员在内存中是连续存储的,和数组非常类似的;但是在编译器的具体实现中,各个成员之间可能会存在缝隙,number和code[4]之间就可能存在缝隙。
结构体赋值与访问
初始化一个结构体变量与初始化数组的语法相似:
struct stu stu1 = {
"qw",
182061
};
使用一对花括号中括起来的初始化列表进行初始化,各初始化项用逗号分隔。
使用结构体成员运算符——点( . )访问结构体中的成员。例如,stu1.num即访问stu1的num部分。可以想使用任何int类型变量那样使用stu1.num。本质上,.num的作用就相当于stu1结构体的下标。
stu1.name = "qw";
stu1.num = 182061;
C99和C11为结构体提供了指定初始化器,其语法与数组的指定初始化器类似。但是,结构体的指定初始化器使用点运算符和成员名(而不是方括号和下标)标识特定的元素。
struct stu stu1 = { .num=182061 };
struct stu stu2 = { .num=182061,
.name = "qw"
};
嵌套结构
#define LEN 20
struct names{ //第1个结构
char first[LEN];
char last[LEN];
};
struct guy{ //第2个结构
struct names handle; //嵌套结构
char favfood[LEN];
char job[LEN];
float income;
};
结构体数组
结构体数组,是指数组中的每个元素都是一个结构体。在实际应用中,结构体数组常被用来表示一个拥有相同数据结构的群体,比如一个班的学生、一个车间的职工等。
struct stu stu1[MAXBKS];
以上代码声明了一个内含MAXBKS各元素的数组,数组中的每个元素都是一个stu类型的结构体。因此,stu1[0]是第一个stu类型的结构体变量,stu1[1]是第二个stu类型的结构体变量,依次类推。
为了标识结构体数组中的成员,可以采用访问单独结构的规则:在结构名后面加一个点运算符,再在点运算符后面写上成员名。如下所示:
stu1[1].name
stu1[4].num
总结一下:
stu1 //一个stu结构的数组
stu1[0] //一个数组元素,该元素是stu结构
stu1[0].num //一个int类型变量(stu1[0]的num成员)
stu1[1].name[0] //数组中stu1[1]元素的name成员的一个字符
结构体指针
声明和初始化结构体指针
声明结构体指针,与其他指针声明一样:
struct stu *stu1;
该声明并未创建一个新的结构体,但是指针stu1现在可以指向任意现有的stu类型的结构体。例如,stu2是一个stu类型的结构体变量、stu3是一个结构体数组,则可以这样写:
stu1 = &stu2;
stu1 = &stu3[0];
和数组不同的是,结构题变量并不是结构体的地址,因此要在结构体变量名前面加上&运算符。
使用指针访问成员
第1种方法(常用):使用 -> 运算符。该运算符由一个连接号(-)后跟一个大于号(>)组成。
如果stu1 = &stu2,那么stu1->num即是stu2.num;
如果stu1 = &stu2[0],那么stu1->num即是stu2[0].num
第2种方法:
如果stu1 = &stu2[0],那么*stu1 = stu2[0],因为&和 *是一对互逆运算符。
stu2[0].num = (*stu1).num必须使用圆括号,因为 .运算符比 * 运算符的优先级高。
stu2.num = (*stu1).num = stu1->num;
使用typedef
利用typedef可以为某一类型自定义名称。
使用关键字 typedef 可以为类型起一个新的别名,语法格式为:
typedef oldName newName;
oldName 是类型原来的名字,newName 是类型新的名字。
typedef struct stu{
char *name;
int num;
} STU,*STU1;
STU 是 struct stu 的别名,可以用 STU 定义结构体变量:
STU stu1,stu2; //结构体变量
STU1 stu2; //结构体指针
typedef与#define的不同点:
- 与#define不同,typedef创建的符号名只受限于类型,不能用于值。
- typedef由编译器解释,不是预处理器。
- 在其受限范围内,typedef比#define更灵活。