day09 结构体等复合类型(自定义类型)

一、结构体

1.概述

  我们知道数组用来描述一组具有相同类型数据的有序集合,用于处理大量相同类型的数据运算。

  有时我们需要将不同类型的数据组合成一个有机的整体,来满足需求,因为普通的数据类型无法满足需求。比如说需要打印一个学生的具体信息,一个学生有学号/姓名/性别/年龄/地址等属性。显然用一种变量是无法完成的,但是单独定义以下变量比较繁琐,数据不便于管理。那么能不能有一种数据类型,把学生的属性都包含进去了,所以C语言中给出了另一种构造数据类型——结构体。

  我觉得结构体也是抽象化的一种体现,比如说定义了一个学生结构体,将相当于把学生的属性都抽出来,拿出来了,这个结构体就代表了学生。
在这里插入图片描述

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

2.1定义结构体变量的方式:

(1) 先声明结构体类型再定义变量名

(2) 在声明类型的同时定义变量

(3) 直接定义结构体类型变量(无类型名)

在这里插入图片描述

2.2 结构体类型和结构体变量关系

  (1) 结构体类型:指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元。或者说结构体类型是你自己定义的一种数据类型,普通的变量类型是一个单词比如说 int,char;但是你自己定义的数据类型比较特别,是两个单词 struct xxx。

  (2)结构体变量:系统根据结构体类型(内部成员状况)为之分配空间。在定义变量的时候,就是数据类型+变量名,比如说定义一个整型的变量 a,int a;那么定义结构体变量也是一样,struct xxx b,只不过数据类型是两个单词一起。

//结构体类型的定义
struct stu
{
	char name[50];
	int age;
};

//先定义类型,再定义变量(常用)
struct stu s1 = { "mike", 18 };


//定义类型同时定义变量
struct stu2
{
	char name[50];
	int age;
}s2 = { "lily", 22 };

struct
{
	char name[50];
	int age;
}s3 = { "yuri", 25 };

2.3 结构体成员的使用

2.3.1 定位结构体成员变量的方式

  结构体是一种复合型的数据类型,一个结构体里面可能包含多个数据类型变量。当需要对结构体中的某个成员变量操作时,首先要找到那个成员变量。结构体就相当于一个班集体,结构体的成员变量相当于班集体里面的名同学,要找某个同学谈话,就先要找到那个同学。

  那么在结构体体中,怎么定位到要操作的成员变量呢?有两种方式:

  (1)如果是普通变量,通过点运算符操作结构体成员。也就是 结构体变量名+ . +成员变量名。如下面的代码所示:

	struct stu s1;
	strcpy(s1.name, "abc");
	s1.age = 18;
	printf("s1.name = %s, s1.age = %d\n", s1.name, s1.age);

  (2)如果是指针变量,通过 -> 操作结构体成员。也就是 结构体指针 + -> + 成员变量名。如下面代码所示:

	struct stu s1;
	strcpy((&s1)->name, "test");
	(&s1)->age = 22;
	printf("(&s1)->name = %s, (&s1)->age = %d\n", (&s1)->name, (&s1)->age);
2.3.2 注意事项

  如果是给结构体的字符串成员变量赋值,要使用 strcpy() 等复制函数。错误的原因下面有。
在这里插入图片描述

在这里插入图片描述

2.3.3 测试代码
#include<stdio.h>
#include<string.h>

//结构体类型的定义
struct stu
{
	char name[50];
	int age;
};

int main()
{
	struct stu s1;

	//如果是普通变量,通过点运算符操作结构体成员
	strcpy(s1.name, "abc");
	s1.age = 18;
	printf("s1.name = %s, s1.age = %d\n", s1.name, s1.age);

	//如果是指针变量,通过->操作结构体成员
	strcpy((&s1)->name, "test");
	(&s1)->age = 22;
	printf("(&s1)->name = %s, (&s1)->age = %d\n", (&s1)->name, (&s1)->age);

	return 0;
}

2.4 结构体的大小和内存结构

  在测试之前,先了解一句话:结构体需要根据数据类型进行内存对齐。

2.4.1 测试代码

(1)第一次测试:

#include <stdio.h>
#include <string.h>

struct stu
{
        char name[21];
        unsigned int age;
        char tel[16];
        float scores[3];
        char sex;
};

