c的详细学习(10)结构体与共用体的学习(二)

时间:2024-01-18 21:45:32

在c语言中,结构体数据类型与共用体数据类型都属于构造类型。共用体与结构体数据类型在定义上十分相似,但它们在存储空间的占用分配上有本质的区别。结构体变量是各种类型数据的集合,各成员占据不同的存储空间,而共用体变量的所有成员占用相同的存储空间,在某一时刻只有一个成员起作用。

(1)共用体类型的定义

定义共用体类型的一般形式:

union 共用体类型名

{

数据类型  成员名1;

数据类型  成员名2;

数据类型  成员名3;

......

};

以上定义了一个名为data的共用体类型。它说明该类型由三个不同类型的成员组成,这些成员共享同一块存储空间。

(2)共用体变量的定义

与结构体变量的定义类似;

(3)共用体变量的引用和初始化

1.引用共用体变量中的一个成员

引用共用体变量的成员的一般形式

共用体变量名.成员名

共用体指针变量->成员名

第一种引用方式应用于普通共用体变量,第二种引用方式应用于共用体指针变量。

union data a,*p=&a;

2.共用体类型变量的整体引用

可以将一个共用体变量作为一个整体赋给另一个同类型的共用体变量。例如:

union data a,b;

......

a=b;

3.共用体变量的初始化

在共用体变量定义的同时只能用第一个成员的类型值进行初始化,共用体变量初始化的一般形式:

union 共用体类型名 共用体变量={第一个成员的类型名};

例如:

union data a={8};

请注意:

1)在对共用体变量进行初始化时,尽管只能给第一个成员变量赋值,但必须用花括号括起来。

2)不能对共用体变量名赋值,不能通过引用变量名得到其成员的值。

3)不可以在定义共用体变量时对它初始化。(意思可能是在定义共用体类型的同时定义结构体变量时,不能初始化,以后细细探索。)

说明

*共用体变量的地址及其各成员的地址都是同一地址,因为各成员地址的分配都是从共用体变量空间的起点开始的。

*不能使用共用体变量作为函数参数,也不能使用函数返回共用体变量,但可以使用它指向共用体变量的指针(与结构体变量用法相似);

*共用体变量可以出现在结构体类型的定义中,也可以定义结构体数组。

例子:输入学生的不同类型的成绩(百分制,等级制),运行程序后屏幕输出学生的成绩表。以flag为标志不同类型的成绩,flag=0作为百分制成绩的标志,非0是等级制成绩的标志。

 #include<stdio.h>

 struct c
{
char flag;
int num;
union stu
{
int score;
char s;
}cg;
}pe[]; int main(){
int i,m;
for(i=;i<;i++){
printf("在输入成绩之前先输入0或1代表是百分制还是等级制!\n");
scanf("%d",&pe[i].flag);
if(pe[i].flag==){
printf("请连续输入学号和百分制成绩,以逗号间隔!\n");
scanf("%d,%d",&pe[i].num,&pe[i].cg.score);
}else{
printf("请连续输入学号和等级制成绩,以逗号间隔!\n");
scanf("%d,%c",&pe[i].num,&pe[i].cg.s);
}
}
for(i=;i<;i++){
if(pe[i].flag==){
printf("%4d%4d\n",pe[i].num,pe[i].cg.score);
}else
printf("%4d %c\n",pe[i].num,pe[i].cg.s);
}
return ;
}

c的详细学习(10)结构体与共用体的学习(二)

在这个例子中需要注意的有两点:

1.在同时用是scanf输入两个数据时最好自定义它们的分隔方法,例如逗号。

2.在共用体类型的定义中我原先用的是 char s[10],而不是char s;但虽然在是scanf语句中用%c来接收s但并不报错,只是输出结果会出问题。这里要注意%S和%C的正确使用。

(4)枚举类型

当一个变量的取值只限定为几种可能时,就可以使用枚举类型。枚举类型是将可能的取值一一列出来,那么变量的取值范围也就在列举值的范围之内。

1.枚举类型的说明:

枚举类型说明的一般形式:

enum 枚举类型名{枚举值1,枚举值2......};

例如:

enum flag{true,false};//只允许两个值

enum weekday{sun,mon,tue,wed,thu,fri,sat};//只允许7个值

枚举类型的说明只是规定了枚举的类型和该类型只允许取哪几个值,它并不分配内存。

2.枚举类型变量的定义

