今日笔者在快乐地敲着PTA,突然被一道很nb的改错题乱了阵脚。这道题让笔者意识到理解和课本的重要性。
原题
请用指针数组编程实现按奥运会参赛国的国名在字典中的顺序对其入场次序进行排序。 假设参赛国不超过150个,参赛国的国名不超过9个字符。 下面程序中存在比较隐蔽的错误,请通过分析和调试程序,发现并改正程序中的错误。源代码:
#include <stdio.h>
#define MAX_LEN 10
#define N 150
void SortString(char *ptr[], int n);
main()
{
int i, n;
char *pStr[N];
printf("How many countries?\n");
scanf("%d",&n);
printf("Input their names:\n");
for (i=0; i<n; i++)
{
gets(pStr[i]);
}
SortString(pStr[i], n);
printf("Sorted results:\n");
for (i=0; i<n; i++)
{
puts(pStr[i]);
}
}
void SortString(char *ptr[], int n)
{
int i, j;
char temp;
for (i=0; i<n-1; i++)
{
for (j = i+1; j<n; j++)
{
if (ptr[j] < ptr[i]);
{
temp = ptr[i];
ptr[j] = ptr[i];
ptr[j] = temp;
}
}
}
}
注意: (1)请将修改正确后的完整源程序拷贝粘贴到答题区内。 (2)对于没有错误的语句,请不要修改,修改原本正确的语句也要扣分。 (3)当且仅当错误全部改正,且程序运行结果调试正确,才给加5分。 (4)改错时不能改变程序原有的意图,也不要改变代码的输入输出格式。 (5)经教师手工核对后,如果未使用指针数组做函数参数编程,那么即使运行结果正确也不给分。
笔者对于指针和数组的组合及其用法一直模糊不清,今天给大伙端上来这道硬控我一小时的题目,也算是自我鞭策一番。
首先,让我们了解什么是char *ptr形式。
char* ptr[]到底是什么?
字符指针数组。
与其它指针数组类型一样,它也有独特的运用方式,我们从字面意思上来观察,
首先,很显然,它是一个数组;其次,观察他它的数组类型。OK,char*型。
什么,char*型??这意味着什么?这意味着,该数组本身是由几个指针型变量组成的。
指针,指针,指针。它是一个指针。因此你要计算的时候,引入其中的比如ptr[0],它是一个字符型指针,你不应该将它看作数组的一部分,应该将它看作 char* ptr_0,这就造就了很多很有意思的特性。
它的特性,放一张图便一目了然:
char* p[N];
指针 ---> 字符所存地址
p[0] | ----> | str0 |
p[1] | ----> | str1 |
. | ----> | . |
. | ----> | . |
. | ----> | . |
p[N-1] | ----> | str(N-1) |
当你向其中输入(甭管是什么东西)时,它指向了你输入的字符串。而众所周知,字符串本身就是地址,所以在用gets或者scanf时,无需加&符号,因为它等价于一个char *ptr_0的变量。一个指针直接输入地址就可以,用&必然会出现错误。
char* p[N];//the p[i](0 <= i < N) is a point.
gets(p[0]);//true
gets(*p[0]);//false
scanf("%s",p[0]);//true
scanf("%s",&p[0]);//false
同样,在输出时,也必须遵守其规律。由于字符指针本身指向了字符串,所以在输出时也不需要对其进行解引用。
puts(p[0]);
printf("%s\n",p[0]);
//true
再多bb两句:如果进行解引用,程序会理解为输出字符指针p所要指向的第一个字符,而无论是puts还是printf("%s",*p),都不能输出单个字符串,这就会导致程序崩溃。
puts(*p[0]);//false
printf("%s\n",*p[0]);//false
printf("%c",*p[0]);//true
OK.当笔者快乐地解决这个问题,并且修复其中的大大小小的bug,再点击运行时,却发现,程序根本就不给我输入的机会TAT
代码1
#include <stdio.h>
#include "string.h"
#define MAX_LEN 10
#define N 150
void SortString(char *ptr[], int n);
int main()
{
int i, n;
char *pStr[N];
printf("How many countries?\n");
scanf_s("%d",&n);
printf("Input their names:\n");
for (i=0; i<n; i++)
{
gets(pStr[i]);
}
SortString(pStr, n);//仅输入首个字符的地址
printf("Sorted results:\n");
for (i=0; i<n; i++)
{
puts(pStr[i]);
}
return 0;
}
void SortString(char *ptr[], int n)
{
int i, j;
char* temp;
for (i=0; i<n-1; i++)
{
for (j = i+1; j<n; j++)
{
if (strcmp(ptr[i],ptr[j]) > 0)//使用strcmp函数比较大小
{
temp = ptr[i];
ptr[i] = ptr[j];//正确地使用指针交换
ptr[j] = temp;
}
}
}
}
How many countries?
5
Input their names:
d
进程已结束,退出代码为 -1073741819 (0xC0000005)
这又是为什么呢?
让我们再仔细想想指针的定义。哦,没错,我定义了N个字符指针,并试图向其中输入数据。定义指针,向其中输入数据...等等,我们是不是忘了分配给指针内存?!没错,我们没有给指针分配内存。指针没有地儿,那分配个毛线?
于是我们再次快乐地使用malloc函数,采用遍历对指针进行逐步分配。顺带记得在输出完数据后,使用free函数对其进行内存释放。
代码变成了这样:
#include <stdio.h>
#include <stdlib.h>
#include "string.h"
#define MAX_LEN 10
#define N 150
void SortString(char *ptr[], int n);
int main()
{
int i, n;
char *pStr[N];
printf("How many countries?\n");
scanf_s("%d",&n);
printf("Input their names:\n");
for (int j = 0;j < n;j ++)
{
pStr[j] = (char*)malloc(MAX_LEN * sizeof(char));//采用遍历逐个分配内存
}
for (i=0; i<n; i++)
{
gets(pStr[i]);
}
SortString(pStr, n);
printf("Sorted results:\n");
for (i=0; i<n; i++)
{
puts(pStr[i]);
free(pStr[i]);//输出完后让内存滚回老家
}
return 0;
}
void SortString(char *ptr[], int n)
{
int i, j;
char* temp;
for (i=0; i<n-1; i++)
{
for (j = i+1; j<n; j++)
{
if (strcmp(ptr[i],ptr[j]) > 0)
{
temp = ptr[i];
ptr[i] = ptr[j];
ptr[j] = temp;
}
}
}
}
大致没有问题,但是运行后,却又有一个小小的问题。我输入的国家明明是5,为啥输入4个国家就出结果了?而且a前面还输出一个不知道是什么玩意的空格...
How many countries?
5
Input their names:
a
c
d
b
Sorted results:
a
b
c
d
进程已结束,退出代码为 0
缓冲区的清除
众所周知,c语言在输入时存在缓冲区,缓冲区会把我们在键盘上敲下的所有键位都读取进去(是的,包括\n)。所有的输入函数都会从缓冲区中读取函数,这也不可避免留下了一些问题。我们的代码中使用了scanf函数,在上一篇文章我们讲过,它在读取缓冲区时并不会读取换行符\n,但是这并不意味着缓冲区内不存在\n:
输入 | 5 | \n |
缓冲区 | 5 | \n |
scanf读入 | 5 | |
缓冲区剩余 | \n |
因此在我们的下一次输入时(输入字符串),gets函数会先从缓冲区中读取\n换行符,同时跳转到下一次输入(因为\n也代表输入完成的信号),占用了一次 输入国家的名额。因此我们的代码中要加入某个函数,使缓冲区内的换行符清空,这里我们使用getchar()函数。
最终代码
#include <stdio.h>
#include <stdlib.h>
#include "string.h"
#define MAX_LEN 10
#define N 150
void SortString(char *ptr[], int n);
int main()
{
int i, n;
char *pStr[N];
printf("How many countries?\n");
scanf_s("%d",&n);
getchar();//读取缓冲区内的\n,清空缓冲区
printf("Input their names:\n");
for (int j = 0;j < n;j ++)
{
pStr[j] = (char*)malloc(MAX_LEN * sizeof(char));
}
for (i=0; i<n; i++)
{
gets(pStr[i]);
}
SortString(pStr, n);
printf("Sorted results:\n");
for (i=0; i<n; i++)
{
puts(pStr[i]);
free(pStr[i]);
}
return 0;
}
void SortString(char *ptr[], int n)
{
int i, j;
char* temp;
for (i=0; i<n-1; i++)
{
for (j = i+1; j<n; j++)
{
if (strcmp(ptr[i],ptr[j]) > 0)
{
temp = ptr[i];
ptr[i] = ptr[j];
ptr[j] = temp;
}
}
}
}
至此,大功告成。
下期预告
下期预告:一道更加nb的算法题,对于医生一周值班排序。csdn上竟然有嵌套七层循环的暴力算法,简直像屎山一样不忍直视。笔者研究一星期的成果,明天就给大家端上来(喜)
...
断断续续写了也有两小时,写到现在,不禁想感慨两句。
作为一名末流211的垃圾学生,我写博客的目的仅仅是写给自己看。
写的代码质量堪忧,里面也是一大堆啰里啰唆的废话,估计同学们没有耐心看,也从没想过会有人来看。
这种事情值得吗?写博客的这两小时时间,我可以省下来去刷更多的题,可以学到更有用的知识,可以更高效地提升自己,努努力摆脱身上和过去的阴影...
可是,可是,我说过我写博客的目的是为了给自己看,给以后的自己看。给他说,看,这是你努力的痕迹,很长一段时间过去了,你有做得更好么。
如果某天的自己翻到这句话,如果他累了,消沉了、堕落了,早已忘却了写博客这件事情,看到当初的自己,那么努力码上四五千字,花费两小时只为了把自己做题的过程、思路完完整整写清楚,如果他透过文章看到了那个曾经努力挣扎着前进的自己,他会不会也有一点伤感,会不会还会重新燃起一点希望的火花,为了那个拼命前进的自己。
看到这里的各位,我们皆是有缘人。加油,你我共勉。