int main()
{
        struct stu Mikou;

        printf("结构体预期的占用内存 = %d\n",\
        sizeof(Mikou.name)+sizeof(Mikou.age)+sizeof(Mikou.tel)+sizeof(Mikou.scores)+sizeof(Mikou.sex));
        
        printf("结构体实际占用内存 = %d\n",sizeof(Mikou));
		return 0;
        }

  测试结果:预期(将结构体的成员变量占用的空间加起来)的占用空间和实际占用空间不一样。
在这里插入图片描述
(2)第二次测试:修改代码,将 char sex 移动到 char name[21] 的下面,其他的不变:

struct stu
{
        char name[21];
        char sex;
        unsigned int age;
        char tel[16];
        float scores[3];
};

  再次运行,测试结果为:实际占用的内存空间变小了,这是怎么回事。
在这里插入图片描述

2.4.2 分析测试结果

  我们在开头就写了一句话: 结构体需要根据数据类型进行内存对齐。 下面就用这句话来分析刚才测试结果。

  我们先来了解一下内核是如何给结构体分配内存的,是如何存储结构体的。在存储结构体之前,先检查结构体中那种数据类型占用的内存最大,然后就以这种数据类型的占用内存大小为单位分配给结构体。可能还是有点懵,举个例子:

struct stu
{
        char name[21];
        unsigned int age;
        char tel[16];
        float scores[3];
        char sex;
};

  (1)在 stu 有 char ,unsigned int,tel,float 这四种数据类型,unsigned int 和 float 占用的空间最大,一个 float 变量就占用 4 个字节。所以内核就以 4 个字节为单位分配给结构体,然后按结构体内的成员声明先后顺序分配。

  (2)在结构体中 char name[21]; 最先声明,所以先分配内存给 char name[21] 。现在一个内存单位是4个字节,那么 char name[21] 占用 21 个字节,那么需要 6 个内存单位。就相当于一辆法拉利能载4个美女,现在有21个美女,要将她们一次带回家,那么我要叫多少法拉利过来,至少要 6 辆吧。
在这里插入图片描述
  (2)我叫了 6 辆法拉利过来,载了 21 个美女,现在还剩下 3 个座位。那么我要看看能不能在附近再拉几个美女回去吧,下一个成员是unsigned int age 占用4个字节,这里有4个美女,但是她们说要乘同一辆车,不能分开坐,不然不跟我回去。

那么没有办法了,那21辆法拉利只能空闲3个座位了。只能再叫一辆法拉利过来了,一次将这4个美女拉回去,没有剩下空座位。

  (3)第三个成员是char tel[16],分配4个单位内存刚合适。就相当于16个美女,叫4辆法拉利刚刚合适,没有空闲的座位。

  (4) 第四个成员是 float scores[3],一共占用 12 个字节。相当于12个美女,叫三辆法拉利过来刚刚合适。

  (5)第五个成员是 char sex,占用一个字节;相当于有一个美女,也得叫一辆车过来,但是空闲了3个座位。

  根据上面的过程,我一共叫了 15辆法拉利过来,一共有60个座位,但是空闲了 6 个,因为一共有54 个美女。

  为什么将 char sex,移动到第一个成员后面,结构体实际用的空间就减小了,是因为第一个成员叫了6两辆车过来,还剩下3个座位,那么就叫 char sex 跑过去坐顺风车了,最后就不用特地再叫一辆车了,所以就少叫了一辆车,少分配了内存空间。记得要紧接着有空闲空间才有用,搭顺风车,就是在一起紧挨着方便,才能搭,这是不完全正确的比喻。

3.结构体数组

#include <stdio.h>

//统计学生成绩
struct stu
{
	int num;
	char name[20];
	char sex;
	float score;
};

int main()
{
	//定义一个含有5个元素的结构体数组并将其初始化
	struct stu 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 }};

	int i = 0;
	int c = 0;
	float ave, s = 0;
	for (i = 0; i < 5; i++)
	{
		s += boy[i].score;	//计算总分
		if (boy[i].score < 60)
		{
			c += 1;		//统计不及格人的分数
		}
	}

	printf("s=%f\n", s);//打印总分数
	ave = s / 5;					//计算平均分数
	printf("average=%f\ncount=%d\n\n", ave, c); //打印平均分与不及格人数


	for (i = 0; i < 5; i++)
	{
		printf(" name=%s,  score=%f\n", boy[i].name, boy[i].score);
           // printf(" name=%s,  score=%f\n", (boy+i)->name, (boy+i)->score);

	}

	return 0;
}

4.结构体指针

4.1 结构体指针定义

  与普通的数据类型定义指针类似:数据类型 *指针名,那么结构体指针就是:struct 结构体名 *指针名

4.2 结构体指针两种引用成员变量方式

  我们知道引用结构体成员变量的方式有两种:

  (1)如果是普通变量,通过 点运算符 操作结构体成员。当我们定义了一个结构体指针,比如说p,*p 就相当于普通的结构体变量。

  (2)如果是指针变量,通过 -> 操作结构体成员。

  这两种方式的具体使用看测试代码。

4.3 测试代码

#include<stdio.h>

//结构体类型的定义
struct stu
{
	char name[50];
	int age;
};

int main()
{
	struct stu s1 = { "lily", 18 };

	//如果是指针变量,通过->操作结构体成员
	struct stu *p = &s1;
	printf("p->name = %s, p->age=%d\n", p->name, p->age);
	printf("(*p).name = %s, (*p).age=%d\n",  (*p).name,  (*p).age);

	return 0;
	}

在这里插入图片描述

5.结构体数组与指针

  结构体数组与指针也是与普通的数据类型数组用法一样。数组名是首元素的地址,只不过多了定位到成员变量而已。看测试代码

5.1 测试代码

#include <stdio.h>
#include <string.h>

struct stu
{
        char *name;
        unsigned int age;
};

int main()
{
        // 在堆区申请空间定义结构体
        struct stu *p = (struct stu *)malloc(sizeof(struct stu)*2);

        // 为结构体里的名字字符数组开辟空间
        p[0].name = (char *)malloc(sizeof(char) * 21);
        (p+1)->name = (char *)malloc(sizeof(char) * 21);

        // 用普通变量的方式操作结构体成员变量
        strcpy( p[0].name,"小香香");
        strcpy( (p+1)->name,"大臭臭");

        p[0].age = 18;
        (p+1)->age = 28;

        printf("(*p).name = %s\n",p[0].name);
        printf("(*p).age  = %d\n",p[0].age);

        printf("(p+1)->name = %s\n",(p+1)->name);
        printf("(p+1)->age  = %d\n",(p+1)->age);

        free(p[0].name);
        free((p+1)->name);
        free(p);
        //free(p+1);

        return 0;

}

5.2 free() 释放的顺序

  在代码中我们是申请了结构体的内存,还有结构体成员变量name 的内存,那么在释放的时候,先释放哪个内存?

  先是放结构体成员变量申请的内存,再释放结构体的内存。为什么呢?因为要定位到成员变量,需要到结构体变量;就比如说要找2班的马化腾同学,那么你要先找到2班,才能找到他。如果是先释放结构体变量申请的内存,那么结构体变量就没有了,也找不到结构体的成员变量了,也就无法释放了。

  从这里,可以总结出释放的顺序,只要有包含的关系的,大包含小的,那么就先释放小的。

5.3 再次提醒 free() 所填的地址

  在释放一片申请的内存空间的时候,free() 函数填的地址是什么?是这 一片空间的首地址。如果不是首地址就会出现意想不到的现象。

  如下图,我将 free§ 改为了 free(p+1) ;
在这里插入图片描述
  执行程序,看结果,出现了一堆不知道是什么东西:
在这里插入图片描述

6.结构体与函数

6.1 结构体普通变量做函数参数(值传递)

  值传递不会改变实参的值,在结构体普通变量里也是。之前我们说过,因为操作形参和实参是操作两个不同地址的内存中的值,没有相关性,形参不会影响到实参的值。

(1)测试代码:

#include <stdio.h>
#include <string.h>

struct stu
{
        char name[21];
        int age;
};

void OperateStruct(struct stu tmp);

int main()
{
        struct stu s;
        strcpy(s.name,"小香香");
        s.age = 19;

        printf("调用前:s.name = %s,s.age =%d \n",s.name,s.age);
        // 调用OperateStruct函数
        OperateStruct(s);

        printf("调用后:s.name = %s,s.age =%d \n",s.name,s.age);

        return 0;
}

void OperateStruct(struct stu tmp)
{
        printf("tmp.name = %s,tmp.age =%d \n",tmp.name,tmp.age);

        strcpy(tmp.name,"大臭臭");
        tmp.age = 21;
        printf("tmp.name = %s,tmp.age =%d \n",tmp.name,tmp.age);

        return ;
}

(2)测试结果:
在这里插入图片描述

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

  修改 6.1 的代码:

//(1)void OperateStruct(struct stu *tmp);
//(2)OperateStruct(&s);3void OperateStruct(struct stu *tmp)
{

        printf("tmp为:%p\n",tmp);

        printf("tmp->name = %s,tmp->age =%d \n",tmp->name,tmp->age);

        strcpy(tmp->name,"大臭臭");
        tmp->age = 21;

        printf("tmp->name = %s,tmp->age =%d \n",tmp->name,tmp->age);

        return ;
}

测试结果:
在这里插入图片描述

6.3 结构体数组名做函数参数

  与普通的数据类型数组名,作为函数的参数一样。参数的写法一般有一下三种:

n:结构体数组的元素数目

(1)用一个较大容量的数组来接收,如下tmp[100];注意这个不是传参喔,在调用函数的时候,传递的是数组名,本质上是数组的首元素的地址。
void set_stu_pro(struct stu tmp[100], int n)2)不标明容量
void set_stu_pro(struct stu tmp[], int n)3)用一个指针来接收,因为在调用函数的时候,传递的是数组名,本质上是数组的首元素的地址。
void set_stu_pro(struct stu *tmp, int n)

测试代码:

#include<stdio.h>

//结构体类型的定义
struct stu
{
	char name[50];
	int age;
};

//void set_stu_pro(struct stu tmp[100], int n)
//void set_stu_pro(struct stu tmp[], int n)
void set_stu_pro(struct stu *tmp, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		sprintf(tmp->name, "name%d%d%d", i, i, i);
		tmp->age = 20 + i;
		tmp++;
	}
}

int main()
{
	struct stu s[3] = { 0 };
	int i = 0;
	int n = sizeof(s) / sizeof(s[0]);
	set_stu_pro(s, n); //数组名传递

	for (i = 0; i < n; i++)
	{
		printf("%s, %d\n", s[i].name, s[i].age);
	}

	return 0;
}

二、共用体(联合体)

1.概述

  (1)联合union是一个能在同一个存储空间存储不同类型数据的类型;也就是说共用体也是一种数据类型,定义初始化与普通的数据类型相似。但是共用体对应的那片空间能存储不同类型的数据,就像是一个房子,把房子给你了,住人也行,养猪也行。

  (2)联合体所占的内存长度等于其最长成员的长度倍数,也有叫做共用体;

  (3)同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用;

  (4)共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖;

  (5)共用体变量的地址和它的各成员的地址都是同一地址。

2.共用体存在内存对齐

  首先要明白 共用体所占的内存大小等于其最大成员的占用内存倍数

2.1 第一次测试:

(1)测试代码

#include <stdio.h>
#include <string.h>

// 声明一个共用体
union Test
{       
        char str[5];
        int  i_a;
        float f_b;
        double d_c;
};
int main()
{
        union Test uT;

        printf("sizeof(double) = %d \n",sizeof(double));

        printf("sizeof(union Test) = %d \n",sizeof(union Test));

        return 0;
} 

  (2)测试结果分析,共用体的成员变量有四个,分别是四种类型的数据 char,int,double,float,其中一个 double 成员变量占用的内存最大,所以在分配给共用体内存空间的时候,以8个字节为单位分配。这个与结构体的内存分配相似。就不详细说明了,可以看看结构体的内存对齐解释,在目录的2.4。
在这里插入图片描述

2.2 第二次测试:

  (1)修改代码:将 char str[5] 改为 str[9],其他不变

union Test
{       
        char str[9];
        int  i_a;
        float f_b;
        double d_c;
};

  (2)测试结果:共用体的占用内存变为了16,因为 char str[9] 占用9个字节,以8个字节为单位,一次分配 8 个字节不够用,要再分配一次8个字节,一共 16 个字节。
在这里插入图片描述

3. 共用体的一些特性

  (1)同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用;这个是什么意思呢?就是同一时刻,只能够操作共用体里面的一个成员变量,比如说赋值,打印值。这个就像是一个房子,一次只能住一个人,一次只能一个人使用。那么如果同时操作共用体里面的多个变量会怎么样?看第(2)

  (2)共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖;如果同时操作多个变量,只有最后操作的变量有效。也就是说谁狗到最后,谁就能用这个房子。下面就来测试一下

3.1 第一次测试

(1)测试代码

#include <stdio.h>
#include <string.h>

// 声明一个共用体
union Test
{       
        char str[9];
        int  i_a;
        float f_b;
        double d_c;
};
int main()
{
        union Test uT;

        // 对共用体初始化
         strcpy(uT.str,"xxf");
         printf("uT.str = %s\n",uT.str);
        
        return 0;
} 

(2)测试结果:str 正常,这一次我们只给一个变量赋值。
在这里插入图片描述

3.2 第二次测试

  第二次测试,我们同时给多个成员变量赋值,并且打印出成员变量的值,其余的不变。

  (1)测试代码

int main()
{
        union Test uT;

        // 对共用体初始化
         strcpy(uT.str,"xxf");
         uT.i_a = 18;
         uT.f_b = 1.122;
        
         printf("uT.str = %s\n",uT.str);
         printf("uT.i_a = %d\n",uT.i_a);
         printf("uT.f_b = %f\n",uT.f_b);
     
        return 0;
} 

  (2)测试结果:只有 uT.f_b 这个成员变量的值是正常的,其余两个都是不正常的。为什么?因为大家同时想使用共用体的内存的时候,内存只有那么多,前面的变量会被挤出去,只有最后的变量留了下来才能使用。
在这里插入图片描述

3.2 第三次测试

  共用体变量的地址和它的各成员的地址都是同一地址。
(1)测试代码:

int main()
{
         union Test uT;
         printf("p:uT.str = %p\n",uT.str);
         printf("p:uT.i_a = %p\n",&(uT.i_a));
         printf("p:uT.f_b = %p\n",&(uT.f_b));
         printf("p:uT.d_c = %p\n",&(uT.d_c));
		
		 return 0;
}

  (2)测试结果:都是相同的,因为每次使用空间的只有一个变量,并且这个空间是分配好的了。谁最后使用,就得把前面的赶出去。
在这里插入图片描述

4. 共用体与结构体的异同

  (1)相似点,结构体和共用体都存在内存对齐的特点,就是要以占用空间最大的那种成员变量为分配单位。

  (2)但是一个结构体占用的内存大于等于所有的成员变量的占用空间的总和。而共用体是大于等于占用最大空间的那个成员变量。注意这里的“个” 和 “种”,“种”指的是成员变量的种类,比如说 int ,char 等等,“个”是第几个成员变量。

  (3)用一个住房子来做不完全正确说明。现在有一批人想要租房子,有的人想要单间,有的人想要两房一厅,有的人想要三房一厅,但是房地产的地只有一块,为了能兼顾所有人,就建了三房一厅的房子。谁来了就租给谁,前面的人得搬出去。空间是三房一厅的就能满足所有人。这个就像是共用体。

  但是有的人是拖家带口加上丫鬟啥的几十口人,要一起住,那么总空间得扩大,同时容下几十口人,并且是同时住,能同时使用房子。这个就像是结构体

三、枚举类型

  枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。

1.枚举类型的定义

enum  枚举名
{
	枚举值表
};

2.测试代码

#include <stdio.h>

enum weekday
{
	sun = 2, mon, tue, wed, thu, fri, sat
} ;

enum bool
{
	flase, true
};

int main()
{
	enum weekday a, b, c;
	a = sun;
	b = mon;
	c = tue;
	printf("%d,%d,%d\n", a, b, c);

	enum bool flag;
	flag = true;

	if (flag == 1)
	{
		printf("flag为真\n");
	}
	return 0;
}

四、typedef 关键字

1.概述

  (1)typedef 为C语言的关键字,作用是 为一种数据类型(基本类型或自定义数据类型)定义一个新名字,不能创建新类型

   (2)与#define不同,typedef仅限于数据类型,而不是能是表达式或具体的值
  (3)#define发生在预处理,typedef发生在编译阶段

2.测试代码

#include <stdio.h>

typedef int INT;
typedef char BYTE;
typedef BYTE T_BYTE;
typedef unsigned char UBYTE;

typedef struct type
{
	UBYTE a;
	INT b;
	T_BYTE c;
}TYPE, *PTYPE;

int main()
{
	TYPE t;
	t.a = 254;
	t.b = 10;
	t.c = 'c';

	PTYPE p = &t;
	printf("%u, %d, %c\n", p->a, p->b, p->c);

	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值