C语言基础 — ( 用户自己建立数据类型——结构体、共用体、枚举、声明新类型名)

欢迎小伙伴的点评✨✨ 本篇章系列是对C语言的深度思考和总结、关于C语言内容会持续更新


前言

C语言提供了一些由系统已定义好的数据类型,如:int , float , char 等,用户可以在程序中用它们定义变量,解决一般的问题,但是人们要处理的问题往往比较复杂,只有系统提供的类型还不能满足应用的要求,C语言允许用户根据需要自己建立一些数据类型,并用它来定义变量。


一、C语言的结构体

1.1、定义和使用结构体变量

1.1.1、自己建立结构体类型

在前面所见到的程序中,所用的变量大多数是互相独立,无内在联系的。例如定义了整型变量 a , b , c ,它们都是独立存在的变量,在内存中的地址也是互不相干的,但在实际生活和工作中,有些数据是有内在联系的,成组出现的。
有人可能想到数组,能否用一个数组来存放这些数据呢?显然是不行,因为一个数组中只能存放同一类型的数据。
C语言允许用户自己建立由不同类型数据组成的组合型的数据结构,它称为结构体(structre)。在其他一些高级语言中称为 记录(record)。
在程序中自己建立一个 结构体类型。

struct Student
{
int num; // 学号为整型
char name[20]; // 姓名为字符串
char sex; // 性别为字符串
int age; // 年龄为整型
float score; // 成绩为实型
char addr[30]; // 地址为字符串
}; //注意最后有一个分号

上面由程序设计者指定了一个结构体类型 struct Student(struct 是声明结构体类型时必须使用的关键字,不能省略),经过上面的指定,struct Student 就是一个在本程序中可以使用的合法类型名,它向编译系统声明:这是一个 结构体类型 ,它包括 num,name,sex,age,score,addr 等不同类型的成员。它和系统提供的标准类型(如int,char, float, double 等)具有相似的作用,都可以用来定义变量,只不过 int 等类型是系统已声明的,而结构体类型是由用户根据需要在程序中指定的。
声明一个结构体类型的一般形式为
struct 结构体名
{
成员表列
};

注意: 结构体类型的名字是由一个关键字 struct 和结构体名组合而成的(例如 struct Student)。结构体名是由用户指定的,又称 结构体标记 (structure tag) ,以区别于其他结构体类型。上面的结构体声明中 Student 就是结构体名(结构体标记)

花括号内是该结构体所包括的子项,称为结构体成员(member)。上例中的num, name , sex 等都是成员。对各成员都应进行类型声明,即
类型名 成员名;
成员表列 (member list) 也称为 域表 (field list) , 每一个成员是结构体中的一个域。成员名命名规则与变量名相同。
说明: (1) 结构体类型并非只有一种,而是可以设计出许多种结构体类型。
(2) 成员可以属于另一各结构体类型。例如:
struct Date // 声明一个结构体类型 struct Date
{
int month; // 月
int day; // 日
int year; // 年
};
struct Student // 声明一个结构体类型 struct Student
{
int num; // 学号为整型
char name[20]; // 姓名为字符串
char sex; // 性别为字符串
int age; // 年龄为整型
struct Date birthday; // 成员birthday 属于 struct Date 类型
float score; // 成绩为实型
char addr[30]; // 地址为字符串
};
先声明一个struct Date 类型,它代表 日期 , 包括3个成员 : month(月) 、day(日) 、year(年)。
然后在声明 struct Student 类型时,将成员 birthday 指定为 struct Date 类型。已声明的类型 struct Date 与其他类型(如 int , char) 一样可以用来声明成员的类型。

1.1.2、定义结构体类型变量

前面只是建立了一个结构体类型,它相当于一个模型,并没有定义变量,其中并无具体数据,系统对之也不分配存储单元。相当于设计好了图纸,但并未建成具体的房屋。为了能在程序中使用结构体类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据。可以采取以下3种方法定义结构体类型变量。

