C语言基础:结构体

结构体

目录:

  1. 结构体的概念
  2. 结构体类型的定义
  3. 结构体类型的嵌套定义
  4. 结构体类型变量
  5. 结构体类型变量的定义
  6. 结构体类型变量的初始化
  7. 结构体类型变量的引用
  8. 注意事项1
  9. 注意事项2
  10. 注意事项3
  11. 注意事项4
  12. 相关说明
  13. 结构体数组
  14. 结构体数组的定义
  15. 结构体数组的初始化
  16. 分行初始化
  17. 顺序初始化
  18. 结构体数组的访问
  19. 结构体数组应用举例
  20. 结构体与指针
  21. 指向结构体类型数据的指针
  22. 指向结构体变量的指针
  23. 指向结构体数组的指针
  24. 结构体变量和指向结构体的指针做函数参数
  25. 结构体变量做参数(值传递)
  26. 结构体数组做函数参数
  27. 函数返回值是结构体类型

    1.结构体的概念

结构体是一种构造数据类型

数组也是一种构造数据类型(由多个数组合在一起),结构体可以组合不同类型的数

用途:把不同类型的数据组合成一个整体————自定义数据类型。

2.结构体类型的定义

结构体先定义类型,再定义变量


语法格式与实例
  1. 关键字struct代表定义的是结构体类型。
  2. 注意最后的分号。
  3. 在之后定义变量时要使用类型名,注意类型名,在实例中,类型名不是”student”而是”struct student”!!!
  4. 结构体中的成员可以是普通类型,也可以是构造类型(如数组)。因此数组可以作为成员名。

结构体类型的嵌套定义

之后结构体类型的定义可以利用之前所定义过的结构体类型


嵌套定义的例子

3. 结构体类型变量

1. 结构体类型变量的定义

方法一:使用已经定义好的结构体类型定义变量
方法二:在定义结构体类型的同时定义变量
方法三:直接定义结构体变量

在这三种中,通常采用第一种。


方法一实例:struct student为定义好的结构体类型,在其之后定义了fresher,junior两个变量

方法二实例,前方为结构体类型的定义,之后所接的即为变量,之后若想继续定义变量,则需要用方法一

方法三实例,没有定义结构体的类型,直接定义了变量。用这种方式,之后无法继续使用此类型定义变量,只能在这一行添加。

2. 结构体类型变量的初始化

与结构体类型变量的定义相对应,其有三种情形:

  1. 先定义结构体类型,然后定义结构体变量,然后写上了一些内容(初始化)

  2. 定义结构体类型的同时定义变量,并且对它进行初始化

  3. 不定义结构体类型,直接定义结构体类型的变量,并且对它们初始化


情形一,注意不能整体赋值!
  1. 结构体变量不允许整体赋值
  2. 初始化时各成员初值要与数据类型一致!

情形二,同理,不能对结构体变量做整体的赋值运算

情形三实例,不定义结构体类型,直接定义变量并初始化

3. 结构体类型变量的引用

一般形式: 结构体变量名.成员名

注意事项:

不能将结构体变量作为一个整体进行输入和输出
不能将两个结构体变量进行整体比较
可以将一个结构体变量整体赋值给另一个结构体变量
结构体嵌套时逐级引用

引用实例:


赋值运算,自加运算,判断

注意事项1:

结构体变量不能整体输入和输出和赋值!


需要一一对应地输出

不能整体输入,注意要加取地址符号,但是数组(char类型)的名字就是地址(首地址)

结构体变量不能整体赋值。注意第二行,作为数组名,不能出现在赋值等于号的左边(因为它是常量),因此字符串的拷贝用strcpy,第六行的地址同理

注意事项2:

不能对结构体变量做是否相等的运算。
需要比较时,则将里面的每一个字段一一比较以确认相等。


通常此内容对应到数据库知识。注意第二行,对两个字符串进行比较时使用strcmp

注意事项3(可以):

可以把一个结构体变量整体赋值给另一个!


可以将stu1的所有信息赋值给stu2

注意事项4

结构体嵌套时逐级引用!

结构体类型嵌套定义,若想引用该结构体中所嵌套的另一个结构体类型的成员,中的信息,要逐级引用。

如:变量名.成员名.嵌套的结构体的成员名

有几层便要用几层的引用。

相关说明:

注意:结构体类型与结构体变量不可混淆!
结构体成员名与程序中变量名可相同,互不影响!
结构体可复合!

关于结构体复合:
在结构体类型的定义内再定义另一个结构体类型
与嵌套的不同之处:它真的把结构体嵌进去了!


结构体的复合

4. 结构体数组

结构体与数组的联系:

