这个问题尤其适用于数组。
在许多语言中,我会这样做:
#This is in python for simplicity:
def increment(mylist):
for i in mylist:
i += 1;
return mylist
mylist = {0,1,2,3}
mylist = increment(mylist)
我尝试了几种方法在C中返回数组,但没有一种方法像上面那样工作。似乎C并不是有意这样工作的。相反,我必须这样做:
#include <stdio.h>
increment(int *myarray, int size) {
for(int i = 0; i < size; i++){
myarray[i] += 1;
}
}
int main(){
int myarray[4] = {0,1,2,3};
increment(myarray, 4);
}
不用说,C函数改变了数组的状态,因此是一个副作用函数。有很好的理由避免这种情况(这不是这个问题的主题)。
在C语言中有没有办法避免这些类型的副作用?
注意,python中的对象是通过引用传递的。
def increment(mylist):
// mylist is a local reference to the original array
for i in mylist:
i += 1; // i is a local value: nothing is changed in mylist!
return mylist // returns a reference to the original (and unchanged...) array
如何修改原列表:
def increment(mylist):
for i in range(len(mylist)):
mylist[i] += 1
// returning mylist is optional since the caller's list has been modified
在C中与
完全相同int *increment(int array[], int size) {
for (int i=0; i<size; ++i) {
array[i] += 1;
}
return array;
}
但是你可以在Python中构建并返回一个全新的列表:
def increment(mylist):
return [i + 1 for i in mylist]
这在c语言中是不容易做到的,惯用的方法是要么让调用者提供数组和大小(如上所述),要么返回一个动态分配的数组:
int *increment(int array[], int size) {
int *new_array = malloc(size * sizeof(int));
for (int i=0; i<size; ++i) {
new_array[i] = array[i] + 1;
}
return new_array;
}
,当转移所有权时,让调用方释放返回的数组。
首先,在python中,{0,1,2,3}
不是一个列表,而是一个集合。
python代码将更直接地等同于您在C中所做的:
def increment(mylist):
for i in range(len(mylist)):
mylist[i] += 1;
return mylist
mylist = [0,1,2,3]
mylist = increment(mylist)
,在这种情况下,也会对python中的列表产生副作用。这是因为传递数组最常见的方式是通过引用(或C语言中的指针)
C代码将更接近你在python代码中所做的:
void increment(int *myarray, int size) {
for(int i = 0; i < size; i++){
int v = myarray[i]; // copy of the array value here
v += 1;
}
}
int main(){
int myarray[4] = {0,1,2,3};
increment(myarray, 4);
}
在这种情况下,对数组也没有副作用,因为我只是在使用它之前复制了数组的值。
如果你想避免副作用,一般的规则是你必须复制你的数组或你的单个数组值。
编辑:你可能想在你的python函数中做的是
def increment(mylist):
mylist = list(mylist) # copy array
for i in range(len(mylist)):
mylist[i] += 1
return mylist
在C代码中,您传递了指向数组第一个元素的指针,而数组保留在内存中。你能做的就是创建一个新的数组,然后返回一个指向它的指针。然而,要小心。如果你创建一个auto数组(在堆栈上创建),它将只存在于函数内部,因此返回的指针将指向垃圾内存。
int* increment(int *myarray, int size) {
int tempArray[size]; //only exists inside of the function.
for(int i = 0; i < size; i++){
tempArray[i] = myarray[i] + 1;
}
return tempArray; //don't do this, tempArray will not exist outside of this function.
}
您可以使用malloc
函数,它使用堆内存,并且也存在于函数之外。(您需要包含stdlib.h)
#include <stdio.h>
#include <stdlib.h>
int* increment(int *myarray, int size) {
int* tempArray = malloc(size*sizeof(int)); //exists globally
for(int i = 0; i < size; i++){
tempArray[i] = myarray[i] + 1;
}
return tempArray;
}
int main(){
int myarray[4] = {0,1,2,3};
int* newarray = increment(myarray, 4);
//use the newarray - myarray stays the same.
free(newarray); //don't forget to free when you no longer need it
}
改变数组的内容总是有"副作用"。在C语言中,就像正式定义一样。如果你正在寻找一种方法使数组等不可变,如只读并且总是在操作时创建一个新对象,也有方法可以做到这一点。
你必须意识到这通常涉及"硬拷贝"。的数据内容,所以它带来了执行开销。C给了你一个选择,如果你不想的话,你可以选择不那么低效。但如果你想要,那么更灵活的选择是动态分配。像这样:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int* increment(const int* myarray, int size)
{
int* new_obj = malloc(sizeof(int[size]));
for(int i=0; i<size; i++)
{
new_obj[i] = myarray[i] + 1;
}
return new_obj;
}
int main (void)
{
int* myarray = malloc(sizeof(int[4]));
memcpy(myarray, (int[]){0,1,2,3}, sizeof(int[4]));
for(int i=0; i<4; i++)
{
printf("%d ", myarray[i]);
}
puts("");
int* another_array = increment(myarray, 4);
free(myarray);
for(int i=0; i<4; i++)
{
printf("%d ", another_array[i]);
}
free(another_array);
}
注意,这比修改原始数组要慢得多。堆分配和数据复制都相对较慢。
你可以创建"糟糕的api"函数,比如
int* increment(int *myarray, int size) {
for(int i = 0; i < size; i++){
myarray[i] += 1;
}
return myarray;
}
返回指向传递的同一数组的指针。这是糟糕的API,因为它令人困惑,尽管一些C标准函数就是这样设计的(strcpy
等)。为了使用这个函数,你需要一个指针指向数组的第一个元素,而不是数组本身。