1. 先声明结构体类型,再定义该类型的变量
声明一个结构体类型 struct Student ,可以用来定义变量。例如:
struct Student
{
int num; // 学号为整型
char name[20]; // 姓名为字符串
char sex; // 性别为字符串
int age; // 年龄为整型
float score; // 成绩为实型
char addr[30]; // 地址为字符串
}; //注意最后有一个分号
struct Student student1 , student2;
结构体类型名 结构体变量名

这种形式是声明一个结构体类型的一般形式
struct 结构体名
{
成员表列
};

类型名 成员名;
这种形式和定义其他类型的变量形式(如 int a , b; ) 是相似的。上面定义了 student1 和 student2 为struct Student 类型的变量 , 这样 student1 和student2 就具有 struct Student 类型结构。
在定义了结构体变量后,系统会为之分配内存单元。根据结构体类型中包含的成员情况,将所有的成员类型分配的字节,进行相加,就是结构体分配的内存单元。
这种方式是声明类型和定义变量分离,在声明类型后可以随时定义变量,比较灵活。

2. 在声明类型的同时定义变量
struct Student
{
int num; // 学号为整型
char name[20]; // 姓名为字符串
char sex; // 性别为字符串
int age; // 年龄为整型
float score; // 成绩为实型
char addr[30]; // 地址为字符串
}student1 , student2 ; // 注意最后有一个分号
它的作用与第一种方法相同,但是在定义 struct Student 类型的同时定义两个 struct Student 类型的变量 student1 , student2 。这种定义方法的一般形式为
struct 结构体名
{
成员表列
}变量名表列;

声明类型和定义变量放在一起进行,能直接看到结构体的结构,比较直观,在写小程序时用此方法比较方便,但写大程序时,往往要求对类型的声明和变量的定义分别放在不同的地方,以使程序结构清晰,便于维护,所以一般不多用这种方式。

  1. 不指定类型名而直接定义结构体类型变量
    其一般形式为
    struct
    {
    成员表列
    }变量名表列;

    指定了一个无名的结构体类型,它没有名字(不出现结构体名)。显然不能再以此结构体类型去定义其他变量。这种方式用得不多。

说明:
(1) 结构体类型结构体变量 是不同的概念,不要混淆。只能对变量赋值、存取或运算,而不能对一个类型赋值、存取或运算。在编译时,对类型是不分配空间的,只对变量分配空间。
(2) 结构体类型中的成员名可以与程序中的变量名相同,但二者不代表同一对象。例如,程序中可以另定义一个变量 num,它与 struct Student 中的 num 是两回事,互不干扰。
(3) 对结构体变量中的成员(即 ),可以单独使用,它的作用与地位相当于普通变量。

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