在结构体的成员中有数组(如char name[10])
把数组放在外面,把结构体放在里面,便生成结构体数组。

结构体数组的定义

类似于结构体变量定义的三种方法,结构体数组的定义有三种方法:

一、使用已经定义好的结构体类型定义数组

例子:

1
2
3
4
5
6
7
8
struct student
{
int num;
char name[20];
char sex;
int age;
};
struct student stu[2];

结构体数组的分配空间

二、在定义结构体类型的同时定义数组

1
2
3
4
5
6
7
struct student
{
int num;
char name[20];
char sex;
int age;
}stu[2];

三、直接定义结构体数组

1
2
3
4
5
6
7
struct
{
int num;
char name[20];
char sex;
int age;
}stu[2];

2. 结构体数组的初始化

分行初始化

所谓分行,不是局限于行数,而是初始化时,使用了多组大括号。
只要每个初始化信息外有大括号,就是分行初始化。

例子:

1
2
3
4
5
6
7
8
9
10
struct student
{
int num;
char name[20];
char sex;
int age;
};
struct student stu[]={{100,"Wang Lin",'M',20},
{101,"Li Gang",'M',19},
{110,"Liu Yan",'F',19}};

全部初始化时维数(第八号方括号内的数)可省

顺序初始化

例子:

1
2
3
4
5
6
7
8
struct student
{
int num;
char name[20]
char sex;
int age;
};
struct student stu[]={100,"Wang Lin",'M',20,101,"Lin Gang",'M',19,110,"liu Yan",'F',19};

与分行初始化的区别:每个人的信息都用逗号隔开,外面没有大括号了。

结构体数组元素的访问

语法格式: 结构体数组名[下标].成员名
例:
让一号同学的年龄加一:

1
stu[1].age++;

让零号学生姓名为ZhaoDa

1
strcpy(stu[0].name,"ZhaoDa");

作为数组(常量),因此不能用赋值运算,使用strcpy,把字符串拷贝到数组中

结构体数组应用举例:

选举:十三人中,三人为候选人,十人为选举人,选举三人中的一人。每个人那一张纸条,上面写上”Li”或”Wang”或”Zhang”投票。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
#include<string.h>
struct person
{
char name[20];
int count;
}leader[3]={"Li",0,"Zhang",0,"Wang",0};
void 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++;
}
printf("\n");
for(i=0;i<3;i++)
printf("%5s:%d\n",leader[i].name,leader[i].count);
}

5.指向结构体类型数据的指针

1.指向结构体变量的指针
2.指向结构体数组的指针
3.结构体变量和指向结构体的指针做函数参数

指向结构体变量的指针

引例

1
2
3
4
int i;
int *p=&i;
i=10;
/* 上面一句等同于 *p=10; */

由此引出:

1
2
3
4
struct student stu1;
struct student *q=&stu1;
stu1.age=18;
/* 等同于(*q).age=18; */

注:点号为单目运算符,结合性为自右向左,即可能出现忽略左侧星号*的情况。所以要带上小括号。

由于写的时候容易有歧义、或者要带括号,较为麻烦,
因此可以用一种新的写法:

1
2
q->age=18;
/* 相当于 (*q).age=18 */

尤其当之后学了链表后,都是用这种方法,基本上不再用前者。

定义 struct 结构体名 *指针变量;
赋初值 指针变量名=&结构体变量;
访问成员 设p为指向结构体变量的指针:
访问方式一:(*p).成员名
访问方式二:p->成员名

指向结构体数组的指针

定义一个数组,定义一个指针指向数组的第一个地址,指针P就可以通过自加运算访问第一、二、三······个。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>
/*结构体数组的定义与初始化*/
struct student
{
int num;
char name[20];
char sex;
int age;
}stu[3]={{10101,"Li Lin",'M',18},{10102,"Zhang Fun",'M',19},{10104,"Wang Min",'F',20}};/*分行初始化*/
void main()
{
struct student *p;
/*定义了一个指针P,让P等于数组的名字,指向数组的首地址*/
for(p=stu;p<stu+3;p++)
printf ("%d%s%c%d\n",p->num,p->name,p->sex,p->age);
/*都用箭头,看起来很统一*/
}

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

1. *结构体变量作参数(值传递)

例:定义一个复数类型的结构体,通过函数调用,求两个复数的和。

