参考资料:高教版《全国计算机等级考试二级教程——C语言程序设计》、《21天学通C语言》

用typedef说明一种新类型名

用户定义类型是指对已有的类型,另外说明一个新的类型标识符。

C语言允许用typedef说明一种新类型名,语法如下:

1
typedef 类型名 标识符;

“类型名”必须是在此语句之前已有定义的类型标识符。

“标识符”是一个用户定义标识符,用作新的类型名。

注意:typedef语句的作用仅仅是用”标识符“来代表已经存在的”类型名“,并没有产生新的数据类型,原有的类型名仍然是有效的。

例如:

1
typedef int INTEGER

以上语句把一个用户命名的标识符INTEGER说明成了一个int类型的别名,在此说明之后,可以用标识符INTEGER来定义整型变量。例如,在此说明之后,以下两条语句等价:

1
2
3
INTEGER m, n;

int m, n;

注意:一般习惯将新的类型名用大写字母表示。

又如,以下两条语句:

1
2
typedef char *CHARP;
CHARP p;

与以下语句是等价的:

1
char *p;

结构体类型

结构体可以由若干个称为成员(或)的成分组成,不同的结构体可以由不同的成员组成。

注意:对于某个具体的结构体类型,成员的数量必须固定。

例如,“日期”可以用“年”,“月”,“日”三部分组成,它们都可以用整形数表示,可以把这三个成员组成一个整体,并给它取名为date,这就是一个最简单的结构体。

结构体类型的说明

结构体类型说明的语法为:

1
2
3
4
5
6
struct 结构体标识名 {
类型名1 结构体成员名表1;
类型名2 结构体成员名表2;
……
类型名n 结构体成员名表n;
};

struct是关键字,是结构体类型的标志。

“结构体标识名”“结构成员名”都是用户定义的标识符,其中“结构体标识名”是可选项,在说明中可以不出现。

每个“结构成员名表”中都可以含有多个同类型的成员名,它们之间以逗号分隔。

注意:

  1. 结构体的说明同样要以分号结尾。
  2. 结构体中的成员名可以和程序中的其他变量同名。
  3. 不同结构体中的成员也可以同名。

例如,上面创建日期的结构体类型可以如下说明:

1
2
3
struct date {
int year, month, day;
};

当结构体说明中又包含结构体时,称为结构体的嵌套。例如,可以用以下结构体说明一个学生档案:

1
2
3
4
5
6
7
8
struct student {
char name[12]; //姓名:字符串
char sex; //性别:字符型
srtuct{ //生日:date结构体
int year, month, day;
}birthday;
float sc[4]; //四门课的成绩,一维实型数组
};

结构体类型的变量、数组和指针变量的定义

可以用以下四种方式定义结构体类型的变量、数组和指针变量:

紧跟在结构体类型说明之后进行定义

1
2
3
4
5
6
struct student {
char name[12];
char sex;
struct date birthday;
float sc[4];
}std, pers[3],*pstd;

此处在说明结构体类型struct student的同时,定义了一个结构体变量std、具有三个元素的结构数组pers和基类型为结构体类型的指针变量pstd。

具有这一结构体类型的变量中只能存放一组数据,结构体变量中的各成员在内存中按说明中的顺序排列。

如果需要存放多组数据,就要使用结构体类型的数组,以上定义的数组pers就可以存放三组数据,它的每一个元素都是一个struct student类型的变量。

指针变量pstd可以指向具有struct student类型的存储单元,但是还没有具体的指向。

在说明一个无名结构体的同时直接进行定义

例如,以上定义的结构体中可以把student略去,直接写成:

1
2
3
4
5
6
struct {
char name[12];
char sex;
struct date birthday;
float sc[4];
}std, pers[3],*pstd;

这种方式与前一种的区别仅仅是省略了结构体标识名。

先说明结构体类型,再单独进行变量定义

1
2
3
4
5
6
7
struct student {
char name[12];
char sex;
struct date birthday;
float sc[4];
};
struct student std, pers[3],*pstd;

注意:不能只使用struct而不写结构体标识名student,因为struct不可以唯一的标识一种数据类型。由关键字struct和student一起才能唯一确定以上所说明的结构体类型。

使用typedef说明一个结构体类型名,再用新类型名来定义

1
2
3
4
5
6
7
8
typedef struct {
char name[12];
char sex;
struct date birthday;
float sc[4];
STREC;
};
STREC std, pers[3],*pstd;

此处STREC是一个具体的结构类型别名,它能够唯一地标识这种结构体类型,因此可以用它来定义变量。

给结构体变量、数组赋初值

结构体变量和数组也可以在定义的同时赋初值。

给结构体变量赋初值

所赋初值顺序放在一对花括号中:

