一、什么是结构体
1、C语言构造类型构造数据类型:构造数据类型是根据已经定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。
在C语言中,构造类型有以下几种:
数组类型
结构体类型
共用体类型
小结:和我们前面学习的数组相比较结构体不同的是,它的若干个“成员”或“元素”可以是不同的类型,更可以是构造类型。但是数组的若干个“成员”或“元素”必须是类型相同并且不能是构造类型。
2、什么是构造体
在实际问题中,一组数据往往具有不同的数据类型。例如,在学生登记表中,姓名应为字符型,学号可为整形或字符型、年龄应为整形、性别应为字符型、成绩可为整形或是实型。显然不能用一个数组来存放这一组数据。因为数组中各元素的类型和长度都必须一致,以便于编译器系统处理。为了解决这个问题,C语言中给出了另一种构造数据类型——“结构”或叫“构造体”。它相当于其它高级语言中的记录。
“结构”是一种构造类型,它是由若干“成员”组成的。每一个成员可以是一个基本数据类型或者又是一个构造类型。结构既是一种“构造”而成的数据类型,那么在说明和使用之前必须先定义它,也就是构造它。如同在说明和调用函数之前要先定义函数一样。
3、为什么要有构造类型
结构体可以把功能相同的数据组织起来,存在一起,用的时候方便,而且在调用函数时,若传递参数较多,传一个结构体相对而言简单一些,很多系统自带的函数必须用结构体。
二、定义结构体的方法
1、定义结构体的方法
定义一个结构的一般形式为:
struct 结构体
{
成员表列
};
成员表列由若干个成员组成,每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:
类型说明符 成员名;
比如:
下面定义一个学生的结构
struct stu
{
int num;
char name[20]; 或 char *name;
char sex;
float score;
};(注意此处的‘;’不能省)
说明:
struct 是结构体的关键字
stu 是结构体名
结构体最后的分号不能省
在定义姓名时,推荐使用char *name,因为这样可以直接赋值
2、结构体变量定义有三种方法
1>先定义结构体,然后在定义结构体变量
struct student
{
//学生学号
int num;
//学生姓名
char name[20]; 或 char *name;
//学生年龄
char sex;
//学生成绩
float score;
};
接下来定义结构体变量,格式如下:
struct 结构体名 结构体变量名;
struct student 变量1,变量2,变量3.......;
struct student stu1; 这句话表示定义一个student结构体类型的变量,变量的名称是stu1;
stu1因为是student类型,stu1可以存放学生的学号、姓名、年龄、成绩
struct student stu2,stu3,stu4; 这句话表示定义了多个结构体变量
说明:
>结构体定义完成以后,计算机并不会给结构体分配内存空间
>会在定义结构体变量后,分配存储空间
2>定义结构体的同时,定义结构体变量
定义结构体的格式:
struct 结构体名
{
结构体成员;
}结构体变量1,结构体变量2....;
比如:
struct student
{
//学生学号
int num;
//学生姓名
char name[20]; 或 char *name;
//学生年龄
char sex;
//学生成绩
float score;
}stu5,stu6,stu7;
也是用student结构体定义了三个结构体变量,名称分别为 stu5,stu6,stu7
3>使用匿名结构体定义结构体变量
struct
{
//学生学号
int num;
//学生姓名
char name[20]; 或 char *name;
//学生年龄
char sex;
//学生成绩
float score;
}stu8,stu9,stu10;
总结:
第三种方法与第二种方法的区别在于第三种方法中省去了结构体名,而直接给出结构变量,这种结构体最大的问题是,不能再次定义新的结构体变量。
三、结构体变量中成员的访问方法
1、结构体变量中成员的访问方法
在程序中使用结构变量是,往往不把它作为一个整体来使用。在ANSI C中除了允许具有相同类型的结构体变量相互赋值以外,一般对结构变量的使用,包括赋值、输入、输出、运算等都是通过结构变量的成员来实现的。
表示结构变量成员的一般形式是:
结构体变量名.成员名
stu1.num 表示第一个人的学号
stu1.sex 表示第二个人的性别
如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。
比如:
stu1.birthday.month
即第一个人出生的月份成员可以在程序中单独使用,与普通变量完全相同。
2、结构体变量的初始化
结构体初始化的方法也有三种,正如结构体变量定义一样
1>先定义结构体变量,然后再初始化
struct student
{
//学生学号
int num;
//学生姓名
char name[20]; 或 char *name;
//学生年龄
char sex;
//学生成绩
float score;
}stu1,stu2,stu3;
stu1.num=1010;
strcpy(stu2.name,"asdf"); //这样定义是因为数组无法整体赋值,所以推荐使用指针数组
char *name 这样就可以直接赋值 stu3.name="asdf"
stu1.sex='f';
stu1.score=18.0f;
2>定义的同时初始化
定义结构体的时候直接初始化
struct student
{
//学生学号
int num;
//学生姓名
char name[20]; 或 char *name;
//学生年龄
char sex;
//学生成绩
float score;
}stu4={1012,“asdf”,'f',40},stu6;
注意:“asdf”初始化赋值给了name[20]; 所以推荐初始化时使用直接初始化
初始化的顺序必须与结构体定义的时候成员的顺序一致
或
struct student stu5={1011,“asd”,'f',34};
使用另外一已经存在的结构体初始化新的结构体
stu6=stu4;
注意!!!
两个相同类型的结构体变量的赋值,相当普通变量的赋值,是整体拷贝,而不是地址赋值
3>先定义结构体变量,然后再初始化
struct student
{
//学生学号
int num;
//学生姓名
char name[20]; 或 char *name;
//学生年龄
char sex;
//学生成绩
float score;
};
struct student str8;
str8.sno=1010;
str8.age=18;
str8.score=59.9f;
char name[21]="sdf";这是错误的
strcpy(str8.name,"sdf");
下面用实例来看看
程序如下:
打印如下:
总结:
第一:推荐在定义结构体变量和结构体变量初始化的时候尽量一起完成,这样就可以在一个大括号内逐一赋值就好了,后初始化需要一个元素一个元素赋值。
第二:推荐在用到字符串类型的变量时最好把变量定义成字符串指针类型
三、结构体数组
1、结构数组概念
数组的元素也可以是结构类型的。因此可以构成结构型数组。结构数组的每一个元素都是具有相同结构类型的下标结构变量。在实际应用中,经常用结构数组来表示具有相同数据结构的一个群体。如一个班的学生档案,一个车间职工的工资表等。
2、结构数组定义
定义格式:
struct 结构体名
{
成员表列;
};
例如:定义一个长度为5的数组,其中每一个元素都是 student结构类型
struct student
{
//学生学号
int num;
//学生姓名
char *name;
//学生年龄
char *sex;
//学生成绩
float score;
}boy[5];
说明:定义了一个结构数组boy,共有5个元素,boy[0]到boy[4]。每个数组元素都具有struct student的结构形式。
上面的定义方式是定义结构体的同时定义数组,下面介绍先定义结构体,后定义数组
struct student boy1[5];
3、结构数组的初始化和遍历
//结构体
struct student
{
//学生学号
int num;
//学生姓名
char *name;
//学生年龄
char *sex;
//学生成绩
float score;
};
同定义结构数组一样,初始化结构数组也有3种方式
1>定义的同时进行初始化
struct student boy3[2]={{1000,"小东",“男”,87},{1010,“小南”,"女",76},{1020,“小北”,"男",79}};
2>先定义,后初始化,整体赋值
boy3[1]=(struct stu){1010,“小南”,"女",76};
3>先定义,后初始化
boy3[1].num=1010;
boy3[1].name="小南";
boy3[1].sex="男";
boy3[1].score=76;
下面举一个例子
要求:
1、利用stu的结构体,计算学生平均成绩 和 不及格的人数及信息
2、打印80-100分学生的信息
程序如下:
程序打印如下:
总结:
通过上面的例程就可以更好的明白定义结构体、结构体变量、结构数组及其初始化以及如何访问结构数组的成员。
例二:用结构体数组实现通讯录
要求:
让用户从键盘输入要保存的姓名和电话号码
程序代码如下:
打印如下:
总结:
值得注意的一点是:
当
char name[21] 改为char*name
char num[12] 改为char *num
会出错
四、结构指针
1、指向结构体变量的指针
一个指针变量当用来指向一个结构变量时,称之为结构指针变量。结构指针变量中的值是所指向的结构变量的首地址。通过结构指针即可访问该结构变了,这与数组指针和函数指针的情况是相同的。
结构指针变量说明的一般形式为:
struct 结构名 *结构指针变量名
例如,在前面的例题中定义了stu这个结构,如要说明一个指向stu的指针变量pstu,
可写为:
struct stu *pstu; //表示定义了指针只能指向stu结构体类型的结构体变量
当然也可以在定义stu结构是同时说明pstu.与前面讨论的各类指针变量相同,结构指针变量也必须要先赋值后才能使用。
赋值是把结构变量的首地址赋予该指针变量,不能把结构体名赋予该指针变量
比如boy是被说明为stu类型的结构变量,
则:
pstu=&boy;是正确的,
而
pstu=&stu;是错误的
2、结构体名、结构体变量名、结构体指针
结构体名和结构体变量是两个不同的概念,不能混淆。
>结构体名只能表示一个结构形式,编译系统并不对它分配内存空间,只有当某变量被说明为这种类型的结构是,才对该变量分配存储空间。
>结构体指针变量,就是用来存储结构体变量地址的,就能更方便地访问结构变量的各个成员。
//结构体定义
struct student
{
//学生学号
int num;
//学生姓名
char *name;
//学生年龄
char *sex;
//学生成绩
float score;
};
//定义结构体变量和结构体指针
struct student boy;
struct student *p=null;
所以
p = &boy; //这是正确的
p = &student; //这是错误的
如上面:
结构体名:student
结构体变量名:boy
结构体指针: p
小结:
>什么是结构体指针
就是用来存放结构体变量地址的指针变量
>结构体指针定义格式:
struct 结构体名 *指针变量名
3、结构体指针间接访问成员值
1>指向结构体变量的指针
其访问的一般形式为:
(*结构指针变量).成员名
(*p).name
或为:
结构指针变量->成员名
p->name
小结:
1、应该注意(*p)两侧的括号不可少,因为成员符" . "的优先级高于" * ".如果去掉括号写作*p.num则等效于*(p.num),这样,意义就完全不对来。
2、因为结构体指针变量存储的是结构体变量的“首地址”,所以(*结构体变量)就相当于结构体指针p指向了结构体变量的地址
通过下面的例子来看看结构体指针与结构数组的关系
程序代码如下:
打印如下:
第二种访问形式
结构指针用“->”形式来访问结构数组
程序如下:
打印如下:
下面看看结构指针与结构体变量
程序如下:
打印如下:
五、结构体嵌套使用
1、结构体嵌套使用
成员也可以又是一个结构,即构成了嵌套的结构
总结:
结构体嵌套:结构体定义的里面有其他结构体
结构体不可以嵌套自己变量,可以嵌套指向自己这种类型的指针
如:
//定义结构体1
struct Date
{
int year;
int month;
int day;
};
//定义结构体2
struct stu
{
int num;
char *name;
char *sex;
struct Date birthday;
float score;
};
这表明了结构体2中嵌套了结构体1
2、嵌套定义自己的指针
struct person
{
int age;
char *name;
struct person *son;
};
下面用实例来看看
程序如下:
打印如下:
下面实例是关于结构体嵌套自己的指针变量
程序如下:
打印如下:
六、结构体变量与函数
1、成员值做函数的参数
结构体成员属性作为函数的参数就是值传递(成员变量是数组除外)
2、结构体变量名作为函数的参数
在ANSI C标准中允许用结构变量作函数参数进行整体传送。但是这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重的降低了程序的效率。因此最好的办法就是使用指针,即用指针变量作函数 参数进行传送。这时由实参传向形参的只是地址,从而减少了时间和空间的开销。
下面用程序实例来看
本例要求:
就是把p的age=10086调用子函数改为age=10000
程序如下:
打印如下:
总结:
从这个打印图可以看出:在程序调用子函数时,子函数确实是把传送到子函数的内容修改了,但是从修改后的内容可以知道,修改前后的内容是一样的,没有改变。这是为什么?
分析:
当我们在调用子函数的时候会把结构体p中的内容全部都赋值给c,这时结构体c会被分配空间,在c的分配空间中c会修改age,但是当执行到子程序的“}”时,子程序分配的空间会被释放,但是存在存储空间的结构体p的内容并没有受到任何改变,所以没有被修改。
我们给子函数传值改变不了,那给子函数传地址看可不可以修改?
程序如下:
打印如下:
总结:
从这个打印图来看通过子函数内容被修改了,这是为什么?
分析:
这次子函数中形参定义的是结构体指针,所以这次传递的是结构体变量p的地址,在运行程序的时候,系统会分配空间来存储结构体变量p,当执行到调用子函数时,系统会分配另一部分空间来存储结构体指针c,也就是说是一个地址,当执行子程序中的语句c->age=10000;时,指针c会指向存储的地址并且把其中的内容age赋值为10000,当执行到子函数“}”时,子函数的空间被释放,结构体变量p中的内容也被修改了。
在用被调函数来修改住调函数中的内容时,要把主调函数的地址传送给被调函数才可能修改主调函数的内容,这称为“地址传递”;
在用被调函数来修改住调函数中的内容时,如果把主调函数的值传送给被调函数,这是不可能修改主调函数的内容,这称为“值传递”;