C 在函数中修改值的最佳方式

C Best way of modifying values in a function

我用 Java 开发了几年,现在我想学习 C,我发现了一些差异。

在 Java 中,当我想 return 函数中的某些内容时(例如,读取用户输入我会写

String s;
s = new Scanner(System.in).nextLine(); 

在 C 中我会写

char s[20];
scanf("%d", name);

区别在于,在Java中,函数return是一个可以直接赋值给变量的值,而在C中,函数将变量名作为参数(分别是指向变量)。

我在很多 C 函数中都注意到了这一点。当我编写自己的函数时,我应该像在 Java 中那样做,还是应该使用函数中的 assigning/returning 值的 C 风格?

嗯,你应该知道C和Java,

的主要区别

JavaObject Oriented programmingC 不是。

Java 中几乎所有内容都是 Objectprimitives 除外)。

你调用 Scanner class 的方法是一个非常糟糕的方法, 你应该这样做:

Scanner scanner= new Scanner(System.in);

然后调用它来获取字符串:

String s = scanner.nextLine();

原因是因为每次调用 new Scanner(System.in) 时,您都会将扫描仪分配给同一个 Object,因此它将从 System.in 的开头重新开始,这意味着,您会不断得到一遍又一遍地输入相同的内容。

然而在C中,情况并非如此。因为它不是你调用的对象。

所以简而言之,请记住,在 java 中,一切都是 reference(除了源的原始值)到其他东西,而在 C 中不是这样

编辑: 在向方法发送参数方面 java 可能很棘手,因为这取决于您发送的是原始数据还是对象。

出于这个原因,通常最好让方法 return 得到一些东西,而不是发送需要设置为参数的数据

例如看看场景:

 1. Example 1

public void addOne(int i){
   i++;
}


public static void main(String... args){
   int i=0;
   addOne(i);
   System.out.println(i);
}

output= 0;

 2. Example 2


public int addOne(int i){
  return i++;
}

//output = 1


public static void main(String... args){
   int i=0;
   i = addOne(i);
   System.out.println(i);
}

在仅使用 return 值的 C 中做事的最大考虑是没有异常。与可以在常规 call/return 进程之外报告错误情况的 Java 不同,C 没有这样的功能。换句话说,Scanner 有一个选项,当没有下一行时抛出异常; scanf 没有这样的选项。

如果您想 return 函数中的错误状态,您只能 (1) return 将其放入静态变量中(对并发不安全),(2) return将其作为 return 值,或 (3) return将其放入单独的 "error" 对象中。有时选项 (2) 和 (3) 会结合使用。

因此,当状态被 return 编辑为 return 值时,您经常会看到可能会像 scanf 这样结构化的 C API,而其余的通过指针修改值。

if (scanf("%d%d", &i, &j) == 2) {
    ... // Got 2 numbers
} else {
    fprintf(stderr, "Wrong input! Expected two numbers.");
}

不会失败的 API(例如,isalpha(ch)tolower(ch))return 它们的值直接。

Java 是面向对象的,return 是一个指向对象的指针的对象。

在 C 中,您也可以 return 指针,但不能是数组。您显示的 C 代码不正确,因为未初始化声明为指向 char 的指针的 s。 Scanf 期望指向预分配缓冲区的指针作为第二个参数。它将输入字符串写入缓冲区。我猜你的意思是 scanf("%d", s);.

您编写的 C 代码将无法运行,因为 s 没有指向分配的缓冲区来存储字符。您可以将 s 定义为 char s[1024];,在这种情况下,s 相当于指向 1024 个字符的缓冲区的指针。

您还可以定义 s 为 char *s = malloc(1024*sizeof(char));,它将为 s 动态分配缓冲区。然后,当您不再需要缓冲区时,您需要显式调用 free(s) 来释放缓冲区。这是因为 C 没有 Java 中那样的垃圾收集器。

而在 Java 中有一个叫做 garbage collector 的东西,在 C 中你负责内存管理。

Java 是 'give it to me'.

String s = MyFunction();    // Give it to me.

在 Java 中,当您调用一个从某处检索数据的函数时,它只是透明地分配它并 return 它,垃圾收集器稍后会释放它。

C 是 'put it there'.

在 C 中,您必须管理内存。通常的做法是先找个地方放数据,然后尝试检索它。

此外,您的代码应该更像这样:

char s[80];         // Static memory allocation, we hope it is enough.
scanf("%79s", s);   // Now we retrieve data. (Put it there).

或者像这样:

char* s = malloc(80 * sizeof(char)); // Dynamic memory allocation, we hope it is enough.
scanf("%79s", s);                    // Now we retrieve data. (Put it there).

// Do stuff with data 

free(s);                             // Be responsible.

所以,,你应该使用assigning/returning值的C风格(这是有道理的,毕竟你在做C)。

还有一点,如果你像Java这样编码,你将得到:

char* MyTest() {
    char* a = "pipo";
    return a;           // Big fail, 'a' is local and will not exist anymore after return.
}

char* MyOtherTest() {
    char a[]= "pipo";
    char* b = malloc(80 * sizeof(char));
    strncpy(b, a, sizeof(a));
    return b;           // My bet this one will never be freed.
}

最后一个例子打破了一个众所周知的良好实践规则调用者应该负责allocating/freeing内存

我看到的唯一例外是 值类型,例如简单类型或结构。

// Nice and simple.
int MyAdd(int a, int b) { return a+b; }

// Why ? just Why ?
void MyAdd(int a, int b, int* r) { *r = a+b; }

要在 C 中读取 char 数组,您首先必须初始化实际的数组,并限制它的长度。像这样的东西:

char str[20];
scanf("%s", &str);

注意 %s 格式字符串告诉 scanf 捕获字符串,%d 用于整数。

@DasBlinkenLight 有一个很好的观点:有时你需要一个函数的 return 值来传回一个整数,指示发生了错误以及发生了什么类型的错误。

正如@chmike 指出的那样,另一个原因是如果你想在这种情况下 return 一个字符串,你将不得不使用 malloc() (C 相当于 Java的 new)。但是C没有垃圾回收,所以必须显式释放。因此,通常 "bad etiquette" 将必须释放的指针传递给其他外部函数。 谁说程序员甚至希望在堆上分配内存? 使用堆是昂贵的,而且程序员使用 C 的全部原因通常是因为所讨论的程序需要

如果调用你的函数的程序员想要使用堆,在 scanf 的情况下,他们会通过自己调用 malloc() 来实现,然后给 scanf 一个指向新的内存位置,然后您可以用您的功能填充它。这样,scanf 的调用者就可以跟踪所有已分配的内存,以及未分配的内存。

当 APIs do return 分配指针时,通常是因为它无法避免,并且他们明确记录了这一点并且通常提供释放内存的函数正确,就像 POSIX regex's regfree or globglobfree 一样。这样,如果库确实分配了内存,库也会释放它。