第九章 用户自己建立数据类型(一)

原创
2020/10/16 23:16
阅读数 133

章节结构

  • 定义和使用结构体变量
    • 自己建立结构体类型
    • 定义结构体类型变量
    • 结构体变量的初始化和引用
  • 使用结构体数组

    • 定义结构体数组
    • 结构体数组的应用举例
  • 结构体指针

    • 指向结构体变量的指针
    • 指向结构体数组的指针
    • 用结构体变量和结构体变量的指针作函数参数
  • 用指针处理链表

    • 什么是链表
    • 建立简单的静态链表
    • 建立动态链表
    • 静态链表VS动态链表(在代码中的区别)
    • 输出链表
  • 共用体类型

    • 什么是共用体类型
    • 引用共用体变量的方式
    • 共用体类型数据的特点
  • 使用枚举类型

    • 两种使用方式
    • 涉及枚举的几个概念
  • 用typedef声明新类型名

    • 简单的用一个新的类型名代替原有的类型名
    • 命名一个简单的类型名代替复杂的类型表示方法

定义和使用结构体变量

自己建立结构体类型

结构体:C语言允许用户自己建立由不同类型数据组成的组合型的数据结构,称为结构体,有些编程语言称为记录 定义性声明,简称定义:

  • 结构体类型的定义一般形式:
struct 结构体名
{成员表列}
//花括号内是该结构体所包含的自相,称为结构体的成员,一般形式: 类型名 成员名
  • 结构体类型名是由一个关键字struct结构体名组合而成的(如 struct Student)
  • 结构体名是由用户指定的,又称为结构体标记,Student就是结构体名(结构体标记)
  • 结构体的成员:花括号内是该结构体所包括的子项。一般形式为: 类型名 成员名
  • 成员表列也称为域表每一个成员是结构体中的一个域。成员名命名规则和变量名相同
  • 结构体类型可以有很多种
  • 成员可以属于另一个结构体类型

定义结构体类型变量

建立结构体类型,只是定义了一个类型,相当于一个抽象的模型,并没有定义变量,其中并无数据,系统也不会分配存储单元
为了在程序中使用结构体类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据

定义结构体类型变量的3种方法

  • (1)先声明结构体类型,再定义该类型的变量
    这种方式是声明类型和定义变量分离,在声明类型后可以随时定义变量,比较灵活。
// 声明一个结构类型 struct Date
struct Date
{
	int month;
	int day;
	int year;
}
// 定义结构类型 struct Student
struct Student
{
	int num;
	char name[20];
	char gender;
	int age;
	struct Date birthday;
	char addr[30];
}
// 分别定义两个类型为struct Student 的变量student1和student2
struct Student  student1,student2;
/* struct Student 表示的是结构体类型名
   student1和student2表示的是该结构体类型的变量名
*/
  • (2)在声明类型的同时定义变量 形式如下
struct 结构体名
{
	成员表列
}变量名表列;

struct Student
{
	int num;
	char name[20];
	char gender;
	int age;
	struct Date birthday;
	char addr[30];
}student1,student2;
  • (3)不指定类型名而直接定义结构体类型变量
    指定了一个了无名的结构体类型,没有结构体名。这种方式无法再给别的变量定义了。 一般形式如下
struct
{
	成员表列;
}变量名表列;

  • 结构体类型与结构体变量是不同的概念。
    编译时,对类型是不分配空间的,只对变量分配空间。只能对变量赋值、存取或运算,而不能对类型进行赋值、存取或运算。
  • 结构体类型中的成员名可以与程序中的变量名相同,但二者不代表同一个对象
  • 对结构体变量中的成员(即“域”),可以单独使用。即成员的引用方法

结构体变量的初始化和引用

在定义结构体变量时,可以对它初始化,即赋初值。然后可以引用这个变量

  • (1)在定义结构体变量时可以对他的成员初始化
    初始化列表是用花括号括起来的一些常量,这些常量依次赋值给结构体变量中的各成员。
    注意是对结构体变量进行初始化而不是结构体类型。
struct Student
{
	int num;
	char name[20];
	char gender;
	char addr[30];
}student1={1011,"Li Lin",'M',"123 Beijing Road"}  // 定义结构体变量并初始化

