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

结构体

概述

数组:描述一组具有相同类型数据的有序集合,用于处理大量相同类型的数据运算。
有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理。
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
就是说如果中间有一个元素突然被赋值了,后面元素的值会跟在这个数后面继续加一

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值