C语言程序设计—结构体(Part Ⅱ)

C语言程序设计—结构体 的整理笔记,若有错误,欢迎指正。
C语言程序设计—结构体(Part Ⅰ)

结构体数组

  • 一个结构体变量中可以存放一组有关联的数据(如一个学生的学号、姓名、成绩等)。如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组。结构体数组的每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项。
  1. 定义结构体数组的一般形式是:
    (1) struct结构体名
    {成员表列} 数组名[数组长度]
    (2)先声明一个结构体类型,然后再用此类型定义结构体数组
    如:struct person leader[3]; //leader是结构体数组名

  2. 对结构体的数组初始化的形式是在定义数组的后面加上={初值表列};
    例:struct person leader[3]={ "Li", 0, "Zhang", 0, "Fun", 0 };

例7:有3个候选人,每个选民只能投票选ー人,写一个统计选票的程序,先后输入被选人的名字,最后输出各人得票结果。

#include <stdio.h >
#include <string.h>
struct person //声明结构体类型struct person
{
	char name[20]; //候选人姓名
	int count; //候选人得票数
}leader[3] = { "Li", 0, "Zhang", 0, "Fun", 0 }; //定义结构体数组并初始化

int main()
{
	int i, j;
	char leader_name[20]; //定义字符数组
	for (i = 1; i <= 10; i++)
	{
		scanf("%s", leader_name);//输入所选的候选人姓名
		for (j = 0; j < 3; j++)
			if (strcmp(leader_name, leader[j].name) == 0)
				leader[j].count++; // 如果输入的姓名和某一元素中的name成员相同, 就给该元素的count成员加1
	}
	printf("\nResult:\n");
	for (i = 0; i < 3; i++)
		printf("%5s:%d\n", leader[i].name, leader[i].count); //输出数组所有元素中的信息
	return 0;
}

程序分析:

  • !由于成员运算符“.”优先于自增运算符“++”,因此它相当于(leader[j].count)++,使leader[j]成员count的值加1。
  • C语言允许对具有相同结构体类型的变量进行整体赋值,注意:对字符数组型结构体成员进行赋值时一定要使用strcpy()。
    例:strcpy(stu1.studentName, "王刚");,而不能写成stu2.studentName = stu1.studentName。因为结构体成员studentName是一个字符型数组,studentName是该数组的名字,代表字符型数组的首地址,是一个常量,不能作为赋值表达式的左值。

例8:有N个学生的信息(包括学号、姓名、成绩),要求按照成绩的高低顺序输出各学生的信息。(顺序法)

#include<stdio.h>
#define N 5
struct student //声明结构体类型struct student
{
	int num;
	char name[20];
	float score;
};
int main()
{
	struct student stu[N]; //定义结构体数组
	struct student temp; //定义结构体变量temp,用作交换时的临时变量
	int i, j, k;
	for (i = 0; i < N; i++)
	scanf("%d %s %f", &stu[i].num, stu[i].name, &stu[i].score);
	printf("The order is: \n");
	for (i = 0; i < N - 1; i++)
		for (j = i + 1; j < N; j++)
		{
			k = i;
			if (stu[j].score > stu[k].score) //进行成绩的比较
			{
				k = j;
				temp = stu[k]; stu[k] = stu[i]; stu[i] = temp; //stu[k]和 stu[i]元素整体互换
			}
		}
	for (i = 0; i < N; i++)
		printf("%6d %8s %7.2f\n", stu[i].num, stu[i].name, stu[i].score); // 输出结果
	printf("\n");
	return 0;
}

程序分析:

  • 该程序可适用于任意个学生,现定义N为5,如果有30个学生,只需第2行改为“#define N 30”即可,其余各行不必修改。
  • !注意:临时变量temp也应定义为struct student类型,只有同类型的结构体变量才能互相赋值,(所有成员整体互换,不必人为地指定一个一个成员地互换),从这点也可以看到使用结构体类型的好处。

结构体指针
  • 所谓结构体指针就是指向结构体数据的指针,一个结构体变量的起始地址就是这个结构体变量的指针。
  • 如果把一个结构体变量的起始地址存放在一个指针变量中,那么这个指针变量就指向该结构体变量。
  • 指针变量既可以指向结构体变量,也可以用来指向结构体数组中的元素。但是,指针变量的基类型必须与结构体变量的类型相同。

例9:通过指向结构体变量的指针变量输出结构体变量中成员的信息。

#include< stdio.h>
#include <string.h>
int main()
{
	struct student
	{
		long num;
		char name[20];
		float score;
	};
	struct student stu1; //定义struct student类型的变量stu1
	struct student * p; //定义指向struct studen类型数据的指针变量p
	p = &stu1; //p指向结构体变量stu1
	stu1.num = 10101; //对结构体变量的num成员赋值
	strcpy(stu1.name, "Li Lin"); // 对结构体变量的name成员值
	stu1.score = 89.5; //结构体变量的score成员赋值
	printf("No. :%ld\nname: %s\nsex:%c\nscore:%5.1f\n", stu1. num, stu1.name,stu1.score); //输出各成员的值
	printf("\nNo. :%ld\nname: %s\nsex:%c\nscore:%5.1f\n", (* p).num, (* p).name, (* p).score); //输出各成员的值
	return 0;
}

