1.结构体指针的概念
1、知识点:
- 与变量的两种访问方式一样,结构体变量也有两种访问方式:通过结构体变量名访问,通过结构体变量地址访问。这里结构体变量地址就是结构体指针。
- 普通指针可以引用变量,同时普通指针也可以引用一维数组,因为数组的名字就是指针类型的地址常量,也是它第首个元素的地址。类似的,结构体指针可以引用结构体变量,当然结构体指针也可以引用结构体数组,因为结构体数组的名字就是结构体指针类型的地址常量,也是它首个结构体的地址。
2.通过结构体指针间接访问结构体变量
1、标准写法:
- 使用剑号运算符"->"。 结构体指针->成员变量。
#include <stdio.h> struct Test { int idata; char cdata; }; int main(int argc, char const *argv[]) { struct Test t1 = {10, 'c'}; struct Test *ps = &t1; printf("t1的idata=%d\n", ps->idata); //使用剑号运算符"->"读取结构体变量的值 printf("t1的cdata=%c\n", ps->cdata); ps->cdata = '2'; //使用剑号运算符"->"修改结构体变量的值 printf("t1的cdata=%c\n", ps->cdata); return 0; }
2、啰唆的写法:
- 指针取星花,再用点运算符".":不推荐,缺少了对剑号运算符的使用。
printf("t1的cdata=%c\n", (*ps).cdata);
3.通过结构体指针间接访问结构体数组
1、标准写法:
- 结构体指针偏移后用剑号运算符"->"。
#include <stdio.h> struct Student { int num; char name[32]; }; int main(int argc, char const *argv[]) { struct Student arr[3] = { {1, "张三"}, {2, "李四"}, {3, "王五"} }; struct Student *parr = arr; int len = sizeof(arr) / sizeof(arr[0]); for(int i=0; i<len; i++){ printf("学号:%d,姓名:%s\n", (parr+i)->num, (parr+i)->name); //结构体指针偏移 } return 0; }
2、见怪不怪的写法:
- 结构体指针下标法,然后用点运算符".",这种方法比较直观,可以在给函数传递结构体数组后使用
for(int i=0; i<len; i++){ printf("学号:%d,姓名:%s\n", parr[i].num, parr[i].name); //结构体指针下标法 }
- 结构体指针自加,使用的比较多,也比较直观,但是存在缺点(因为对p进行了修改,下次再用容易造成数组越界)。需要每次都让p回到数组头部。
int i; parr = arr; //使用前让指针复位 for(i=0; i<len; i++){ printf("学号:%d,姓名:%s\n", parr->num, parr->name); //结构体指针自加 parr++; }
- 结构体指针偏移后取星花,再使用点运算符"."。不推荐,因为要注意优先级的问题,需要加多个括号,同时缺少了剑号运算符的使用。
for(int i=0; i<len; i++){ printf("学号:%d,姓名:%s\n", (*(parr+i)).num, (*(parr+i)).name); }
- 结构体数组名当做指针来使用。也不推荐用,因为我们终归是使用定义出来的指针,而不是地址常量
for(int i=0; i<len; i++){ printf("学号:%d,姓名:%s\n", (arr+i)->num, (arr+i)->name); }
4.习题(函数形参接收结构体数组)
代码心得:
- 程序写一部分就编译试一下,不要等到全部写完才编译,否则错误很多看不完,而且有些地方虽然没语法错,但是通过提前测试,可以补充一些细节。
- 小技巧,可以根据指针函数返回一个指针类型的数据,从而把函数返回值赋值给指针本身,修改指针自身的值,而不需要通过给函数传递二级指针的方式进行修改。
- 可以通过给指针函数传递结构体数组,获取结构体数组中的的某个结构体的的地址。(结构体数组有点像二维数组,但并不是)
习题1:用函数改进选票系统,让程序看起来更整洁。练习结构体指针、结构体数组、指针函数等知识点
- 思路:显然选民系统中有三个功能,因此我们将这三个功能写成API。通过想象内存空间,我们可以看出这几个函数都是在对堆区开辟的动态内存(可以看成结构体数组)进行操作。
声明结构体1: struct Candidate { char name[32]; int tickets; }; 代表候选人的名字和得票数 f1. 封装初始化选民信息的API: struct Candidate* initXms(struct Candidate *xm, int len); 函数形参接收的是:结构体数组、结构体数组长度的地址; 指针函数,返回结构体数组的首地址 f1.1 调用断言函数assert,断言xm这个指针没被初始化: assert(xm = NULL); f1.2 调用malloc函数,在堆区开辟一个长度为len的结构体数组,数组中一个元素(结构体)的占用 空间用sizeof运算符计算,将首地址保存在xm中: xm = (struct Candidate*)malloc(len*sizeof(struct Candidate)); //内在逻辑:不做free,让结构体数组的生命周期一直到程序结束 f1.3 for循环,代表结构体数组下标(第i个选民)的循环变量i从0开始,<len 时,进入循环 f1.3.1 通过结构体指针下标法,初始化第i位选民的得票数为0: xm[i].tickets = 0; f1.3.2 通过键盘输入来确认结构体数组中每位候选人的名字,保存在每个结构体变量的name中: gets(xm[i].name); //这是结构体指针下标法,或者用指针自加法: gets(xm->name); f1.4 返回初始化完毕后的堆区结构体数组的首地址xm f2. 封装打印选民信息的API: void printXms(struct Candidate *xm, int len); 函数形参接收的是:结构体数组、结构体数组长度 f2.1 for循环,代表结构体数组下标(第i个选民)的循环变量i从0开始,<len 时,进入循环 f2.1.1 通过结构体指针下标法,打印每个候选人的名字和得票数 f3. 封装唱票的API: int doVote(struct Candidate *xm, int len); 函数形参接收的是:结构体数组、结构体数组长度; 返回值是废票数目 f3.1 for循环,代表投票人的循环变量i从0开始,<total 时进入循环 //变量total代表总投票人数 f3.1.1 令标记废票的变量mark,从开始进入循环时变成0,用0代表找不到候选人,用1代表找到候选人 f3.1.2 通过键盘输入的方式来确认投票人投给的对象,将字符串保存在一个临时字符数组tempName[32]中 需要注意的是,每次进入唱票循环时,调用memset()函数用'\0'清空tempName f3.1.3 for循环,代表结构体数组下标(第j个选民)的循环变量j从0开始,<len 时,进入循环 //内在逻辑:每个人投完票后,需要将他输入的字符串与结构体数组中每个候选人的名字进行比较 f3.1.3.1 用strcmp()函数判断,tempName是否和xm[j].name完全相同, f3.1.3.1.1 如果是,说明能找到候选人 f3.1.3.1.1.1 通过指针下标法,让这个人的得票数加1: xm[j].tickets++; f3.1.3.1.1.2 修改代表找到候选人的变量mark: mark = 1; //24行 f3.1.3.1.1.3 找到人后就不用继续往下找了,用break提前退出内层循环 f3.1.4 经过f3.1.3的投票人名比对环节后,判断代表找到候选人的变量mark是否等于0 //24行 f3.1.4.1 如果是,说明这是个弃票 那么,修改记录弃票的循环变量invalidTickets: invalidTickets++; f3.2 返回弃票数目 f4. 封装选出得票最多的人的API: struct Candidate* getMax(struct Candidate *xm, int len); 函数形参接收的是:结构体数组、结构体数组长度; 指针函数,返回结构体数组中某个结构体的地址 f4.1 定义一个结构体指针max,它代表得票数最多人的地址,不妨认为结构体数组xm中首个人得票多: struct Candidate *max = xm; f4.2 for循环,代表结构体数组下标(第i个选民)的循环变量i从0开始,<len 时,进入循环 f4.2.1 用结构体指针取剑号运算符,判断max->tickets是否小于xm->tickets f4.2.1.1 如果是, 那么,认为xm[i]选民的得票数最多,令: max = xm; f4.3 返回得票数最多人的地址,即max 1. 一开始不知道有几个候选人,需要通过开辟动态内存的方式确定结构体数组,故先定义一个结构体指针: struct Candidate *xm = NULL; //用NULL防止野指针,并表示xm还没有具体指向 2. 自定参选的人数,保存在变量len中: scanf("%d%*c", &len);//%*c为了跳过回车,因为后面还要输入 3. 调用API1. 初始化选民信息,在堆区中开辟一个结构体数组,并把地址保存在xm中: xm = initXms(xm, len); //内在逻辑:通过指针函数的方式修改指针的值,传递xm是为了判断xm是否是NULL,其实不传递xm也行 4. 调用API2. 打印投票前的选民信息: printXms(xm, len); 5. 调用API3. 进入唱票环节: 统计得票数和废票数,将返回的废票数保存在变量invalidTickets中: invalidTickets = doVote(xm, len); 6. 调用API2. 打印投票后的选民信息: printXms(xm, len); 7. 调用API4. 选出得票最多的人(结构体),将这个人的地址保存在结构体指针变量final中: final = getMax(xm, len); 8. 打印结果,谁以几票当选
- 代码:
#include <stdio.h> #include <string.h> #include <assert.h> #include <stdlib.h> struct Candidate { char name[32]; int tickets; }; struct Candidate* initXms(struct Candidate *xm, int len); void printXms(struct Candidate *xm, int len); int doVote(struct Candidate *xm, int len); struct Candidate* getMax(struct Candidate *xm, int len); int main(int argc, char const *argv[]) { struct Candidate *xm = NULL; int len = 0; //结构体数组长度,即选民人数 int invalidTickets; struct Candidate *final; puts("请输入参选的人数"); scanf("%d%*c", &len); xm = initXms(xm, len); //1.初始化环节 printXms(xm, len); // 1.展示选民(姓名和票数) invalidTickets = doVote(xm, len); //2.唱票环节 printXms(xm, len); // 2.展示选民(姓名和票数)和废票 printf("废票共计%d张\n", invalidTickets); // 2.展示废票 final = getMax(xm, len); printf("%s以%d票当选!!!\n", final->name, final->tickets); //3.公布结果 return 0; } struct Candidate* initXms(struct Candidate *xm, int len) { assert(xm == NULL); int i; xm = (struct Candidate*)malloc(len*sizeof(struct Candidate)); for(i=0; i<len; i++){ xm[i].tickets = 0; printf("请输入第%d个选民的名字:\n", i+1); gets(xm[i].name); } return xm; } void printXms(struct Candidate *xm, int len) { int i; for(i=0; i<len; i++){ printf("选民:%s,得票数:%d\n", xm[i].name, xm[i].tickets); } } int doVote(struct Candidate *xm, int len) { int i; int j; int mark = 0; char tempName[32]; int invalidTickets = 0; for(i=0; i<5; i++){ mark = 0; printf("第%d个同学,请输入你投票给谁:\n", i+1); memset(tempName, '\0', sizeof(tempName)); //将临时名字每次进行清空 scanf("%[^\n]%*c", tempName); for(j=0; j<len; j++){ //遍历候选者,试着找出用户输入的人名 if( strcmp(tempName, xm[j].name) == 0 ){ xm[j].tickets++; mark = 1; break; } } if(mark == 0){ //一次唱票后,判断本次投票是否有效 puts("没有此候选人,视为弃票"); invalidTickets++; } } return invalidTickets; } struct Candidate* getMax(struct Candidate *xm, int len) { struct Candidate *max = xm; int i; for(i=0; i<len; i++){ if(max->tickets < xm->tickets){ max = xm; } xm++; } return max; }