C语言入门系列之10.结构体和共用体

时间:2022-07-22
本文章向大家介绍C语言入门系列之10.结构体和共用体,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

文章目录

  • 一、结构体变量的基本使用
    • 1.概述
    • 2.定义结构体类型变量的方法
    • 3.结构体变量的引用
    • 4.结构体变量的初始化
  • 二、结构体的高级应用
    • 1.结构体数组
      • Ⅰ定义结构体数组
      • Ⅱ结构体数组的初始化
    • 2.指向结构体变量的指针
    • 3.结构指针变量作函数参数
  • 三、结构体的应用——链表
    • 1.动态存储分配
      • malloc函数
      • calloc函数
      • free函数
    • 2.链表
    • 3.建立动态链表
      • 创建链表
      • 对链表的删除操作
      • 对链表的插入操作
  • 四、共用体
    • 1.基本概念
    • 2.共用体变量的引用
    • 3.共用体类型数据的特点
  • 五、枚举类型
  • 六、用typedef定义类型

后浪程序员版-献给新一代程序员的演讲

一个国家IT技术最被寄予厚望的人,就是这个国家的程序员,是心中有梦想、眼里有希望,致敬最可爱、最可敬的年轻一代程序猿和程序媛。 奔涌吧,程序员们,和1000万程序员一起,让中国变得更强。

一、结构体变量的基本使用

1.概述

引入: 有时需要将不同类型的数据组合成一个有机的整体,以便于引用。 例如,一个学生有学号、姓名、性别、年龄、地址等属性,需要定义int num; char name[20]; char sex; int age; int char addr[30];等属性,如下:

定义结构体的一般形式为:

struct 结构名{
	成员表列
};

成员表列由若干个成员组成,每个成员都是该结构的一个组成部分; 对每个成员也必须作类型说明,其形式为:

类型说明符 成员名;

例如:

struct student{
	int num;
	char name[20];
	char sex;
	int age;
	float score;
	char addr[30];
}

2.定义结构体类型变量的方法

可以用3种方法定义结构体类型变量: (1)先声明结构体类型再定义变量名 例如:

struct student{
	int num;
	char name[20];
	char sex;
	int age;
	float score;
	char addr[30];
};

struct  student        student1, student2;
// |       |               |        |       
//类型名  结构体          变量名    变量名 

定义了student1和student2为struct student类型的变量,即它们具有struct student类型的结构; 在定义了结构体变量后,系统会为之分配内存单元。 例如,student1和student2在内存中各占4 + 20 + 1 + 4 + 4 + 30 = 67个字节。 (2)在声明类型的同时定义变量 这种形式的定义的一般形式为:

struct 结构体名{
	成员表列
}变量名表列;

例如:

struct student{
	int num;
	char name[20];
	char sex;
	int age;
	float score;
	char addr[30];
}student1, student2;

(3)直接定义结构体类型变量 其一般形式为:

struct{
	成员表列
}变量名表列;

显然,即不出现结构体名。

练习: 定义如下的结构体(嵌套定义):

实现思路: 首先定义一个结构date,由month(月)、day(日)、year(年) 三个成员组成,如下:

struct date{
	int month;
	int day;
	int year;
};

在定义并说明结构体变量时,其中的成员birthday被说明为data结构类型,如下:

struct{
	int num;
	char name[20];
	char sex;
	struct date birthday;
	float score;
}boy1, boy2;

完整代码如下:

#include <stdio.h>

int main(){
	struct date{
		int month;
		int day;
		int year;
	};
	
	struct{
		int num;
		char name[20];
		char sex;
		struct date birthday;
		float score;
	}boy1, boy2;
	
	printf("Please input birthday(YY:) ");
	scanf("%d", &boy1.birthday.year);
	printf("Please input birthday(MM:) ");
	scanf("%d", &boy1.birthday.month);
	printf("Please input birthday(DD:) ");
	scanf("%d", &boy1.birthday.day);
	printf("n");
	
	boy2 = boy1;
	
	printf("boy1's birthday is %d-%d-%dn", boy1.birthday.year, boy1.birthday.month, boy1.birthday.day);
	printf("boy2's birthday is %d-%d-%dn", boy2.birthday.year, boy2.birthday.month, boy2.birthday.day);
	
	
	return 0;
} 

打印:

Please input birthday(YY:) 2020
Please input birthday(MM:) 5
Please input birthday(DD:) 14

boy1's birthday is 2020-5-14
boy2's birthday is 2020-5-14

说明: 成员名可与程序中其它变量同名,互不干扰。

3.结构体变量的引用

