如何传递对字符串的引用?

时间:2016-02-11 22:47:11

标签: c

我读到的关于scanfgetsfgets的所有内容都是有问题的;无论是白色空间,溢出还是复杂。我正在参加C课程的介绍,因为我有足够的经验使用Java和其他语言编程以确信这样做,我决定创建自己的函数以使用getchar()函数从用户获取字符串输入。我的代码的相关部分如下:

bool get_string (char prompt[], char* string)
{
    printf(prompt); // Prompt the user

    // Ensure string is allocated to store the null character and reset it if it has been initialized
    do { string = (char*)malloc(sizeof(char)); } while (string == NULL);

    int index = 0;
    char place = getchar();
    while (place != '\n') // Obtain all characters before endl
    {
        string = (char*)realloc(string, sizeof(string) + sizeof(char)); // Create room in the array
        if (string == NULL) return false; // Ensure realloc worked correctly
        string[index++] = place; // Place the new string in the second to last index
        place = getchar();
    }
    string[index] = '\0'; // Append the null character at the end

    return true; // Operation succeeded
}

通过测试和调试,我设法弄明白:

  1. 我的函数在本地符合规范,参数string保存输入的字符串。
  2. 我在main方法中使用的char*指针没有被改变。在调用我的输入函数之后,该指针的取消引用保持与其初始值相同。
  3. 我的印象是,因为我传递了一个指向该函数的指针,所以它会将参数视为参考。事实上,这就是我在课堂上讲授的内容。任何见解都可以提供帮助。

    如果获得奖励积分:

    你可以告诉我为什么它不会让我在main中释放我的char*指针。 (也许是因为它没有通过相同的问题分配?)

    我还做错了什么,例如多次调用realloc?

    注意:我正在使用MSVC C89编译器并定义bool,true和false预编译。

3 个答案:

答案 0 :(得分:3)

  

我的印象是,因为我传递了一个指向该函数的指针,所以它会将参数视为参考。事实上,这就是我在课堂上讲授的内容。任何见解都可以提供帮助。

C中的引用有。您传递的每个参数都是按值

因此:

void foo(int a) {
  a = 21;
  // a == 21 for the rest of THIS function
}
void bar(void) {
  int x = 42;
  foo(x);
  // x == 42
}

同样适用于:

static int john = 21;
static int harry = 42;
void foo(int * a) {
  a = &john;
  // a points to john for the rest of THIS function
}
void bar(void) {
  int * x = &harry;
  foo(x);
  // x still points to harry
}

如果您想通过参数更改指针,那么您需要将指针传递给该指针:

static int john = 21;
static int harry = 42;
void foo(int ** m) {
  *m = &john;
}
void bar(void) {
  int * x = &harry;
  foo(&x); // passing the address of x by value
  // x now points to john
}
  

我还做错了什么,例如多次调用realloc?

printf(prompt);

安全问题:尝试使用"%s"之类的内容作为prompt的值。更好地使用putsprintf("%s", prompt)

do { string = (char*)malloc(sizeof(char)); } while (string == NULL);

这是一个可能的无限循环。如果malloc失败,立即再次调用它将无法进行任何更改。另外:不要转换malloc的返回值。此外,sizeof(char) 定义等于1

int index = 0;

对于索引使用size_t

char place = getchar();

getchar返回int的原因,即能够检查EOF,你...

while (place != '\n')

......不要,但应该!

string = (char*)realloc(string, sizeof(string) + sizeof(char));

不要转换返回值,sizeof(string)没有按照您的想法执行,它是一个编译时常量(在64位系统上可能是8)。

if (string == NULL) return false;

内存泄漏,因为......

  

如果内存不足,则不会释放旧的内存块,并返回空指针。

     

[Source]

以下是我如何读取C中的一行:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

char * readline(char const * const prompt) {
  char buffer[10];
  char * string = malloc(1);
  if (string == NULL) {
    return NULL;
  }
  // The accumulated length of the already read input string.
  // This could be computed using strlen, but remembering it
  // in a separate variable is better, performancewise.
  size_t accumLength = 0;
  string[0] = '\0';
  printf("%s", prompt);
  while (fgets(buffer, 10, stdin) != NULL) {
    // To see what has been read in this iteration:
    // printf("READ: |%s|\n", buffer);
    // Compute the length of the new chunk that has been read:
    size_t const newChunkLength = strlen(buffer);
    // Try to enlarge the string so that the new chunk can be appended:
    char * const newString = realloc(string, accumLength + newChunkLength + 1);
    if (newString == NULL) {
      free(string);
      return NULL;
    }
    string = newString;
    // Append the new chunk:
    strcpy(string + accumLength, buffer);
    accumLength += newChunkLength;
    // Done if the last character was a newline character
    assert(accumLength > 0);
    if (string[accumLength - 1] == '\n') {
      // NOTE: Wasting 1 char, possible solution: realloc.
      string[accumLength - 1] = '\0';
      return string;
    }
  }
  // EOF is not an error!
  if (feof(stdin)) {
    return string;
  }
  free(string);
  return NULL;
}