1)进行枚举类型说明的同时定义枚举类型变量。

enum flag{true,false}a,b;

2)用无名枚举类型

enum {true,false} a,b;

3)枚举类型说明和枚举型变量定义分开。

enum flag{true,flase};

enum flag a,b;

以上三种形式的定义其作用是相同的,他们所定义的枚举变量a和b,都只能取true或者false两个值。在编译时,为变量的定义分配内存,一个枚举类型变量所占的空间与int型相同。

说明:

*枚举类型本身就是常量,不允许对其进行赋值操作;

例如:true=1;false=0;都是错误的。

*在c语言中枚举值被处理成一个整型的常量,此常量的值取决于说明各枚举值排列的先后次序,第一个枚举值序号为0,因此它的值为0,以后依次加一。

例如:enum weekday{sun,mon,tue,wed,thu,fri,sat}workday;其中sun的值为0,sat的值为6。

要想使sun的值为7,mon的值为1,则可以这样指定{sun=7,mon=1,tue,wed,thu,fri,sat};对于没有指定值的元素,其取值规则仍按所处的顺序取。

*枚举值之间可以相互比较

tue>wed。这实际上是在进行2>3的比较

*整数不能直接赋值给枚举变量

例如:workday=3;

这是错误的。因为workday是枚举类型的变量,而3是整型变量,不同的数据类型在赋值时,必须将赋值号右边强制转换为左边变量的类型后再赋值。

例如:workday=(enum weekday)(5-3);

示例:盒子里有若干个五种颜色(红,黄,蓝,白,黑)的彩球。每次从盒子里取出3个球,问得到3种不同颜色的球的可能取法,并输出每种组合的3种颜色。

 #include<stdio.h>

 int main(){
enum color{red=,yellow=,blue=,white=,black=};
//i,j,k用来标识抽出的每种球的颜色,t用来遍历他们三个!
enum color i,j,k,t;
//n用来记录总的可能,lp是用来在每种可能中的循环变量
int n=,lp;
for(i=red;i<=black;i++)
{
for(j=red;j<=black;j++){
if(i!=j){
for(k=red;k<=black;k++){
if((k!=i)&&(k!=j))
{
printf("%-4d",n=n+);
for(lp=;lp<=;lp++){
switch(lp)
{
case :t=i;break;
case :t=j;break;
case :t=k;break;
default:break;
}
switch(t){
case red: printf("%10s","red");break;
case yellow: printf("%10s","yellow");break;
case blue: printf("%10s","blue");break;
case white: printf("%10s","white");break;
case black: printf("%10s","black");break;
}
}
printf("\n");
}
}
printf("total: %5d\n",n);
}
}
}
return ;
}

上述代码产生了这个错误:

c的详细学习(10)结构体与共用体的学习(二)

原因是在c语言中无法进行枚举类型的自增自减运算,因为在i++时,i自动被转化为整型,需要进行强制类型转换才能进行重新赋值给i。

解决方法有:
   1:将代码 enum color i,j,k,t;改为int i,j,l,t;

2或者将i++改为i=(enum color)(i+1);

 #include<stdio.h>

 int main(){
enum color{red,yellow,blue,white,black};
//i,j,k用来标识抽出的每种球的颜色,t用来遍历他们三个!
enum color i,j,k,t;//(1)或者直接改为int i,j,k,t;
//n用来记录总的可能,lp是用来在每种可能中的循环变量
int n=,lp;
//(2)进行强制类型转换
for(i=red;i<=black;i=(enum color)(i+))
{
for(j=red;j<=black;j=(enum color)(j+)){
if(i!=j){
for(k=red;k<=black;k=(enum color)(k+)){
if((k!=i)&&(k!=j))
{
printf("%-4d",n++);
for(lp=;lp<=;lp++){
switch(lp)
{
case :t=i;break;
case :t=j;break;
case :t=k;break;
default:break;
}
switch(t){
case red: printf("%10s","red");break;
case yellow: printf("%10s","yellow");break;
case blue: printf("%10s","blue");break;
case white: printf("%10s","white");break;
case black: printf("%10s","black");break;
}
}
printf("\n");
}
}
}
}
}
printf("total: %5d\n",n);
return ;
}

c的详细学习(10)结构体与共用体的学习(二)

总而言之,课本上的代码也不是全对,总是遇到各种问题,不过总算是解决了。

不过根据错误来看,应该还可以用++操作符重载的方式来解决这个问题。以后再看。

(5)用typedef定义类型

在c语言中,可以用typedef定义新的类型名来代替已有的类型名。定义的一般形式为:

typedef 类型名 新名称;

typedef是类型定义中的关键字,“类型名”是c语言中的已有的类型(如int,float),“新名称”是用户自定义的新名,新名称在习惯上用大些字母表示。

举例:

1)定义一个新的结构体类型DATE:

typedef struct

{

int month;

int day;

int year;

}DATE;

DATE d1,d2;

2)typedef int ARR[10];定义ARR是整型数组类型

ARR m,n;//m和n都被定义为一维数组,包含10个元素。

3)typedef char *STR;//定义STR是字符指针类型

STR p,x[10];//定义p为字符指针变量,x为字符指针数组。

4)typedef int *PI;//定义PI为整型指针类型

PI p,*q,r[20];//定义p为整型指针变量,q为指向int型指针的指针变量,r为具有20个指向整型类型元素的指针数组。

说明:

*用typedef可以声明各种类型名,但不能用来定义变量

*用typedef只是对已经存在的类型增加一个类型的新名称,而没有构造新的类型。

*如果在不同源文件中使用同一类型数据,常用typedef说明这些数据类型,并把它们单独放在一个文件中,然后在需要时,用#include命令把他们包含进来。

(6)链表及其简单操作

数组是一批有先后次序的元素构成的序列,可以用下标随意访问元素而且操作简单。但在利用数组存放数据时必须事先定义固定的数组长度,势必会造成内存空间的浪费。而且不利于插入和删除数据。

链表是结构体类型的重要应用之一,也是一种有先后次序的序列,但是链表中的元素可以根据需要动态开辟内存单元。链表常被看作是与数组互补存在的一种数据构成方式。

1)链表的概念

在链表中,所有数据元素都被保存在一个据有相同数据结构的节点中,节点是链表的基本存储单位。一个节点与一个数据元素对应。每个节点在内存中使用一块连续的存储空间。把线性表的元素存放到一个由这种节点组成的链式存储中,每个节点之间可以占用不连续的内存空间,节点与节点之间通过指针链接在一起,这种存储方式叫做链表。

链表中每个节点至少由两个部分组成,即数据域和指针域。节点的定义需要采用结构类型,其一般形式为

struct node{

int data;              //数据域

struct node *link;//指针域

} ;

在实际应用中,要建立一个链表通常包括三部分内容:

头指针,头节点,数据节点。

链表的存储空间可以在程序的运行期间动态分配,如果需要向链表中插入一个结点,只需调用C语言的动态空间分配函数(alloc,malloc函数)动态申请一个存储结点存放相应信息,并把新申请的结点插入到链表的适当位置上。删除一个结点意味着该结点被系统回收。C语言中free函数用来动态回收结点。

链表存储结构具有如下特点:

*插入、删除运算灵活方便,不需要移动结点,只需改变节点中指针域的值即可。

*可以实现动态分配和扩展空间,当表中数据是动态产生的时侯,最好使用链表。

*查找操作只能从链表的头结点开始顺序查找,不适合有大量频繁查找的表。

2)链表的基本操作

链表的基本操作有:建立链表、遍历链表、向链表中插入或删除结点、求链表长度等,这里仅介绍有关链表最基本的概念和相关操作。后续将会在数据结构的博客中详细介绍。

1.建立链表

建立链表首先要定义一个包含数据域和指针域的结构类型,然后定义一个指向表头结点的头指针,最后通过调用malloc函数动态申请结点的方法建立整个链表。

例子:程序中统计90分以上的成绩个数,当学号输入为零时将结束链表的建立。

 #include<stdio.h>
#include<stdlib.h>
typedef struct student{
int num;
int score;
struct student *link;
} NODE; NODE *creat(){
NODE *head=NULL,*p1=NULL,*p2=NULL;
head=p1=p2=(NODE *)malloc(sizeof(NODE));
printf("请输入学号和成绩:\n");
scanf("%d,%d",&p1->num,&p1->score);
p1->link=NULL;
while(p1->num!=){
p1=(NODE *)malloc(sizeof(NODE));
p1->link=NULL;
printf("请输入学号和成绩:\n");
scanf("%d,%d",&p1->num,&p1->score);
p2->link=p1;
p2=p1;
}
return head;
}
int count(NODE *p){
int sum=;
while(p!=NULL){
if(p->score>=){
sum++;
}
p=p->link;
}
return sum;
}
int main(){
NODE *p;
p=creat();
printf("\n the count of 90 =%d\n",count(p));
return ;
}

c的详细学习(10)结构体与共用体的学习(二)

这里用一个头指针指向该链表,然后用两个指针变量的交替操作增加链表的长度,过程如下:

c的详细学习(10)结构体与共用体的学习(二)

2.链表的遍历

所谓链表的遍历就是逐一访问链表中的每个结点:

设线性链表头指针为h,设指针变量p指向不同的结点。沿着链表开头向后查找结点中学号为x的结点。若找到则返回链表中该结点的位置,否则返回空地址。

    NODE *search(NODE *L,int x){

      NODE *p;

      p=L->link;

      while(p!=NULL&&p->num!=x){

        p=p->link;

      }

      return (p);

    }

3.链表的插入

要在单链表中的两个数据元素a和b之间插入一个数据元素x,首先将指针p指向结点a,然后生成一个新的结点,使其数据域为x,并使结点a中的link域指向新结点x,x的link域则指向结点b,即完成插入操作。

插入操作的程序如下:

void listInsert_L(NODE *L,int i,int x){
NODE *p=L,*s;
int j=;
//寻找第i个结点
while(p&&j<i-){
p=p->link;
++j;
}
//插入位置错误
if(!p||j>i-){
return ;
}
s=(NODE *)malloc(sizeof(NODE));
s->num=x;
s->link=p->link;
p->link=s;
return ();
}

4.线性链表的删除

在a和b和c相邻的单链表中删除中间结点b的操作,只需将结点a的link域指向结点c,并释放接待你占用的存储空间即可。

单链表的删除操作的程序如下:

void  ListDelete(NODE *L,int i,int,x){
NODE *p=L,*q;
int j=;
while(p->link&&j<i-){
p=p->link;
++p; //寻找第i个结点,并令p指向该前驱
}
if(!(p->link)||j>i-) return ();//删除位置错误
q=p->link; //删除并释放结点
p->link=q->link;
free(q);
return ();
}

求链表长度

由于链表的长度不是预先确定,而是动态建立的,因此求链表的长度需要遍历链表的所有结点才能完成.

    int Length(){
NODE *p=L;
int count=;
while(p!=NULL){
count++;
p=p->link;
}
return (count);
}

链表实例:某班有20名学生,每名学生的数据包括学号、姓名、3门课的成绩,从键盘输入20名学生的数据,要求打印出3门课的总平均成绩,以及最高分的学生的数据。

 #include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.h> struct student{
long num;
char name[];
int course[];
int aver;
struct student *next;
};
typedef struct student NODE; int main(){
srand(time(NULL));
NODE *head=NULL;
NODE *p1=NULL,*p2=NULL;
int i;
for(i=;i<;i++){
int j;
if(i==){
head=p2=p1=(NODE *)malloc(sizeof(NODE));
}else{
p1=(NODE *)malloc(sizeof(NODE));
}
p1->num=+i;
int sum=;
for(j=;j<;j++){
p1->course[j]=rand()%+;
}
for(j=;j<;j++){
sum+=p1->course[j];
}
p1->aver=sum/;
char s[];
itoa(i+,s,);
strcpy(p1->name,"student");
strcat(p1->name,s);
if(i!=){
p2->next=p1;
p2=p1;
}
}
printf("下面打印出20名学生的信息:\n");
p1=head;
NODE *maxNode=head;
while(p1!=NULL){
if(maxNode->aver<p1->aver){
maxNode=p1;
}
printf("num=%ld,name=%9s,aver=%d\n",p1->num,p1->name,p1->aver);
p1=p1->next;
}
printf("平均分最高的学生信息为:\n");
printf("num=%d,name=%9s,course[0]=%d,course[2]=%d,course[3]=%d,aver=%d",
maxNode->num,maxNode->name,maxNode->course[],maxNode->course[],maxNode->course[],maxNode->aver);
return ;
}

c的详细学习(10)结构体与共用体的学习(二)

值得注意的地方:

1.c语言中是没有string类型的,所以当用到字符串时都是用char类型的数组;
    2.strcpy(s1,s2)是s2中内容加到s1上;
    3.strcat(s1,s2)是将s2的内容加到s1的尾部;
    4.itoa(a,str,10);将10进制整数转化为字符串后存到str中;