在《C 编程语言》第 1 章的末尾,有一些练习需要完成。我现在正在做的一个程序要求您制作一个程序,将一长串文本以特定长度包装成多行。无论指定的行的最大宽度如何,除了最后一行不被换行之外,以下函数都 100% 有效。
// wrap: take a long input line and wrap it into multiple lines
void wrap(char s[], const int wrapline)
{
int i, k, wraploc, lastwrap;
lastwrap = 0; // saves character index after most recent line wrap
wraploc = 0; // used to find the location for next word wrap
for (i = 0; s[i] != '\0'; ++i, ++wraploc) {
if (wraploc >= wrapline) {
for (k = i; k > 0; --k) {
// make sure word wrap doesn't overflow past maximum length
if (k - lastwrap <= wrapline && s[k] == ' ') {
s[k] = '\n';
lastwrap = k+1;
break;
}
}
wraploc = 0;
}
} // end main loop
for (i = 0; i < wrapline; ++i) printf(" ");
printf("|\n");
printf("%s\n", s);
}
我发现问题出在变量
wraploc
上,该变量会递增,直到大于
wrapline
(一行的最大索引)。一旦大于 wrapline
,就会在适当的位置插入换行符,并将 wraploc
重置为 0。 问题在于,在最后一行,
wraploc
永远不会大于
wrapline
,即使它应该是。它在字符串的整个迭代过程中完美递增,直到最后一行。举个例子:char s[] = "This is a sample string the last line will surely overflow";
wrap(s, 15);
$ ./a.out
|
this is a
sample string
the last line
will surely overflow
该线代表应该换行的位置。在本例中,当字符数明显多于该值时,
wraploc
的值为 14。
我不知道为什么会发生这种情况,有人可以帮助我吗?
(而且我是 C 语言的初学者,没有任何指针经验,所以请远离您答案中的内容,谢谢)。
这是我的回答:
inline int wordlen(const char * str){
int tempindex=0;
while(str[tempindex]!=' ' && str[tempindex]!=0 && str[tempindex]!='\n'){
++tempindex;
}
return(tempindex);
}
void wrap(char * s, const int wrapline){
int index=0;
int curlinelen = 0;
while(s[index] != '\0'){
if(s[index] == '\n'){
curlinelen=0;
}
else if(s[index] == ' '){
if(curlinelen+wordlen(&s[index+1]) >= wrapline){
s[index] = '\n';
curlinelen = 0;
}
}
curlinelen++;
index++;
}
}
wraploc
递增
i
,直到达到 wrapline
(示例中为 15)。当您换行时,您将从
i
这意味着在下一行中,在
lastwrap
i
之间已经有一些字符,即,您无法在那里将 wraploc
重置为 0。尝试设置
wraploc = i-lastwrap
所做的调整:
扩展可断行字符集以包括连字符、制表符等
。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define SPACE (char)('+') /*visible representation of tab replacement for analysis */
void ErrorExit(char *str)
{
puts(str);
exit(0);
}
/*--------------------------------------------------------------------------
next_break()
Algo: function does a look-ahead for a space, a hyphen... anything that
constitutes a natural sentence break oppty. Returns the index of
the break oppty to the caller.
*--------------------------------------------------------------------------*/
int next_break(const char * str)
{
int done = FALSE, tempindex= -1;
char ch;
while(!done)
{
ch = str[++tempindex];
switch( ch )
{
case 0:
case (char)' ':
case (char)'\n':
case (char)'\t':
case (char)'-':
done = TRUE;
break;
default:
break;
}
}
return(tempindex);
}
/*-------------------------------------------------------------------------------------
wordwrap()
Algo: parses a long string looking for line break opportunities with
every char. If a break oppty is found at cuurent offs, does a qwk scan ahead
via next_break() to see if a better oppty exists ahead. ('Better' means closer
to the margin but NOT past the margin)
If no better oppty found ahead, inserts a newline into buffer & restarts the line
count. Else, postpones the newline until chars are read up to the better oppty.
Inputs: char *src buffer needing word wrap formatting.
int max_line_len for wrap margin.
int pointer *ugly_breaks for returning number of middle-of-word breaks.
Returns a buffer having the formatted text.
*-------------------------------------------------------------------------------------*/
char *wordwrap(const char *src, const int max_line_len, int *ugly_breaks)
{
int src_idx=0, dest_idx = 0, cur_line_len = 0, done = FALSE;
char ch;
char *dest = malloc(strlen(src)*3); /* Enough space for even the worst of wrap possibilities.*/
int new_line_needed = FALSE;
if(!dest)
ErrorExit("Memory Allocation error in wordwrap");
while(!done)
{
ch = src[src_idx];
switch(ch)
{
case 0:
done = TRUE;
break;
case (char)' ':
case (char)'-':
dest[dest_idx++]=ch; /* No matter what happens next, we will include this char... */
cur_line_len++; /* ... and so of course we need to say this. */
/* Would the next break oppty put us past the margin/line limit? */
if(cur_line_len + next_break(&src[src_idx+1]) >= max_line_len)
{
/* A: Yes. Take the break oppty here, Now*/
new_line_needed = TRUE;
}
break;
case (char)'\n':
/* NOTE: you don't have to honor existing line breaks in the text.
* You can comment out these next 2 lines (and remove the newline ('\n') case in
* function next_break()) to completely reformat paragraphs. It's actually more
* aesthetic if you do, but this is an opinion, and so I leave the code here. */
dest[dest_idx++]=ch;
cur_line_len=0;
break;
case (char)'\r': /* Nope, stripping these */
break;
case (char)'\t': /* Tab, replace with space(s)*/
if(cur_line_len+1 + next_break(&src[src_idx+1]) >= max_line_len)
{
/* We have a tab as the last character of the current line.
* You can expect this to be rare and it is. But if you don't
* care for it, result will be disappointing sooner or later*/
new_line_needed = TRUE;
}
else
{
/* Replace the 4s here with any tab stop you like. 8 is the standard.*/
int to_add = 4-((cur_line_len)%4);
while(to_add-- && cur_line_len < max_line_len)
{
dest[dest_idx++]=SPACE; /* Adaptable space replacement char */
cur_line_len++;
}
}
break;
default:
dest[dest_idx++]=ch;
cur_line_len++;
break;
}
/* Has one of our cases flagged a need for newline? */
if(new_line_needed)
{
int space_remaining = (max_line_len-cur_line_len);
double percent_remain = 0.0;
new_line_needed = FALSE;
/* We now take the newline request as advisement. We inspect
* the length of remaining chars on the current line before we agree.
* If some long word is next, then we're going to break it up ugly
* instead of leaving a lot of unused space in our buffer/application.
* It's merely trading one kind of ugly (unused space) for another (broken word).
*
* We want to keep going (no newline) if more than -- say 10% -- of current line
* would become white space by newlining right now.
*
* Set percent_remain tolerance lower than 10% to get more greedy
* with space conservation but get more ugly word breaks.
*
* 5% (0.05) is pretty nice with an avg of only 2 ugly breaks per
* a paragraph with a "reasonable" margin (70 chars or more).
*
* Set to 100% (1.0) and you won't get any ugly breaks -- unless
* you encounter a Huge word that is longer than your margin limit.
*/
if(cur_line_len > 0 )
percent_remain = (double)space_remaining/cur_line_len;
if(percent_remain < 0.10)
{
/* Not much space remaining, we can newline here */
dest[dest_idx++]='\n';
cur_line_len = 0;
}
}
/* Since we are habitually ignoring new line requests made by the cases,
* -- AND because it is possible to get some long character sequence or word
* which may exceed our margin --
* ... check for margin overflow with every loop. */
if(cur_line_len >= max_line_len)
{
/* We have or will overflow with next char.
* This is called breaking the word ugly. Sorry babe.*/
dest[dest_idx++]='\n';
cur_line_len = 0;
/* Track ugly breaks for tolerance & adjusting newline rejections*/
(*ugly_breaks)++;
}
src_idx++;
}
dest[dest_idx++]='\0'; /* cap it */
return dest;
}