在定义结构体变量时,可以对它初始化,即赋予初始值。然后可以引用这个变量,例如:
struct Student // 声明结构体类型 struct Student
{
long int num; // 以下4行为结构体的成员
char name[30];
char sex;
char addr[20];
}a = {10101,“Qiao Yi Bo Yi”,‘M’,“BeiJing”}; // 定义结构体变量 a 并初始化
printf(“NO.:%ld\nname:%s\nsex:%c\naddress:%s\n”,a.num,a.name,a.sex,a.addr);
变量引用:
(1) 在定义结构体变量时可以对它的成员初始化。初始化列表是用花括号括起来的一些常量,这些常量依次赋给结构体变量中的各成员。
注意:是对结构体变量初始化,而不是对结构体类型初始化。
C99 标准允许对某一成员初始化,如:
struct Student b = {.name = " Qiao Yi Bo Yi"} ; // 在成员名前有成员运算符 " . "
“ .name” 隐含代表结构体变量b中的成员 b.name 。 其他未被指定初始化的数值型成员被系统初始化为 0 ,字符型成员被系统初始化为’\0’ ,指针型成员被系统初始化为 NULL 。
(2) 可以引用结构体变量中成员的值,引用方式为
结构体变量名.成员名
student1.num = 10010;
“ . ” 是成员运算符,它在所有的运算符中优先级最高,因此可以把 student1.num 作为一个整体来看待,相当于 一 个变量。上面赋值语句的作用是将整数10010赋给student1 变量中的成员 num 。
注意: 不能企图通过输出结构体变量名来达到输出结构体变量所有成员的值。
下面用法不正确:
printf(“%s\n”,student1); // 企图用结构体变量名输出所有成员的值
只能对结构体变量中的各个成员分别进行输入和输出。
(3) 如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低级的一级成员。只能对最低级的成员进行赋值或存取以及运算。如果在结构体 struct Student 类型的成员中包含另一个结构体 struct date 类型的成员 birthday ,则引用成员的方式为
student1.num (结构体变量 student1 中的成员 num)
student1.birthday.month (结构体变量 studebt1 中的成员 birthday 中的成员 month)
不能用 student1.birthday 来访问 student1 变量中的成员 birthday ,因为birthday 本身是一个结构体成员。
(4) 对结构体变量的成员可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)例如:
student2.score = student1.score; (赋值运算)
sum = student1.score + student2.score; (加法运算)
student1.age++ (自加运算)
由于 “ . ” 运算符的优先级最高,因此student1.age++是对(student1.age)进行自加运算,而不是先对age进行自加运算。
(5) 同类的结构体变量可以互相赋值,如:
student1 = student2 ; // 假设 student1 和 student2 已定义为同类型的结构体变量
(6) 可以引用结构体变量成员的地址,也可以引用结构体变量的地址。例如:
scanf(“%d”,&student1.num); (输入student1.num的值)
printf(“%o”,&student1); (输出结构体变量 student1 的起始地址)
但不能用以下语句整体读入结构体变量,例如:
scanf(“%d,%s,%c,%d,%f,%s\n”,&student1);
说明: 结构体变量的地址主要用作函数参数,传递结构体变量的地址。

1.2、使用结构体数组

一个结构体变量中可以存放一组有关联的数据(如一个学生的学号、姓名、成绩等数据)。如果有10个学生的数据需要参加运算,显然应该用数组,这就是 结构体数组。 结构体数组与以前介绍过的数值型数组的不同之处在于每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项。

1.2.1、定义结构体数组

(1) 定义结构体数组一般形式是
① ** struct 结构体名
{成员表列} 数组名[数组长度];**
② 先声明一个结构体类型(如 struct Person) ,然后再用此类型定义结构体数组:
结构体类型 数组名[数组长度];
如:
struct Perdson leader[3]; //leader 是结构体数组名
(2) 对结构体数组初始化的形式是在定义数组的后面加上:
= {初始表列};
如:
struct Person leader[3] = {“LI”,0,“Zhang”,0,“Sun”,0};

1.3、结构体指针

所谓结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就指向该结构体变量。

1.3.1、指向结构体变量的指针

指向结构体对象的指针变量既可指向结构体变量,也可以指向结构体数组中的元素。指针变量的基类型必须与结构体变量的类型相同。
例如:
struct Student *pt; // pt 可以指向 struct Student 类型的变量或数组元素
说明: 为了使用方便和直观,C语言允许(*p).num 用 p->num 代替,“ -> ” 代表一个箭头,p->num 表示p 所指向的结构体变量中的 num成员。同样,(*p).name等价于p->name。 “ -> ” 称为指向运算符。

如果 p 指向一个结构体变量 stu ,以下3种用法等价:
① stu.成员名(如 stu.num);
② (*p).成员名(如 (*p).num);
③ p->成员名(如 p->num);

1.3.2、指向结构体数组的指针

可以用指针变量指向结构体数组的元素。
(1) 声明结构体类型 struct Student ,并定义结构体数组,同时使之初始化;
(2) 定义一个指向 struct Student 类型数据的指针变量 p;
(3) 使P指向结构体数组的首元素,输出它指向的元素中的有关信息;
(4) 使P指向结构体数组的下一个元素,输出它指向的元素中的有关信息;

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

