首页 » C语言解惑 » C语言解惑全文在线阅读

《C语言解惑》21.2 使用键盘赋值

关灯直达底部

结构最大的优点是它的域可以含有不同的数据类型,包括数组和指向自己的指针。因此,常常设计使用键盘完成人机交互。从理论上讲,赋值很简单。但是,如果使用键盘赋值,则不是语法意义的正确与否所能解决的,还存在着如何克服键盘抖动所带来的一系列问题。

实际上,对一个简单的结构变量,关键是要注意字符和指针。对结构数组,则还要兼顾数组的特点。常常需要为结构申请动态内存,这都与赋值相关联。

21.2.1 为结构变量赋值

本节不涉及结构数组,仅针对结构变量。对结构变量用scanf语句赋值时,一定要注意成员的数据类型。如果成员是普通变量,则要使用地址符号“&”。如果成员是数值数组,为它的各个元素赋值时,可以对每个元素使用“&”号构成显式表示方法。因为数组本身代表地址,也可以使用以数组首地址为基准的表示法。对于字符串,因其作为整体而无需使用“&”号(与显式表示等效)。要特别留意单个字符变量的赋值,以免引发其他问题。

【例21.4】为结构变量赋值的例子。


#include <stdio.h>const double K=0.5;struct  List{        char name[12];        char sex;        int num;        double score[2];        double total;        double mean;}a;int main( ){      int i=0;      char st[12]={"语文分数:","算数分数:"};      a.total=0.0;      printf("性别(F/M):");      scanf("%c",&a.sex);      printf("学号:");      scanf("%d",&a.num);      printf("名字:");      scanf("%s",a.name);     //&a.name等效      for(i=0;i<2;i++)      {          printf(st[i]);          scanf("%lf",(a.score+i));     //a.score错误,等效&a.score[i]            a.total=a.total+a.score[i];     //不能用a.total=a.total+(a.score+i);      }      a.mean=a.total*K;      printf("%s,%c,%d,%lf,%lf,%lf,%lf/n",            a.name,a.sex,a.num,a.score[0],a.score[1],a.total,a.mean);      printf("%s,%c,%d,%lf,%lf,%lf,%lf/n",            a.name,a.sex,a.num,*(a.score),*(a.score+1),a.total,a.mean);      return 0;}  

运行示范如下:


性别(F/M):F学号:205名字:王莹莹语文分数:87算数分数:94王莹莹,F,205,87.000000,94.000000,181.000000,90.500000王莹莹,F,205,87.000000,94.000000,181.000000,90.500000  

【解释】字符的读入最麻烦,如果不相信,可以换一下顺序。例如,将性别换到学号之后,典型的运行为


学号:234性别(F/M):名字:F语文分数:  

回答学号之后,它跳过这一项,直接询问名字!这就是本程序首先输入性别的原因。因为赋值的顺序与结构定义变量的顺序无关,所以可以充分利用数据类型的特点预防干扰。

如果一定要求按名字和性别的顺序输入,那就要采取措施。例如,可以在scanf语句之前加一条语句“getchar();”,或者将scanf语句改为


scanf(" %c",&a.sex);  

形式,均可以解决这个问题。

还有一种办法可以尝试,那就是将字符设计为字符串。例如,将sex声明为


char sex[4];  

形式。不过要验证测试,因为有时也会受到干扰产生错误。但要注意,使用getchar有时反而真的需要输入空行。最简单有效的方法可能是在“%c”之前增加一个空格。

在读分数时,对数组score,使用如下两种格式是等效的。


scanf("%lf",(a.score+i));scanf("%lf",&a.score[i]);  

对total而言,对应的格式分别为


a.total=a.total+*(a.score+i);a.total=a.total+a.score[i];  

一定要分清地址和数据,例如使用


a.total=a.total+(a.score+i);     //错误  

则是错误的。因为加的是地址,不是地址里的内容。

按此推理,printf的输出格式就很容易掌握了。字符串数组name的名字是数组的首地址,也可以用显式的方式,所以“a.name”和“&a.name”是等效的。实数数组a.score是首地址,也就是第一个元素的地址,第二个元素的地址则是a.score+1。与之等效的显式表示分别为“&a.score[0]”和“&a.score[1]”。如果用数组首地址的方式输出其值,第1个元素为*(a.score),第2个为*(a.score+1)。

21.2.2 为结构指针变量赋值

假设定义如下一个List结构和结构变量a。


struct  List{             char *name;             char sex[6];            int age;}a;  

如何给指针变量name赋值呢?关键是使用scanf语句赋值时,需要给出变量的地址。在使用结构变量a的成员时,“a.sex”和“&a.sex”确实分别代表字符串的值和地址值,而且“a.sex”又代表存储字符串的首地址,这在编译时就由系统予以分配。