程序分析:

  • 在主函数中声明了struct student类型,然后定义一个struct student类型的变量stu1,又定义一个指针变量p,它指向一个struct student类型的数据。将结构体变量stu1的起始地址赋给指针变量p,也就是使p指向stu1,然后对stu1的各成员赋值。第一个printf函数是通过结构体变量名stu1访问它的成员,输出stu1的各个成员的值。用stu1.num表示stu1中的成员num,依此类推。第二个printf是通过指向结构体变量的指针变量访问它的成员,输出stu1各成员的值,使用的是(* p).num这样的形式。(* p)表示p指向的结构体变量,(* p).num是p指向的结构体变量中的成员num。
  • 注意* p两侧的括号不可省,因为成员运算符 “.” 优先于 “” 运算符,如果不加括号, p.num就等价于(p.num)了。
  • 说明:C语言允许用pー>num代替(* p).num,它表示p所指向的结构体变量中的num成员。同样,pー>name等价于(* p).name。“ー>”称为指向运算符。

如果p指向一个结构体变量,以下3种形式等价:

  • 结构体变量.成员名
  • (* p).成员名
  • p->成员名

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

例10:有3个学生的信息,放在结构体数组中,要求输出全部学生的信息。

#include<stdio.h>
struct student //声明结构体类型struct student
{
	int num;
	char name[15];
	int age;
};
struct student stu[3] = { {10101, "Li Lin", 18}, {10102, "Zhang Fun", 19},{10104, "Wang Min", 20} };
//定义结构体数组并初始化
int main()
{
	struct student* p; //定义指向struct student结构体变量的指针
	printf("No.   Name         age\n");
	for (p = stu; p < stu + 3; p++)
		printf("%5d %-10s %4d\n", p->num, p->name, p->age); //输出结果
	return 0;
}

程序分析:

  • 在for语句中先使p的初值为stu,也就是数组stu第一个元素的起始地址。
  • 在第一次循环中输出stu[0]的各个成员值,然后执行p++,使p自加1,p+1意味着p所增加的值为结构体数组stu的一个元素所占的字节数(一个元素所占的字节数为4+15+1+4=24字节)。
  • 执行p++后,p的值等于stu+1,p指向stu[1],在第二次循环中输出stu[1]的各成员值。
  • 再执行p++后,p的值等于stu+2,再输出stu[2]的各成员值。
  • 再执行p++后,p的值变为stu+3,已不再小于stu+3了,不再执行循环。

注意:

  • 如果p的初值为stu,即p指向stu的第一个元素,p加1后,p就指向下一个元素。
    例:
(++P)->num //先使p自加1,然后得到p指向的元素中的num成员值(即10102)
(p++)->num //先求得p->num的值(即10101),然后再使P自加1,指向stu[1]

