结构体的意义
之前学习的,整型浮点型,字符,数组,字符串等都是分散的数据,有时候我们需要很多类型的数来表示一个整体,比如学生信息包含整型的学号,字符串的名字,字符的性别,浮点型的分数等,这就是结构体存在的意义。结构体和数组的区别是,数组中的元素的类型一致,而结构体允许不同的类型的数据集合。
定义一个结构体:
struct Student //编程习惯要求结构体名字以大写开头
{
int num;
char name[32];
char sex;
int age;
double score;
char addr[32];
}; //不要忘记分号
以上的定义属于一个模板,在定义一个结构体时,一般不会先赋值。
结构体概念
1. 每个成员都是结构体中的一个域,也称为域表或成员列表
2. 在申明一个结构体的时候在 结束大括号 和 分号 之间可以直接定义几个 属于这个结构体的变量 ,但是尽量不要这样。
3. 更好的定义和使用的方法:
int main()
{
int a =10;
struct Student stu1;
struct Student stu2;
stu1.num = 1;//通过”.“运算符来访问结构体当中的成员(域)
stu1.age = a;
stu1.score = 98.5;
strcpy(stu1.name,"majiaming"); //不能写成” stu1.name = "majiaming"; ”
strcpy(stu1.addr,"上海");
printf("学号:%d,年龄:%d,分数:%lf,名字:%s,地址:%s\n",
stu1.num,stu1.age,stu1.score,stu1.name,stu1.addr);
return 0;
}
或者使用更便利的方式:
struct Student stu2 = {2,"张三",'g',12,99.5,"北京s"};
或者使用类似数组的结构:
int main()
{
struct Student arr2[3] =
{{1,"张三",'g',17,99.5,"北京"},{2,"李斯",'m',14,69.5,"上海"},{3,"王五",'g',19,100,"深圳"}};
int len = sizeof(arr2)/sizeof(arr2[0]); //len其实就是“struct Student arr2[3]”中的3
for(int i=0; i<len; i++){
printf("学号:%d,年龄:%d,分数:%lf,名字:%s,地址:%s\n",
arr2[i].num,arr2[i].age,arr2[i].score,arr2[i].name,arr2[i].addr);
}
return 0;
}
使用结构体实现“选票系统”的应用例子:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct XuanMin
{
char name[32];
int tickets;
};
int main()
{
struct XuanMin xm[3];
int i;
int len = sizeof(xm)/sizeof(xm[0]);//len其实就是“XuanMin xm[3]”中的3
int total = 5; //假设共有5人投票
char tempName[32];
int error = 0;
//登记选民的名字(name)
for(i=0; i<len; i++){
xm[i].tickets = 0;
printf("请输入第%d个选民的名字:");
scanf("%s",xm[i].name);
}
//登记投票情况(tickets)
for(i=0; i<total; i++){
printf("你是%d号投票人,请输入你要投的人名:(一人一票)\n",i);
memset(tempName,'\0',sizeof(tempName)); //先将tempName变量内容清空
scanf("%s",tempName);
for(int j=0; j<len; j++){
if(strcmp(tempName,xm[j].name) == 0){ //如果判断写的人名和候选人人名一致,则该候选人加一票
xm[j].tickets++;
break;
}else{
error++; //如果名字对不上则error增加1
}
}
if(error == len){ //如果error和名字数量一样,则说明找不到对应的名字
printf("没有找到对应候选人,你没有机会了,有请下一位投票者!\n");
}
error = 0; //error信号清零
}
printf("正在计算...\n");
struct XuanMin max = xm[0];
for(i = 0; i<len; i++){
printf("候选人%d号姓名为:%s, 得%d票!\n",i, xm[i].name, xm[i].tickets);
if(max.tickets < xm[i].tickets){
max = xm[i];
}else if(max.tickets == xm[i].tickets && i != 0){ //因为max一开始就等于xm[0],所以i=0的时候必然等于,没有意义!
puts("检测到平票,本次投票结果作废,请重新投票");
exit(-1);
}
}
printf("最后宣布结果,名为%s的候选人以%d票位列第一,当选!",max.name, max.tickets);
return 0;
}
结构体指针
通过结构体变量地址来访问该结构体需要一个变量来保持这个地址,这和之前说的指针其实是一样的,只不过指针的类型是结构体。
结构体指针定义方式和普通int型和char型指针定义类比,其实换汤不换药:
#include <stdio.h>
struct Test
{
int idata;
char cdata;
};
int main()
{
int a;
int *p = &a;
char c;
char *p = &c;
struct Test t1;
struct Test *ps = &t1; // “struct Test” 就相当于 “int" 或 “char”
printf("t1的idata= %d\n",t1.idata);//变量名访问,用点运算符
printf("t1的idata= %d\n",ps->idata);//变量名访问,用“->”运算符 !!!!
return 0;
}
那么也可以通过指针修改之前 Student 结构体的代码:
#include <stdio.h>
#include <string.h>
struct Student
{
int num;
char name[32];
char sex;
int age;
double score;
char addr[32];
};
int main()
{
struct Student arr2[3] =
{{1,"张三",'g',17,99.5,"北京"},{2,"李斯",'m',14,69.5,"上海"},{3,"王五",'g',19,100,"深圳"}};
struct Student *p;
p = arr2; //数组名依然是首地址,所以arr2不需要要取地址符&!!
//struct Student *p = arr2; //也可以这样写
int len = sizeof(arr2)/sizeof(arr2[0]);
for(int i=0; i<len; i++){
printf("学号:%d,年龄:%d,分数:%lf,名字:%s,地址:%s\n",
p->num,p->age,p->score,p->name,p->addr); //直接用地址来调用
p++;//使用地址调用后不要忘记每次循环结束前要将地址偏移到下一位
}
return 0;
}
同时,也可以用结构体指针,结合函数和二级指针改写选票系统:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct XuanMin
{
char name[32];
int tickets;
};
void initXms(struct XuanMin **pxm, int *pt)
{
if(*pxm == NULL){
printf("请输入有几个人参选:\n");
scanf("%d",pt);
*pxm = (struct XuanMin*)malloc(*pt * sizeof(struct XuanMin)); //根据输入的人数多少来动态分配内存字节
}
for(int i=0; i<*pt; i++){
(*pxm)->tickets = 0;
printf("请输入第%d个选民的名字:\n",i+1);
scanf("%s",(*pxm)->name);
(*pxm)++;
}
*pxm = *pxm - *pt; //由于使用了二级指针,因此函数不需要返回值,*pxm的值就是结构体指针xm,但是由于pxm是个二级指针,所以可以直接影响结构体xm
//所以,在函数结束的时候就有必要将*pxm(即xm)指回结构体首地址
}
void printXms(struct XuanMin *p, int len)
{
for(int i=0; i<len; i++){
printf("候选人%d号姓名为:%s, 得%d票!\n",i+1, p->name, p->tickets);
p++;
}
}
void doVot(struct XuanMin *p, int len)
{
int error = 0;
char tempName[32]; //注意这里一定是定义字符串变量,而不是*tempName这种字符串常量!!!
struct XuanMin *pbak = p;
for(int i=0; i<5; i++){
printf("你是%d号投票人,请输入你要投的人名:(一人一票)\n",i+1);
memset(tempName,'\0',sizeof(tempName)); //先将tempName变量内容清空
scanf("%s",tempName);
for(int j=0; j<len; j++){
if(strcmp(tempName,p->name) == 0){ //如果判断写的人名和候选人人名一致,则该候选人加一票
(p->tickets)++;
break;
}else{
error++; //如果名字对不上则error增加1
}
p++;
}
if(error == len){ //如果error和名字数量一样,则说明找不到对应的名字
printf("没有找到对应候选人,你没有机会了,有请下一位投票者!\n");
}
error = 0; //error信号清零
p = pbak; //每次遍历结束要把指针重新指回开头
}
}
struct XuanMin* getMax(struct XuanMin *p, int len)
{
struct XuanMin *max = p;
for(int i = 0; i<len; i++){
printf("候选人%d号姓名为:%s, 得%d票!\n",i+1, p->name, p->tickets);
if(max->tickets < p->tickets){
max = p;
}else if(max->tickets == p->tickets && i != 0){ //因为max一开始就等于xm[0],所以i=0的时候必然等于,没有意义!
puts("检测到平票,本次投票结果作废,请重新投票");
exit(-1);
}
p++;
}
return max;
}
int main()
{
struct XuanMin *xm = NULL;
struct XuanMin *final;
int total = 0;
struct XuanMin **pxm = &xm; //定义一个二级指针指向结构体指针xm的地址
int *pt = &total;
initXms(pxm,pt);
doVot(xm,total);
final = getMax(xm,total);
printf("%s以%d票当选!", final->name, final->tickets);
return 0;
}
共用体/联合体
1. 有时候同一内存空间存放类型不同,不同类型的变量共享一块空间
2. 像结构体但是有区别:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct TestT //结构体
{
int idata;
char cdata;
double ddata;
};
union TestU //联合体
{
int idata;
char cdata;
double ddata;
};
int main()
{
struct TestT t1;
union TestU u1;
printf("结构体t1的大小是:%d\n",sizeof(t1));
printf("联合体u1的大小是:%d\n",sizeof(u1));
return 0;
}
联合体的大小取决于其内部数据中所占空间最大的那个类型的大小。对于这个例子,double占8个字节,比int(4) 和 char(1) 都大,所以联合体的大小就是8个字节。
如果打印这些变量的地址:
printf("idata:%p\n",&t1.idata);
printf("cdata:%p\n",&t1.cdata);
printf("ddata:%p\n",&t1.ddata);
uu
printf("idata:%p\n",&u1.idata)u;
printf("cdata:%p\n",&u1.cdata);
printf("ddata:%p\n",&u1.ddata);
可见,联合体的起始地址都是相同的,占用的是同一片内存空间。
联合体/共用体的数据覆盖
对于联合体,如果定义了联合体一个成员的值之后再定义另一个,然后要求打印最开始的那个值可能出现错误,因为公用的内存空间,第一个写的值可能被新的值覆盖。
使用联合体实现以下问题:“有若干个人员的数组,其中有学生和教师,学生的数据中必须包括:姓名,班级;教师的数据包括:姓名,科目。要求用同一个表格来处理”
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct Person
{
char name[32];
char zhiYe;
union S{
int class;
char keMu[12];
}mes; //虽然结构体定义中不推荐直接创建变量,但是这是特殊情况,直接定义了结构体中一个名为mes的联合体变量
};
int main()
{
struct Person p[2];
int i;
for(i=0; i<2; i++){
printf("请输入职业:t代表老师,s代表学生\n");
scanf("%c",&(p[i].zhiYe));
if(p[i].zhiYe == 's'){
printf("请输入学生班级:\n");
scanf("%d",&(p[i].mes.class));
printf("请输入学生名字:\n");
scanf("%s",&(p[i].name));
getchar();//吸收回车!!否则第二次就无法识别s和t
}else if (p[i].zhiYe == 't'){
printf("请输入老师的科目:\n");
scanf("%s",&(p[i].mes.keMu));
printf("请输入老师名字:\n");
scanf("%s",&(p[i].name));
getchar();//吸收回车!!否则第二次就无法识别s和t
}else{
printf("无法识别,程序退出");
exit(-1);
}
}
for(i=0; i<2; i++){
if(p[i].zhiYe == 's'){
printf("学生的名字是:%s,班级是%d\n",p[i].name, p[i].mes.class);
}else{
printf("老师的名字是:%s,科目是%s\n",p[i].name, p[i].mes.keMu);
}
}
return 0;
}