使用结构变量a的指针成员时,“a.name”和“&a.name”确实也分别代表指针变量的值和地址值,但“a.name”代表的地址却与“&a.name”不一样。“a.name”所代表的地址应该是它指向的存储字符串的首地址值。由于这个指针没有被初始化,所以不能直接给它赋值,下面的例子将验证这一点。

【例21.5】为结构指针变量赋值的例子。


#include <stdio.h>#include <string.h>struct List{           char *name;           char sex[6];           int age;}a;int main( ){     char c[ ]="men";     printf("%s,0x%x,0x%x,%s,0x%x,0x%x/n", a.name,a.name,&a.name,                                           a.sex,a.sex,&a.sex);     a.name=c; strcpy(a.sex,c);     printf("%s,0x%x,0x%x,%s,0x%x,0x%x/n", a.name,a.name,&a.name,                                           a.sex,a.sex,&a.sex);     printf("%s,0x%x,0x%x/n", c,c,&c);   printf("姓名:");   scanf("%s",a.name);     //注意字符串中不能有空格   printf("%s,0x%x,0x%x,%s,0x%x,0x%x/n", a.name,a.name,&a.name,a.sex,a.sex,&a.sex);     printf("%s,0x%x,0x%x/n", c,c,&c);     return 0;}  

例中分别使用%s和%x输出a.name的值和地址并与a.sex的情况进行比较。程序运行后,第1行输出如下:


(null),0x0,0x4257d0,,0x4257d4,0x4257d4  

由此可见,指针没有初始化,指向的地址值为0,所以很危险,必须尽快初始化。而且a.name的地址确实与&a.name不一样。对于a.sex而言,a.sex和&a.sex是一样的。所以说对结构变量a的字符指针域的处理,不能搬用字符域的处理方法。

当用字符串c初始化a.name之后,a.name指向的地址就是存储变量c的地址0x12ff7c。以后重新改变指针内容时,仍然使用这个地址,但字符串的长度受初始化字符串长度的限制。对照下面第2-3行的输出以加深理解。


men,0x12ff7c,0x4257d0,men,0x4257d4,0x4257d4men,0x12ff7c,0x12ff7c  

&a.name的地址是固定不变的,仍为0x4257d0,不过这个地址并没有用处。另外,对字符串c初始化时可以有空格,但用键盘赋值时不能有空格(为了使用空格,可以使用gets函数接收键盘输入),下面是键盘赋值的示范。


姓名:张三张三,0x12ff7c,0x4257d0,men,0x4257d4,0x4257d4张三,0x12ff7c,0x12ff7c  

思考:为何分别使用“a.name=c;”和“strcpy(a.sex,c);”两种形式?

也可以使用一个字符串数组接收输入,然后再赋给name。也可以申请动态内存初始化指针变量。下面分别给出三种完整的程序。

【例21.6】使用字符串初始化程序的例子。


#include <stdio.h>#include <string.h>struct  List{            char *name;            char sex[6];            int age;}a;int main( ){         char c[ ]="12345678910w";     //要满足预定长度         a.name=c;         printf("姓名:");         gets(a.name);     //注意字符串中可以有空格         printf("性别:");         scanf(" %s",&a.sex);         printf("年龄:");         scanf("%d",&a.age);         printf("/n姓  名  性别 年龄/n");         printf("%6s  %3s %4d/n", a.name,a.sex,a.age);         return 0;}  

程序运行示范如下。


姓名:王  平性别:男年龄:16姓  名  性别 年龄王  平   男   16  

【例21.7】使用字符串变量中转实现的程序实例。


#include <stdio.h>#include <string.h>struct  List{        char *name;        char sex[6];        int age;}a;int main( ){        char c[12];        printf("姓名:");        gets(c);        // a.name=c;     //不推荐在此位置赋值        printf("性别:");        // getchar();     //根据情况设置,本程序在scanf里面解决        scanf(" %s",a.sex);     //注意空格的用途        printf("年龄:");        scanf("%d",&a.age);        a.name=c;     //推荐的位置        printf("/n姓  名  性别 年龄/n");        printf("%6s  %3s %4d/n", a.name,a.sex,a.age);        return 0;}  

【例21.8】使用动态内存初始化指针实现的程序实例。