int main(int argc, char ** argv) {
  char const * const input = readline(">");
  printf("---\n%s\n---\n", input);
  return 0;
}

答案 1 :(得分:2)

  

我在main方法中使用的char *指针没有被改变。在调用我的输入函数之后,该指针的取消引用保持与其初始值相同。

这是第一次开始编写C时让很多人绊倒的事情。如果你想要一个函数来更新一个指针值的参数,你必须将指针传递给指针。

假设如下:

void foo( T *p )
{
  *p = new_value(); // update the thing p is pointing to
}

void bar( void )
{
  T val;
  foo( &val ); // update val
}

非常简单 - 我们希望foo函数将新值写入val,因此我们将指针传递给val。现在将T类型替换为类型R *

void foo( R **p )
{
  *p = new_value(); // update the thing p is pointing to
}

void bar( void )
{
  R *val;
  foo( &val ); // update val
}

语义完全相同 - 我们正在为val写一个新值。所有改变的都是valp的类型。

所以,你的函数原型需要

bool get_string (char prompt[], char **string)

因为您要修改string指向的指针值。这也意味着在您的函数正文中,您写的是*string,而不是string

撰写malloc电话的首选方法是

T *p = malloc( sizeof *p * number_of_elements );

T *p;
...
p = malloc( sizeof *p * number of elements );

从C89 1 开始不需要演员表,而在C89下实际上可以抑制有用的诊断。由于C99取消了隐式int声明,因此它不再是一个问题,但最好还是将它关闭。另请注意sizeof的操作数;我们使用表达式(char)而不是像*p这样的类型表达式。由于表达式*p的类型为T,因此sizeof *p会提供与sizeof (T)相同的结果。它不仅看起来更干净,而且如果您决定更改p的类型,它会减少维护。

在您的情况下,p*string,向我们提供

*string = malloc( sizeof **string );

由于realloc是一项潜在的昂贵操作,因此您真的不想为每个新角色调用它。一个更好的策略是最初分配一个应该处理大多数情况的缓冲区,然后根据需要将其扩展一些当前大小的因子(例如加倍)。在这种情况下,我会做类似以下的事情:

size_t stringSize = INITIAL_SIZE; // keeps track of the physical buffer size

*string = malloc( sizeof *string * stringSize );
if ( ! *string )
  // initial memory allocation failed, panic

while ((place = getchar()) != '\n' && place != EOF) 
{
   if ( index == stringSize )
   {
     // double the buffer size
     char *tmp = realloc( *string, sizeof **string * ( stringSize * 2 ) );
     if ( tmp )
     {
       *string = tmp;
       stringSize *= 2;
     }
   }
   (*string)[index++] = place; 
}  

这会减少对realloc的通话总次数,从而最大限度地提高您的效果。

此外,如果realloc失败,它将返回NULL并保留当前分配的缓冲区;但是,如果发生这种情况,您确实不希望将该结果分配回*string,否则您将失去对该内存的唯一引用。您应始终realloc的结果分配给临时变量,并在分配回*string之前检查

还要注意我们如何下标*string;由于下标[]运算符的优先级高于一元*运算符,*string[index++]将被解析为*(string[index++]),这不是我们想要的 - 我们想要索引进入*string,而不是string。因此,我们必须使用括号明确地对*运算符进行分组,为我们提供

(*string)[index++] = place;

<小时/> 1。但是,在C ++中这是必要的,但是如果你正在编写C ++,那么你应该使用new运算符。

答案 2 :(得分:0)

OP希望&#34;传递对字符串&#34;的引用,然后将函数传递给存储字符串第一个元素地址的地址。

char *s = NULL;
while (get_string("Hello ", &s) > 0) {
  puts(s);
}
free(s);
s = NULL;

使用

git config core.pager `fold -w 80 | less`