在这里插入图片描述

  • 程序已定义了p是一个指向struct student类型数据的指针变量,它用来指向ー个struct student类型的数据(p的值是stu数组的一个元素(如stu[0]、stu[1]的起始地址),不应用来指向stu数组元素中的某一成员。
    例:(错误❌)
p=stu[1].name;
  • 编译时将给出“警告”信息,表示地址的类型不匹配。
  • !不要认为反正p是存放地址的,可以将任何地址赋给它。如果要将某一成员的地址赋给p,可以用强制类型转换,先将成员的地址转换成p的类型。
    例: p=(stu student * )stu[0].name 此时,p的值是stu[0]元素的name成员的起始地址。可以用"printf("%s",p;)“输出stu中成员name的值。但是p保持原来的类型。如果执行"printf(”%s",p+1);"则会输出stu[1]中name的值。执行p++时,p的值增加了结构体struct student的长度。

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

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

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

例:有N个结构体变量stu,内含学生学号、姓名和3门课程的成绩。要求输出平均成绩最高的学生的信息(包括学号、姓名、3门课程成绩和平均成绩)。

#include<stdio.h>
#define N 3 //学生数为3
struct student //声明结构体类型struct student
{
	int num; //学号
	char name[20]; //姓名
	float score[3];  //三门课成绩
	float aver; //平均成绩
};

	int main()
	{
		void input(struct student stu[]); //函数声明
		struct student max(struct student stu[]); //函数声明
		void print(struct student stud); //函数声明
		struct student stu[N], * p = stu; //定义结构体数组和指针
		input(p); //调用input函数
		print(max(p)); //调用print函数, 以max函数的返回值作为实参
		return 0;
	}

	void input(struct student stu[]) //定义input函数
	{
		int i;
		printf("请输入各学生的信息:学号、姓名、三门课成绩:\n");
		for (i = 0; i < N; i++)
		{
			scanf("%d %s %f %f %f", &stu[i].num, stu[i].name, &stu[i].score[0], &stu[i].score[1], &stu[i].score[2]);//输入数据
			stu[i].aver = (stu[i].score[0] + stu[i].score[1] + stu[i].score[2]) / 3.0; //求各人平均成绩
		}
	}

	struct student max(struct student stu[]) // 定义max函数
	{
	int i, m = 0; //用m存放成绩最高的学生在数组中的序号
	for (i = 0; i < N; i++)
		if (stu[i].aver > stu[m].aver) //找出平均成绩最高的学生在数组中的序号
			m = i; 
	return stu[m]; //返回包含该生信息的结构体元素
	}

void print(struct student stud) //定义print函数
{
	printf("\n成绩最高的学生是:\n");
	printf("学号:%d\n姓名:%s\n三门课成绩:%5.1f,%5.1f,%5.1f\n平均成绩:%6.2f",
	 stud.num, stud.name, stud.score[0], stud.score[1], stud.score[2], stud.aver);
}

//输入:
10101 Li 78 89 98
10102 Wang 98.5 87 69
10103 Fun 88 76.5 89

//输出结果为:
成绩最高的学生是:
学号:10101
姓名:Li
三门课成绩: 78.0, 89.0, 98.0
平均成绩: 88.33

程序分析:

  • 在主函数中定义了结构体struct student类型的数组stu和指向struct student类型数据的指针变量p,使p指向stu数组的首元素stu[0]。
  • 在调用input函数时,用指针变量p作为函数实参,input函数的形参是struct student类型的数组stu(!注意:形参数组stu和主函数中的数组stu都是局部数据,虽然同名,但在调用函数进行虚实结合前二者代表不同的对象,互相间没有关系)。在调用input函数时,将主函数中的stu数组的首元素的地址传给形参数组stu,使形参数组stu与主函数中的stu数组具有相同的地址。因此在input函数中向形参数组stu输人数据就等于向主函数中的stu数组输入数据。input函数无返回值,它的作用是给stu数组各元素赋予确定的值。
  • 在用scanf函数输入数据后,立即计算出该学生的平均成绩,stu[i].aver代表序号为i的学生的平均成绩。
  • 在主函数中调用print函数,实参是max§。其调用过程是先调用max函数(以p为实参),得到max§的值,然后用它调用print函数。
  • 现在先分析调用max函数的过程:与前相同,指针变量p将主函数中的stu数组的首元素的地址传给形参数组stu,使形参数组stu与主函数中的stu数组具有相同的地址。在max函数中对形参数组的操作就是对主函数中的stu数组的操作。在max函数中,将各人平均成绩与当前的“最高平均成绩”比较,将平均成绩最高的学生在数组stu中的序号存放在变量m中,通过return语句将stu[m]的值返回主函数。
  • !注意:stu[m]是一个结构体数组的元素。max函数的类型为struct student类型。
  • 用max( p)的值是结构体数组的元素作为实参调用print函数。print函数的形参stud是struct student类型的变量(而不是struct student类型的数组)。在调用时进行虚实结合,把stu[m]的值(是结构体元素)传递给形参stud,这时传递的不是地址,而是结构体变量中的信息。在print函数中输出结构体变量中各成员的值。
  • 以上3个函数的调用,情况各不相同:
  1. 调用input函数时,实参是指针变量p,形参是结构体数组名,传递的是结构体元素的地址,函数无返回值。
  2. 调用max函数时,实参是指针变量p,形参是结构体数组名,传递的是结构体元素的地址,函数的返回值是结构体类型数据。
  3. 调用print函数时,实参是结构体变量(结构体数组元素),形参是结构体变量,传递的是结构体变量中各成员的值,函数无返回值。

补充:

typedef struct tagNode
{
  char *pItem;
  pNode pNext;
} *pNode;
  • 上述代码编译阶段会报错,原因:
    在上面的代码中,新结构建立的过程中遇到了pNext声明,其类型是pNode。这里要特别注意的是,pNode表示的是该结构体的新别名。
  • 于是问题出现了,在结构体类型本身还没有建立完成的时候,编译器根本就不认识 pNode,因为这个结构体类型的新别名还不存在,所以自然就会报错。
  • 因此,我们要做一些适当的调整,比如将结构体中的pNext声明修改成如下方式:
typedef struct tagNode
{
  char *pItem;
  struct tagNode *pNext;
} *pNode;

或者将struct与typedef分开定义

typedef struct tagNode *pNode;
struct tagNode
{
  char *pItem;
  pNode pNext;
};
  • 在上面的代码中,我们同样使用typedef给一个还未完全声明的类型tagNode起了一个新别名。不过,虽然C语言编译器完全支持这种做法,但不推荐这样做,建议改为:
struct tagNode
{
   char *pItem;
   struct tagNode *pNext;
};
typedef struct tagNode *pNode;
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值