最近重新拾起C语言,打算过一遍C Primer Plus的课后题,今天在第11章第10题时有点卡壳,犯了几个错误。在此记录一下,顺便梳理下指针的知识。
题目:
编写一个程序,读取输入,直到读入了10个字符串或遇到EOF,由二者中最先被满足的那个终止读取过程。这个程序可以为用户提供一个有5个选项的菜单:输出初始字符串列表、按ASCII顺序输出字符串、按长度递增顺序输出字符串、按字符串中第一个单词的长度输出字符串和退出。菜单可以循环,直到用户输入退出请求。当然,程序要能真正完成菜单中的各项功能。
解答:
初读题目,框架应该是首先读入几个字符串,然后输入一个菜单选项,根据输入的选项,执行相应操作。很自然的想到switch()语句。
先贴出来自己做错的函数:
#include <stdio.h>
int main(void)
{
char ch;
int t;
int ct1,ct2;
char *temp;
int i,j;
char str[10][81];
char *p[10];
int n = 0;
puts("Input string:");
while( n<10 && gets(str[n]))
{
puts("Input string:");
n++;
}
p = str;
puts("1:输出字符串");
puts("2:按 ASCII 顺序输出");
puts("3:按长度递增");
puts("4:按第一个单词的长度");
puts("0:推出");
while(getchar(ch))
{
while(getchar()!='\n')
continue;
switch(ch)
{
case '1':
break;
case '2':
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
{
if(strcmp(str[i],str[j])>0)
{
temp = *str[i];
*str[i] = *str[j];
*str[j] = temp;
}
}
break;
case '3':
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
{
if(strlen(str[i])>strlen(str[j]))
{
temp = p[i];
p[i] = p[j];
p[j] = temp;
}
}
break;
case '4':
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
{
while(*str[i]!=' ')
ct1++;
while(*str[j]!= ' ')
ct2++;
if(ct1>ct2)
{
temp = *str[i];
*str[i] = *str[j];
*str[j] = temp;
}
}
break;
case '0':
puts("Bye!");
return 0;
default:
puts("Please input ");
break;
}
for (i=0;i<n;i++)
puts(p[i]);
}
}
编译无所通过。提示错误 3 error C2106: “=”: 左操作数必须为左值 d:\program files (x86)\microsoft visual studio 9.0\project\11-10\11-10\10.c 20 11-10
这个错误的原因是对字符串数组以及指针理解不清。错误的认为str和p都是首地址,通过把str赋给p,然p指向str。但这里p是一个包含10个指针的数组,重点是数组。虽然数组的名字和指针一样,都是数组首地址,但是数组名是常量,不可以改变。
改正的方法:p是一个有10个指针的数组,也就是说p[n]是指针,而指针是变量,因此可以这样改:
while( n<10 && gets(str[n]))
{
p[n] = str[n];
puts("Input string:");
n++;
}
再次编译,通过,运行,1没有问题,但是选2的时候仍然是按数序输入。通过调试发现,在switch()语句中,没有任何以个和输入的1相匹配,之后做如下修改:
while(scanf("%d",&c))
编译、运行,结果错误,每个字符串只改变了首个字符。问题出在*str[i]只代表第一个元素的值。做如下修改:
case 2:
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
{
if(strcmp(str[i],str[j])>0)
{
temp = p[i];
p[i] = p[j];
p[j] = temp;
}
}
break;
编译、运行,1.2都能得到正确结果,但3仍然按原顺序给出。此处犯的错误依旧是对指针理解不透彻。我们通过
while( n<10 && gets(str[n]))
{
p[n] = str[n];
puts("Input string:");
n++;
}
使数组p中储存的每个指针依次指向str,也就是说p中每个元素储存的都是对应str的地址。错误的认为,改变p中元素的地址,str也相应改变了。这里用错了一个概念,也就是,虽然*p[n]和str[n]指向相同的位置,但是改变p[n],并不能改变*p[n]指向的值,改变的只是p[n]中储存的地址的值。
此处直接改变str[n]的值是不对的,如问题1一样,str是一个二维数组,str[n]是一个一位数组,她是一个常量。
关于这个问题的解决办法,考虑了两个错误的,一个是让*str[n] = *p[n],但问题是,这只改变了首元素的值;第二个是想用strcpy(str[n],p[n]),存在的问题是可能导致数据溢出。所以最简单的方法就是输出p。
经过接近一天的时间,终于把这个函数实现了,虽然程序不完美,但是能运行出结果。过程收获很多。
最终代码:
#include <stdio.h>
int main(void)
{
int c;
int t;
int ct1=0;
int ct2=0;
char *temp;
int i,j;
char str[10][81];
char *p[10];
int n = 0;
puts("Input string:");
while( n<10 && gets(str[n]))
{
p[n] = str[n];
puts("Input string:");
n++;
}
puts("1:输出字符串");
puts("2:按 ASCII 顺序输出");
puts("3:按长度递增");
puts("4:按第一个单词的长度");
puts("0:推出");
while(scanf("%d",&c))
{
while(getchar()!='\n')
continue;
switch(c)
{
case 1:
break;
case 2:
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
{
if(strcmp(str[i],str[j])>0)
{
temp = p[i];
p[i] = p[j];
p[j] = temp;
}
}
break;
case 3:
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
{
if(strlen(str[i])>strlen(str[j]))
{
temp = p[i];
p[i] = p[j];
p[j] = temp;
}
}
break;
case 4:
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
{
while(*(str[i]+ct1)!=' ' && *(str[i]+ct1)!='\0')
ct1++;
while(*(str[j]+ct2)!= ' '&& *(str[j]+ct2)!='\0')
ct2++;
if(ct1>ct2)
{
temp = p[i];
p[i] = p[j];
p[j] = temp;
}
ct1 = 0;
ct2 = 0;
}
break;
case 0:
puts("Bye!");
return 0;
default:
puts("Please input right");
break;
}
for (i=0;i<n;i++)
puts(p[i]);
}
}