避免c的副作用



这个问题尤其适用于数组。

在许多语言中,我会这样做:

#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等)。为了使用这个函数,你需要一个指针指向数组的第一个元素,而不是数组本身。

相关内容

  • 没有找到相关文章

最新更新