将一个结构体变量的值传递给另一个函数,有3个方法:
(1) 用结构体变量的成员作参数。例如用 stu[1]. num 或 stu[2]. name 作函数实参,将实参值传给形参。用法和用普通变量作实参是一样的,属于 值传递 方式。应当注意实参与形参的类型保持一致。
(2) 用结构体变量作实参。用结构体变量作实参时,采取的也是 值传递 的方式,将结构体变量所占的内存单元的内容全部按顺序传递给形参,形参也必须是同类型的结构体变量。在函数调用期间形参也要占用内存单元。这种传递方式在空间和时间上开销较大,如果结构体的规模很大时,开销是很可观的。此外,由于采用值传递方式,如果在执行被调用函数期间改变了形参(也是结构体变量)的值,该值不能返回主调函数,这往往造成使用上的不便。因此一般较少用这种方法。
(3) 用指向结构体变量(或数组元素)的指针作实参,将结构体变量(或数组元素)的地址传给形参。

二、C语言的共用体

2.1 什么是共用体类型

有时想用同一段内存单元存放不同类型的变量。例如,把一个整型变量,和一个字符型变量放在同一个地址开始的内存单元中。
这种使不同的变量共享同一段内存的结构,称为 共用体类型的结构。
定义共用体类型变量的一般形式为
union 共用体名
{
成员表列
} 变量表列;

例如:
union Data // 表示不同类型的变量i ,ch ,可以存放到同一段存储单元中
{
int i;
char ch;
}a,b,c; // 在声明类型同时定义变量
也可以将类型声明与变量定义分开:
union Data // 声明共用体类型
{
int i;
char ch;
};
union Data a,b,c; // 用共用体类型定义变量

即先声明一个union Data 类型,再将 a,b,c 定义为 union Data 类型的变量。 当然也可以直接定义共用体变量。例如:
union // 没有定义共用体类型名
{
int i;
char ch;
} a,b,c ;
可以看到,共用体 与 结构体 的定义形式相似。但它们的含义是不同的。
结构体变量所占内存长度是各成员占内存长度之和。每个成员分别占有其自己的内存单元。而共用体变量所占的内存长度等于最长的成员长度。

2.2 引用共用体变量的方式

只有先定义了共用体变量才能引用它,但应注意,不能引用共用体变量,而只能引用共用体变量中的成员。
a.i (引用共用体变量中的整型变量 i)
a.ch (引用共用体变量中的字符变量 ch)
不能只引用共用体变量,例如下面的引用是错误的:
printf(“%d”,a);
因为a的存储区可以按不同的类型存放数据,有不同的长度,仅写共用体变量名a,系统无法知道究竟应输出哪一个成员的值。应该写成
printf(“%d”,a.i);

printf(“%c”,a.ch);

2.3 共用体类型数据的特点

在使用共用体类型数据时要注意以下一些特点:
(1) 同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一个成员,而不是同时存放几个。其道理是显然的,因为在每一个瞬时,存储单元只能有唯一的内容,也就是说,在共用体变量中只能存放一个值。
(2) 可以对共用体变量初始化,但初始化表中只能有一个常量。
(3) 共用体变量中起作用的成员是最后一次被赋值的成员,在对共用体变量中的一个成员赋值后,原有变量存储单元中的值就被取代。
(4) 共用体变量的地址和它的各成员的地址都是同意地址。
(5) 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。
(6) 以前的C规定不能把共用体变量作为函数参数,但可以使用指向共用体变量的指针作函数参数。C99允许用共用体变量作为函数参数。
(7) 共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。

三、使用枚举类型

如果一个变量只有几种可能的值,则可以定义为 枚举(enumeration)类型 ,所谓 枚举 就是指把可能的值 一 一 列举出来,变量的值只限于列举出来的值的范围内。