1
2
3
4
5
6
struct student{
char name[12];
char sex;
struct date birthday;
float sc[4];
} std = {"Li Ming", 'M', 1962, 5, 10, 88, 76, 85.5, 90};

给结构体数组赋初值

给结构体数组赋初值的规则与给数组赋初值的规则相同,只是由于数组中的每个元素都是一个结构体,因此通常将其成员的值依次放在一对花括号中。如:

1
2
3
4
struct bookcard {
char num[5];
float money;
} bk[3] = {{"NO.1", 35.5}, {"NO.2", 25.0}, {"NO.3", 66.7}};

引用结构体变量中的数据

对结构体变量成员的引用

若已定义了一个结构体变量和基类型为同意结构体类型的指针变量,并使该指针指向同类型的变量,则可用以下三种形式来引用结构体变量中的成员:

  1. 结构体变量名. 成员名
  2. 指针变量名->成员名
  3. (*指针变量名).成员名

其中点号.称为成员运算符;箭头->称为结构指向运算符(由减号和大于号组成)。这些运算符与圆括号和下标运算符在C语言的优先级相同,都是最高。

例如,有以下定义和语句:

1
2
3
4
5
6
struct student {
char name[12];
char sex;
struct date birthday;
float sc[4];
} std, arr[5], *ps;

可以通过以下语句引用结构体变量std中的sex成员:

1
2
3
std.sex
ps -> sex
(*ps).sex

注意:这时指针变量ps必须已指向确切的存储单元。

若要引用结构体数组arr的第0个元素arr[0]中的sex成员,可写作arr[0].sex。

若要引用结构体变量std的数组成员sc中的元素sc[2]时,可写作std.sc[2]或ps -> sc[2]或(*ps).sc[2]。

若结构体变量中的成员是作为字符串使用的字符型数组,如name,由于可以将其看作“字符串变量”,因此其引用形式可以是std.name或ps -> name或(*ps).name或arr[0].name。

注意:访问结构体变量中各内嵌结构体成员时,必须逐层使用成员名定位。

例如,引用结构体变量std中的出生年份时,可写作std.birthday.year或ps -> birthday.year、(*ps).birthday.year。

以下程序声明并使用了一个简单的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int length, width;
long area;

struct coord{
int x;
int y;
}myPoint;

int main(void){
myPoint.x = 12;
myPoint.y = 14;

printf("\nThe coordinates are:(%d, %d).", myPoint.x, myPoint.y);

return 0;
}

输出:

1
The coordinates are:(12, 14).

对结构体变量中的成员进行操作

结构体变量的每个成员都属于某个具体的数据类型,因此,结构体变量的每个成员都可以对它进行同类变量所允许的任何操作。

若有定义:

1
struct student std, pers[5], *pstd;

以下对相应变量中的name成员所进行的操作都是合法的

1
2
3
4
5
6
7
8
9
scanf("%s", std.name);

gets(std.name);

pstd = &std; scanf("%s", pstd -> name);或gets(pstd -> name);

for(i = 0; i < 3; i++) scanf("%s", pers[i].name);

strcpy(std.name, "Li Ming");

以下对相应变量中的sex成员进行操作

1
2
3
4
5
6
7
scanf("%c", &std.sex);或std.sex = getchar();

pstd = &std; scanf("%c", pstd -> sex);或std ->sex = getchar();

for(i = 0; i < 3; i++) scanf("%c", &pers[i].sex);

std.sex = 'M';

以下对相应变量中的birthday成员中的year进行操作

1
2
3
4
5
6
7
scanf("%d", &std.birthday.year);

pstd = &std; scanf("%d", &pstd -> birthday.year);

for(i = 0; i < 3; i++) scanf("%d", &pers[i].birthday.year);

std.birthday.year = 1962;

以下对相应变量中的成员数组sc中的元素进行操作

1
2
3
4
5
6
7
8
9
for(j = 0; j < 5; j++) scanf("%f", &std.sc[j]);

pstd = &std; for(j = 0; j < 5; j++) scanf("%f", pstd -> sc[j]);

for(i = 0; i < 3; i++)
for(j = 0; j < 5; j++){
scanf("%f", &std.sc[j]);
pers[i].sc[j] = std.sc[j];
}

通过指针变量引用结构体成员的注意事项

当通过指针变量来引用结构体成员,并且与++,–等运算符组成表达式时,应当根据运算符的优先级来确定表达式的含义。

相同类型结构体变量之间的整体赋值

相同类型的结构体变量之间可以进行整体赋值。设有定义:

1
2
3
4
struct {
char name[10];
int num;
} per1, per2 = {"YANGGM", 46};

执行赋值语句per1 = per2之后,per2中每个成员的值都赋给了per1中对应的同名成员。

函数之间结构体变量的数据传递

