Bootstrap

Struct结构体

结构体

C语言的结构体(struct)从本质上讲自定的数据类型,只不过这种数据类型比较复杂,是由int、float、char等基本数据类型组成。是一种聚合数据类型。

在实际开发中,我们可以将一组类型不同,但是用来描述同一件事物的变量放到结构体中。比如:学生有名字、年龄、身高、成绩等多个属性。使用结构体之后,就不需要零散定义多个变量,可以将他们都放到结构体中。

1.1 结构体概述

数组是用来存储一组相同数据类型的集合,如果要存储不同数据类型,那么数组就满足不了需求,这个时候用到结构体。

C语言结构体的格式:

struct 结构体名{
结构体所要包含的数据类型
};

我的理解是:结构体是一种集合,里面定义了多个变量或者数组。他们的数据类型可以相同,可以不同,每一个这样的变量或者数组,称为结构体的成员。一般的数据类型只能定义一个变量,数组类型可以定义多个相同类型变量,而结构体可以定义多个不同类型变量。

struct Student
{
	char name[30];//学生姓名
	int age;//学生年龄
	float tall;//学生身高
	double score;//学生成绩
};

Student结构体名,包含4个成员:name、age、tall、score。结构体成员的定义和普通数据类型的定义相同,只是不做初始化。

1.2结构体变量

既然结构体是一种自定义的数据类型,那么就可以通过它来定义变量,比如

struct Student stu1,stu2,stu3;

使用结构体Student创建了三个结构体变量,分别是stu1、stu2、stu3。

注意:定义结构体变量时,struct关键字不能少。(struct Student是一个数据类型,单struct或Student定义变量是不行的)

可以将结构体Student看成一个模板,定义出来的变量都具有相同的性质。也可以在定义结构体的时候,同时定义结构体变量,只需要将变量名放到结构体定义后面即可。

在这里,stu2是在定义结构体时,同时定义变量;stu1是使用struct Student这个数据类型定义的

#include <stdio.h>

struct Student
{
	char name[30];//学生姓名
	int age;//学生年龄
	float tall;//学生身高
	double score;//学生成绩
}stu2;


int main()
{
	struct Student stu1;
	return 0;
}

也可以在定义结构体的时候不给结构体名字,这种没有名字的结构体,称为:匿名结构体。

struct 
{
	int a;
	float b;
	double c;
	char d;
}node1,node2;

因为匿名结构体没有名字,所以后续没有办法再给这个结构体定义结构体变量,只能在定义结构体的时候创建变量。匿名结构体只能在定义结构体时创建变量

1.3结构体成员的赋值和获取

结构体中使用点号(.)来访问结构体成员,格式

结构体变量名.成员名;

#include <stdio.h>
#include <string.h>
struct Student
{
	char name[30];//学生姓名
	int age;//学生年龄
	float tall;//学生身高
	double score;//学生成绩
}stu2;//结构体变量stu2

int main()
{
	struct Student stu1;//结构体变量stu1
	stu1.age=18;
	stu1.tall=180;
	stu1.score=99.5;
	//stu1.name="小明";//数组不能这样赋值
	strcpy(stu1.name,"小明");
	printf("%s %d %.1f %.1lf\n",stu1.name,stu1.age,stu1.tall,stu1.score );
	return 0;
}

除了这种对成员变量单一赋值的方法,也可以在定义时整体赋值(注意这里,只能在定义的时候整体赋值,否则只能单个赋值

这里主要是因为当结构体变量被声明后,它在内存中已经有了确定的存储位置和布局。如果允许整体赋值,可能会引发内存管理上的混乱。

#include <stdio.h>
#include <string.h>
struct Student
{
	char name[30];//学生姓名
	int age;//学生年龄
	float tall;//学生身高
	double score;//学生成绩
}stu2={"张三",29,175,100};//结构体变量stu2

int main()
{
	struct Student stu1;//结构体变量stu1
	stu1.age=18;
	stu1.tall=180;
	stu1.score=99.5;
	//stu1.name="小明";//数组不能这样赋值
	strcpy(stu1.name,"小明");
	printf("%s %d %.1f %.1lf\n",stu1.name,stu1.age,stu1.tall,stu1.score );
	printf("%s %d %.1f %.1lf\n",stu2.name,stu2.age,stu2.tall,stu2.score );
	//只能在定义时整体赋值
	// struct Student stu3;
	// stu3={"tom",30,160,80};
	// printf("%s %d %.1f %.1lf\n",stu3.name,stu3.age,stu3.tall,stu3.score );
	return 0;
}

这种方式仅限于定义结构体变量的时候,在使用过程中,只能对成员逐一赋值。

需要注意的是,结构体是一种自定义的数据类型,是创建变量的模板,他不占用内存,结构体变量包含实实在在的数据,需要内存来存储。

1.4结构体成员修改

结构体成员只能单个修改不能整体赋值。

给字符型数组和字符型指针赋值,只能用strcpy()

#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct A
{
	char node1[30];
	char *node2;
	int node3;
};

int main()
{
	//通过指针的形式先开辟空间使用等于号赋值,用数组的形式需要用函数strcpy()
	struct A a;
	strcpy(a.node1,"lllll");
	//开辟空间
	a.node2=(char *)malloc(30);
	a.node2="aaaaa";
	printf("%s\n",a.node1 );
	printf("%s\n",a.node2 );
	a.node3=100;
	printf("%s %s %d\n",a.node1,a.node2,a.node3 );
	printf("=====================================\n");
	a.node3=50;
	strcpy(a.node1,"LLLLL");
	a.node2="AAAAA";
	printf("%s %s %d\n",a.node1,a.node2,a.node3 );

	a.node1[0]='Z';
	printf("%s %s %d\n",a.node1,a.node2,a.node3 );
	return 0;
}

1.5结构体数据大小

结构体大小不是结构体中所有变量大小的和。

1.补齐

struct A
{
	int a;//4
	char b;//1
};//8

2.寻址

struct B
{
	int data;//4
	char a;//1
	float b;//4
	int *c;//8
	int e;//4
};//32

1.6清空结构体

清空结构体中的数据:memset

memset(&s,int ch,size_t n);

表示:将s中当前位置后面n个字节用ch来替换。

memset:作用是在一段内存中填充某个给定的值。它是对较大结构体或者数组清0的一种方式。

memset(&stu1,0,sizeof(struct Student));
#include <stdio.h>
#include <string.h>
struct Student
{
	char *name;
	int age;
	float score;
	char job[20];
}stu1={"张三",18,99.5,"computer"};


int main()
{
	printf("%s %d %f %s\n",stu1.name,stu1.age,stu1.score,stu1.job );
	memset(&stu1,0,sizeof(struct Student));
	printf("%s %d %f %s\n",stu1.name,stu1.age,stu1.score,stu1.job );
	return 0;
}

1.7结构体数组

所谓的结构体数组,是指数组中每个元素都是结构体变量,在实际开发中C语言结构体数组常被用来表示一个拥有相同数据结构的群体。

#include <stdio.h>
#include <stdlib.h>
#define LEN 2

struct Student
{
	char *name;
	int num;//学号
	char sex;//M W
	float score[3];
	char job[20];//专业
}clsz[LEN];

int main()
{
	//struct Student stu1;

	for (int i = 0; i < LEN; ++i)
	{
		printf("请输入%d位学生信息\n",i+1 );
		printf("请输入姓名:");
		char *addname=(char *)malloc(20);
		scanf("%s",addname);
		clsz[i].name=addname;

		printf("请输入人学号:");
		scanf("%d",&clsz[i].num);

		getchar();//获取回车这个字符

		printf("请输入性别:");
		scanf("%c",&clsz[i].sex);

		printf("请输入语文成绩:");
		scanf("%f",&clsz[i].score[0]);

		printf("请输入数学成绩:");
		scanf("%f",&clsz[i].score[1]);

		printf("请输入英语成绩:");
		scanf("%f",&clsz[i].score[2]);

		printf("请输入专业:");
		scanf("%s",clsz[i].job);
	}

	for (int i = 0; i < LEN; ++i)
	{
		printf("%s %d %c %.1f %.1f %.1f %s\n",clsz[i].name,clsz[i].num,clsz[i].sex,clsz[i].score[0],clsz[i].score[1],clsz[i].score[2],clsz[i].job );
	}
	return 0;
}

1.8结构体嵌套

结构体内部的结构体,必须要定义结构体变量才能使用。

#include <stdio.h>
// struct score
// {
// 	float math;
// 	float computer;
// 	float chinese;
// };

struct Student
{
	char *name;
	int num;
	struct score 
	{
		float math;
		float computer;
		float chinese;
	}sc;
	//struct score sc;
};

int main()
{
	struct Student stu1;
	stu1.name="qlf";
	stu1.num=1001;
	stu1.sc.math=99.5;
	stu1.sc.computer=80.5;
	stu1.sc.chinese=100;
	printf("%s %d %f %f %f\n",stu1.name,stu1.num,stu1.sc.math,stu1.sc.computer,stu1.sc.chinese );
	struct Student stu2={"lll",1002,80,50,100};
	printf("%s %d %f %f %f\n",stu2.name,stu2.num,stu2.sc.math,stu2.sc.computer,stu2.sc.chinese );
	return 0;
}

1.9结构体指针

1.9.1结构体指针定义

当一个指针变量指向结构体时,我们就称它为结构体指针,C语言结构体定义的形式:

struct 结构体名 * 变量名;
#include <stdio.h>
struct Student
{
	char *name;
	int num;//学号
	char sex;//M W
	double score;
}stu1={"qlf",1001,'M',99.5},* stp2=&stu1;

int main()
{
	//定义了一个结构体指针stp,指向stu1
	struct Student * stp=&stu1;

	return 0;
}

需要注意的是:

结构体变量名和数组变量名不同,数组名在表达式中会被转换成数组的指针,而结构体名不会,无论在任何的表达式中他表示的是整个集合的本身。要想取得结构体变量的地址,必须在前面加上&。结构体变量名并不是结构体首地址,也不能像使用数组名一样使用结构体变量名。

struct Student *pstu=&Student;
struct Student *pstu=Student;

不能取一个结构体名的地址,也不能将它赋值给其他变量。

1.9.2结构体指针访问结构体成员

格式:struct Student stu1,*pointer=stu1;

stu1.member;

(*pointer).member;
pointer->member;

需要注意的问题:

1.第一种写法的.点优先级高于*,()不能省略掉,如果省略*pointer.member,这样意义不对

2.第二种写法:->是一个运算符,通过结构体指针直接获取结构体成员

#include <stdio.h>

struct Student
{
	char *name;
	int num;
	int age;
	char group;
	double score;
}stu1={"Tom",1001,18,'A',99.5},*pstu1=&stu1;

int main()
{

	struct Student *pstu2=&stu1;
	printf("%s %d %d %c %lf\n",stu1.name,stu1.num,stu1.age,stu1.group,stu1.score );
	printf("%s %d %d %c %lf\n",(*pstu1).name,(*pstu1).num,(*pstu1).age,(*pstu1).group,(*pstu1).score);
	printf("%s %d %d %c %lf\n",pstu2->name,pstu2->num,pstu2->age,pstu2->group,pstu2->score);
	return 0;
}

1.9.3结构体数组指针

#include <stdio.h>

struct Student
{
	char *name;
	int num;
	int age;
	char group;
	double score;
}clasz[3]={
	{"tom",1001,18,'A',99.5},
	{"jerry",1002,20,'B',100},
	{"marry",1003,30,'C',60}
},*ps;

int main()
{
	//数组长度
	int len=sizeof(clasz)/sizeof(clasz[0]);
	printf("%d\n",len );

	for(int i=0;i<len;i++)
	{
		printf("%s %d %d %c %lf\n",i[clasz].name,i[clasz].num,i[clasz].age,i[clasz].group,i[clasz].score );//a[i]-->*(a+i)-->*(i+a)-->i[a]
		//printf("%s %d %d %c %lf\n",(*(clasz+i)).name,(*(clasz+i)).num,(*(clasz+i)).age,(*(clasz+i)).group,(*(clasz+i)).score );
	}
	// for (ps=clasz;ps<clasz+len;ps++)
	// {
	// 	//printf("%s %d %d %c %lf\n",ps->name,ps->num,ps->age,ps->group,ps->score);
	// 	//printf("%s %d %d %c %lf\n",(*ps).name,(*ps).num,(*ps).age,(*ps).group,(*ps).score);
	// }
	return 0;
}

1.9.4结构体指针作为函数参数

结构体变量名代表整个集合数据的本身,作为函数参数时传递的是整个集合数据,也就是所有的成员。而不像数组会被编译转换为一个指针。如果结构体成员较多,传递的时间和空间开销就会很大。所以最好的办法就是用结构体指针,这是由实参向形参传递一个地址,速度非常快。

总而言之,指针除了对初学者不友好以外,它真是个好东西

#include <stdio.h>
struct Student
{
	char name[10];
	int age;
	float score;
};


//函数结束之后,形参stu空间会被释放
void inputStudent(struct Student stu)
{
	printf("请输入姓名:");
	scanf("%s",stu.name);
	printf("请输入年龄:");
	scanf("%d",&stu.age);
	printf("请输入分数:");
	scanf("%f",&stu.score);
}

void inputStudent2(struct Student *stu)
{
	printf("请输入姓名:");
	scanf("%s",stu->name);
	printf("请输入年龄:");
	scanf("%d",&stu->age);
	printf("请输入分数:");
	scanf("%f",&stu->score);
}

void showStudent(struct Student stu)
{
	printf("%s %d %f\n",stu.name,stu.age,stu.score );
}

void showStudent2(struct Student * stu)
{
	printf("%s %d %f\n",stu->name,stu->age,stu->score );
}


int main()
{

	struct Student stu1;
	//inputStudent(stu1);
	inputStudent2(&stu1);
	//showStudent(stu1);
	showStudent2(&stu1);
	return 0;
}

练习:定义一个学生结构体,结构体成员包括姓名、年龄、分数,通过函数的方式执行输入和遍历,计算平均分和60分以下学生人数。

#include <stdio.h>

struct Student
{
	char name[20];
	int age;
	float score;
}clasz[3],*ps=clasz;

void inputStudent(struct Student *stu,int len)
{
	for (int i = 0; i < len; ++i)
	{
		printf("请输入姓名:");
		scanf("%s",(stu+i)->name);
		printf("请输入年龄:");
		scanf("%d",&(stu+i)->age);
		printf("请输入分数:");
		scanf("%f",&(stu+i)->score);
	}
}

void outputStudent(struct Student *stu,int len)
{
	for (int i = 0; i < len; ++i)
	{
		printf("%s %d %f\n",(stu+i)->name,(stu+i)->age,(stu+i)->score );
	}
}

float avg(struct Student *stu,int len)
{
	float avg=0;
	for (int i = 0; i < len; ++i)
	{
		avg+=(stu+i)->score;
	}
	avg=avg/len;
	return avg;
}

int num(struct Student *stu,int len)
{
	int sum=0;
	for (int i = 0; i < len; ++i)
	{
		if((stu+i)->score<60)
		{
			sum++;
		}
	}
	return sum;
}

int main()
{
	int len=sizeof(clasz)/sizeof(clasz[0]);
	inputStudent(ps,len);
	outputStudent(ps,len);
	printf("平均分:%f\n",avg(ps,len) );
	printf("不及格人数:%d\n",num(ps,len) );
	return 0;
}

1.9.5结构体成员添加函数指针

结构体成员是函数指针

结构体中除了可以定义变量外,也可以添加函数行为。

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

struct Student{
	char name[10];
	int age;
	void (*pfunc)(void);//函数指针,指向一个无返回无参数的函数的指针
	//void *pfunc(void);//返回值为指针的函数,指针函数
	void (*padd)(int a,int b);
};

void study()
{
	printf("学习\n");
}

void play()
{
	printf("玩\n");
}

void add(int a,int b)
{
	printf("%d\n",a+b );
}

int main()
{
	struct Student stu1,stu2;
	strcpy(stu1.name,"qlf");
	stu1.age=18;
	//函数指针指向对应的函数
	stu1.pfunc=study;
	//调用结构体中指针函数
	stu1.pfunc();

	stu1.pfunc=play;
	stu1.pfunc();

	strcpy(stu2.name,"王钢蛋");
	stu2.age=30;
	stu2.padd=add;
	//调用函数指针
	stu2.padd(1,2);


	return 0;
}

练习:定义一个矩形结构体,在结构体中声明一个计算矩形面接的函数指针,在main中调用结构体的函数,实现计算矩形面积。

#include <stdio.h>

struct Rect
{
	double width;//长
	double height;//宽
	double (*parea)(double width,double height);
};

//计算面积的函数
double area(double width,double height)
{
	return width*height;
}

int main()
{
	struct Rect rect;//通过结构体定义结构体变量
	rect.width=3.14;
	rect.height=4;
	rect.parea=area;
	double result=rect.parea(rect.width,rect.height);//定义变量保存返回值
	printf("矩形面积为:%lf\n",result );
	return 0;
}

1.9.6 typedef在结构体中的应用

这里就是使用typedef为Student结构体取了一个别名,之后初始化一个变量就不需要用struct Student,直接使用别名 变量名;就可以了。

#include <stdio.h>

typedef struct Student
{
	int age;
	float score;
}Stu,*STU;



int main()
{
	Stu stu1={17,99.5};
	printf("%d %f\n",stu1.age,stu1.score );

	//原类型依然可以用
	struct Student stu2={18,99};
	printf("%d %f\n",stu2.age,stu2.score );

	// Stu Stu={17,99.5};
	// printf("%d %f\n",Stu.age,Stu.score );

	STU pstu=&stu1;
	printf("%d %f\n",pstu->age,pstu->score );

	return 0;
}

这里有一个点需要注意一下

为匿名结构体取别名应该怎么取呢,

typedef struct
{
	int age;
	float score;
}Stu;

这样就可以了。

要熟记typedef取别名的规则:

1.定义一个要取别名的数据类型的变量,例如int a;

2.前面加上typedef,例如typedef int a;

3.将变量名修改成你想取的别名,例如typedef int INT;这样就为int取了一个别名INT,以后可以使用int定义变量,同样可以使用INT定义变量:int b;等同于INT b;

;