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,
的主要区别
Java
是 Object Oriented programming
而 C
不是。
在 Java
中几乎所有内容都是 Object
(primitives
除外)。
你调用 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 glob 的 globfree
一样。这样,如果库确实分配了内存,库也会释放它。
我用 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,
的主要区别Java
是 Object Oriented programming
而 C
不是。
在 Java
中几乎所有内容都是 Object
(primitives
除外)。
你调用 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 glob 的 globfree
一样。这样,如果库确实分配了内存,库也会释放它。