1、结构体(struct)类型的基本使用
1.1 为什么需要结构体?
C 语言内置的数据类型,除了几种原始的基本数据类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用场景受限。
1.2 结构体的理解
C 语言提供了 struct关键字,允许自定义复合数据类型,将不同类型的值组合在一起,这种类型称为结构体(structure)类型。
C 语言没有其他语言的对象(object)和类(class)的概念,struct 结构很大程度上提供了对象和类的功能。
1.3 声明结构体
构建一个结构体类型的一般格式:
C |
举例:学生
C |
1.4 声明结构体变量并调用成员
定义了新的数据类型以后,就可以声明该类型的变量,这与声明其他类型变量的写法是一样的。
声明结构体变量格式1:
C |
注意,声明自定义类型的变量时,类型名前面,不要忘记加上 struct 关键字。
C |
调用结构体变量的成员:
C |
举例:
C |
说明:
1)先声明了一个 struct Student类型的变量 stu1,这时编译器就会为 stu1 分配内存,接着就可以为 stu1 的不同属性赋值。可以看到,struct 结构的属性通过点( . )来表示,比如 id 属性要写成 stu1.id。
2)字符数组是一种特殊的数组,直接改掉字符数组名的地址会报错,因此不能直接通过赋值运算符来对它进行赋值。你可以使用字符串库函数 strcpy() 来进行字符串的复制操作。
声明结构体变量格式2:
除了逐一对属性赋值,也可以使用大括号,一次性对 struct 结构的所有属性赋值。此时,初始化的属性个数最好与结构体中成员个数相同,且成员的先后顺序一一对应。格式:
C |
C |
注意:如果大括号里面的值的数量少于属性的数量,那么缺失的属性自动初始化为 0 。
声明结构体变量格式3:
方式2中大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。此时,可以为每个值指定属性名。
格式:
C |
声明变量以后,可以修改某个属性的值。
C |
声明结构体变量格式4: 声明类型的同时定义变量
struct 的数据类型声明语句与变量的声明语句,可以合并为一个语句。格式:
C |
举例:同时声明了数据类型 Circle 和该类型的变量 c1
C |
声明结构体变量格式5: 不指定类型名而直接定义结构体类型变量
如果类型标识符(比如Student、Circle、Employee等)只用在声明时这一个地方,后面不再用到,那就可以将类型名省略。 该结构体称为匿名结构体。
C |
C |
struct 声明了一个匿名数据类型,然后又声明了这个类型的两个变量emp1、emp2 。与其他变量声明语句一样,可以在声明变量的同时,对变量赋值。
C |
上例在声明变量 emp1 和 emp2 的同时,为它们赋值。
声明结构体变量格式6:使用 typedef 命令
使用 typedef 可以为 struct 结构指定一个别名,这样使用起来更简洁。
举例:
C |
上例中, Phone 就是 struct cell_phone 的别名。声明结构体变量时,可以省略struct关键字。
这种情况下,C 语言允许省略 struct 命令后面的类型名。进一步改为:
C |
说明:
1、在创建一个结构体变量后,需要给成员赋值。在没有给成员赋值的情况下调用,打印的值是垃圾数据,可能导致程序异常终止。
2、不同结构体变量的成员是独立的,互不影响,一个结构体变量的成员更改,不影响另外一个。
1.6 小 结
区分三个概念:结构体、结构体变量、结构体变量的成员。
C |
2、进一步认识结构体
2.1 结构体嵌套
结构体的成员也是变量,那么成员可以是基本数据类型,也可以是数组、指针、结构体等类型 。如果结构体的成员是另一个结构体,这就构成了结构体嵌套。
C |
2.2 结构体占用空间
结构体占用的存储空间,不是各个属性存储空间的总和。为了计算效率,C 语言的内存占用空间一般来说,都必须是 int 类型存储空间的整数倍。如果 int 类型的存储是4字节,那么 struct 类型的存储空间就总是4的倍数。
2.3 结构体变量的赋值操作
同类型的结构体变量可以使用赋值运算符( = ),赋值给另一个变量,比如
C |
这时会生成一个全新的副本。系统会分配一块新的内存空间,大小与原来的变量相同,把每个属性都复制过去,即原样生成了一份数据。
也就是说,结构体变量的传递机制是值传递,而非地址传递。这一点跟数组的赋值不同,使用赋值运算符复制数组,不会复制数据,只是传递地址。
C |
上例中,变量 b 是变量 a 的副本,两个变量的值是各自独立的,修改掉 b.name 不影响 a.name 。
注意:C 语言没有提供比较两个自定义数据结构是否相等的方法,无法用比较运算符(比如 == 和 != )比较两个数据结构是否相等或不等。
3、结构体数组
3.1 对比结构体与数组
C |
语句int b[3];定义了一个数组,名字为b,由3个整型分量组成。而语句A a;可以类似认为定义了一个数组,名字为a,只不过组成a数组的3个分量是不同类型的。对于数组b,b[0]、b[1]、b[2]分别代表数组中第1、第2、第3个同为int类型的元素的值。而结构体a中,a. a、a. b、a. c分别对应于结构体变量a 中第1、第2、第3个元素的值,两者十分相似。
3.2 结构体数组的声明
结构体数组:数组元素是结构体变量而构成的数组。先定义结构体类型,然后用结构体类型定义数组变量。
方式1:先声明一个结构体类型,再用此类型定义结构体数组
C |
方式2:定义结构体类型的同时,定义数组变量。
C |
3.3 初始化数组元素
对应前面的声明方式1:
举例:
C |
3.4 结构体数组元素的成员的调用
方式1:使用数组角标方式
C |
方式2:使用指向数组或数组元素的指针(下节讲)
C |
4、结构体指针
4.1 结构体指针格式
结构体指针:指向结构体变量的指针 (将结构体变量的起始地址存放在指针变量中)
具体应用场景:①可以指向单一的结构体变量 ②可以用作函数的参数 ③可以指向结构体数组
定义结构体指针变量格式:
C |
说明:变量 b1 是一个指针,指向的数据是 struct Book 类型的实例。
4.2 结构体传参
如果将 struct 变量传入函数,函数内部得到的是一个原始值的副本。
C |
函数 addAge() 要求传入一个 struct 变量 per,但实际上传递的是 struct 变量p1的副本,改变副本影响不到函数外部的原始数据。
通常情况下,开发者希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。而且,传入的是同一份数据,也有利于提高程序性能。这时就需要将 struct 变量的指针传入函数,通过指针来修改 struct 属性。如下
C |
3 -> 操作符
前面例子中,(*per).age 的写法很麻烦,C 语言就引入了一个新的箭头运算符( -> ),可以从结构体指针上直接获取属性,大大增强了代码的可读性。
C |
C |
总结:如果指针变量p指向一个结构体变量stu,以下3种用法等价:
C |
4.4 指向结构体数组的指针
C |
5、结构体在数据结构中的应用
6、共用体类型(union)
6.1 共用体概述
有时需要一种数据结构,不同的场合表示不同的数据类型。比如,如果只用一种数据结构表示学生的“成绩”,这种结构就需要有时是整数(80、90),有时是字符('A'、'B'),又有时是浮点数(80.5、60.5)。
C 语言提供了共用体类型(Union 结构),用来自定义可以灵活变更的数据结构。它内部可以包含各种属性,但同一时间只能有一个属性,因为所有属性都保存在同一个内存地址,后面写入的属性会覆盖前面的属性。这样做的最大好处是节省内存空间。
“共用体”与“结构体”的定义形式相似,但它们的含义是不同的。
6.2 声明共用体
格式:
C |
上例中, union 命令定义了一个包含三个属性的数据类型 Data。虽然包含三个属性,但是同一时间只能取到一个属性。最后赋值的属性,就是可以取到值的那个属性
6.3 声明共用体变量
方式1:先定义共用体类型,再定义共用体变量
C |
方式2:定义共用体类型的同时定义共用体变量
C |
方式3:直接定义共用体类型变量
C |
6.4 调用共同体变量的成员
正确的方式
方式1:
C |
方式2:声明共同体变量的同时,给任一成员赋值
C |
方式3:声明共同体变量的同时,给首成员赋值
C |
6.5 ->操作符
Union 结构也支持指针运算符 -> 。
C |
6.6 补充说明
7、typedef 的使用(熟悉)
7.1 为什么使用typedef
C语言允许为一个数据类型起一个新的别名,就像给人起“绰号”一样。
起别名的目的不是为了提高程序运行效率,而是为了编码方便。例如,有一个结构体的名字是 student,定义一个结构体变量stu1,代码如下:
C |
struct 看起来就是多余的,但不写又会报错。如果为 struct student起了一个别名 Student,书写起来就简单了:
C |
这种写法更加简练,意义也非常明确,不管是在标准头文件中还是以后的编程实践中,都会大量使用这种别名。
7.2 使用格式
用typedef声明数组类型、指针类型,结构体类型、共用体类型等,使得编程更加方便。
1、为某个基本类型起别名
typedef 命令用来为某个类型起别名
C |
2、为结构体、共用体起别名
为 struct、union等命令定义的复杂数据结构创建别名,从而便于引用。
3、为指针起别名
typedef 可以为指针起别名。
4、为数组类型起别名
typedef 也可以用来为数组类型起别名。
5、为函数起别名
typedef 为函数起别名的写法如下
7.3 小 结
(1) typedef的方法实际上是为特定的类型指定了一个同义字(synonyms)。
(2) 用typedef只是对已经存在的类型指定一个新的类型名,而没有创造新的类型。
(3) typedef与#define是不同的。#define是在预编译时处理的,它只能作简单的字符串替换,而typedef是在编译阶段处理的,且并非简单的字符串替换。
(4) 当不同源文件中用到同一类型数据(尤其是像数组、指针、结构体、共用体等类型数据)时,常用typedef 声明这些同一的数据类型。
技巧:可以把所有的typedef名称声明单独放在一个头文件中,然后在需要用到它们的文件中用#include指令把它们包含到文件中。这样编程者就不需要在各文件中自己定义typedef名称了。
(5) 使用typedef名称有利于程序的通用与移植。有时程序会依赖于硬件特性,用typedef类型就便于移植。