C语言程序设计—结构体 的整理笔记,若有错误,欢迎指正。
C语言程序设计—结构体(Part Ⅰ)
结构体数组
- 一个结构体变量中可以存放一组有关联的数据(如一个学生的学号、姓名、成绩等)。如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组。结构体数组的每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项。
-
定义结构体数组的一般形式是:
(1) struct结构体名
{成员表列} 数组名[数组长度]
(2)先声明一个结构体类型,然后再用此类型定义结构体数组
如:struct person leader[3]; //leader是结构体数组名
-
对结构体的数组初始化的形式是在定义数组的后面加上
={初值表列};
例: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个函数的调用,情况各不相同:
- 调用input函数时,实参是指针变量p,形参是结构体数组名,传递的是结构体元素的地址,函数无返回值。
- 调用max函数时,实参是指针变量p,形参是结构体数组名,传递的是结构体元素的地址,函数的返回值是结构体类型数据。
- 调用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;