如果想深入理解C语言的指针,很多人会推荐你读《C和指针》。
本书在第一章给出了一个快速入门的例子,虽然作者的目的是让读者通过这个例子快速的了解C语言的语法情况,但是这个例子本身的处理逻辑、尤其是一些细节,还是比较晦涩难懂(尽管个人觉着这是一个不错的例子),希望这篇文章能够在逻辑上给需要的人提供一些帮助。至于语法方面,这里不再做解释,如果有需要的话,还是去读原书吧,书上比较详细。最后再总结一下来自练习题的几个扩展。
话不多说,下面介绍这个程序:先输入字符串,然后再输出特定的一部分;截取的规则是通过下标,前后都包括,具体的范围在第一行的时候输入。下面是程序运行的过程示例:
运行结果:
4 9 12 20 -1 abcdefghijklmnopqrstuvwxyz Original input : abcdefghijklmnopqrstuvwxyz Rearranged line: efghijmnopqrstu Hello there, how are you? Original input : Hello there, how are you? Rearranged line: o ther how are I am fine, thanks. Original input : I am fine, thanks. Rearranged line: fine,hanks. See you! Original input : See you! Rearranged line: you! Bye Original input : Bye Rearranged line:
说明:
第一行输入的一串数字是指定截取字符串的范围,通过列标号指定,最后的负数是结束的标志,即”4 9 12 20 -1”表示从第4列到第9列(注:这里的范围和我们常见的前闭后开区间不一样,平时我们说的范围是包括前面但不包括后面,这里是前后都包括,即第4列和第9列也包括,一共有(9-4+1)=6列),第12列到第20列(也是前后都包括,共(20-12+1)=9列)。
首先输入26个字母做了测试,截取第4列到第9列,第12列到第20列,然后输出。这里的列是从0开始计数的(和字符数组的计数方式相同)。
程序代码:
/* This program reads input lines from the standard input and prints ** each input line, followed by just some portions of the lines, to ** the standard output. ** ** The first input is a list of column numbers, which ends with a ** negative number. The column numbers are paired and specify ** ranges of columns from the input line that are to be printed. ** For example, 0 3 10 12 -1 indicates that only columns 0 through 3 ** and columns 10 through 12 will be printed. 输入一个字符串,并打印出来,然后再打印出字符串的特定的一部分 第一行输入几个列标号,列标号成对出现,其最后一个为负数(用来标识列标号已经输入完毕) 例如:输入 0 3 10 12 -1 ,标识将字符串的第0列到第3列,第10列到第12列,之间的内容打印出来 注:这里输入的列标号从零开始计数,而且是前后都包括(比如:0 3 是包括第0列到第3列,一共4列,不是3列) */ #include #include #include #define MAX_COLS 20 /* max # of columns to process */ #define MAX_INPUT 1000 /* max len of input & output lines */ int read_column_numbers(int columns[], int max); void rearrange(char *output, char const *input, int n_columns, int const columns[]); int main(void) { int n_columns; /* # of columns to process */ int columns[MAX_COLS]; /* the columns to process */ char input[MAX_INPUT]; /* array for input line */ char output[MAX_INPUT]; /* array for output line */ /* ** Read the list of column numbers */ n_columns = read_column_numbers(columns, MAX_COLS); /* ** Read, process and print the remaining lines of input. */ while (gets(input) != NULL) { printf("Original input : %s/n", input); rearrange(output, input, n_columns, columns); printf("Rearranged line: %s/n", output); } return EXIT_SUCCESS; } /* ** Read the list of column numbers, ignoring any beyond the specified ** maximum. */ int read_column_numbers(int columns[], int max) { int num = 0; int ch; /* ** Get the numbers, stopping at eof or when a number is < 0. */ while (num < max && scanf("%d", &columns[num]) == 1 && columns[num] >= 0) num += 1; /* ** Make sure we have an even number of inputs, as they are ** supposed to be paired. */ if (num % 2 != 0) { puts("Last column number is not paired."); exit(EXIT_FAILURE); } /* ** Discard the rest of the line that contained the final ** number. */ while ((ch = getchar()) != EOF && ch != '/n') ; return num; } /* ** Process a line of input by concatenating the characters from ** the indicated columns. The output line is then NUL terminated. */ void rearrange(char *output, char const *input, int n_columns, int const columns[]) { int col; /* subscript for columns array */ int output_col; /* output column counter */ /*注:output_col的值等于strlen(output),即output数组的长度*/ int len; /* length of input line */ /*注:len的值等于strlen(input),即input数组的长度*/ len = strlen(input); output_col = 0; /* ** Process each pair of column numbers. */ for (col = 0; col < n_columns; col += 2) { int nchars = columns[col + 1] - columns[col] + 1; /* ** If the input line isn't this long or the output ** array is full, we're done. columns中指定的范围大于inpput数组的长度, 或者output数组已满, 这时循环结束 */ if (columns[col] >= len || output_col == MAX_INPUT - 1) break; /* ** If there isn't room in the output array, only copy ** what will fit. output数组剩余空间不足以 存放nchars个字符 */ if (output_col + nchars > MAX_INPUT - 1) nchars = MAX_INPUT - output_col - 1; /* ** Copy the relevant data. */ strncpy(output + output_col, input + columns[col], nchars); output_col += nchars; } output[output_col] = '/0'; }
程序讲解:
主要讲rearrange函数,在循环中的逻辑。这里的各种条件语句主要是为了处理 “复制范围” 和 “输入数组 以及 输出数组” 的关系。
首先,讲一下 范围 和 一个数组的长度 的三种关系:①范围在长度内;②范围部分在长度内;③范围在长度外;例如数组长度为10,那么范围[4,8]在长度内,[6, 9]部分在范围内;[11, 14]在范围外。
接着,再看 范围 和 两个数组长度 的对应关系:这时有9种情况。
范围在数组1内,并且在
①
数组2内
②
部分在数组2中
③
数组2外;
范围部分在数组1中,并且在
④
数组2内
⑤
部分在数组2中
⑥
在数组2外;
范围在数组1外,并且在
⑦
数组2内
⑧
部分在数组2中
⑨
在数组外。
知道范围和两个数组长度的对应关系,就很容易分析这里的程序,在rearrange函数中的每一次循环中,存在一个范围,columns[col]到columns[col+1];两个数组,input和output数组。后面的各种判断,都是为了针对这个范围和两个数组的的9种关系,当然,根据题目的具体情况,这9种情况有部分情况不需要处理。
假定程序中input是是数组1,output是数组2,那么
1.
不需要处理的情况有: ①④;(对于情况④,隐含在函数strncpy函数中处理,strncpy和strcpy函数一样,在遇到字符串的结束字符’/n’时,会停止复制。 因为程序默认是范围是递增的顺序,那么出现情况④时,那么说明这就是最后一个循环,也不存在将’/0’提前复制的问题。(参见下面扩展3,那里需要考虑提前复制字符’/0’的问题)
2.
if(columns[col] >= len):用来处理情况⑦⑧⑨;
3.
if(output_col == MAX_INPUT – 1):用来处理情况③⑥;
4.
if(output_col + nchars > MAX_INPUT – 1):用来处理情况②⑤;
程序扩展:
1.
在rearrange函数中,能够用strcpy函数来代替strncpy函数,即写成”strncpy(output + output_col, input + columns[col], nchars);”?
答:
可以,但不推荐。你可能认为是不可以的,但有意思的是这里确实是可以的。
①
由于strcpy会复制到字符串的结尾,即会比原来复制的字符要多。这里有意思的事情就发生了,如果你做试验的话,你会发现程序是能够正确运行的。原因在于,像output数组复制时,开始位置和output_col变量指定,无论使用的是strcpy函数还是strncpy函数,该变量都是正确更新的,(语句为:output_col += nchars;)。下次复制会覆盖前面复制的结果,在复制完成后也能够正确添加结束字符 ‘/0’,所以程序能够正确执行。
②
有一个隐患是:如果output数组不够大,那么有可能复制的数据超过output数组的容量,导致溢出,其实这就是程序中nchar的主要作用,用来防止复制的数据容量查过output的容量。但是这个在本例中是假定了input和output数组一样大,所以不会出现output的容量不够的现象。
2.
程序中while(gets(input)!=NULL){……}的隐患是什么?
答:
也是和数组溢出有关,gets函数无法防止一个非常长的输入。尽管这里假定input的长度是1000,但是当我们舒服一个超过1000个字符的行,就会引发错误。在文件操作时的fgets函数是没有这个问题的,因为它需要指定输入的长度。
3.
在rearrange函数中,
if (columns[col] >= len ……)
break;
该语句的意思是当字符的列范围(在运行时第一行输入指定的)超过输入行的末尾时就停止复制。这条语句如果想成功执行,就需要第一行所给的列范围是
递增顺序
输入的,如何修改程序,让程序允许我们不使用递增顺序。注:输入的时候是成对输入的,这里的变化是讲:后两个可以比前两个小,例如原来的递增顺序是 “4 9 12 20 -1” ,现在程序修改后要可以处理:”12 20 4 9 -1″。当然在成对的两个数中,后面的书还是要比前面的数大,比如:20要大于12,9要大于4,不能写为20 12 9 4 -1。
分析:
这时候在rearrange函数中,如果遇到范围大于输入数组的长度的情况,即 columns[col] >= len,那么就不能退出循环,而是要跳过本次,因为有可能以为后面还有符合要求的范围。另外,对于输入数组,给出的范围可能是一部分在input数组中,即上面讨论一个范围和两个数组的关系时的情况④。
代码如下:
void rearrange(char *output, char const *input, int n_columns, int const columns[]) { int col; /* subscript for columns array */ int output_col; /* output column counter */ /*注:output_col的值等于strlen(output),即output数组的长度*/ int len; /* length of input line */ /*注:len的值等于strlen(input),即input数组的长度,不包括最后的字符'/0',即这里最大为(MAX-INPU-1)*/ len = strlen(input); output_col = 0; /* ** Process each pair of column numbers. */ for (col = 0; col < n_columns; col += 2) { int nchars = columns[col + 1] - columns[col] + 1; /*给出的数据超过input的长度*/ if(columns[col] >= len){ continue; } /*输出数组已满*/ if ( output_col == MAX_INPUT - 1) break; /*为了防止过早的复制进,inout末尾的'/0'字符*/ if(columns[col]+nchars-1 >= len){ nchars = len-columns[col]; printf("输入的长度为:%d /n", len); printf("为了防止过早复制/0,这里做了调整"); } /* ** If there isn't room in the output array, only copy ** what will fit. */ if (output_col + nchars > MAX_INPUT - 1){ nchars = MAX_INPUT - output_col - 1; printf("输出数组不够,这里做了调整"); } /* ** Copy the relevant data. */ strncpy(output + output_col, input + columns[col], nchars); output_col += nchars; } output[output_col] = '/0'; }
4.
在源程序中,第一行指定范围时,一定是要成对输入,即要同时指定开始和结束的位置。 那么怎么修改使程序可以处理奇数个输入?如果是奇数个,那么程序把最后一个列范围的结束设置为到输入行的结束。
分析:
由于但输入的确定范围的数目是奇数时,是将最后一个范围扩展到input的结束位置,所以这里还是要求输入时按照递增顺序的。
这时首先要将read_column_numbers函数中的判断输入的个数是奇数还是偶数的代码删掉;然后要先进行判断,才能计算nchars。
代码如下:
删去read_column_numbers函数中的下列代码:
if (num % 2 != 0) { puts("Last column number is not paired."); exit(EXIT_FAILURE); }
将rearrang函数修改为:
void rearrange(char *output, char const *input, int n_columns, int const columns[]) { int col; /* subscript for columns array */ int output_col; /* output column counter */ /*注:output_col的值等于strlen(output),即output数组的长度*/ int len; /* length of input line */ /*注:len的值等于strlen(input),即input数组的长度*/ len = strlen(input); output_col = 0; /* ** Process each pair of column numbers. */ for (col = 0; col < n_columns; col += 2) { int nchars; if(col+1 < n_columns){ nchars = columns[col + 1] - columns[col] + 1; }else{ nchars = len ;/*如果输入的为奇数,那么这里将input的长度赋值给nchars,后面strcpy函数复制时会将从此处开始到结尾*/ } /* ** If the input line isn't this long or the output ** array is full, we're done. columns中指定的范围大于inpput数组的长度, 或者output数组已满, 这时循环结束 */ if (columns[col] >= len || output_col == MAX_INPUT - 1) break; /* ** If there isn't room in the output array, only copy ** what will fit. output数组剩余空间不足以 存放nchars个字符 */ if (output_col + nchars > MAX_INPUT - 1) nchars = MAX_INPUT - output_col - 1; /* ** Copy the relevant data. */ strncpy(output + output_col, input + columns[col], nchars); output_col += nchars; } output[output_col] = '/0'; }
本文链接:
http://blog.csdn.net/daheiantian/archive/2011/01/04/6151433.aspx