C99标准允许对某一成员初始化struct Student b = {.name="zhang fang"
其他未被初始化的成员,若是数值型的,则被系统初始化为0,字符型成员被系统初始化为'\0‘,指针型成员被系统初始化为NULL

  • (2)可以引用结构体变量中成员的值,引用方式为: 结构体变量名.成员名
    注意不能企图通过输出结构体变量名来达到输出结构体变量所有成员的值。(即printf("%s\n",student1;这是不行的)
    只能对结构体变量中的各个成员分别进行输入和输出。

  • (3)如果成员本身有属于一个结构体类型,则要用若干个成员运算符,一级一级的找到最低级的以及的成员。
    只能对最低级的成员进行赋值或存取以及运算

  • (4)对结构体变量的成员可以向普通变量一样进行各种运算(根据其类型决定可以进行的运算)
    注意成员运算符的优先级最高

  • (5)同类的结构体变量可以互相赋值,如student1=student2

  • (6)可以引用结构体变量成员的地址,也可以引用结构体变量的地址
    但不能用scanf("%d,%s,%c,%d,%f,%s\n",&student1);这种方式来整体读入结构体变量

结构体变量的地址主要用作函数参数,传递结构体变量的地址

使用结构体数组

定义结构体数组

  • (1)定义结构体数组的一般形式(两种形式)
    • 定义结构体类型的同时定义结构体数组;
    • 先声明一个结构体类型,然后再用此类型定义结构体数组
// 结构体和结构体数组同时定义
// 这种方式导致定义的结构体无法再被其他结构体数组使用
struct 结构体名
{
	成员表列
}数组名[数组长度]


//  先声明一个结构体类型,然后再用此类型定义结构体数组
struct 结构体名
{
	成员表列
};
struct 结构体名 数组名[数组长度];
  • (2)对结构体数组初始化的形式是在定义数组的后面加上:
    ={初值表列};
struct 结构体名 数组名[数组长度]; // 此处的数组名又称为结构体数组名

结构体指针

结构体指针就是指向结构体变量的指针

一个结构体变量的起始地址就是结构体变量的指针。

  • 指向结构体对象的指针变量既可以指向结构体变量,也可以指向结构体数组中的元素

指向结构体变量的指针

  • 指针变量的基类型必须与结构体变量的类型相同(指针变量的含义可知)
    struct Student * pt
  • C语言允许把(* p).成员名p ->成员名代替
  • 三种引用结构体变量中的成员的方法:

    p为指向一个结构体变量stu的指针

    • (1)stu.成员名,如 stu.num
    • (2)(* p).成员名,如(* p).num
    • (3)p -> 成员名,如p -> num

指向结构体数组的指针

指向结构体数组元素的指针变量。
核心操作:将结构体数组首元素的地址赋给一个指针变量,这样这个指针变量就是指向结构体数组的指针。
结构体数组具有数组的特性,

  • 结构体数组名就是结构体数组的地址
  • 结构体数组首元素就是结构体数组的地址

因此,指针变量 = 结构体数组名即可(类似于用一个指针变量指向数组一样)
同时根据指针变量的特性,此处指针变量的基类型必须是相对应的结构体类型

指向结构体数组的指针的用法

  • (1)结构体数组的指针,如何表示结构体数组中的每一个元素及每个元素中的某个成员

(++p) // 结构体数组的下一个元素
(++p)-> num // 结构体数组的下一个元素的num成员

(++p)->num VS (++p)->num

(++p)->num 表示的是先将指针变量p的值加1,即指向结构体数组的下一个元素,然后根据指向运算符得到下一个元素的num成员

(p++)->num 表示的是先根据指向运算符,得到结构体数组指针指向的元素的num成员;然后在使p自加1,指向下一个元素

(2)记住指针变量是由类型的,因此不能随便赋值,否则就会涉及类型转换

比如如果p是一个指向结构体类型对象的指针变量。那么就不能用来指向结构体数组元素中的某个成员

  • p = stu[1].name // 编译器会警告地址的类型不匹配
    // stu[1].name表示的是stu[1]元素中的成员name的首字符地址
    此时可以使用强制类型转换,即将成员的地址类型转换成指针变量的类型
  • 正确的做法: p = (struct Student *)stu[0].name 此时p的值表示的就是stu[0]元素的name成员的起始地址,
    使用printf("%s",p)既可以输出stu[0]中成员name的值,下一个元素的成员name的值则可以使用printf("%s",p+1)

指向结构体变量的指针vs**指向结构体数组的指针 **

两者之间的关系类似于二维数组,指向结构体数组的指针类似于第1维,指向结构体变量的指针类似于第2维。

  • 两者指针的基类型都是相应的结构体类型,
  • 不同之处在于,初始化时指向结构体变量的指针存储的是结构体变量的地址,指向结构体数组的指针存储的是结构体数组的地址

用结构体变量和结构体变量的指针作函数参数

将一个结构体变量的值传递给另一个函数,有3种方法:

  • (1)用结构体变量的成员做参数
  • (2)用结构体变量作参数
  • (3)用指向结构体变量(或数组元素)的指针作实参,结构体变量(或数组元素)的地址传给形参。

用指针处理链表

什么是链表

链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。

链表有一个“头指针变量”,它存放一个地址,改地址指向一个元素。

链表中每一个元素称为“结点”,每个结点都应包括两个部分:
* 用户需要用的实际数据 * 下一个节点的地址

分析上述链表的特点可以知道用结构体变量来建立链表比较合适。

// 此处只是声明了一个结构体类型,还没有定义该类型的变量,
//即还没有实际分配内存空间
struct Student
{
	int num;
	float score;
	strcut Student * next; 
	// next是指针变量,指向结构体变量,表示指向下一个结点
}

建立简单的静态链表

所有结点都是在程序中定义的,不是临时开辟的,也不能用完后释放,这种链表称为“静态链表”。

建立动态链表

建立动态链表是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结点和输入各结点数据,并建立起前后相链的关系。

静态链表VS动态链表(在代码中的区别

  • 静态链表:结构体变量已经定义好了,已经确定了几个变量(即几个结点)后面仅仅只是需要赋值操作而已。
  • 声明结构体类型时记住其中一个成员类型为指针,且这个指针的基类型为结构体类型,用于指向下一个指针变量
  • 定义结构体变量(这里定义几个,那这个链表就有几个结点)
  • 给各个结构体变量的各成员赋值
  • 在给各个结构体变量的各成员赋值时,其中代表结构体指针的那个变量值要赋值成下一个结点的地址(即另一个结构体变量名)

通过上面的操作就建立了链表,这样形成的链表就是静态链表

  • 动态链表

输出链表

将链表中各结点的数据依次输出。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部