C语言结构体知识温故知新
1. 定义一个结构体的一般形式
struct 结构名
{成员表列};//成员列表=类型说明符 成员名;
2. 结构类型变量的说明
说明结构变量有以下三种方法
(1). 先定义结构,再说明结构变量
这种形式的说明的一般形式为:
struct 结构名
{
成员表列
};
struct 结构名 结构变量1, 结构变量2;
struct stu
{
int num;
char name[20];
char sex;
float score;
};
struct stu boy1,boy2;
(2). 可以用宏定义使用一个符号常量来表示一个结构类型。
#define STU struct stu
STU
{
int num;
char name[20];
char sex;
float score;
};
STU boy1,boy2;
(3). 直接说明结构变量
这种形式的说明的一般形式为:
struct
{
成员表列
}变量名表列;
如:
struct
{
int num;
char name[20];
char sex;
float score;
}boy1,boy2;
第三种方法与第二种方法的区别在于第三种方法中省去了结构名,而直接给出结构变量。
在上述stu结构定义中,所有的成员都是基本数据类型或数组类型。
成员也可以又是一个结构,即构成了嵌套的结构。例如,下面给出了另一个数据结构。
struct date
{
int month;
int day;
int year;
};
struct{
int num;
char name[20];
char sex;
struct date birthday;
float score;
}boy1,boy2;
首先定义一个结构date,由month(月)、day(日)、year(年) 三个成员组成。 在定义并说明变量 boy1 和 boy2 时,其中的成员birthday被说明为data结构类型。成员名可与程序中其它变量同名,互不干扰。
注:在第三种中,当出现两次定义结构变量时,第一次的和第二次的是不能直接用上等号给赋值的,因为这种不带结构名的,相当于一次性的了,下次就是相当于不同的结构体了。如下:
struct {
int x;
int y;
}p1,p2={2,3};
struct {
int x;
int y;
}p3;
p1=p2;
//p3=p2;//这个是不可以如此赋值的!,因为两次就是不同的结构了。会报错
3. 结构变量成员的表示方法
往往不把它作为一个整体来使用,包括赋值、输入、输出、运算等都是通过结构变量的成员来实现的.
表示结构变量成员的一般形式是:
结构变量名.成员名
如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。
如:
boy1.birthday.month
//即第一个人出生的月份成员可单独使用,与普通变量完全相同。
4. 结构体变量的赋值
结构变量的赋值就是给各成员赋值。可用输入语句或赋值语句来完成。
main()
{
struct stu
{
int num;
char *name;
char sex;
float score;
} boy1,boy2;
boy1.num=102;
boy1.name="Zhang ping";
printf("input sex and score\n");
scanf("%c %f",&boy1.sex,&boy1.score);
boy2=boy1;
printf("Number=%d\nName=%s\n",boy2.num,boy2.name);
printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);
}
5. 结构变量的初始化
和其他类型变量一样,对结构变量可以在定义时进行初始化赋值。
main()
{
struct stu /*定义结构*/
{
int num;
char *name;
char sex;
float score;
}boy2,boy1={102,"Zhang ping",'M',78.5};
//初始化Boy1
boy2=boy1;
printf("Number=%d\nName=%s\n",boy2.num,boy2.name);
printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);
}
6. 结构数组的定义
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Li ping","M",45},
{102,"Zhang ping","M",62.5},
{103,"He fang","F",92.5},
{104,"Cheng ling","F",87},
{105,"Wang ming","M",58};
}
定义了一个结构数组boy,共有5个元素,boy[0]~boy[4]。每个数组元素都具有struct stu的结构形式。这个[5]中的5可以不给。
7. 结构指针变量的说明和使用
(1). 指向结构变量的指针
一个指针变量当用来指向一个结构变量时,称之为结构指针变量。结构指针变量中的值是所指向的结构变量的首地址。通过结构指针即可访问该结构变量,这与数组指针和函数指针的情况是相同的。
结构指针变量说明的一般形式为:
struct 结构名 *结构指针变量名
如要说明一个指向stu的指针变量pstu,可写为:
struct stu *pstu;
当然也可在定义stu结构时同时说明pstu。与前面讨论的各类指针变量相同,结构指针变量也必须要先赋值后才能使用。
赋值是把结构变量的首地址赋予该指针变量,不能把结构名赋予该指针变量。如果boy是被说明为stu类型的结构变量,则:
pstu= &boy
是正确的,而:
pstu=stu
是错误的。
结构名和结构变量是两个不同的概念,不能混淆。结构名只能表示一个结构形式,编译系统并不对它分配内存空间。只有当某变量被说明为这种类型的结构时,才对该变量分配存储空间。因此上面&stu这种写法是错误的,不可能去取一个结构名的首地址。
有了结构指针变量,就能更方便地访问结构变量的各个成员。
其访问的一般形式为:
(*结构指针变量).成员名
结构指针变量->成员名
例如:
(*pstu).num
pstu->num
注意(*pstu)
两侧的括号不可少,因为成员符“.”
的优先级高于“*”
。如去掉括号写作*pstu.num
则等效于*(pstu.num)
,这样,意义就完全不对了。
例题:
struct stu
{
int num;
char *name;
char sex;
float score;
} boy1={102,"Zhang ping",'M',78.5},*pstu;
main()
{
pstu=&boy1;
printf("Number=%d\nName=%s\n",boy1.num,boy1.name);
printf("Sex=%c\nScore=%f\n\n",boy1.sex,boy1.score);
printf("Number=%d\nName=%s\n",(*pstu).num,(*pstu).name);
printf("Sex=%c\nScore=%f\n\n",(*pstu).sex,(*pstu).score);
printf("Number=%d\nName=%s\n",pstu->num,pstu->name);
printf("Sex=%c\nScore=%f\n\n",pstu->sex,pstu->score);
}
可以看出:
结构变量.成员名
(*结构指针变量).成员名
结构指针变量->成员名
这三种用于表示结构成员的形式是完全等效的。
(2). 指向结构数组的指针
指针变量可以指向一个结构数组,这时结构指针变量的值是整个结构数组的首地址。结构指针变量也可指向结构数组的一个元素,这时结构指针变量的值是该结构数组元素的首地址。
设ps为指向结构数组的指针变量,则ps也指向该结构数组的0号元素,ps+1指向1号元素,ps+i则指向i号元素。这与普通数组的情况是一致的。
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Zhou ping",'M',45},
{102,"Zhang ping",'M',62.5},
{103,"Liou fang",'F',92.5},
{104,"Cheng ling",'F',87},
{105,"Wang ming",'M',58},
};
main()
{
struct stu *ps;
printf("No\tName\t\t\tSex\tScore\t\n");
for(ps=boy;ps<boy+5;ps++)
printf("%d\t%s\t\t%c\t%f\t\n",ps->num,ps->name,ps->sex,ps->score);
}
在程序中,定义了stu结构类型的外部数组boy并作了初始化赋值。在main函数内定义ps为指向stu类型的指针。在循环语句for的表达式1中,ps被赋予boy的首地址,然后循环5次,输出boy数组中各成员值。
注意,一个结构指针变量虽然可以用来访问结构变量或结构数组元素的成员,但是,不能使它指向一个成员。也就是说不允许取一个成员的地址来赋予它。
因此,下面的赋值是错误的。
ps=&boy[1].sex;
只能是:
ps=boy;//(赋予数组首地址)
或:
ps=&boy[0];//(赋予0号元素首地址)
注意这是对于数组的,如果是对于非数组的结构变量,要使用如下方法:
pstu= &boy
(3). 结构指针变量作函数参数
在ANSI C标准中允许用结构变量作函数参数进行整体传送。但是这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。这时由实参传向形参的只是地址,从而减少了时间和空间的开销。
struct stu
{
int num;
char *name;
char sex;
float score;}boy[5]={
{101,"Li ping",'M',45},
{102,"Zhang ping",'M',62.5},
{103,"He fang",'F',92.5},
{104,"Cheng ling",'F',87},
{105,"Wang ming",'M',58},
};
main()
{
struct stu *ps;//说明Ave的形参
void ave(struct stu *ps);
ps=boy;//使指针指向Boy地址
ave(ps);//以Ps作实参调用Ave
}
void ave(struct stu *ps)//定义函数,并用结构指针变量作函数形参。
{
int c=0,i;
float ave,s=0;
for(i=0;i<5;i++,ps++)
{
s+=ps->score;
if(ps->score<60) c+=1;
}
printf("s=%f\n",s);
ave=s/5;
printf("average=%f\ncount=%d\n",ave,c);
}
程序中定义了函数ave,其形参为结构指针变量ps。boy被定义为外部结构数组,因此在整个源程序中有效。在main函数中定义说明了结构指针变量ps,并把boy的首地址赋予它,使ps指向boy数组。然后以ps作实参调用函数ave。在函数ave中完成计算平均成绩和统计不及格人数的工作并输出结果。
本程序全部采用指针变量作运算和处理,故速度更快,程序效率更高。
8. 动态存储分配
C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间。
(1). 分配内存空间函数malloc
调用形式:
(类型说明符 *)malloc(size)
功能:在内存的动态存储区中分配一块长度为"size"字节的连续区域。函数的返回值为该区域的首地址。
“类型说明符”表示把该区域用于何种数据类型。
(类型说明符*)表示把返回值强制转换为该类型指针。
“size”是一个无符号数。
如:
pc=(char *)malloc(100);
表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc。
(2). 分配内存空间函数 calloc
调用形式:
(类型说明符 *)calloc(n,size)
功能:在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。
(类型说明符*)用于强制类型转换。
calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。
例如:
ps=(struet stu*)calloc(2,sizeof(struct stu));
其中的sizeof(struct stu)是求stu的结构长度。该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。
(3). 释放内存空间函数free
调用形式:
free(ptr);
功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域。
#include <stdio.h>
#include<stdlib.h>
main()
{
struct stu
{
int num;
char *name;
char sex;
float score;
} *ps;
ps=(struct stu *)malloc(sizeof(struct stu));
ps->num=102;
ps->name="Zhang ping";
ps->sex='M';
ps->score=62.5;
printf("Number=%d\nName=%s\n",ps->num,ps->name);
printf("Sex=%c\nScore=%f\n",ps->sex,ps->score);
free(ps);
}
定义结构stu,定义stu类型指针变量ps。然后分配一块stu大内存区,并把首地址赋予ps,使ps指向该区域。再以ps为指向结构的指针变量对各成员赋值,并用printf输出各成员值。最后用free函数释放ps指向的内存空间。整个程序包含申请内存空间、使用内存空间、释放内存空间三个步骤,实现存储空间的动态分配。
9. 链表的概念
在上面学生成绩例子中采用了动态分配的办法为一个结构分配内存空间。每一次分配一块空间可用来存放一个学生的数据,称之为一个结点。有多少个学生就应该申请分配多少块内存空间,就是要建立多少个结点。当然用结构数组也可以完成上述工作,但如果预先不能准确把握学生人数,也就无法确定数组大小。而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。
用动态存储的方法可以很好地解决这些问题。有一个学生就分配一个结点,无须预先确定学生的准确人数,某学生退学,可删去该结点,并释放该结点占用的存储空间。从而节约了宝贵的内存资源。另一方面,用数组的方法必须占用一块连续的内存区域。而使用动态分配时,每个结点之间可以是不连续的(结点内是连续的)。结点之间的联系可以用指针实现。 即在结点结构中定义一个成员项用来存放下一结点的首地址,这个用于存放地址的成员,常把它称为指针域。
可在第一个结点的指针域内存入第二个结点的首地址,在第二个结点的指针域内又存放第三个结点的首地址,如此串连下去直到最后一个结点。最后一个结点因无后续结点连接,其指针域可赋为0。这样一种连接方式,在数据结构中称为“链表”。
下图为一简单链表的示意图。
图中,第0个结点称为头结点,它存放有第一个结点的首地址,它没有数据,只是一个指针变量。以下的每个结点都分为两个域,一个是数据域,存放各种实际的数据,如学号num,姓名name,性别sex和成绩score等。另一个域为指针域,存放下一结点的首地址。链表中的每一个结点都是同一种结构类型。
一个存放学生学号和成绩的结点应为以下结构:
struct stu
{ int num;
int score;
struct stu *next;
}
前两个成员项组成数据域,后一个成员项next构成指针域,它是一个指向stu类型结构的指针变量。
链表的基本操作对链表的主要操作有以下几种:
1. 建立链表;
2. 结构的查找与输出;
3. 插入一个结点;
4. 删除一个结点;
三个学生结点:
#define NULL 0
#define TYPE struct stu
#define LEN sizeof (struct stu)
struct stu
{
int num;
int age;
struct stu *next;
};
TYPE *creat(int n)
{
struct stu *head,*pf,*pb;
int i;
for(i=0;i<n;i++)
{
pb=(TYPE*) malloc(LEN);
printf("input Number and Age\n");
scanf("%d%d",&pb->num,&pb->age);
if(i==0)
pf=head=pb;
else pf->next=pb;
pb->next=NULL;
pf=pb;
}
return(head);
}
10. 枚举类型
在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围,像极了子集只能在父集。应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
(1). 枚举的定义枚举类型定义
一般形式为:
enum 枚举名{ 枚举值表 };
枚举值表中应罗列出所有可用值。这些值也称为枚举元素
如:
枚举名为weekday,枚举值共有7个,即一周中的七天。凡被说明为weekday类型变量的取值只能是七天中的某一天。
(2). 枚举变量的说明
如同结构和联合一样,枚举变量也可用不同的方式说明,即先定义后说明,同时定义说明或直接说明。
设有变量a,b,c被说明为上述的weekday,可采用下述任一种方式:
enum weekday{ sun,mou,tue,wed,thu,fri,sat };
enum weekday a,b,c;
或者为:
enum weekday{ sun,mou,tue,wed,thu,fri,sat }a,b,c;
或者为:
enum { sun,mou,tue,wed,thu,fri,sat }a,b,c;
(3). 枚举类型变量的赋值和使用
A. 枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值。
例如对枚举weekday的元素再作以下赋值:
sun=5;
mon=2;
sun=mon;
都是错误的。
B. 枚举元素本身由系统定义了一个表示序号的数值,从0开始顺序定义为0,1,2…。
如在weekday中,sun值为0,mon值为1,…,sat值为6
例:
main(){
enum weekday
{ sun,mon,tue,wed,thu,fri,sat } a,b,c;
a=sun;
b=mon;
c=tue;
printf("%d,%d,%d",a,b,c);
}
说明:
只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。如:
a=sum;
b=mon;
是正确的。而:
a=0;
b=1;
是错误的。如一定要把数值赋予枚举变量,则必须用强制类型转换。
如:
a=(enum weekday)2;
其意义是将顺序号为2的枚举元素赋予枚举变量a,相当于:
a=tue;
应该说明的是枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。
#include <stdio.h>
#include<stdlib.h>
void main(){
enum body
{ a,b,c,d } month[31],j;
int i;
j=a;
for(i=1;i<=30;i++)
{
month[i]=j;
j=(enum body)(i);
if (i%4==0) j=a;
else if (i%4==1) j=b;
else if (i%4==2) j=c;
else if (i%4==3) j=d;
}
for(i=1;i<=30;i++)
{
switch(month[i])
{
case a:printf(" %2d %c\t",i,'a'); break;
case b:printf(" %2d %c\t",i,'b'); break;
case c:printf(" %2d %c\t",i,'c'); break;
case d:printf(" %2d %c\t",i,'d'); break;
default:break;
}
}
printf("\n");
}
11. 类型定义符typedef
C语言还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。
typedef定义的一般形式为:
typedef 原类型名 新类型名
========
则
typedef int INTEGER
INTEGER a,b;
等效于 int a,b;
用typedef定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读性。
typedef char NAME[20];
表示NAME是字符数组类型,数组长度为20。然后可用NAME 说明变量
如
NAME a1,a2,s1,s2
;完全等效于 char a1[20],a2[20],s1[20],s2[20]
typedef struct stu
{ char name[20];
int age;
char sex;
} STU;
定义STU表示stu的结构类型,然后可用STU来说明结构变量:
STU body1,body2;
以上学习内容来来自谭浩强C语言《结构体与共用体 》章节
练习:
- 定义以下结构体类型
struct s
{
int a;
char b;
float f;
};
printf("%d",sizeof(struct s));
输出结果为________。
A:3 B.4 C.7 D:6
因为在C中,Int为2个字节,long int 为4 个字节(在C++中为4 ,C++会直接让其为long int),char为1个字节,float为4个字节。故在这个结构体中为2+1+4=7个字节。
2. 定义以下结构体类型
struct s
{
int x;
float f;
}a[3];
printf("%d",sizeof(a));
输出结果为________。
A:4 B:12 C:18 D:6
如上题:3*(2+4)=18字节。
3. 定义以下结构体类型
struct student
{
char name[10];
int score[50];
float average;
}stud1;
则 stud1占用内存的字节数是_______ 。
A)64 B)114 C)228 D)7
Stud1即是student的内存大小,10* 1+50* 2+4=114个字节。
4. 设有一结构体类型变量定义如下:
struct date
{
int year;
int month;
int day;
};
struct worklist
{
char name[20];
char sex;
struct date birthday;
}person;
若对结构体变量 person 的出生年份进行赋值时,下面正确的赋值语句是_________。
A.year=1976
B.birthday.year=1976
C.person.birthday.year=1976
D.person.year=1976
5. 定义一个结构,用来保存学员的信息,包括:学号、姓名、C课程成绩、Jave课程成绩。编写一个程序,用来接收五位学员的信息,计算每位学员的平均成绩并显示详细信息,计算平均成绩的功能通过函数来实现。
#include <stdio.h>
#include <stdlib.h>
struct student_members
{
int num;
char name[30];
int C_promgram;
int java;
}stud[5]={
{1,"LIANG",77,90},
{2,"HUANG",58,88},
{3,"CHENG",45,85},
{4,"QI",98,39},
{5,"CHAI",88,85}
};
int main()
{
int i=0;
for (i=0;i<=4;i++)
{
printf("学号:%d\t,姓名:%s\t,C_program成绩:%d\t,Java成绩:%d\t\n",stud[i].num,stud[i].name,stud[i].C_promgram,stud[i].java);
}
struct student_members *ps;
float ave(struct student_members *ps);
ps=stud;//或ps= &stud[0];
ave(ps);
return 0;
}
float ave(struct student_members *ps)
{
int i;
float s_c=0,s_j=0;
for (i=0;i<=4;i++,ps++)
{
s_c+=ps->C_promgram;
s_j+=ps->java;
}
s_c=s_c/5;
s_j=s_j/5;
printf("C_program_average_score=%f\t",s_c);
printf("java_average_score=%f\t\n",s_j);
return 0;
}
运行结果如下:
6 . 定义一个三角形结构,包括三个成员,分别为三角形的三条边。编写一个函数,用来判断三角形的类型是等边三角形、等腰三角形还是不等边三角形。在主函数中输入三角形的信息,并用判断函数确定三角形的类型,并输出相应的信息。
#include <stdio.h>
#include <stdlib.h>
struct sanjiaoxing
{
float a;
float b;
float c;
}sjx;
int main()
{
int num;
printf("请输入三角形三边,以空格分开:\n");
scanf("%f %f %f",&sjx.a,&sjx.b,&sjx.c);
printf("%f %f %f\n",sjx.a,sjx.b,sjx.c);
int run(struct sanjiaoxing *pstr);
struct sanjiaoxing *pstr;
pstr = &sjx;
num=run(pstr);
printf("%d\n",num);
if (num ==1 )
printf("这个三角形是等边三角形\n");
if (num ==2 )
printf("这个三角形是等腰三角形\n");
if (num ==3 )
printf("这个三角形是不等边三角形\n");
return 0;
}
int run(struct sanjiaoxing *pstr)
{
if ((*pstr).a==pstr->b && pstr->a==pstr->c && pstr->b==pstr->c )
return 1;
else if ( pstr->a==pstr->b || pstr->b==(*pstr).c)
return 2;
return 3;
}
运行结果如下: