C语言学习第013课——结构体、共用体、枚举

it2024-11-27  19

结构体

概述

数组:描述一组具有相同类型数据的有序集合,用于处理大量相同类型的数据运算。 有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理。 C语言中给出了另一种构造数据类型——结构体。

结构体变量的定义和初始化

先声明结构体类型再定义变量名在声明类型的同时定义变量直接定义结构体类型变量(无类型名)

代码示例:

#include<stdio.h> #include<string.h> struct student{ char name[21]; 一个汉字在windows内存中2个字节,在Linux内存中3个字节 int age; int score; char addr[51]; }; int main(void){ struct student stu; /*stu.name = "张三"; */ 因为数组名是一个常量值,所以不能这样给数组名赋值 strcpy(stu.name,"张三"); 使用strcpy函数,可以给name里面的每个内存空间赋值 stu.age = 23; stu.score = 105; //stu.addr = "北京市丰台区"; strcpy(stu.addr,"北京市丰台区"); printf("name = %s\n",stu.name); printf("age = %d\n",stu.age); printf("score = %d\n",stu.score); printf("addr = %s\n",stu.addr); }

运行结果: 上面代码中,一行一行的赋值,太麻烦,字符串赋值还要调用函数,可以更简单一些

struct student stu = {"张三",18,100,"北京市丰台区"};

还有一种方式:定义结构体同时赋值

struct student{ char name[21]; int age; int score; char addr[51]; }stu = {"张三",18,100,"北京市丰台区"};

还可以在定义结构体的时候,多定义几个变量名

struct student{ char name[21]; int age; int score; char addr[51]; }stu1,stu2,stu3;

使用键盘输入为结构体的字段赋值

#include<stdio.h> struct student{ char name[21]; int age; int score; char addr[51]; }stu; int main(void){ struct student stu; scanf("%s%d%d%s",stu.name,&stu.age,&stu.score,stu.addr); 这里int类型的字段要使用取地址符,字符串类型的不用,因为他本身就是一个地址 printf("name = %s\n",stu.name); printf("age = %d\n",stu.age); printf("score = %d\n",stu.score); printf("addr = %s\n",stu.addr); }

运行结果: 以上是一个结构体的初始化和使用,也就是打印一个学生的基本信息,假如我需要打印很多学生的信息呢?就需要用到结构体数组

结构体数组

类似于

int a; 这是一个整型变量 int arr[]; 这是一个整型数组

数组基本都是 类型 变量名 [] 这样的样式 所以定义一个结构体数组就是

struct student stu[3];

整体代码:

#include<stdio.h> struct student{ char name[21]; int age; char sex; int score[3]; char addr[51]; }; int main(void){ struct student stu[3] = { {"张三",18,'m',89,89,100,"河北唐山"}, {"李四",21,'m',59,60,70,"山西运城"}, {"王五",22,'f',100,100,100,"北京朝阳"} }; for(int i = 0;i<3;i++){ printf("姓名 = %s\n",stu[i].name); printf("年龄 = %d\n",stu[i].age); printf("性别 = %s\n",stu[i].sex=='m'?"男":"女"); printf("语文 = %d\n",stu[i].score[0]); printf("数学 = %d\n",stu[i].score[1]); printf("英语 = %d\n",stu[i].score[2]); printf("地址 = %s\n",stu[i].addr); printf("\n"); } }

运行结果:

结构体数组和结构体的大小

以前学习的,如果想要获取到一个变量所占用内存的大小 就用sizeof() 但是sizeof的对象如果是一个指针变量的话,不管是什么类型,多大的指针,返回结果都是4或者8 我们来试试看,sizeof函数对结构体适用不适用 代码:

#include<stdio.h> struct student{ char name[21]; int age; char sex; int score[3]; char addr[51]; }; int main(void){ struct student stu[3] = { {"张三",18,'m',89,89,100,"河北唐山"}, {"李四",21,'m',59,60,70,"山西运城"}, {"王五",22,'f',100,100,100,"北京朝阳"} }; printf("数组占用内存大小为%d\n",sizeof(stu)); printf("数组元素占用内存大小为%d\n",sizeof(stu[0])); printf("数组元素个数为%d\n",sizeof(stu)/sizeof(stu[0])); }

说明sizeof函数对结构体是有用的,我们简单的计算一下sizeof计算的结构体元素大小对不对

struct student{ char name[21]; 21 int age; 4 char sex; 1 int score[3]; 12 char addr[51]; 51 89 };

我们手动计算的结果是89 sizeof计算的结果是96呢? 这是因为结构体的成员需要偏移对齐,结构体的成员在内存中存放时会根据最大类型进行偏移对齐,最大类型是int类型,也就是说,所有的内容在内存中开始存放的时候,他的起始地址一定是4的倍数,如果不是,会跳到下一个4的倍数位置上, 以上面代码为例,name占用了21个字节,下一个age存放的时候,不会从第22个字节开始存,而是跳到第24个字节开始存放,以此类推,name后面+3,sex后面+3,addr后面+1,就是89+7=96

结构体数组排序

将学生成绩按照语文成绩从高到低排序

#include<stdio.h> struct student{ char name[21]; int age; char sex; int score[3]; char addr[51]; }; int main(void){ struct student stu[3] = { {"张三",18,'m',89,89,100,"河北唐山"}, {"李四",21,'m',59,60,70,"山西运城"}, {"王五",22,'f',100,100,100,"北京朝阳"} }; for(int i=0;i<3-1;i++){ for(int j = 0;j<3-1-i;j++){ if(stu[j].score[0]<stu[j+1].score[0]){ struct student temp = stu[j]; stu[j] = stu[j+1]; stu[j+1] = temp; } } } for(int i = 0;i<3;i++){ printf("姓名 = %s\n",stu[i].name); printf("年龄 = %d\n",stu[i].age); printf("性别 = %s\n",stu[i].sex=='m'?"男":"女"); printf("语文 = %d\n",stu[i].score[0]); printf("数学 = %d\n",stu[i].score[1]); printf("英语 = %d\n",stu[i].score[2]); printf("地址 = %s\n",stu[i].addr); printf("\n"); } }

打印结果:

开辟堆空间存储结构体

首先回忆一下开辟堆空间存储int类型数据:

int* p = (int*)malloc(sizeof(int)*3);

以此类推,堆空间存储结构体的方式应该是

struct student * p = (struct student *)malloc(sizeof(struct student)*3);

这样写好像有点长了,代码阅读性不强,哪里都要加个struct,很烦人,可以给他缩写一些,就是给结构体起个别名,使用typedef

typedef struct student ss; 这样就给一个结构体类型起好了一个别名ss

使用的时候就可以写成这样:

ss* p = (ss*)malloc(sizeof(ss)*3);

代码看起来就简单多了 其实之前用到的size_t也是这样的道理,关联源码后会发现

typedef unsigned int size_t;

也可以在定义结构体的时候,直接起别名

typedef struct student{ char name[21]; int age; int score[3]; char addr[51]; }stu; int main(void){ stu s = {"张三",23,90,90,90,"张家村"}; printf("%s\n",s.name); }

这样stu就直接是个别名了。

然后我们通过键盘输入给堆空间的结构体赋值,并打印出来

#include<stdio.h> typedef struct student ss; 起个别名 struct student{ char name[21]; int age; char sex; int score[3]; char addr[51]; }; int main(void){ ss* p = (ss*)malloc(sizeof(ss)*3); for(int i = 0;i<3;i++){ scanf("%s%d,%c%d%d%d%s", p[i].name,&p[i].age,&p[i].sex,&p[i].score[0],&p[i].score[1],&p[i].score[2],p[i].addr); } 上面的scanf函数,注意中间有一个sex字段,是char类型的,所以输入空格或者回车都有可能被这个char类型的字段接收,造成数据错误, 所以在%c占位符之前写一个逗号,在键盘输入的时候这里也写一个逗号,就不会接收错误了 for(int i = 0;i<3;i++){ printf("姓名 = %s\n",p[i].name); printf("年龄 = %d\n",p[i].age); printf("性别 = %s\n",p[i].sex=='m'?"男":"女"); printf("语文 = %d\n",p[i].score[0]); printf("数学 = %d\n",p[i].score[1]); printf("英语 = %d\n",p[i].score[2]); printf("地址 = %s\n",p[i].addr); printf("\n"); } free(p); p=NULL; }

看一下结构体指针的大小

sizeof(ss*); sizeof(p); 都可以

运行结果是4

结构体嵌套结构体

例如,一个学生有基本信息,和三门成绩,我们就可以把三门成绩单独定义成一个结构体

#include<stdio.h> typedef struct student stu; 为了书写方便和阅读性强,起个别名 typedef struct score sc; struct score{ 将分数单独写成一个结构体,三门课 int cl; int cpp; int cs; }; struct student{ char name[21]; int age; char sex; sc s; 这里就可以直接使用结构体的别名来定义了 char addr[51]; }; int main(void){ stu stu1 = {"王二",19,'m',98,98,96,"乌鲁木齐"}; printf("%s\n%d\n%s\n%d\n%d\n%d\n%s\n", stu1.name,stu1.age,stu1.sex=='m'?"男":"女", stu1.s.cl,stu1.s.cpp,stu1.s.cs,stu1.addr); 取结构体的结构体的值的时候,方式类似:学生.成绩.课程 }

运行结果:

计算一下结构体中结构体的大小

#include<stdio.h> typedef struct student student; typedef struct score score; struct score{ int cl; int cpp; int cs; }; struct student{ char name[21]; int age; char sex; score s; char addr[51]; }; int main(void){ student stu; printf("学生结构体的大小为%d\n",sizeof(stu)); 96 printf("分数结构体的大小为%d\n",sizeof(stu.s)); 12 }

结构体的赋值

考虑以下代码的运行结果

#include<stdio.h> typedef struct student student; struct student{ char name[21]; int age; int score; char addr[51]; }; int main(void){ student stu = {"孙尚香",25,89,"巴蜀"}; 定义一个stu,并且初始化 student stu1 = stu; 定义一个stu1,将stu赋值给stu1 strcpy(stu1.name,"甘夫人"); 改变stu1.name的值, printf("stu.name = %s\n",stu.name); 是否会影响stu.name的值 }

运行结果为 以上代码的运行过程类似于:

int a = 10; int b = a; b = 20; 问a等于多少? 当然是10,改变b的值,不会影响到a的值

结构体和指针

指针指向一个结构体

#include<stdio.h> typedef struct student student; 定义一个别名就叫student struct student{ char name[21]; int age; int score[3]; char addr[51]; }; int main(void){ student stu = {"孙尚香",25,89,90,100,"巴蜀"}; student* p= &stu; student就相当于类型,student* p就是指针 printf("姓名 = %s\n",p->name); 指针用小箭头,变量用点 printf("年龄 = %d\n",p->age); printf("语文 = %d\n",p->score[0]); printf("数学 = %d\n",p->score[1]); printf("英语 = %d\n",p->score[2]); printf("住址 = %s\n",p->addr); }

运行结果

结构体指针->成员 结构体变量.成员

结构体成员如果用指针表示,如何在堆空间中分配一个结构体指针

#include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct student student; struct student{ char* name; 这里使用指针类型来定义结构体的成员 int age; int* score; char* addr; }; int main(void){ student* pstu = (student*)malloc(sizeof(student)*3); 创建一个结构体的指针,3个student大小,也就是4*4=12个字节大小 其中name,score,addr是指针,里面的值放的是地址 for(int i = 0;i < 3;i++){ 依次给每一个成员指针开辟空间 pstu[i].name = (char*)malloc(sizeof(char)*21); pstu[i].score = (int*)malloc(sizeof(int)*3); pstu[i].addr = (char*)malloc(sizeof(char)*51); } for(int i = 0;i<3;i++){ 通过键盘输入给每个成员赋值 scanf("%s%d%d%d%d%s", pstu[i].name, 字符串类型不用加&,因为他的值就是首地址 &pstu[i].age, int类型的值要加& &pstu[i].score[0], &pstu[i].score[1], &pstu[i].score[2], pstu[i].addr); } for(int i = 0;i<3;i++){ 循环打印 printf("%s %d %d %d %d %s\n", pstu[i].name, pstu[i].age, pstu[i].score[0], 指针pstu后面用[i]也代表取值, (pstu+i)->score[1], 指针pstu+i再用箭头也代表取值, (pstu+i)->score[2], 两个用哪个都行 (pstu+i)->addr); } for(int i = 0;i<3;i++){ 释放内存 free(pstu[i].name); 先释放后分配的内存,再释放pstu这样的总内存 free(pstu[i].score); 如果先释放pstu,那里面存放的地址就释放掉了,下面成员的内存都不知道在哪里了,怎么释放 free(pstu[i].addr); } free(pstu); }

运行结果:

结构体做函数参数

结构体普通变量做函数参数

运行以下代码

#include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct student student; struct student{ char name[21]; int age; int score; char addr[51]; }; void fun01(student param){ printf("func01 %s\n",param.name); strcpy(param.name,"卢俊义"); printf("func01 %s\n",param.name); } int main(void){ student stu = {"宋江",50,98,"郓城"}; fun01(stu); printf("main %s\n",stu.name); }

运行结果: 结果可想而知,stu是实参,param是形参,改变形参的值,不会对实参的值有影响。 如果定义结构体的时候,name使用的是指针定义方式呢? 那么初始化结构体方式就要变成下面这样

student stu = {NULL,50,98,"郓城"}; stu.name = (char*)malloc(sizeof(char)*21); strcpy(stu.name,"宋江");

因为是指针,所以要先开辟内存空间,用name的指针指向该内存空间 然后给该空间写入数据 但是运行程序,发现结果发生了变化 不是形参不能改变实参的值吗?就改了个name为指针定义方式,为什么结果发生变化了呢? 因为name定义方式是指针,所以当stu传递给函数的时候,name是将他的地址值传递过去的, param接收到的也是地址,用strcpy直接修改地址对应的值,导致main函数中的实参发生了变化 为了验证上面的结论,我们将给形参改变name值之前,重新给name分配一块内存,代码如下

void fun01(student param){ printf("func01 %s\n",param.name); param.name = (char*)malloc(sizeof(char)*21); 给param.name重新指向了另外一块内存,而不是实参传过来的内存地址 strcpy(param.name,"卢俊义"); printf("func01 %s\n",param.name); }

这样运行结果为: 结论成立!

结构体指针变量做函数参数

#include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct student student; struct student{ char name[21]; int age; int score; char addr[51]; }; void fun01(student* param){ 这里用一个指针对象接着 printf("func01 %s\n",param->name); strcpy(param->name,"公孙胜"); printf("func01 %s\n",param->name); } int main(void){ student stu = {"吴用",50,98,"梁山"}; fun01(&stu); 传递的是地址 printf("main %s\n",stu.name); }

运行结果; 结果显示,结构体指针作为函数的参数,函数里面修改是会影响到原来的值的

结构体数组名做函数参数

定义一个结构体数组,并且初始化,然后调用函数给他们排序

#include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct student student; struct student{ char name[21]; int age; int score; char addr[51]; }; void bubbleSort(student* pstu,int len){ for(int i=0;i<len-1;i++){ for(int j=0;j<len-1-i;j++){ if(pstu[j].age>pstu[j+1].age){ student temp = pstu[j]; pstu[j] = pstu[j+1]; pstu[j+1] = temp; } } } } int main(void){ student stu[3] = { 定义结构体数组,并初始化 {"张三",50,98,"张家庄"}, {"李四",25,88,"李家庄"}, {"王五",33,99,"王家庄"} }; bubbleSort(stu,3); 调用函数进行排序 数组作为函数参数会退化为指针,丢失元素精度,需要传递个数 for(int i = 0;i<3;i++){ 打印排序后的数组 printf("%s %d %d %s\n",stu[i].name,stu[i].age,stu[i].score,stu[i].addr); } }

运行结果:

const修饰结构体指针形参变量

其实和const修饰的指针变量一个意思,看下面代码

student stu1 = {"张三",23,89,"张村"}; student stu2 = {"李四",30,90,"李村"}; const student* p = &stu1; const修饰student*,可以修改p的值, 不能修改p指向的地址的值,也就是不能修改student的值 p = &stu2;//ok p->age = 40;//err student* const p = &stu1; const修饰p,可以修改student的值,不能修改p的值 p = &stu2;//err p-name = "王五";//err 这一行出错不是因为p->name不能修改,而是因为p->name是数组名,是常量,不能修改 // 应该用strcpy(p->name,"王五"); p->age = 40;//ok const student* const p = &stu1; const既修饰student* 又修饰p,student的值和p的值都不可以修改 p = &stu2;//err p->age = 40;//err

一样的道理,const修饰的一级只读指针,可以通过二级指针进行修改

const student* const p = &stu1; student** pp = &p; *pp = &stu2;//OK (*pp)->age = 99;//err

共用体(联合体)

联合体union是一个能在同一个存储空间存储不同类型数据的类型联合体所占的内存空间等于其最大成员类型的长度倍数,同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用共用体变量中起作用的成员是最后一次存放的成员,在存入一个新成员后,原有的成员值会被覆盖共用体变量的地址和他的各个成员的地址都是同一地址。 定义一个联合体 union var{ int a; char b; float c; double d; short f; }; 简单的使用一下 int main(void){ union var v; v.a = 100; printf("%d\n",v.a); 100 }

以上代码展示了如何定义一个联合体,并且简单的使用了一下,接下来,在这个联合体的基础上,展示一下他的其他特性

union var v; v.a = 100; v.b = 'a'; printf("%d\n",v.a); printf("%c\n",v.b); printf("大小:%d\n",sizeof(v)); printf("v=%p\n",&v); printf("a=%p\n",&v.a); printf("b=%p\n",&v.b);

运行结果:

发现给a赋值的100已经被b的值给覆盖了, 而且联合体的大小是最大元素dubble的长度8 无论是v还是v.a v.b地址都相同 如果有以下联合体,他的长度是多少

union var{ int a; char b; float c; double d; short f[6]; };

长度为16 因为最大元素类型是double,但是一个长度是8,够不了short[6]的长度,只能是8的倍数,2个8就够了 联合体的优点是,节省内存空间,如果遇到不同类型的数据,频繁的需要改变,瞬时只使用一个,这个时候就可以使用联合体

枚举

枚举是将变量的值一一列举出来,变量的值只限于列举出来的范围内 枚举类型定义:以红绿灯为例

enum color{ red,yellow,green }; 在枚举值表中应列出所有可用值,也成为枚举元素枚举值是常量,不能在程序中用赋值语句再对他赋值枚举元素本身由系统定义了一个表示序号的数值,从0开始0,1,2,3… enum color{ red,yellow,green }c; int main(void){ int value; scanf("%d",&value); switch(value){ case red: case后面直接写枚举元素就可以 printf("红色\n"); break; case yellow: printf("黄色\n"); break; case green: printf("绿色\n"); break; } }

以上枚举值中,虽然没有具体描述red是几,yellow是几,但是默认的,第一个元素是0,第二个元素是1这样依次排下去 如果中间这样写

enum color{ red,yellow=20,green };

这样red=0,yellow=20,green = 21 就是说如果中间有一个元素突然被赋值了,后面元素的值会跟在这个数后面继续加一

最新回复(0)