在定义了结构体变量以后,可以引用这个变量。 规则如下: (1)不能将一个结构体变量作为一个整体进行输入和输出。 例如,打印student1的各个变量的值不能写成printf("%d, %s, %c, %d,%fn″,student1);

正确引用结构体变量中成员的方式为结构体变量名.成员名。 例如student1.num表示student1变量中的num成员,即student1的num(学号)项; 可以对变量的成员赋值,例如student1.num = 100;

. 是成员(分量)运算符,它在所有的运算符中优先级最高,因此可以把student1.num作为一个整体来看待; 上面赋值语句的作用是将整数100赋给student1变量中的成员num。

练习:

#include <stdio.h>

int main(){
	struct student{
		int num;
		char *name;
		char sex;
		float score;
	}boy1, boy2;
	
	boy1.num = 007;
	boy1.name = "Corley";
	printf("Please input sex and score:n");
	scanf("%c %f", &boy1.sex, &boy1.score);
	
	boy2 = boy1;
	printf("Number = %dnName = %snSex = %cnScore = %fn", boy2.num, boy2.name, boy2.sex, boy2.score);
	
	
	return 0;
} 

打印:

Please input sex and score:
M 95
Number = 7
Name = Corley
Sex = M
Score = 95.000000

(2)如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级的成员; 只能对最低级的成员进行赋值、存取和运算。

对上面定义的结构体变量student1, 可以这样访问各成员:

student1.num
student1.birthday.month

(3)对结构体变量的成员可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。 例如:

student2.score = student1.score;
sum = student1.score + student2.score;
student1.age++;  
++student2.age;

(4)可以引用结构体变量成员的地址,也可以引用结构体变量的地址; 但不能用以下语句整体读入结构体变量:

scanf("%d, %s, %c, %d, %f, %s", &student);

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

练习如下:

#include <stdio.h>

int main(){
	struct student{
		int num;
		char *name;
		char sex;
		float score;
	}boy;
	
	boy.num = 007;
	boy.name = "Corley";
	printf("The address of struct is %on", &boy);
	printf("The address of num is %on", &boy.num);	
	
	return 0;
} 

打印:

The address of struct is 30577000
The address of num is 30577000

4.结构体变量的初始化

初始化示例如下:

#include <stdio.h>

int main(){
	struct student{
		int num;
		char *name;
		char sex;
		float score;
	}boy1, boy2 = {123, "Corley", 'M', 99};
	
	boy1 = boy2;
	printf("boy1:Number = %dnNmae = %snScore = %dn", boy1.num, boy1.name, boy1.score);
	printf("boy2:Number = %dnNmae = %snScore = %dn", boy2.num, boy2.name, boy2.score);
	
	return 0;
}

打印:

boy1:Number = 123
Nmae = Corley
Score = 0
boy2:Number = 123
Nmae = Corley
Score = 0

二、结构体的高级应用

1.结构体数组

一个结构体变量中可以存放一组数据(如一个学生的学号、姓名、成绩等数据),如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组。

结构体数组与之前的数值型数组不同之处在于每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员(分量)项。

Ⅰ定义结构体数组

和定义结构体变量的方法相似,只需说明其为数组即可。 例如:

struct student{
	int num;
	char name[20];
	char sex;
	int age;
	float score;
	char addr[30];
};
struct student student[3];

或者:

struct student{
	int num;
	char name[20];
	char sex;
	int age;
	float score;
	char addr[30];
}student[3];

练习如下:

#include <stdio.h>
#include <stdlib.h>

#define NUM 3

struct person{
	char name[20];
	char phone[11];
};

int main(){
	struct person man[NUM];
	int i;
	for(i = 0; i < NUM; i++){
		printf("Input name:n");
		gets(man[i].name);
		printf("Input phone:n");
		gets(man[i].phone);
	}
	printf("nametttphonen");
	for(i = 0; i < NUM; i++){
		printf("%sttt%sn", man[i].name, man[i].phone);
	}
	
	return 0;
}

打印:

Input name:
Corley
Input phone:
123123
Input name:
Jack
Input phone:
321321
Input name:
Jane
Input phone:
112233
name                    phone
Corley                  123123
Jack                    321321
Jane                    112233

Ⅱ结构体数组的初始化

与其他类型的数组一样,可以对结构体数组初始化。 例如:

struct student{
	int num;
	char name[20]; 
	char sex;
  	int age;
	float score;
	char addr[30];
}stu[2] = {
	{101, "Corley", 'M', 18, 98, 'Beijing}, {102, "Shirley", 'F', 95, "Chengdu"}
};

还可以如下:

struct student{
	int num;
	char name[20]; 
	char sex;
  	int age;
	float score;
	char addr[30];
};
struct student stu[2] = {
	{101, "Corley", 'M', 18, 98, 'Beijing}, {102, "Shirley", 'F', 95, "Chengdu"}
};

即先声明结构体类型,然后定义数组为该结构体类型,在定义数组时初始化。

练习: 对候选人得票的统计程序: 设有3个候选人,每次输入一个得票的候选人的名字,要求最后输出各人得票结果。 代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define NUM 4
struct person
{
      char name[20];
      int count;
}candidate[NUM] = {
                  {"光头强", 0},
                  {"灰太狼", 0}, 
                  {"猪猪侠", 0},
                  {"阿衰", 0}
                };

void main()
{
	char *winner();
	int i, j;
	char candidate_name[20];
	
	printf("欢迎进入最爱动漫评选投票系统:nn");
	printf("候选人有: 光头强, 灰太狼, 猪猪侠, 阿衰nn");
	
	for( i=1; i <= 10; i++)
	{
	    printf("第 %2d 位投票, 请写下支持的候选人名字: ", i);
	    scanf("%s", candidate_name);
	    for( j=0; j < NUM; j++ )
	    {
			if(strcmp(candidate_name, candidate[j].name) == 0)
			{
			    candidate[j].count++;
			}
	    }
	}
	printf("n");
	
	for( i=0; i < 4; i++ )
	{
	    printf("%s 同学得票数为: %dn", candidate[i].name, candidate[i].count );
	}
	printf("n");
	printf("本次投票活动的胜利者: %s", winner() );
	
	printf("n");
	system("pause");
}

char *winner(){
	int i = 0, winner = i;
	
	for( i=1; i < NUM; i++ )
	{
	    if( candidate[winner].count < candidate[i].count )
	    {
	        winner = i;
	    }
	}
	
	return candidate[winner].name;
}

打印:

欢迎进入最爱动漫评选投票系统:

候选人有: 光头强, 灰太狼, 猪猪侠, 阿衰

第  1 位投票, 请写下支持的候选人名字: 阿衰
第  2 位投票, 请写下支持的候选人名字: 光头强
第  3 位投票, 请写下支持的候选人名字: 猪猪侠
第  4 位投票, 请写下支持的候选人名字: 大灰狼
第  5 位投票, 请写下支持的候选人名字: 阿衰
第  6 位投票, 请写下支持的候选人名字: 灰太狼
第  7 位投票, 请写下支持的候选人名字: 猪猪侠
第  8 位投票, 请写下支持的候选人名字: 光头强
第  9 位投票, 请写下支持的候选人名字: 阿衰
第 10 位投票, 请写下支持的候选人名字: 猪猪侠

光头强 同学得票数为: 2
灰太狼 同学得票数为: 1
猪猪侠 同学得票数为: 3
阿衰 同学得票数为: 3

本次投票活动的胜利者: 猪猪侠

2.指向结构体变量的指针

一个结构体变量的指针就是该结构体变量所占据的内存段的起始地址; 可以定义一个指针变量,用来指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。

指针变量也可以用来指向结构体数组中的元素。

结构指针变量说明的一般形式为:

struct 结构名 *结构指针变量名;

例如,在前面定义了student这个结构体,如要说明一个指向student的指针变量pstu,可写为:

struct student *pstu;

也可在定义student结构时同时说明pstu。 与之前的各类指针变量相同,结构指针变量也必须要先赋值后才能使用。

赋值是把结构变量的首地址赋予该指针变量,不能把结构名赋予该指针变量。 如果boy是被说明为student类型的结构变量,则pstu=&boy是正确的,pstu=&student是错误的。 原因说明: 结构名和结构变量是两个不同的概念: 结构名只能表示一个结构形式,编译系统并不对它分配内存空间; 只有当某变量被说明为这种类型的结构时,才对该变量分配存储空间

因此pstu=&student写法是错误的,不能这样去取一个结构名的首地址。

有了结构指针变量,就能更方便地访问结构变量的各个成员。 其访问的一般形式为: (*结构指针变量).成员名或者结构指针变量->成员名。 例如(*pstu).num或者pstu->num

指向结构体变量的指针变量练习如下:

#include <stdio.h>

struct stu{
	int num;
	char *name;
	char sex;
	float score;
} boy = {123, "COrley", 'M', 95};


int main(){
	struct stu *pstu;
	pstu = &boy;
	
	printf("Number = %dnName = %snScore = %fn", boy.num, boy.name, boy.score);
	printf("Number = %dnName = %snScore = %fn", (*pstu).num, (*pstu).name, (*pstu).score);
	printf("Number = %dnName = %snScore = %fn", pstu->num, pstu->name, pstu->score);
	
	return 0;
}

打印:

Number = 123
Name = COrley
Score = 95.000000
Number = 123
Name = COrley
Score = 95.000000
Number = 123
Name = COrley
Score = 95.000000

3.结构指针变量作函数参数

将一个结构体变量的值传递给另一个函数,有3种方式: (1)用结构体变量的成员作参数; (2)用结构体变量作实参 (3)用指向结构体变量(或数组)的指针作实参,将结构体变量(或数组)的地址传给形参

练习: 有一个结构体变量student,内含学生学号、姓名和3门课程的成绩,通过调用函数print中将它们输出。 用结构体变量作函数参数代码如下:

#include <stdio.h>
#include <string.h>

struct student{
	int num;
	char name[20];
	float score[3];
};


int main(){
	void print(struct student);
	struct student stu;
	stu.num = 123;
	strcpy(stu.name, "Corley");
	stu.score[0] = 99;
	stu.score[1] = 98;
	stu.score[2] = 97;
	print(stu);
	
	return 0;
}

void print(struct student stu){
	printf("num    : %dn", stu.num);
	printf("name   : %sn", stu.name);
	printf("score_1: %fn", stu.score[0]);
	printf("score_2: %fn", stu.score[1]);
	printf("score_3: %fn", stu.score[2]);
}

打印:

num    : 123
name   : Corley
score_1: 99.000000
score_2: 98.000000
score_3: 97.000000

下面代码与之等效:

#include <stdio.h>
#include <string.h>

struct student{
	int num;
	char *name;
	float score[3];
};


int main(){
	void print(struct student);
	struct student stu;
	stu.num = 123;
	stu.name = "Corley";
	stu.score[0] = 99;
	stu.score[1] = 98;
	stu.score[2] = 97;
	print(stu);
	
	return 0;
}

void print(struct student stu){
	printf("num    : %dn", stu.num);
	printf("name   : %sn", stu.name);
	printf("score_1: %fn", stu.score[0]);
	printf("score_2: %fn", stu.score[1]);
	printf("score_3: %fn", stu.score[2]);
}

这种方式是将字符串常量的地址传递给name。

用指向结构体变量的指针作实参代码如下:

#include <stdio.h>
#include <string.h>

struct student{
	int num;
	char *name;
	float score[3];
};


int main(){
	void print(struct student *);
	struct student stu;
	stu.num = 123;
	stu.name = "Corley";
	stu.score[0] = 99;
	stu.score[1] = 98;
	stu.score[2] = 97;
	print(&stu);
	
	return 0;
}

void print(struct student *p){
	printf("num    : %dn", p->num);
	printf("name   : %sn", p->name);
	printf("score_1: %fn", p->score[0]);
	printf("score_2: %fn", p->score[1]);
	printf("score_3: %fn", p->score[2]);
}

三、结构体的应用——链表

1.动态存储分配

我们知道,C语言中不允许动态数组类型,数组的长度是预先定义好的,在整个程序中固定不变。 例如,int a[n];用变量表示长度,想对数组的大小作动态说明,是错误的。

但是在实际的编程中,往往会出现所需的内存空间取决于实际输入的数据、而无法预先确定的情况。 为了满足这个需求,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间回收待用,为有效地利用内存资源提供了手段。

常用的内存管理函数有以下三个: (1)分配内存空间函数malloccalloc; (2)释放内存空间函数free

malloc函数

函数原型为void *malloc(unsigned int size);,其作用是在内存的动态存储区中分配一个长度为size的连续空间(size是一个无符号数)。

此函数的返回值是一个指向分配域起始地址的指针(类型为void); 如果此函数未能成功地执行(例如内存空间不足),则返回空指针(NULL)。

calloc函数

函数原型为void *calloc(unsigned n, unsigned size);,其作用是在内存的动态存储区中分配n个长度为size的连续空间。

函数返回一个指向分配域起始地址的指针; 如果分配不成功,返回NULL。 用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。

free函数

函数原型为void free(void *p);,其作用是释放由p指向的内存区,使这部分内存区能被其他变量使用。

其中,p是最近一次调用calloc或malloc函数时返回的值; free函数无返回值。

2.链表

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

  • 头指针 存放一个地址,该地址指向第一个元素。
  • 结点 用户需要的实际数据和链接节点的指针。

示例如下:

练习: 根据下图建立链表:

代码如下:

#include <stdio.h>

struct student{
	long num;
	float score;
	struct student *next;
};


int main(){
	struct student a, b, c, *head;
	a.num = 10101;
	a.score = 89.5;
	b.num = 10103;
	b.score = 90;
	c.num = 10107;
	c.score = 85;
	
	head = &a;
	a.next = &b;
	b.next = &c;
	c.next = NULL;
	
	do{
		printf("%ld %5.1fn", head->num, head->score);
		head = head->next;
	}while(head);	
	
	return 0;
}

打印:

10101  89.5
10103  90.0
10107  85.0

3.建立动态链表

创建链表

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

练习: 写一函数建立一个含有学生(学号,成绩)数据的单向动态链表。 实现思路: 约定:我们约定学号不会为零,如果输入的学号为0,则表示建立链表的过程完成,该结点不应连接到链表中。 如下:

(1)如果输入的p1->num不等于0,则输入的是第一个结点数据(n=1),令head=p1,即把p1的值赋给head,也就是使head也指向新开辟的结点p1所指向的新开辟的结点就成为链表中第一个结点,如下:

(2)再开辟另一个结点并使p1指向它,接着输入该结点的数据,如果输入的p1->num≠0,则应链入第2个结点(n=2), 将新结点的地址赋给第一个结点的next成员,接着使p2=p1,也就是使p2指向刚才建立的结点,如下:

(3)再开辟一个结点并使p1指向它,并输入该结点的数据,如下:

(4)再开辟一个新结点,并使p1指向它,输入该结点的数据,由于p1->num的值为0,不再执行循环,此新结点不应被连接到链表中,将NULL赋给p2->next,建立链表过程结束,p1最后所指的结点未链入链表中,第三个结点的next成员的值为NULL,它不指向任何结点,如下:

链表输出实现思路如下:

(1)首先要知道链表第一个结点的地址,也就是要知道head的值。 (2)然后设一个指针变量p,先指向第一个结点,输出p所指的结点,然后使p后移一个结点,再输出,直到链表的尾结点,如下:

代码如下:

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

#define LEN sizeof(struct student)

struct student{
	int num;
	float score;
	struct student *next;
};

int n;

int main(){
	struct student *create();
	void print(struct student *head);
	struct student *stu;
	stu = create();
	print(stu);
	printf("nn");
	
	return 0;
}

struct student *create(){
	struct student *head, *p1, *p2;
	p1 = p2 = (struct student *)malloc(LEN);
	printf("Please input the num:");
	scanf("%d", &p1->num);
	printf("Please input the score:");
	scanf("%f", &p1->score);
	
	head = NULL;
	n = 0;
	while(p1->num){
		n++;
		if(n == 1){
			head = p1;
		}
		else{
			p2->next = p1;
		}
		p2 = p1;
		p1 = (struct student *)malloc(LEN);
		printf("Please input the num:");
		scanf("%d", &p1->num);
		printf("Please input the score:");
		scanf("%f", &p1->score);
	}
	p2->next = NULL;
	return head;
}

void print(struct student *head){
	struct student *p;
	printf("nThere are %d records.n", n);
	p = head;
	if(head){
		do{
			printf("%d学号的成绩是:%5.2fn", p->num, p->score);
			p = p->next;
		}while(p);
	}
}

打印:

Please input the num:10101
Please input the score:98
Please input the num:10102
Please input the score:97
Please input the num:10103
Please input the score:96
Please input the num:0
Please input the score:0

There are 3 records.
10101学号的成绩是:98.00
10102学号的成绩是:97.00
10103学号的成绩是:96.00

对链表的删除操作

从一个动态链表中删去一个结点,并不是真正从内存中把它抹掉,而是把它从链表中分离开来,只要撤销原来的链接关系即可,如下:

练习: 写一个函数以删除动态链表中指定的结点。 实现思路: 如下:

(1)从p指向的第一个结点开始,检查该结点中的num值是否等于输入的要求删除的那个学号。 (2)如果相等就将该结点删除,如不相等,就将p后移一个结点,再如此进行下去,直到遇到表尾为止。 (3)设两个指针变量p1和p2,先使p1指向第一个结点 。 (4)如果要删除的不是第一个结点,则使p1后移指向下一个结点(将p1->next赋给p1),在此之前应将p1的值赋给p2 ,使p2指向刚才检查过的那个结点。

代码如下:

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

#define LEN sizeof(struct student)

struct student{
	int num;
	float score;
	struct student *next;
};

int n, num;

int main(){
	struct student *create();
	struct student *del(struct student *head, int num);
	void print(struct student *head);
	struct student *stu;
	stu = create();
	print(stu);
	
	printf("nPlease input the node to delete: ");
	scanf("%d", &num);
	print(del(stu, num));
	printf("nn");
	
	return 0;
}

struct student *create(){
	struct student *head, *p1, *p2;
	p1 = p2 = (struct student *)malloc(LEN);
	printf("Please input the num:");
	scanf("%d", &p1->num);
	printf("Please input the score:");
	scanf("%f", &p1->score);
	
	head = NULL;
	n = 0;
	while(p1->num){
		n++;
		if(n == 1){
			head = p1;
		}
		else{
			p2->next = p1;
		}
		p2 = p1;
		p1 = (struct student *)malloc(LEN);
		printf("nPlease input the num:");
		scanf("%d", &p1->num);
		printf("Please input the score:");
		scanf("%f", &p1->score);
	}
	p2->next = NULL;
	return head;
}

struct student *del(struct student *head, int num){
	struct student *p1, *p2;
	if(head == NULL){
		printf("n This list is null!n");
		goto end;
	}
	p1 = head;
	while(p1->num != num && p1->next != NULL){
		p2 = p1;
		p1 = p1->next;
	}
	if(num == p1->num){
		if(p1 == head){
			head = p1->next;
		}
		else{
			p2->next = p1->next;
		}
		printf("nDelete No. %d succeed!n", num);
		n -= 1;
	}
	else{
		printf("%d not found!n", num);
	}
	end:return head;
}

void print(struct student *head){
	struct student *p;
	printf("nThere are %d records.n", n);
	p = head;
	if(head){
		do{
			printf("%d学号的成绩是:%5.2fn", p->num, p->score);
			p = p->next;
		}while(p);
	}
}

打印:

Please input the num:10101
Please input the score:98

Please input the num:10102
Please input the score:97

Please input the num:10103
Please input the score:96

Please input the num:0
Please input the score:0

There are 3 records.
10101学号的成绩是:98.00
10102学号的成绩是:97.00
10103学号的成绩是:96.00

Please input the node to delete: 10102

Delete No. 10102 succeed!

There are 2 records.
10101学号的成绩是:98.00
10103学号的成绩是:96.00

对链表的插入操作

对链表的插入是指将一个结点插入到一个已有的链表中。 为了能做到正确插入,必须解决两个问题:

  • 怎样找到插入的位置;
  • 怎样实现插入。

练习: 写一个函数在有序链表的指定位置添加元素。 实现思路如下:

我们可以先用指针变量p0指向待插入的结点,p1指向第一个结点; 将p0->num与p1->num相比较,如果p0->num>p1->num,此时将p1后移,并使p2指向刚才p1所指的结点,如下:

其中a、b、c代表一般情况,d代表链表头插入,e代表链表尾插入。 代码如下:

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

#define LEN sizeof(struct student)

struct student{
	int num;
	float score;
	struct student *next;
};

int n, num;

int main(){
	struct student *create();
	struct student *del(struct student *head, int num);
	struct student *insert(struct student *head, struct student *stu);
	void print(struct student *head);
	struct student *stu, *p, stu_2;
	stu = create();
	print(stu);
	
	printf("nPlease input the node to delete: ");
	scanf("%d", &num);
	print(del(stu, num));
	
	printf("nPlease input the num to insert: ");
	scanf("%d", &stu_2.num);
	printf("Please input the score: ");
	scanf("%f", &stu_2.score);
	p = insert(stu, &stu_2);
	print(p);
	printf("nn");
	
	return 0;
}

struct student *create(){
	struct student *head, *p1, *p2;
	p1 = p2 = (struct student *)malloc(LEN);
	printf("Please input the num:");
	scanf("%d", &p1->num);
	printf("Please input the score:");
	scanf("%f", &p1->score);
	
	head = NULL;
	n = 0;
	while(p1->num){
		n++;
		if(n == 1){
			head = p1;
		}
		else{
			p2->next = p1;
		}
		p2 = p1;
		p1 = (struct student *)malloc(LEN);
		printf("nPlease input the num:");
		scanf("%d", &p1->num);
		printf("Please input the score:");
		scanf("%f", &p1->score);
	}
	p2->next = NULL;
	return head;
}

struct student *del(struct student *head, int num){
	struct student *p1, *p2;
	if(head == NULL){
		printf("n This list is null!n");
		goto end;
	}
	p1 = head;
	while(p1->num != num && p1->next != NULL){
		p2 = p1;
		p1 = p1->next;
	}
	if(num == p1->num){
		if(p1 == head){
			head = p1->next;
		}
		else{
			p2->next = p1->next;
		}
		printf("nDelete No. %d succeed!n", num);
		n -= 1;
	}
	else{
		printf("%d not found!n", num);
	}
	end:return head;
}

struct student *insert(struct student *head, struct student *stu){
	struct student *p0, *p1, *p2;
	p1 = head;
	p0 = stu;
	if(head == NULL){
		head = p0;
		p0->next = NULL;
	}
	else{
		while(p0->num > p1->num && p1->next != NULL){
			p2 = p1;
			p1 = p1->next;
		}
		if(p0->num <= p1->num){
			if(head == p1){
				head = p0;
			}
			else{
				p2->next = p0;
			}
			p0->next = p1;
		}
		else{
			p1->next = p0;
			p0->next = NULL;
		}
	}
	n += 1;
	
	return head;
}

void print(struct student *head){
	struct student *p;
	printf("nThere are %d records.n", n);
	p = head;
	if(head){
		do{
			printf("%d学号的成绩是:%5.2fn", p->num, p->score);
			p = p->next;
		}while(p);
	}
}

打印:

Please input the num:10101
Please input the score:98

Please input the num:10102
Please input the score:99

Please input the num:10103
Please input the score:97

Please input the num:0
Please input the score:0

There are 3 records.
10101学号的成绩是:98.00
10102学号的成绩是:99.00
10103学号的成绩是:97.00

Please input the node to delete: 10102

Delete No. 10102 succeed!

There are 2 records.
10101学号的成绩是:98.00
10103学号的成绩是:97.00

Please input the num to insert: 10102
Please input the score: 97.5

There are 3 records.
10101学号的成绩是:98.00
10102学号的成绩是:97.50
10103学号的成绩是:97.00

四、共用体

1.基本概念

使几个不同的变量共占同一段内存的结构称为共用体类型结构。 定义共用体类型变量的一般形式为:

union 共用体名{          
    成员表列
}变量表列;

例如:

union data{
	int i;
	char ch;
	flaot f;
}a, b, c;

或者:

union data{
	int i;
	char ch;
	flaot f;
};
union data a, b, c;

共用体和结构体的比较: 结构体变量所占内存长度是各成员占的内存长度之和,每个成员分别占有其自己的内存单元; 共用体变量所占的内存长度等于最长的成员的长度

例如,前面定义的共用体变量a、b、c各占4个字节(因为一个实/整型变量占4个字节),而不是各占4+1+4=9个字节。

2.共用体变量的引用

只有先定义了共用体变量才能引用它,而且不能引用共用体变量,而只能引用共用体变量中的成员。 例如,前面定义了a、b、c为共用体变量:

a.i 	// 引用共用体变量中的整型变量i
a.ch	// 引用共用体变量中的字符变量ch
a.f 	// 引用共用体变量中的实型变量f

3.共用体类型数据的特点

(1)同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一种,而不是同时存放几种; 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用; 共用体变量的地址和它的各成员的地址都是同一地址

(2)不能对共用体变量名赋值,也不能企图引用变量名来得到一个值,还不能在定义共用体变量时对它初始化; 不能把共用体变量作为函数参数,也不能使函数带回共用体变量,但可以使用指向共用体变量的指针;

(3)共用体类型可以出现在结构体类型定义中,也可以定义共用体数组; 反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。

练习: 设有若干个人员的数据,其中有学生和教师。学生的数据中包括:姓名、号码、性别、职业、班级。教师的数据包括:姓名、号码、性别、职业、职务。可以看出,学生和教师所包含的数据是不同的。现要求把它们放在同一表格中,如下图:

实现思路如下:

代码如下:

#include <stdio.h>

struct{
	int num;
	char name[10];
	char sex;
	char job;
	union{
		int gradeclass;
		char position[10];
	}category;
}person[2];

int n, num;

int main(){
	int i;
	for(i = 0; i < 2; i++){
		printf("Please input the num: ");
        scanf("%d", &person[i].num);
        printf("Please input the name: ");
        scanf("%s", person[i].name);
        fflush(stdin);
        printf("Please input the sex(M/F): ");
        scanf("%c", &person[i].sex);
        fflush(stdin);
        printf("Please input the job(s/t): ");
        scanf("%c", &person[i].job);
        fflush(stdin);

        if(person[i].job == 's')
        {
            printf("Please input the class: ");
            scanf("%d", &person[i].category.gradeclass);
            fflush(stdin);
        }
        else if(person[i].job == 't')
        {
            printf("Please input the position: ");
            scanf("%s", &person[i].category.position);
            fflush(stdin);
        }
        else
        {
            printf("Input Error!!n");
        }     
        
        printf("n");
	}
	
	printf("No.    name    sex job class/positionn");
    for(i = 0; i < 2; i++){
        if( person[i].job == 's'){
            printf("%-6d%-10s%-3c%-3c%10dn", person[i].num, person[i].name, person[i].sex, person[i].job, person[i].category.gradeclass);
        }
        else{
            printf("%-6d%-10s%-3c%-3c%10sn", person[i].num, person[i].name, person[i].sex, person[i].job, person[i].category.position);
        }
    }
	
	return 0;
}

打印:

Please input the num: 10101
Please input the name: Corley
Please input the sex(M/F): M
Please input the job(s/t): s
Please input the class: 501

Please input the num: 101
Please input the name: Shirley
Please input the sex(M/F): F
Please input the job(s/t): t
Please input the position: Prof

No.    name    sex job class/position
10101 Corley    M  s         501
101   Shirley   F  t        Prof

五、枚举类型

在实际问题中,有些变量的取值被限定在一个有限的范围内,例如一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。 如果把这些量说明为整型,字符型或其它类型显然是不妥当的。 为此,C语言提供了枚举类型。

枚举类型的声明: 设有变量a、b、c被说明为枚举类型weekday,可采用以下方式: 方式一:

enum weekday{Sun, Mon, Tue, Wed, Thu, Fri, Sat};
enum weekday a, b, c;

方式二:

enum weekday{Sun, Mon, Tue, Wed, Thu, Fri, Sat}a, b, c;

方式三:

enum {Sun, Mon, Tue, Wed, Thu, Fri, Sat}a, b, c;

需要注意: (1)在枚举类型的定义中列举出所有可能的取值,被说明为该枚举类型的变量取值不能超过定义的范围。

(2)枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。

(3)在枚举值表中应罗列出所有可用值,这些值也称为枚举元素。

(4)在C编译中,对枚举元素按常量处理,故称枚举常量,它们不是变量,不能对它们赋值; 枚举元素作为常量,它们是有值的,C语言编译按定义时的顺序使它们的值为0, 1, 2, ……

(5)枚举值可以用来作判断比较。

(6)一个整数不能直接赋给一个枚举变量。

练习1:

#include <stdio.h>

int main(){
	enum weekday{Sun, Mon, Tue, Wed, Thu, Fri, Sat}a, b, c;
	a = Sun;
	b = 3;
	c = Sat;
	printf("a = %d, b = %d, c = %dn", a, b, c); 
	
	return 0;
}

打印:

a = 0, b = 3, c = 6

练习2:

#include <stdio.h>

int main(){
	enum boys{Corley, Jack, Tom, Sony}month[31], j;
	j = Corley;
	int i;
	for(i = 1; i < 31; i++){
		month[i] = j;
		j++;
		if(j > Sony){
			j = Corley;
		}
	}
	for(i = 1; i < 31; i++){
		switch(month[i]){
			case Corley:{
				printf("%4d %st", i, "Corley");
				break;
			}
			case Jack:{
				printf("%4d %st", i, "Jack");
				break;
			}
			case Tom:{
				printf("%4d %st", i, "Tom");
				break;
			}
			case Sony:{
				printf("%4d %st", i, "Sony");
				break;
			}
			default:
				break;
		}
	}
	printf("n");
	
	return 0;
}

打印:

   1 Corley        2 Jack          3 Tom           4 Sony          5 Corley        6 Jack          7 Tom           8 Sony          9 Corley       10 Jack         11 Tom          12 Sony         13 Corley       14 Jack         15 Tom
          16 Sony         17 Corley       18 Jack         19 Tom          20 Sony         21 Corley       22 Jack
          23 Tom          24 Sony         25 Corley       26 Jack         27 Tom          28 Sony         29 Corley
          30 Jack

六、用typedef定义类型

用typedef声明新的类型名来代替已有的类型名。

具体使用方法如下: (1)先按定义变量的方法写出定义体,如int i; (2)将变量名换成新类型名,将i换成COUNT,在最前面加typedef,如typedef int COUNT; (3)用新类型名去定义变量,如COUNT i, j;

常见使用如下: (1)声明INTEGER为整型:

typedef int INTEGER;

练习如下:

#include <stdio.h>

typedef int INTEGER;

int main(){
	INTEGER i = 1;
	int j = 2;
	printf("i = %d, j = %d", i, j);
	
	return 0;
}

打印:

i = 1, j = 2

(2)声明结构体类型:

typedef  struct{
int month;
int day;
int year;}DATE;

typedef定义结构体类型练习如下:

#include <stdio.h>

typedef struct{
	int month;
	int day;
	int year;
}DATE;

int main(){
	DATE date;
	date.day = 17;
	date.month = 5;
	date.year = 2020;
	printf("%d - %d - %d", date.year, date.month, date.day);
	
	return 0;
}

打印:

2020 - 5 - 17

(3)声明NUM为整型数组类型

typedef int NUM[100];

typedef定义数组测试:

#include <stdio.h>

typedef int NUM[100];

int main(){
	NUM num;
	printf("%dn", sizeof(num));
	
	return 0;
}

打印:

400

(4)声明字符指针类型

typedef char* STRING;

typedef定义字符指针测试:

#include <stdio.h>

typedef char* P;

int main(){
	P p;
	p = "I love C!!";
	printf("%sn", p);
	
	return 0;
}

打印:

I love C!!

(5)声明指向函数的指针类型,该函数返回整型值:

typedef int (*POINTER)();

typedef定义指向函数的指针练习:

#include <stdio.h>

typedef void (*POINTER)();

int main(){
	void fun();
	POINTER pf;
	pf = fun;
	pf();
	
	return 0;
}

void fun(){
	printf("I love C!!n");
}

打印:

I love C!!

也可以为:

#include <stdio.h>

typedef void (*POINTER)();

int main(){
	void fun();
	POINTER pf;
	pf = &fun;
	pf();
	
	return 0;
}

void fun(){
	printf("I love C!!n");
}
运行效果是相同的。

区别在于pf = fun;pf = &fun;,因为函数和函数的地址相同,所以是等效的。

关于typedef的一些说明; (1)用typedef可以声明各种类型名,但不能用来定义变量。

(2)用typedef只是对已经存在的类型增加一个类型名,而没有创造新的类型。

(3)当不同源文件中用到同一类型数据时,常用typedef声明一些数据类型,把它们单独放在一个文件中,然后在需要用到它们的文件中用#include命令把它们包含进来。

(4)使用typedef 有利于程序的通用与移植。

(5)typedef与#define有相似之处,例如typedef int COUNT;#define COUNT int的作用都是用COUNT代表 int,但是,它们是不同的: #define是在预编译时处理的,它只能作简单的字符串替换; 而typedef是在编译时处理的,它不是作简单的字符串替换,而是采用如同定义变量的方法那样来声明一个类型。 例如,已知有

typedef  (int*)  p1;
#define  p2  int*

p1 a1, a2等同于int *a1, *a2,是将a1、a2均声明为指针变量; 而p2 a1, a2等同于int* a1, a2int *a1, a2,将a1声明为指针变量,而将a2声明为整型变量。