【问题标题】:Why is string modified in C even when I'm not trying to modify it?即使我不尝试修改字符串,为什么还要在 C 中修改字符串?
【发布时间】:2020-09-02 18:51:09
【问题描述】:

我正在尝试解决 K&R C 第二版中的练习 1-19。 “编写一个函数 reverse 来反转字符串 s。用它来编写一次将其输入反转一行的程序。”

我的解决方案采用两个输入字符串 sts 是源,t 是目标。并将源s 中的数据复制到t。我能够解决这个问题,但我很难理解为什么要修改源字符串s,即使它不在等号运算符的左侧。

#include <stdio.h>

/* Solution to Exercise 1-19. Chapter 1 */

#define MAXLENGTH 10

int getln(char s[], int lim);
void reverse(char s[], char t[]);

int main()
{
  int i, len;

  char s[MAXLENGTH]; /* original string */
  char t[MAXLENGTH]; /* reversed string */

  while ((len = getln(s, MAXLENGTH)) > 0) {
    printf("before reverse: %s", s);
    reverse(s,t);
    printf("reversed string: %s\n", t);
    printf("after reverse: %s", s);
  }  
  return 0; 
}

/* getln: read a line into s, return length */
int getln(char s[], int lim)
{
  int c, i, l; 

  l = 0; 
  for (i = 0; ((c = getchar()) != EOF) && (c != '\n'); ++i) {
    if (i < (lim - 1)) {
      s[l] = c; 
      ++l;
    }  
  }  

  if (c == '\n') {
    s[l] = c; 
    ++l;
  }  

  s[l] = '\0';
  return l; 
}

/* reverse: reverses s to target t */
void reverse(char s[], char t[])
{
  int i, j; 
  for (i = 0; s[i] != '\0'; ++i)
   ;  
  --i;                                                                                                                                                                             
  if (s[i] == '\n') {
    --i;
  }  
  for (j = 0; i >= 0; ++j) {
    t[j] = s[i];
    --i;
  }  
  t[j] = '\0';
}

测试用例:

$ ./a.out < testdata 
before reverse: abcdefghi
reversed string: ihgfedcba
after reverse: abcdefghi
ihgfedcba$ 

文件testdata的内容:

$ cat testdata 
abcdefghijklmnopqrstuvwxyz
$ 

【问题讨论】:

    标签: c reverse c-strings kernighan-and-ritchie function-definition


    【解决方案1】:

    您没有为ct 分配内存,因此您正在覆盖内容。

    【讨论】:

      【解决方案2】:

      函数getln有一个bug为了简化函数的分析我们假设lim等于2。

      然后在这个循环中

        l = 0; 
        for (i = 0; ((c = getchar()) != EOF) && (c != '\n'); ++i) {
          if (i < (lim - 1)) {
            s[l] = c; 
            ++l;
          }  
        }  
      

      你可以写lim-1 个字符,只有一个字符。当用户按下向输入缓冲区发送换行符 '\n' 的 Enter 键时,循环停止其迭代。

      所以最后读取的字符是换行符'\n'。该字符在循环后存储在字符串中

        if (c == '\n') {
          s[l] = c; 
          ++l;
        }  
      

      现在限制已经用完了。设置了传递的字符数组的两个字符。

      但是在下一个语句中

        s[l] = '\0';
      

      l等于2时,内存访问超出限制。

      就是这样。如果参数lim 的值等于传递的字符数组的大小,该函数将调用未定义的行为。终止零字符'\0'写入字符数组外的内存中,以后可以覆盖。

      我将按照下面的演示程序中所示的方式定义函数。

      #include <stdio.h>
      
      size_t getln( char s[], size_t n )
      {
          size_t i = 0;
      
          if ( n )
          {
              int c;
      
              while ( i + 1 < n && ( c = getchar() ) != EOF && c != '\n' )
              {
                  s[i++] = c;
              }
      
              if ( c == '\n' && i + 1 < n ) s[i++] = c;
      
              s[i] = '\0';
          }       
      
          return i;   
      }
      
      int main(void) 
      {
          enum { N = 10 };
          char s[N];
      
          while ( getln( s, N ) ) printf( "\"%s\"\n", s );
      
          return 0;
      }
      

      如果进入

      abcdefghijklmnopqrstuvwxyz
      

      那么程序输出将是

      "abcdefghi"
      "jklmnopqr"
      "stuvwxyz
      "
      

      即只有最后输入的字符串包含换行符。

      注意练习中有写

      编写一个函数reverse,将字符串s反转。

      这意味着您需要将原始字符串本身反转,而不是将其以相反的顺序复制到另一个字符数组。

      这样的函数可以如下所示

      #include <stdio.h>
      
      char * reverse( char *s )
      {
          size_t n = 0;
      
          while ( s[n] != '\0' ) n++;
      
          if ( n && s[n-1] == '\n' ) --n;
      
          for ( size_t i = 0; i < n / 2; i++ )
          {
              char c = s[i];
              s[i] = s[n-i-1];
              s[n-i-1] = c;
          }
      
          return s;
      }
      
      size_t getln( char s[], size_t n )
      {
          size_t i = 0;
      
          if ( n )
          {
              int c;
      
              while ( i + 1 < n && ( c = getchar() ) != EOF && c != '\n' )
              {
                  s[i++] = c;
              }
      
              if ( c == '\n' && i + 1 < n ) s[i++] = c;
      
              s[i] = '\0';
          }       
      
          return i;   
      }
      
      int main(void) 
      {
          enum { N = 10 };
          char s[N];
      
          while ( getln( s, N ) ) printf( "\"%s\"\n", reverse( s ) );
      
          return 0;
      }
      

      如果输入是,则再次

      abcdefghijklmnopqrstuvwxyz
      

      那么程序输出是

      "ihgfedcba"
      "rqponmlkj"
      "zyxwvuts
      "
      

      如果您想从函数reverse 内的字符串中删除换行符'\n',请替换此语句

          if ( n && s[n-1] == '\n' ) --n;
      

      为了这个

          if ( n && s[n-1] == '\n' ) s[--n] = '\0';
      

      【讨论】:

      • 可能是我弄错了。但是如果getln 有错误,则字符串s 在调用函数reverse 之前不应该正确打印。它在调用reverse 之前正确打印。然后是修改后reverse.
      • @NachiketChinchure 以零结尾的字符存储在字符数组 s 之外(似乎放在数组 t 中)。函数调用后,这个零终止字符被覆盖。
      • @NachiketChinchure 此输出“反向后:abcdefghi ihgfedcba”表明在数组 s 中找不到零终止字符。:)
      猜你喜欢
      • 2021-08-12
      • 2018-04-10
      • 1970-01-01
      • 1970-01-01
      • 2019-11-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多