复数包括实部、虚部,结构体变量可以带多个成员。因此可以定义一个复数的结构体类型,带三个成员:实部、虚部和模。

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
#include<stdio.h>
#include<math.h>
struct complex_t
{
double real;
double imag;
double modular;
};/*结构体类型的定义*/
void add(struct complex_t,struct complex_t);/*函数的声明,声明只要含有参数的数量以及类型,因此不必将参数的名字写在上面*/
int main()
{
struct complex_t c1.c2;
scanf("%lf %lf",&c1.real,&c1.imag);
scanf("%lf %lf",&c2.real,&c2.imag);/*分别读入C1C2的实部虚部*/
add(c1,c2);/*调用add函数*/
return 0;
}
void add(struct comples_t c1,struct complex_t c2)
{
struct comples_t c;
c.real=c1.real+c2.real;
c.imag=c1.imag+c2.imag;
c.modular=sqrt(c.real*c.real+c.imag*c.imag);
printf("%lf,%lf,%lf\n",c.real,c.imag,c.modular);
}

结构体数组作为函数参数

(例题:学生的记录由学号和成绩组成,N名学生的数据已在主函数中放入结构体数组s中,请编写函数fun,其功能是:把分数最低的学生数据放在h所指的数组中。注意:分数最低的学生可能不止一个,函数返回分数最低的学生的人数。

通过结构体数组作为参数的形式实现排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void SortData(struct student *pstu,int n)
{
int i,j;
struct student temp;
for(i=0;i<n-1;i++)
for(j=0;j<n-1-i;j++)
if(pstu[j].sum<pstu[j+1].sum)
{
temp=pstu[j];
pstu[j]=pstu[j+1];
pstu[j+1]=temp;
}
/*冒泡排序算法*/
}

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

1
2
3
4
5
6
7
8
struct complex_t add(struct complex_t c1,struct complex_t c2)/*可以看出,函数的返回值是结构体类型*/
{
struct complex_t c;
c.real=c1.real+c2.real;
c.imag=c1.imag+c2.imag;
c.modular=sqrt(c.real*c.real+c.imag*c.imag);
return c;/*返回C(多值返回),尽管只返回一个C,但C可以待会多个东西*/
}

主函数的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <math.h>
struct complex_t
{
double real;
double imag;
double modular;
};
struct complex_t add(struct complex_t,struct complex_t);
int main()
{
struct complex_t c,c1,c2;
scanf("%lf %lf",&c1.real,&c1.imag);
scanf("%lf %lf",&c2.real,&c2.imag);
c=add(c1,c2);
printf("%lf,%lf,%lf\n",c.real,c.imag,c.moudlar);
return 0;
}/*主函数所做的事:数据的输入、函数的调用、数据的输出*/

例:

设一个班级有三十个人,学生信息包括姓名、学号外、三科成绩。利用函数的返回值找出总分最高分和最低的学生的信息。

函数只能返回一个回来,如何解决这个问题——可以用指针带回来一些值

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<stdio.h>
#define N 30
struct student
{
char name[20];/*学生姓名*/
long num;/*学号*/
float score[3];/*三科考试成绩*/
float sum;/*总成绩*/
};
struct student max(struct student [],struct student */*用这个指针带回来一些值*/,int);/*在函数声明时只要写类型*/
void inputdata(struct student stu[],int);
void inputdata(struct student stu[],int n)
{
int i,j;
for(i=0;i<n;i++)/*一重循环*/
{
scanf("%s %ld %f %f %f",stu[i].name,&stu[i].num,&stu[i].score[0],&stu[i].score[1],&stu[i].score[2]);
stu[i].sum=(stu[i].score[0]+stu[i].scpre[1]+stu[i].scpre[2]);
}
}
struct student max(struct student stu[],struct student *s,int n)/*第一个参数为结构体数组,第二个参数为结构体指针,结构体指针可以用来返回最小值)*/
{
struct student t;
int i;/*会出现循环,所以定义i*/
float m,p;/*存放最高分最低分的分值*/
m=p=stu[0].sum;/*让分值m,p都等于0号同学的sun*/
for(i=0;i<n;i++)
{
if(m<=stu[i].sum)
{
m=stu[i].sum;
t=stu[i];
}/*找最高分*/
if(p>=stu[i].sum)
{
p=stu[i].sum
*s=stu[i];/*用主函数的指针,因为return时只能返回一个值*/
}/*找最低分*/
}
return t;/*只能返回一个值,最低分的人的信息用指针S带回*/
}
int main()
{
struct student st[N],maxst,minst;
inputdata(st,N);
maxst=max(st,&minst,N);/*定义minst,指针S指向minst的地址,则可以用*s带回需要的值*/
printf("最高分学生信息:\n");
printf("%s %ld %.2f %.2f %.2f %.2f\n",maxst.name,maxst.num,maxst.score[0],maxst.score[1],maxst.scpre[2],maxst.sum);
printf("最低分学生信息:\n");
printf("%s %ld %.2f %.2f %.2f %.2f\n",minst.name,minst.num,minst.score[0],minst.score[1],minst.score[2],minst.sum);
return 0;
}