在调用函数时,可以将结构体变量中的成员作为实参单独传递,也可以将结构体变量作为实参进行整体传递。

向函数传递结构体变量中单个成员的数据

结构体变量中的每个成员可以参与所属类型允许的任何操作,包括参数传递。

向函数传递整个结构体变量中的数据

当把结构体变量中的数据作为一个整体传送给相应的形参时,传递的是实参结构体变量中的值。

传递结构体变量的地址

可以将结构体变量的地址作为实参传递,这时,对应的形参是一个基类型相同的结构体类型的指针变量。

向结构体传递数组名

向函数传递结构体数组名时,传递的是实参结构体数组的首地址,函数中对应形参应该是指向结构体的指针变量。

函数的返回值是结构体类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
typedef struct{
int a;
char b;
}ST;

ST fun(ST x){
x.a = 99;
x.b = 'S';
return x;
}

int main(void){
ST y;

y.a = 0;
y.b = 'A';
printf("y.a = %d, y.b = %c\n", y.a, y.b);
y = fun(y);
printf("y.a = %d, y.b = %c\n", y.a, y.b);

return 0;
}

输出:

1
2
y.a = 0, y.b = A
y.a = 99, y.b = S

函数的返回值是指向结构体变量的指针类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

typedef struct{
int a;
char b;
}ST;

ST *fun(ST x){
ST *px;
x.a = 100;
x.b = 'C';
px = &x;
return px;
}

int main(void){
ST y, *p;

y.a = 999;
y.b = 'X';
printf("y.a = %d, y.b = %c\n", y.a, y.b);

p = fun(y);
printf("y.a = %d, y.b = %c\n", (*p).a, p -> b);

return 0;
}

输出:

1
2
y.a = 999, y.b = X
y.a = 100, y.b = C

利用结构体变量构成链表

结构体变量中含有可以指向本结构体的指针成员

当一个结构体中有一个或多个成员的基类型是本结构体类型时,通常把这种结构体称为可以“引用自身的结构体”。

例如:

1
2
3
4
struct link{
char ch;
struct link *p;
}a;

在这里,p是一个指向struct link类型变量的指针成员,因此a.p = &a是合法的表达式。

以下程序定义了一个简单的链表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>

struct node{
int data;
struct node *next;
};

typedef struct node NODETYPE;

int main(void){
NODETYPE a, b, c, *h, *p;

a.data = 10;
b.data = 20;
c.data = 30; //给变量中的data域赋值
h = &a; //将结点相连

a.next = &b;
b.next = &c;
c.next = '\0';
p = h;

while(p){
printf("%d", p -> data);
p = p -> next;
}
printf("\n");

return 0;
}

动态链表的概念

在动态链表的每个结点中,除了有存放数据本身的数据域外,至少还要有一个指针域,用它来存放下一个结点元素的地址。

由于链表中每个存储单元都由动态存储分配获得,故称这样的链表为“动态链表”。

这种链表的每个结点只有一个指针域,每个指针域存放着下一个结点的地址,因此,这种链表只能从当前结点找到后继结点,故称为“单向链表”。

单向链表

为构成单向链表,每个结点应该由两个成员组成:一个是整型的成员,一个是指向自身结构的指针类型成员。结点的定义类型如下:

1
2
3
4
5
struct slist{
int data;
struct slilt *next;
};
typedef struct slist SLIST;

共用体

共用体的类型说明和变量的定义方式与结构体的类型说明和变量定义的方式完全相同,不同的是,共用体变量中的所有成员占有同一个存储空间。

共用体的类型说明和变量定义

共用体的类型说明

共用体的类型说明形式如下:

1
2
3
4
5
6
union 共用体标识名{
类型名1 共用体成员名1;
类型名2 共用体成员名2;
……
类型名n 共用体成员名n;
};

例如:

1
2
3
4
5
union un_1{
int i;
float x;
char ch;
};

其中union关键字,是共用体类型的标志。un_1是共用体标识名,是可选项。

共用体变量的定义

共用体变量的定义和结构体变量的定义相似,也可以使用定义结构体变量的四种方式。

说明:

  1. 共用体变量在定义的同时只能用第一个成员的类型的值进行初始化。
  2. 共用体变量中的所有成员共享一段公共存储区,所以共用体变量所占内存字节数与其成员中占字节数最多的那个成员相等。
  3. 共用体变量中的所有成员的首地址相同,而且变量的地址也就是该变量成员的地址。

共用体变量的引用

共用体变量中成员的引用

共用体变量中成员的引用方式与结构体完全相同。

共用体变量的整体赋值

在两个相同的共用体变量之间允许进行赋值操作。

向函数传递共用体变量的值

共用体类型的变量也可以作为实参进行传递,也可以传送共用体变量的地址。