声明枚举类型用 enum 开头。例如:
enum Weekday {sun , mon , tue , wed , thu , fri , sat};
以上声明了一个枚举类型 enum Weekday 。 然后可以用此类型来定义变量。例如:
enum Weekday workday , weekend;
枚举类型 枚举变量
workday 和 weekend 被定义为 枚举变量 , 花括号中的 sun ,mon , … ,sat 称为 枚举元素枚举常量
它们是用户指定的名字。枚举变量 和 其他数值型量不同, 它们的值只限于花括号中指定的值之一。 例如枚举变量 workday 和 weekend 的值只能是 sun 到 sat 之一。
workday = mon; // 正确, mon 是指定的枚举常量之一
weekday = sun; // 正确,sun 是指定的枚举常量之一
weekday = monday; //不正确,monday 不是指定的枚举常量之一
枚举常量是由程序设计者命名的,用什么名字代表什么含义,完全由程序员根据自己的需要而定,并在程序中作相应处理。
也可以不声明有名字的枚举类型,而直接定义枚举变量,例如:
enum {sun , mon , tue , wed , thu , fri , sat } workday ,weekend ;
声明枚举类型的一般形式为
enum [枚举名] { 枚举元素列表 };
其中,枚举名应遵循 标识符 的命名规则,上面的Weekday 就是合法的枚举名。
说明:
(1) C编译对枚举类型的枚举元素按常量处理,故称枚举常量。不要因为它们是标识符(有名字)而把它们看作变量,不能对它们赋值。例如:
sun = 0;mon = 1; // 错误,不能对枚举元素赋值
(2) 每一个枚举元素都代表一个整数,C语言编译按定义时的顺序默认它们的值为0,1,2,3,4,5…。在上面的定义中,sun 的值自动设为0, mon 的值为1,…,sat 的值为6。如果有赋值语句:
workday=mon;
相当于
workday=1;
枚举常量是可以引用和输出的。例如:
printf( “%d”, workday);
将输出整数1。
也可以人为地指定枚举元素的数值,在定义枚举类型时显式地指定,例如:
enum Weckday{ sun=7,mon=1,tue,wed,thu,fri,sat }workday,weekend;
指定枚举常量 sun 的值为 7, mon 为 1,以后顺序加 1,sat为6
由于枚举型变量的值是整数,因此C99把枚举类型也作为整型数据中的一种,即用户自行定义的整数类型。
(3)枚举元素可以用来作判断比较。例如:
if(workday==mon)…
if(workday>sun)…
枚举元素的比较规则是按其在初始化时指定的整数来进行比较的。如果定义时未人为指定,则按上面的默认规则处理,即第1个枚举元素的值为0,故 mon>sun,sat>fri。

四、用typedef 声明新类型名

除了可以直接使用C提供的标准类型名(如 int ,char ,float ,double 和 long 等)和程序编写者自己声明的结构体、共用体、枚举类型外,还可以用 typedef 指定新的类型名来代替已有的类型名。有以下两种情况:

1. 简单地用一个新的类型名代替原有的类型名

例如:
typedef int Integer; //指定用 Integer 为类型名,作用与 int 相同
typedef float Real; //指定用 Real 为类型名,作用与 float 相同
指定用 Integer 代表 int 类型,Real 代表 float。这样,以下两行等价:
①int i,j; float a,b;
②Inteser i,j; Real a,b;
这样可以使熟悉 FORTRAN的人能用 Integer 和 Real 定义变量,以适应他们的习惯。
又如在一个程序中,用一个整型变量来计数,则可以命名 Count 为新的类型名,代表 int 类型:
typedef int Count; // 指定Count 代表 int
Count i , j ; // 用Count 定义变量 i 和 j ,相当于 int i,j;
将变量 i , j 定义为Count 类型,而Count 等价于 int ,因此 i , j 是整型。 在程序中将 i , j 定义为 Count 类型,可以使人更一目了然地知道它们是用于计数的。

2. 命名一个简单的类型名代替复杂的类型表示方法
归纳起来,声明一个新的类型名的方法是:
① 先按定义变量的方法写出定义体(如:int i;)。
② 将变量名换成新类型名(例如: 将 i 换成 Count)。
③ 在最前面加 typedef (例如:typedef int Count)。
④ 然后可以用新类型名去定义变量。
简单地说,就是按定义变量的方式,把变量名换上新类型名,并且在最前面加 typedef ,就声明了新类型名代表原来的类型。

五、总结

C语言允许用户根据需要自己建立一些数据类型,并用它来定义变量。

猜你喜欢

转载自blog.csdn.net/weixin_44759598/article/details/128738935