#include <stdio.h>#include <stdlib.h>#include <string.h>struct  List{        char *name;        char sex[6];        int age;}a;int main( ){        char *p=(char *)malloc (12*sizeof(char));        printf("姓名:");        gets(p);     //注意字符串中可以有空格        printf("性别:");        scanf(" %s",&a.sex);        printf("年龄:");        scanf("%d",&a.age);        a.name=p;        printf("/n姓  名  性别 年龄/n");        printf("%6s  %3s %4d/n", a.name,a.sex,a.age);        return 0;}  

21.2.3 为链表赋值

为链表赋值仍然需要克服为字符串和字符赋值的干扰。请仔细研读这个赋值的例子和采取的措施。

【例21.9】使用键盘为链表赋值的例子。


#include<stdio.h>#include<stdlib.h>struct person * CreateList(void);     //声明返回结构指针的建表函数void PrintList(struct person *);     //输出链表内容struct person {      int num;     //职工编号      char name[12];      float salary;     //职工工资      struct person *next;     //指向自身的结构指针(指向下一个)};int main(){     struct person *head;     head=CreateList();     //建立链表     PrintList(head);     //遍历链表     return 0;}struct person * CreateList(void)     //返回结构指针的建表函数{     int number;     struct person *head;     //头指针     struct person *rear;     //尾指针     struct person * p ;     //新结点指针     head=NULL;     //置空链表     printf("输入职工编号,输入0结束. /n");     printf("编号:");     scanf("%d",&number);     //读入第一个职工号     if(number==0)  return head;     //退出建表函数     while(number!=0)     //读入职工号不是结束标志(0)时做循环     {       p=(struct person *)malloc(sizeof(struct person));     //申请新结点       p->num=number;                         //数据域赋值       printf("姓名:");       scanf(" %s",p->name);     //输入职工姓名       printf("工资:");       scanf("%f",&p->salary);     //输入职工工资       if(head==NULL)head=p;     //将p指向的新结点插入空表       else rear->next=p;     //新结点插入到表尾结点(rear指向的结点)之后       rear=p;     //表尾指针指向新的表尾结点       printf("编号:");       scanf(" %d",&number);     //读入下一个职工号     }     if(rear!=NULL)     rear->next=NULL;     //终端结点置空     printf("/n建表结束!/n");     return head;     //返回表头指针}void PrintList(struct person *head){   struct person *p=head;     //p指向表头   while(p!=NULL)   {       printf("%d %s %6.2f/n", p->num,       p->name, p->salary);     //输出职工的信息       p=p->next;     //使p指向下一个结点   }}  

程序运行示范如下。


输入职工编号,输入0结束.编号:1002姓名:李一鸣工资:3455.56编号:1003姓名:张玉萍工资:2356.45编号:0建表结束!1002 李一鸣 3455.561003 张玉萍 2356.45  

21.2.4 为结构数组的变量赋值

结构数组是由若干组相同的结构组成,所以重点就是如何表示结构数组的问题。如wk[3]表示有3个相同的结构wk[0]、wk[1]和wk[2]。结构数组的名称就是数组存储的首地址,每个结构的名称就是各个结构的存储首地址。结构数组2的名称是wk[2],也就是结构数组2的首地址。wk[2]类似于单个结构的名称,余下的问题也就迎刃而解了。

注意区分它们域的类型,也就是数组域与变量域的表示方法,对于数组域,数组名就是存储的首地址,但使用显式表示法更容易理解,wk[2].score[0]就是score数组的第1个元素的地址,显式表示为&wk[2].score[0],两者是完全等效的。至于其他数值型,则将&号冠于数组元素名之前即可。

【例21.10】为结构数组变量赋值的例子。


#include <stdlib.h>#include <stdio.h>struct wkrs{       char num[6];       char name[10];       int score[3];}wk[3];void main ( ){   int i=0,j=0;   char *c[4]={"序号","姓名","数学","语文"};   printf("准备输入信息/n");   for( i=0; i<3; i++)   {       printf("序号:");       scanf("%s",wk[i].num);     //使用数组名表示       printf("姓名:");       scanf("%s",wk[i].name);       printf("成绩:");       {        for(j=0;j<2;j++)              scanf("%d",&wk[i].score[j]);     //使用显式表示       }   }   printf("/n%8s/t%8s/t%6s/t%4s/n",c[0],c[1],c[2],c[3]);   for(i=0;i<3;i++)       printf("%8s/t%8s/t%6d/t%4d/n",wk[i].num, wk[i].name,          wk[i].score[0],wk[i].score[1]);     //使用数组名表示}  

运行示例如下:


准备输入信息序号:1001姓名:张晓红成绩:65 78序号:1002姓名:李小刚成绩:76 88序号:1003姓名:黄小华成绩:86 89序号            姓名          数学   语文   1001          张晓红          65      78   1002          李小刚          76      88   1003          黄小华          86      89  

21.2.5 为含有指针域的结构数组赋值

【例21.11】这个程序是为使用指针的结构数组赋值,但得到错误的结果。分析错在何处并改正之。


//含有错误的源程序#include <stdlib.h>#include <stdio.h>#include <string.h>struct wkrs{       int num;       char *name;       int score[3];}wk[3];int main ( ){       int i=0;       char s[12];       char *c[4]={"序号","姓名","数学","语文"};       printf("准备输入信息/n");       for( i=0; i<3; i++)       {             printf("序号:");            scanf(" %d",&wk[i].num);            printf("姓名:");            scanf(" %s",s);            wk[i].name=s;            for( j=0; j<2; j++)            {                printf("成绩:");                scanf("%d",&wk[i].score[i]);            }       }       printf("/n%8s/t%8s/t%6s/t%4s/n",c[0],c[1],c[2],c[3]);       for(i=0;i<3;i++)                printf("%8s/t%8s/t%6d/t%4d /n",wk[i].num, wk[i].name,                           wk[i].score[0],wk[i].score[1]);       return 0;}  

【解答】程序声明一个字符串数组s作为中转站,但是每次执行程序段


scanf(" %s",s);wk[i].name=s;  

的时候,又会将上一个数组元素的wk也更新为新输入的名字。这样一来,数组的所有name均等于最后一次赋给的名字。对数组来说,必须每次使用新的字符串数组中转。本程序的数组分量是3,所以需要声明


char s[3][12];  

将接收键盘输入部分改为


printf("姓名:");scanf(" %s",s[i]);wk[i].name=s[i];  

即可。另外,有些头文件是多余的,可以去掉。这个程序取消输入成绩的内循环语句,简化了设计。


//改正错误后的源程序#include <stdio.h>struct wkrs{       int num;       char *name;       int score[3];}wk[3];int main ( ){    int i=0,j=0;    char s[3][12];    char *c[4]={"序号","姓名","数学","语文"};    printf("准备输入信息/n");    for( i=0; i<2; i++)    {         printf("序号:");         scanf(" %d",&wk[i].num);         printf("姓名:");         scanf(" %s",s[i]);         wk[i].name=s[i];         printf("成绩:");         scanf("%d%d",&wk[i].score[0],&wk[i].score[1]);     }     printf("/n%8s/t%8s/t%6s/t%4s/n",c[0],c[1],c[2],c[3]);     for(i=0;i<3;i++)           printf("%8s/t%8s/t%6d/t%4d /n",wk[i].num, wk[i].name,                   wk[i].score[0],wk[i].score[1]);     return 0;}  

【例21.12】下面这个程序也是为了给使用指针的结构数组赋值,采用动态内存作为中转,但也得到了错误的结果。分析错在何处并改正之。


#include <stdlib.h>#include <stdio.h>struct wkrs{          int num;          char *name;          int score[3];}wk[3];void main ( ){        int i=0;        char *p;        char *c[4]={"序号","姓名","数学","语文"};        p=(char *)malloc (12*sizeof(char));        printf("准备输入信息/n");        for( i=0; i<2; i++)        {               printf("序号:");               scanf(" %d",&wk[i].num);               printf("姓名:");               scanf(" %s",p);               wk[i].name=p;               printf("成绩:");               scanf("%d%d",&wk[i].score[0],&wk[i].score[1]);        }        printf("/n%8s/t%8s/t%6s/t%4s/n",c[0],c[1],c[2],c[3]);        for(i=0;i<2;i++)            printf("%8s/t%8s/t%6d/t%4d /n",wk[i].num, wk[i].name,                 wk[i].score[0],wk[i].score[1]);} 

【解答】与上题犯的错误一样。最简单的方法就是将语句


p=(char *)malloc (12*sizeof(char));  

移到for语句下面,作为循环体的第1条语句即可。这就能保证每次重新申请内存,不会覆盖原来的存储信息。

实际上,可以一次申请足够的内存,把它当做数组使用。例如,这里的数组是3个元素,一个元素申请12个字符,现在申请36个字符的内存,即


char *p=(char *)malloc (36*sizeof(char));  

然后像使用指针那样使用这块内存。例如:


printf("姓名:");scanf(" %s",p+i);wk[i].name=p+i;  

还可以申请对等的字符指针数组,如:


char *p[3];  

至于在哪里初始化,都是可以的。例如,在使用前先完成初始化


for( i=0; i<3; i++)       p[1]=(char *)malloc (12*sizeof(char));  

在for语句里像下面那样使用。


scanf(" %s",p[i]);wk[i].name=p[i];  

当然,也可以放到for循环里初始化,但都不如第1种简单。