9.1 定义和使用结构体变量
9.1.1 自己建立结构体类型
C语言允许用户自己建立由不同类型数据组成的复合型数据结构,它称为结构体。
struct Student
{
int num;
char name[20];
char sex;
int age;
float age;
float score;
char addr[30];
};
/*
struct是声明结构体类型的关键字,不能省略。
*/
声明一个结构体类型的一般形式为:
struct 结构体名
{成员列表};
注意:
1.结构体类型的名字是由一个关键字struct和结构体名组合成的(例如:struct Student)
2.结构体名,如:Student(结构体名又叫结构体标记)
3.花括号内的子项,称为结构体成员,对成员都应进行类型声明
4.结构体类型可以有多种,成员可以属于另一个结构体类型。
struct Date
{
int month;
int year;
int day;
};
struct Student
{
int num;
char name[20];
char sex;
int age;
float age;
float score;
char addr[30];
struct Date birthday; //成员birthday属于struct Date类型
}
模型图
9.1.2 定义结构体类型变量
结构体被定义声明后,没有使用,系统对之不分配存储单元。
定义结构类型变量:
先声明结构体类型,再定义该类型的变量
如:struct Student(结构体类型名) student1,student2(结构体变量名);
定义结构体变量后,系统会为之分配内存单元。
在声明类型的同时定义变量
struct Student { int num; char name[20]; char sex; int age; float age; float score; char addr[30]; } student1,student2; /* 在写大程序时,往往要求对类型的声明和对变量的定义分别放在不同的地方,以便程序结构清晰,便于维护,一般不采用这种方式。 */
不指定类型名而直接定义结构体类型变量
一般形式:
struct { 成员列表 } 变量名列表; /* 说明: 1.结构体类型和结构体变量是不同的东西,只能对变量赋值,在编译时,只对变量分配空间。 2.结构体类型成员名可以与程序中变量名相同 3.结构体变量中的成员,可以单独使用,作用相当于普通变量 */
9.1.3 结构体变量的初始化和引用
在定义结构体变量时,可以对它初始化,即赋予初始值,然后引用这个变量。
# include "stdio.h"
int main ()
{
struct Student //定义声明结构体Student
{
int num;
char name[20];
char sex;
char addr[30];
};
struct Student stu={101,"ahuang",'W',"123 Beijing Road"};//进行初始化赋值
printf("No.:%d\nname:%s\nsex:%c\naddr:%s\n",stu.num,stu.name,stu.sex,stu.addr);
return 0;
}
/*
1. 进行初始化时,初始化列表使用花括号括起来的一些常量。
C99标准允许对某一成员初始化。如:
struct Studnet stu={.name="xiehuangbao"};
其他数值型被初始化为0,字符型被初始化为'\0',指针变量被初始化为NULL
2. 可以引用结构体变量中的值,引用方式:结构体变量名.成员名
如:stu.num=10001;//在程序中对变量的成员赋值。
. 是成员运算符,它在所有运算符中优先级最高,因此可以把stu.num作为一个整体看待,相当于一个变量
注意:不能通过输出结构体变量名输出结构体变量所有成员的值
3. 如果成员本身又是一个结构体类型,则要用若干个成员运算符,一级一级找到最低的一级的成员。只能对最低级的成员进行赋值或存取以及运算。
stu.num //访问stu成员num
stu.birthday.month //访问stu成员中的成员month
4. 对结构体变量的成员可以向普通变量一样进行各种运算(根据其类型决定可以进行的运算)。
5. 同类的结构体变量可以进行相互赋值,如:
stu1=stu2
6. 可以引用结构体变量成员的地址,也可以引用结构体变量的地址。例如:
scanf("%d",&stu.num); //输入&stu.num的值
printf("%o",&stu); //输出结构体变量&stu的首地址
scanf("%d,%s,%c,%d,%f,%s\n",&stu);//不能用这样的语句读入结构体变量
结构体变量的地址主要用作函数参数,传递结构体变量地址。
*/
# include "stdio.h"
/*
定义和使用结构体
*/
int main()
{
struct Student{ //定义和声明结构体Student
int num;//学号
char name[20];
float score;
};
struct Student stu1,stu2;
scanf("%d%s%f",&stu1.num,stu1.name,&stu1.score);
scanf("%d%s%f",&stu2.num,stu2.name,&stu2.score);
if(stu1.score>stu2.score) printf("%d\t%s\t%f\n",stu1.num,stu1.name,stu1.score);
if(stu1.score<stu2.score) printf("%d\t%s\t%f\n",stu2.num,stu2.name,stu2.score);
if(stu1.score=stu2.score)
{
printf("%d\t%s\t%f\n",stu1.num,stu1.name,stu1.score);
printf("%d\t%s\t%f\n",stu2.num,stu2.name,stu2.score);
}
return 0;
}
9.2 使用结构体数组
9.2.1 定义结构体数组
# include "stdio.h"
# include "string.h"
/*
定义和引用结构体数组
*/
int main()
{
struct Person //声明结构体 struct Person
{
char name[20];
int count;
};
struct Person leader[3]={ //定义结构体数组并初始化
"Li",0,"Zhang",0,"Sun",0
};
int i,j;
char lead_name[20];
for(i=1;i<=10;i++)
{
scanf("%s",lead_name);
for(j=0;j<3;j++)
if(!strcmp(lead_name,leader[j].name))leader[j].count++;
}
for(i=0;i<3;i++)
printf("%s\t%d\n",leader[i].name,leader[i].count);
return 0;
}
说明:
1.定义结构体数组一般形式是
- struct 结构体名{成员列表} 数组名[数组长度];
- 先声明一个结构体类型,再用此类型定义结构体数组;
2.对结构体数组初始化的形式是在定义数组的后面加上:={初值列表};
如:
struct Person leader[3]={ //定义结构体数组并初始化
“Li”,0,”Zhang”,0,”Sun”,0
};
9.2.2 结构体数组的应用举例
# include "stdio.h"
# include "string.h"
/*
定义和引用结构体数组
*/
int main()
{
struct Student //声明结构体 struct Person
{
int num;
char name[20];
float score;
};
struct Student stu[5]={ //定义结构体数组并初始化
10101,"Zhang",78,
10103,"Wang",98.5,
10106,"Li",86,
10108,"Ling",73.5,
10110,"Sun",100
};
struct Student temp;//临时交换存储变量
int i,j;
for(i=0;i<=5-1;i++)
for(j=i;j<5;j++)
{
if(stu[i].score>stu[j].score)
{
temp=stu[i];
stu[i]=stu[j];
stu[j]=temp;
}
}
for(i=0;i<5;i++)
printf("%d\t%d\t%f\n",stu[i].num,stu[i].name,stu[i].score);
return 0;
}
说明:
- 在定义结构体数组进行初始化时,将每个学生的信息用一对花括号包起来,这样做,阅读和检查比较方便。(提供程序的可读性)
- 注意临时变量temp定义为struct Student类型,只有同类型的结构体变量才能互相赋值,不必人为指定一个一个成员互换,这体现了结构体类型的好处。
9.3 结构体指针
9.3.1 指向结构体变量的指针
指针变量的基类型必须与结构体变量的类型相同。
例如:struct Student *p;
# include "stdio.h"
# include "string.h"
/*
了解什么是指向结构体变量的指针变量以及
怎么使用该类型的指针变量
*/
int main()
{
struct Student
{
int num;
char name[20];
char sex;
int age;
};
struct Student stu,* p;
p=&stu;
stu.num=10101;
strcpy(stu.name,"LiLin");
stu.sex='M';
stu.age=25;
printf("No.:%d\nname:%s\nsex:%c\nage:%d\n",stu.num,stu.name,stu.sex,stu.age);
printf("No.:%d\nname:%s\nsex:%c\nage:%d\n",(*p).num,(*p).name,(*p).sex,(*p).age);
return 0;
}
/*
如果一个p指向一个结构体变量stu,以下3种方法等价:
1.stu.成员名(如:stu.num);
2.(*p).成员名((*p).num);
3.p->成员名(如p->num). ->称为指向运算符
*/
9.3.2 指向结构体数组的指针
# include <stdio.h>
# include <string.h>
/*
初步了解指向结构体数组的指针
*/
int main()
{
struct Student{
int num;
char name[20];
char sex;
int age;
};
struct Student stu[3]={
{10101,"LiLin",'M',18}
,{10102,"ZhangFang",'M',19}
,{10103,"WangMin",'F',20}
}; //定义结构体数组并初始化
struct Student *p;
for(p=stu;p<stu+3;p++) //使指向stu数组的第一个元素
printf("%d\t%s\t%c\t%d\n",p->num,p->name,p->sex,p->age);
return 0;
}
/*
注意:
1.程序定义了p是指向struct Student类型的指针变量,它用来指向一个struct Student类型的对象,但是不能指向stu数组元素中的某一成员。
2.如果要将某一成员地址赋值给p,可以使用强制类型转换。如:p=(struct Student *)stu[0].name;
*/
9.3.3 用结构体变量和结构体变量的指针作函数参数
将一个结构体变量的值传递给另一个函数,有3种方法:
- 用结构体变量的成员作参数
- 用结构体变量作实参。结构体作实参时,采取的也是值“传递方式”,形参也必须是同类型的结构提提变量。在函数调用期间形参也要占用内存单元。这种传递方式在空间和时间上开销较大,且因采用值传递的方式,如果在执行期间,形参改变的haul,该值是无法返回给主调函数的,这往往造成使用上的不便。
- 用指向结构体变量的指针作实参。传递地址(推荐:减少内存开销,避免用结构体作实参的缺陷)。
# include "stdio.h"
# include "string.h"
# include "stdlib.h"
/*
了解和使用结构体变量和结构体指针作函数参数
有结构体变量,成员是学号,姓名,3门课成绩。输出平均成绩最高的学生信息
*/
# define N 3 //定义有3个学生
struct Student{
int num;
char name[20];
float score1;
float score2;
float score3;
float aver;
};
int main ()
{
struct Student stu[N],*p,student;
int i=0;
void input_information(struct Student *p);
struct Student * max_aver(struct Student stu[]);
p=stu;//指向首个元素
input_information(p); //输入学生数据
for(;i<N;i++) //输出输入的学生信息
printf("%d\t%s\t%f\t%f\t%f\t%f\t\n",stu[i].num,stu[i].name,stu[i].score1,stu[i].score2,stu[i].score3,stu[i].aver);
p=max_aver(p);
printf("%d\t%s\t%f\t%f\t%f\t%f\t\n",p->num,p->name,p->score1,p->score2,p->score3,p->aver);//输出最高平均学生信息
return 0;
}
void input_information(struct Student *p)
{
int i=0;
for(;i<N;p++,i++)
{
printf("请输入学生信息:\n");
scanf("%d%s%f%f%f",&(p->num),&(p->name),&(p->score1),&(p->score2),&(p->score3));
p->aver=(p->score1+p->score2+p->score3)/3;
}
}
struct Student * max_aver(struct Student stu[])
{
int i=0;
struct Student * student;
for(;i<N-1;i++)
if(stu[i].aver>stu[i+1].aver)
student=&stu[i];
else
student=&stu[i+1];
return student;
}
9.4 用指针处理链表
9.4.1 什么是链表
链表是一种常见的重要的数据结构。它是动态的进行存储分配的一种结构。
链表根据需要开辟内存单元。
链表有一个“头指针”变量,它存放一个地址,该地值指向一个元素。链表中每个元素称为“结点”
每个结点应该包括两个部分:
- 用户需要用的实际数据;
- 下一个结点的地址。最后一个元素的地址部分存放NULL表示链表到此结束
链表元素在内存中的地址可以是不连续的。
找一个元素,必须知道上一个元素存放该元素的地址。没有头指针整个链表无法访问。
链表必须利用指针变量才能实现。一个姐弟啊应包含一个指针变量,用它存放下一个结点的地址。
/*
可以设计这样的结构体类型
*/
struct Student
{
int num;
float score;
struct Student * next; //next是指针变量,指向结构体变量(即下一个元素地址)
};
/*
num和score 是存放的实际数据
next 是下一个元素地址
一个指针类型的成员既可以指向其他类型的结构体数据,也可以指向自身所在的结构体类型的数据。
*/
9.4.2 建立简单的静态链表
# include "stdio.h"
/*
建立简单的静态链表
*/
int main ()
{
struct Student //定义声明一个结构体
{
int num;
float score;
struct Student * next;
};
//定义结构体变量以及指针变量
struct Student stu1,stu2,stu3,*p1,*p2,*head;
p1=&stu2;
p2=&stu3;
//进行赋值操作
stu1.num=10101;
stu1.score=56.5;
stu1.next=p1;
stu2.num=10310;
stu2.score=66.5;
stu2.next=p2;
stu3.num=10102;
stu3.score=55.0;
stu3.next=NULL;
//没有头指针,链表无法访问
head=&stu1;
do
{
printf("%d\t%f\n",head->num,head->score);
head=head->next;
}while(head!=NULL);
return 0;
}
/*
head头指针指向stu1,stu.next指向了stu2,stu2.next指向stu3,stu3.next=NULL表示这个末尾元素,每个元素中都有实际数据,且每个元素中都有下一个元素地址,这就形成了简单的链表关系
静态链表:结点是在程序中定义的,不是临时开辟的,也不能用完后释放。
*/
9.4.3 建立动态链表
动态链表是指在程序执行过程中建立一个链表
# include "stdio.h"
# include "stdlib.h"
/*
建立动态链表
*/
struct Student //定义声明一个结构体
{
int num;
float score;
struct Student * next;
};
int main ()
{
struct Student * pt;
struct Student * creat();
pt=creat();
printf("%d\t%f\n",pt->num,pt->score);
return 0;
}
struct Student * creat()
{
struct Student *head,*p1,*p2;
head=NULL;//先使头指针指向空 ,表示现在的链表是个空链表
int n=0;//表示结点个数
p1=p2=(struct Student *)malloc(sizeof(struct Student));//开辟第一个结点,将地址赋值给p1,p2
printf("请分别输入学号和成绩:\n");
scanf("%d%f",&p1->num,&p1->score);//输入实际数据
while(p1->num!=0)
{
n+=1;
if(n==1)
head=p1;//表示创建一个新的链表。 当n==1时
else{
p2->next=p1;//存放下个结点的地址
}
p2=p1;//指向同一个结点
p1=(struct Student *)malloc(sizeof(struct Student));//开辟新的结点
printf("请分别输入学号和成绩:\n");
scanf("%d%f",&p1->num,&p1->score);//输入实际数据
}
p2->next=NULL;//没有创建结点,表示表尾
return head;
}
9.4.4 输出链表
将链表中各结点的数据依次输出。
# include "stdio.h"
# include "stdlib.h"
/*
建立动态链表
*/
struct Student //定义声明一个结构体
{
int num;
float score;
struct Student * next;
};
int main ()
{
struct Student * pt;
struct Student * creat();
void printf_linked_list(struct Student * head);
pt=creat();
printf_linked_list(pt);
return 0;
}
struct Student * creat()
{
struct Student *head,*p1,*p2;
head=NULL;//先使头指针指向空 ,表示现在的链表是个空链表
int n=0;
p1=p2=(struct Student *)malloc(sizeof(struct Student));//开辟第一个结点,将地址赋值给p1,p2
printf("请分别输入学号和成绩:\n");
scanf("%d%f",&p1->num,&p1->score);//输入实际数据
while(p1->num!=0)
{
n+=1;
if(n==1)
head=p1;//表示创建一个新的链表。 当n==1时
else{
p2->next=p1;//存放下个结点的地址
}
p2=p1;//指向同一个结点
p1=(struct Student *)malloc(sizeof(struct Student));//开辟新的结点
printf("请分别输入学号和成绩:\n");
scanf("%d%f",&p1->num,&p1->score);//输入实际数据
}
p2->next=NULL;//没有创建结点,表示表尾
return head;
}
void printf_linked_list(struct Student * head)
{
struct Student * pt;
pt=head;
if(head!=NULL) //判断头指针是否为空,为空的话,链表无法访问
while(pt!=NULL)
{
printf("%d\t%f\n",pt->num,pt->score);
pt=pt->next;
}
}
9.5 共用体类型
9.5.1 什么是共用体类型
在同一个地址上,使用覆盖技术,后一个数据覆盖了前面的数据。这种使几个不同的变量共享同一段内存结构,称为“共用体”类型的结构。
共用体类型一般定义形式:
union 共用体名
{成员列表} 变量表列;
定义声明和定义变量与结构体相同。但是含义不一样:
共用体变量所占的内存长度等于最长成员的长度。
9.5.2 引用共用体变量的方式
必须先定义共用体变量,后引用。注意引用的是共用体的成员,不是共用体变量。
例如:
union Data //声明定义一个共用体
{
int I;
char ch;
float f;
};
union Data a,b,c; //用共用体类型定义变量
a.I //引用共用体成员,是正确的
a.ch
a.f
printf("%d",a); //这样是错误的,不能引用共用体变量。
printf("%d",a.I); //可以写成这样
/*
union //这样也可以声明定义
{
int i;
char ch;
float f;
}a,b,c;
*/
9.5.3 共用体类型数据的特点
使用共用体类型数据是要注意以下一些特点:
同一个内存段可以用来存放几种不同类型的成员。在每一瞬时,只能存放一个成员,也就是说,共同体变量只能存放一个值。
union Date { int i; char ch; float f; }; union Date a; a.i=97; //表示将整数97存放在共用体变量中,可以用以下输出语句: printf("%d",a.i); //输出整数97 printf("%c",a.ch); //输出字符a printf("%f",a.f); //输出实数0.000000
可以对共用体变量初始化,但初始化表中有一个常量。
union Data { int i; char ch; float f; }a={1,'a',1.5}; //不能初始化3个成员,它们占用同一段存储单元 union Data a={16}; //正确,对第一个成员初始化 union Data a={.ch='j'};//C99允许对指定的一个成员初始化
共用体变量中起作用的成员是最后一次被赋值的成员,在共用体变量中的成员赋值后,原来变量存储单元中的值就被取代。
共用体变量的底子和它的各成员的地址都是相同的,因为共用同一块内存段。
不能对共用体变量名赋值,不能企图引用变量名来得到一个值。
C99允许同类型的共用体变量互相赋值。
a=b; //a和b同类型的共用体变量,合法
C99之前规定共用体变量不能作为函数参数,但可以使用指向共用体变量的指针作函数参数。C99允许共用体变量作函数参数。
共用体可以出现在结构体类型定义中,数组可以作为共用体成员。
什么时候会用到共用体呢?
处理数据是,有时需要对同一段空间安排不同的用途。
例如:
# include "stdio.h"
/*
共用体的简单使用场景
*/
struct {
int num;
char name[10];
char sex;
char job;
union
{
int clazz;
char position[10];
} category;
}person[2];
int main()
{
int i=0;
for(;i<2;i++)
{
printf("请输入当前人的信息:\n");
scanf("%d %s %c %c",&person[i].num,&person[i].name,&person[i].sex,&person[i].job);
if(person[i].job=='s')//判断是学生还是老师
scanf("%d",&person[i].category.clazz);//是学生输入相应的班级
else if(person[i].job=='t')
scanf("%s",person[i].category.position);//是老师的话,输入相应的职位
else
printf("输入信息有误\n");
}
for(i=0;i<2;i++)
{
if(person[i].job=='s')
printf("%d\t%s\t%c\t%c\t%d\n",person[i].num,person[i].name,person[i].sex,person[i].job,person[i].category.clazz);
if(person[i].job=='t')
printf("%d\t%s\t%c\t%c\t%s\n",person[i].num,person[i].name,person[i].sex,person[i].job,person[i].category.position);
}
return 0;
}
9.6 使用枚举类型
一个变量只要几种可能的值,则可以定义为枚举类型。
枚举:把可能的值一一列举出来,变量值只限于列举出来的值的范围内。
例如:
enum Weekday {sun,mon,tue,wed,thu,fri,sat};
Weekday 是枚举变量
{枚举元素或是枚举常量}
声明枚举类型的一般形式为:
enum [枚举名]{枚举元素列表};
说明:
1.C编译对枚举类型的枚举元素按常量处理,故称为枚举常量
2.每个枚举元素都代表一个整数,C语言编译按定义时顺序默认它们的值为0,1,2,3…
例如: Weekday=mon; 相当于 Weekday=1;
3.枚举元素可以用来作判断比较。
下面我们用一个例子来介绍枚举类型的使用。
# include "stdio.h"
/*
简单了解枚举类型的使用
*/
int main()
{
enum Color{red,yellow,blue,white,black};//定义枚举类型 枚举元素是常量 每个元素代表一个整数
enum Color i,j,k,pri;//定义枚举变量
int n=0,loop;//loop 是循环的层数
for ( i = red; i <=black; i++)//第一次摸球
{
/* code */
for ( j = red; i <=black; i++)
{
/* code */
if (i!=j)
{
/* code */
for(k=red;k<=black;k++)
{
if (k!=i&&k!=j)
{
/* code */
n+=1;//开始统计有几中摸球方法数量
// printf("一共有%d摸球方法",n);
for ( loop = 1; loop <=3; loop++)
{
/* code */
switch (loop)
{
case 1/* constant-expression */:
pri=i;
/* code */
break;
case 2/* constant-expression */:
pri=j;
/* code */
break;
case 3/* constant-expression */:
pri=k;
/* code */
break;
default:
break;
}
switch (pri)
{
case red/* constant-expression */:
printf("%-10s","red");/* code */
break;
case yellow/* constant-expression */:
printf("%-10s","yellow");/* code */
break;
case blue/* constant-expression */:
printf("%-10s","blue");/* code */
break;
case white/* constant-expression */:
printf("%-10s","white");/* code */
break;
case black/* constant-expression */:
printf("%-10s","black");/* code */
break;
default:
break;
}
}
printf("\n");
}
}
}
}
}
printf("\ntotal:%5d\n",n);
return 0;
}
/*
使用枚举常量red 和用0代表红,有什么区别,其实没啥问题。
使用枚举常量比使用常数的优点是更加直观,当出错时,更易于检查错误信息。
*/
9.7 用typedef声明新类型名
用typedef给新的类型名来替代已有的类型名。(给已有类型取别名)
有两种情况:
简单地用一个新类型名代替已有的类型名。
如:typedef int Integer; //指定用Integer为类型名,作用与int相同
命名一个简单的类型名代替复杂的类型表示方法。
命名一个新的类型名代表结构体类型
typedef struct { int day; int month; int year; }Date; //定义新类型名Date,代表上面的一个结构体类型。 Date birthday; //定义结构体类型变量,因为用typedef Date就是结构体的别名。
命名一个新类型名代表数组类型
typedef int Num[100]; //声明Num为整形数组类型名
Num a; //定义a为整形数组名,它有100个元素
命名一个类型代表指针类型
typedef char * String; //声明String为字符指针类型
String p,s[10]; //定义p为字符指针变量,s为字符指针数组
命名一个新类型名代表只想函数的指针。
归纳总结:声明新类型名的方法
按照定义变量方法写出定义体
将变量名换成新类型名
在最前面加关键字
- 用新类型名区定义变量
注意:习惯上typedef声明的类型名第一个字母大写
以上方法实际上是为为特定的类型指定了一个同义字。
使用typeder只是给已有类型取了一个别名,并没有创造新类型
typedef声明数组类型、指针类型、结构体类型、共用体类型、枚举类型,使得编程更加方便。
typedef与#define表面上有相似之处。
如: typedef int Count;
#define Count int;
作用都是用Count代表int。
1.#define是在预编译时完成的,只能做简单的字符串替换。
2.typedef是在编译阶段处理的。并不是简单的字符串替换。例如:typedef int Num[10];Num a;并不是用Num[10]去替代int ,而是采用定义变量的方法那样先生成一类型名,然后用它去定义变量。
当不同源文件用到同一类型数据时,常用typedef声明一些数据类型。
使用typedef名称有利于程序的通用和移植。可以看书上的数据类型长度在不同编译器上不同的案例 p329。
- 本文链接:https://www.nscblog.top/posts/7cad0bc5/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues