结构体
1.什么是结构体
之前我们学过很多的数据类型,但是在我们平时处理的一些数据是C语言无法给我们提供的,例如:如何表式一个学生、一本书、一辆车;因为这些事务他既不是一个整型、也不是一个字符串等单一的数据类型,而是由这些基本数据类型的组合起了,组成的一个复合的数据类型,。
结构体:是用户自定义的一种复合数据类型;
结构体:是用户自定义的多个数据类型变量的集合
结构体可以包含什么数据类型:
基本数据类型: char/short/int/long/float/double
非基本数据类型:数组/指针/结构体
*结构体一定不能包含函数(可以包含函数指针)
2.结构体可以解决什么问题
在我们知道什么是结构体后,我们就需要明白结构体可以解决什么问题,对吧,不然我们为什么要学习结构体呢,肯定是结构体可以帮我们解决一些问题,所以我们才学习它,其实我们在前面已经介绍了,如果我们描述一个信息类型比较多的东西,我们单一的基本数据类型是无法去有效的描述的,那么我们就可以考虑使用结构体。
例如:
我们要描述一个学生的基本信息,姓名、学号、年龄,但是这个数据的数据类型是不一样的,那么我们就可以考虑使用结构体。
3.声明和定义结构体变量
结构体声明的作用:因为系统不知道这个结构体是由哪些数据类型组成的,所以我们要先告诉系统该结构体包含哪些数据类型。
在声明结构体的过程中,我们有几个注意事项:
1.大括号的后面需要加分号
2.结构体的成员之间以分号隔开
3.结构体的成员不能是函数
关键词:struct
模型:
struct 结构体名字{ -> 结构体名字最好与功能相符合。
/* 结构体成员列表 */
…;
…;
…;
}; -> 后面定义完结构体之后,记得这个位置是有一个分号。
定义结构体:
公式: 数据类型 变量名;
//那我们来声明一个存放学生基本信息的这样一个结构体,结构体名呢,我们就使用student,虽然C语言对结构体的命名没有什么特殊要求,比如我们命名位A,B,C都可以,但是这样的话我们就很难通过结构体的名字去知道它的功能了,所以我们最好使用与功能相符合的名字,这样我们一眼就可以看出这个结构体是什么功能了。
例子:
//声明一个结构体
struct student{
char name[10]; // 姓名 数组 -> 非基本数据类型
int num; // 学号 int -> 基本数据类型
int age; // 年龄 数组 -> 非基本数据类型
};
我们这样声明了一个结构体后,
系统就会知道"struct student"(新类型)是由"char name[10];" + "int num;" + "int age;"这三个东西组成。我们可以理解成"struct student"就是一个新的数据类型。
//定义一个结构体变量
struct student stu;
这个"struct person"就是一种新类型,这种类型是由一个字符变量,一个整型变量,一个整型数组组成。
4.如何声明结构体指针
//我们要怎么样去用一个结构体指针指向一个结构体呢,其实我们可以分三部走:
//1.先声明好结构体长什么样子
struct student{
char name[10];
int num;
int age;
};
//2.先声明好指针指向的目标
struct student stu;
//3.定义一个结构体指针并赋值
struct student *p = &stu;
数据类型:struct student *
变量名:p
5.成员的初始化与访问
我们在知道如何定义结构体变量和结构体指针后,我们就要知道怎么去给他们赋值,如何去使用他们访问结构体中的成员
- 定义结构体变量的同时进行初始化
struct student{
char name[10]; //姓名
int num; //学号
int age; //年龄
};
//注意初始化值的类型和顺序要与结构体声明时成员的类型和顺序一致(不常用)
struct student stu = {"shl", 1, 25};
//该方法不适用,因为如果我后续在该结构体中插入了一个新的成员,这样就把原有的成员数据类型的顺序打乱了,这样的赋值可能就会出现问题;针对这个问题,我们再来介绍另一种结构体初始话的方法,使用点后面跟成员名对指定的成员变量进行赋值,这样成员赋值的顺序是可以随意打乱的,就算是我们以后插了新的成员变量进来也没有关系
//按照任意的顺序指定初始化成员(常用),这样写有一个很大的好处,不用担心以后在结构体中增加成员
struct student stu = {
.name = "shh",
.age = 20;
};
-
结构体变量的初始化可以放在定义之后
在了解了 结构体变量在定义时进行初始化后,我们再来介绍一下在结构体变量定义之后进行初始话,在这里我们就需要知道结构体变量时如何去访问成员的:
结构变量不能像数组那样使用下标去访问其中的各个元素,而应该用结构成员引用符(.)
我们也可定义一个结构体指针去指向这个结构体变量,在使用这个指针去访问结构体的成员,但是要注意它不在是使用(.)了,而是使用(->).
struct student stu; //定义结构体变量 struct student *p = &stu; //定义一个结构体指针,并指向这个结构体变量 strcpy(stu.name, "jack");//字符数组不能直接赋值 stu.num = 3; (*p).price = 18;//这样的写法很不方便,所以我们就引入一个(->),方便我们使用结构体指针引用成员 p->age =18;//使用结构指针访问成员
-
使用一个已经存在的结构体去初始划一个新的相同类型的结构体变量(将每一个成员都一一赋值给新的结构提变量)
//我们在定义相同类型的结构体,并将之前那个已经初始的结构体变量赋值给这个新的结构体变量,结构体变量是可以直接赋值的,但是要注意数据类型一定要相同。 struct student stu2; stu2 = stu1;
6.结构体在内存中占用空间大小
相同数据类型数据,在不同的操作系统下,可能占的内存不一样。所以计算变量占多大内存时,一定要先说明是多少位的操作系统。
我先来介绍一下,在64位系统中我们的常用数据类型占多大内存,为什么是介绍一下64位系统呢,因为我现在用的是64系统,我们后面计算结构体大小,也是按照这个来的。
变量地址对齐:在内存中开辟一块变量空间时,并不是随便找一个适当大小的内存就可以了,我们对这块内存地址是由要求的,比如int型的地址必须是4的整数倍,short型数据的地址必须是2的整数倍,这些要求就是所谓的地址对齐。
常见数据类型占字节数:
常用类型 | 32位系统 | 64位系统 |
---|---|---|
char | 1 | 1 |
short | 2 | 2 |
int | 4 | 4 |
float | 4 | 4 |
double | 8 | 8 |
long | 4 | 8 |
long long | 8 | 8 |
*p | 4 | 8 |
/*那么我们如何计算结构体占用内存空间的大小呢,那我们先使用sizeof函数计算下面几个结构体的大小,通过这些结构体大小的计算,我们再来总结一下计算结构体大小的步骤:
我们用sizeof计算的结构体大小,要么是和它所有成员加起来的大小一样,要么就是比它所有成员加起来的还要大,那这是为什么呢,其实在计算结构体的大小时,我们需要注意一个地址对齐原则:这个原则是怎么样的呢,是这样的:在内存中开辟一块变量空间时,并不是随便找一个适当大小的内存就可以了,我们对这块内存地址是由要求的,比如double型变量的地址必须是8的整数倍,int型的地址必须是4的整数倍,short型数据的地址必须是2的整数倍,这些要求就是所谓的地址对齐。*/
struct data1{ //4字节
char A;
char B;
short C;
};
/*因为char型只占一个内存所以,所以无论在几号位都可以,而short只能在1/2、3/4、5/6、7/8这四种情况下占位,如果不满足,就需要补0,等补到满足情况再占位。*/
struct data2{ //6字节
char A;
short B;
char C;
};
struct data3{ //8字节
char A;
short C;
int B;
};
struct data4{ //12字节
char A;
int B;
short C;
};
struct data5{ //16字节
char A;
int B;
double C;
};
struct data6{ //24字节
char A;
double B;
int C;
};
struct data7{ //10字节
char A;
short B[3];
short C;
};
/*因为我们是64位的操作系统,所以存取内存数据的时候每次至少存取8个字节,如果是32位的操作系统每次至少存取4个字节*/
总结计算结构体大小的步骤:
1)先画图,64位系统以8个字节为一组。 1 2 3 4 5 6 7 8
2)从上往下依次计算。
3)char -> 无论是几号位都可以放。
short -> 2个字节(只能1/2、3/4、5/6、7/8)这四种情况。 -> 如果位置不对,先补0,再填充。
int -> 4个字节(只能1/2/3/4、5/6/7/8)这两种情况。 -> 如果位置不对,先补0,再填充。
long -> 8个字节(只能1/2/3/4/5/6/7/8)这种情况 -> 不能换行。
float -> 4个字节(只能1/2/3/4、5/6/7/8)这两种情况。 -> 如果位置不对,先补0,再填充。
double -> 8个字节(只能1/2/3/4/5/6/7/8)这种情况 -> 不能换行。
如果是数组,就看成单个单个成员就可以了,例如:char B[4] 等价于 char a,char b,char c,char d
如果是指针 -> 8个字节(只能1/2/3/4/5/6/7/8)这种情况 -> 不能换行。
4)找到这个结构体中最长的数据类型x,那么结果就一定是他的倍数x*n。
8 8的倍数
5)看看计算完之后的结果一共占用m个字节,如果结果是x的倍数,那么m就是结果。
如果结果不是x的倍数,那么就需要补0补到最靠近m的x倍数的值。 -> 结果最靠近m的x倍数
48个字节 48是8的倍数 结果就是48
53个字节 53不是8的倍数, 最靠近53的8的倍数是56,所以补三个0 -> 结果就是56
7.结构体数组
与普通的数组声明一样,int a[10]; int为元素的数据类型,a为数组名[10]表示申请了10的int单元的内存;
再看结构体声明:struct studentstu[10];是不是类似,struct student为数组元素的数据类型,stu为数组名,[10]为申请了10个struct Stu单元的内存;
//定义int型数组
int a[10];
struct student{
char name[10]; //姓名
int num; //学号
int age; //年龄
};
//定义结构体数组
struct student stu[10];
案例1
做一个通讯录,里面可以存放三个同学的信息,每一个同学的信息都包含:姓名,年龄,电话号码
要求: 程序运行之后,先依次对三个同学的姓名,年龄,电话号码进行注册 -> scanf()
全部同学的信息注册完之后,就输出全部的同学的信息。
#include <stdio.h>
//1. 先声明好这种新类型"struct student"长什么样子。
struct student{
char name[10]; //姓名
int age; //年龄
char tel[12]; //电话号码
};
int main(int argc,char *argv[])
{
//2. 定义一个结构体数组
struct student stu[3];
//3. 依次对结构体成员赋值,怎么样一次对三个结构体进行赋值呢,我们可以使用一个for循环,
int i;
for(i=0;i<3;i++)
{
printf("pls input no.%d name:",i+1);
scanf("%s",stu[i].name);
printf("pls input no.%d age:",i+1);
scanf("%d",&(stu[i].age));
printf("pls input no.%d tel:",i+1);
scanf("%s",stu[i].tel);
}
//4. 输出全部人的信息
for(i=0;i<3;i++)
{
printf("%s %d %s\n",stu[i].name,stu[i].age,stu[i].tel);
}
return 0;
}
案例2
使用结构体数组,设计一个简易的学生成绩管理系统,实现学生成绩的增删改查
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Student
{
char name[10]; //姓名
int num; //学号
char class[10]; //班级
int score; //分数
}student;
student stu[10]; //学生信息结构体数组
int total_num; //学生总人数
//添加学生
void add_student()
{
int n, i;
printf("请输入添加学生的个数:");
scanf("%d" ,&n);
for(i=0;i<n;i++)
{
printf("请输入第%d个学生的信息\n", total_num + 1);
printf("请输入姓名:");
scanf("%s", stu[total_num].name);
printf("请输入学号:");
scanf("%d", &stu[total_num].num);
printf("请输入班级:");
scanf("%s", stu[total_num].class);
printf("请输入分数:");
scanf("%d", &stu[total_num].score);
total_num++;
}
}
//显示所有学生信息
void show_student()
{
int i;
printf("姓名\t学号\t班级\t成绩\n");
for(i=0;i<total_num;i++)
{
printf("%s\t%d\t%s\t%d\n", stu[i].name, stu[i].num, stu[i].class, stu[i].score);
}
}
//输入姓名查找学生
int serch_student()
{
int i=0;
char s[10];
int flag = 0;
printf("请输入学生姓名:");
scanf("%s", s);
for(i=0; i<total_num; i++)
{
if(strcmp(s,stu[i].name) ==0)//查到了学生信息了
{
flag = 1;//查找到学生信息,flag置1
printf("姓名\t学号\t班级\t成绩\n");
printf("%s\t%d\t%s\t%d\n", stu[i].name, stu[i].num, stu[i].class, stu[i].score);
return i;
}
}
if(flag == 0)
{
printf("未查找到该学生信息!\n");
return -1;
}
}
//删除学生信息
void delete_student()
{
int i;
int ret;
ret = serch_student();
for(i=ret;i<total_num-1;i++)
{
stu[i] = stu[i+1];
}
total_num = total_num -1;
}
//修改学生分数
void modify_student()
{
int i;
int ret;
int buf;//临时存放修改的新成绩
ret = serch_student();
if(ret != -1)//查到了该学生
{
printf("请输入新成绩:");
scanf("%d", &buf);
stu[ret].score = buf;
}
}
int main(int argc,char *argv[])
{
int temp;
do{
printf("/*************/ \n");
printf("1.添加学生信息 \n");
printf("2.显示学生信息 \n");
printf("3.查询学生信息 \n");
printf("4.删除学生信息 \n");
printf("5.修改学生信息 \n");
printf("6.退出学信系统 \n");
printf("/*************/ \n");
printf("请输入你的选择: ");
scanf("%d",&temp);
switch (temp)
{
case 1:
add_student();
break;
case 2:
show_student();
break;
case 3:
serch_student();
break;
case 4:
delete_student();
break;
case 5:
modify_student();
break;
case 6:
break;
default:
printf("输入错误!");
break;
}
}while (temp !=6);
return 0;
}