C的实用笔记41——结构体指针引用结构体(变量/数组)

1.结构体指针的概念

1、知识点:

  1. 与变量的两种访问方式一样,结构体变量也有两种访问方式:通过结构体变量名访问,通过结构体变量地址访问。这里结构体变量地址就是结构体指针。
  2. 普通指针可以引用变量,同时普通指针也可以引用一维数组,因为数组的名字就是指针类型的地址常量,也是它第首个元素的地址。类似的,结构体指针可以引用结构体变量,当然结构体指针也可以引用结构体数组,因为结构体数组的名字就是结构体指针类型的地址常量,也是它首个结构体的地址。

2.通过结构体指针间接访问结构体变量

1、标准写法:

  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、啰唆的写法:

  1. 指针取星花,再用点运算符".":不推荐,缺少了对剑号运算符的使用。
        printf("t1的cdata=%c\n", (*ps).cdata);

3.通过结构体指针间接访问结构体数组

1、标准写法:

  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、见怪不怪的写法:

  1. 结构体指针下标法,然后用点运算符".",这种方法比较直观,可以在给函数传递结构体数组后使用
        for(int i=0; i<len; i++){
            printf("学号:%d,姓名:%s\n", parr[i].num, parr[i].name);		//结构体指针下标法
        }

  2. 结构体指针自加,使用的比较多,也比较直观,但是存在缺点(因为对p进行了修改,下次再用容易造成数组越界)。需要每次都让p回到数组头部。
        int i;
    	parr = arr;												      //使用前让指针复位
        for(i=0; i<len; i++){								  
            printf("学号:%d,姓名:%s\n", parr->num, parr->name);		//结构体指针自加
            parr++;
        }

  3. 结构体指针偏移后取星花,再使用点运算符"."。不推荐,因为要注意优先级的问题,需要加多个括号,同时缺少了剑号运算符的使用。
        for(int i=0; i<len; i++){
            printf("学号:%d,姓名:%s\n", (*(parr+i)).num, (*(parr+i)).name);		
        }

  4. 结构体数组名当做指针来使用。也不推荐用,因为我们终归是使用定义出来的指针,而不是地址常量
        for(int i=0; i<len; i++){
            printf("学号:%d,姓名:%s\n", (arr+i)->num, (arr+i)->name);		
        }

4.习题(函数形参接收结构体数组)

代码心得:

  1. 程序写一部分就编译试一下,不要等到全部写完才编译,否则错误很多看不完,而且有些地方虽然没语法错,但是通过提前测试,可以补充一些细节。
  2. 小技巧,可以根据指针函数返回一个指针类型的数据,从而把函数返回值赋值给指针本身,修改指针自身的值,而不需要通过给函数传递二级指针的方式进行修改。
  3. 可以通过给指针函数传递结构体数组,获取结构体数组中的的某个结构体的的地址。(结构体数组有点像二维数组,但并不是)

习题1:用函数改进选票系统,让程序看起来更整洁。练习结构体指针、结构体数组、指针函数等知识点

  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. 打印结果,谁以几票当选

  2. 代码:
    #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; 
    }

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值