避免 C 中的副作用
Avoiding Side Effects in 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 中 return 数组的方法,其中 none 的工作方式与上述相同。似乎 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中{0,1,2,3}
不是列表而是集合。
与您在 C 中所做的更直接等效的 python 代码是:
def increment(mylist):
for i in range(len(mylist)):
mylist[i] += 1;
return mylist
mylist = [0,1,2,3]
mylist = increment(mylist)
在这种情况下,python 中的列表也会产生副作用。
这是因为传递数组的最常见方式是通过引用(或 C 中的指针)
更接近您在 python 代码中所做的 C 代码是:
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 语言中始终是“副作用”。如果您更愿意寻找一种方法来制作数组等不可变,就像在只读中一样,并且总是在操作时创建一个新对象,也有一些方法可以做到这一点。
您必须知道,这通常涉及数据内容的“硬拷贝”,因此会带来执行开销。如果您不想,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);
}
请注意,这比就地修改原始数组要慢得多。堆分配和数据拷贝都比较慢
虽然您可以在 C 中创建“糟糕的 API”函数,例如
int* increment(int *myarray, int size) {
for(int i = 0; i < size; i++){
myarray[i] += 1;
}
return myarray;
}
此 returns 指向传递的同一数组的指针。这很糟糕 API 因为它令人困惑,尽管一些 C 标准函数就是这样设计的(strcpy
等)。为了使用这个函数,你需要一个指向数组第一个元素的指针,而不是数组本身。
在 C 代码中,您将指针传递给数组的第一个元素,而数组保留在内存中。您可以做的是创建一个新数组,然后 return 一个指向它的指针。但是,请小心。如果你创建一个 auto 数组(在栈上创建),它只会存在于函数内部,所以 returned 指针将指向垃圾内存。
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
}
注意,在 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 中构建并 return 一个全新的列表:
def increment(mylist):
return [i + 1 for i in mylist]
这在 C 中不容易完成。惯用的方法是让调用者提供数组和大小(如上所述)或 return 动态分配的数组:
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;
}
并让调用者在通过转让所有权完成后释放 returned 数组。
这个问题特别适用于数组。
在许多语言中,我会这样做:
#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 中 return 数组的方法,其中 none 的工作方式与上述相同。似乎 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中{0,1,2,3}
不是列表而是集合。
与您在 C 中所做的更直接等效的 python 代码是:
def increment(mylist):
for i in range(len(mylist)):
mylist[i] += 1;
return mylist
mylist = [0,1,2,3]
mylist = increment(mylist)
在这种情况下,python 中的列表也会产生副作用。 这是因为传递数组的最常见方式是通过引用(或 C 中的指针)
更接近您在 python 代码中所做的 C 代码是:
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 语言中始终是“副作用”。如果您更愿意寻找一种方法来制作数组等不可变,就像在只读中一样,并且总是在操作时创建一个新对象,也有一些方法可以做到这一点。
您必须知道,这通常涉及数据内容的“硬拷贝”,因此会带来执行开销。如果您不想,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);
}
请注意,这比就地修改原始数组要慢得多。堆分配和数据拷贝都比较慢
虽然您可以在 C 中创建“糟糕的 API”函数,例如
int* increment(int *myarray, int size) {
for(int i = 0; i < size; i++){
myarray[i] += 1;
}
return myarray;
}
此 returns 指向传递的同一数组的指针。这很糟糕 API 因为它令人困惑,尽管一些 C 标准函数就是这样设计的(strcpy
等)。为了使用这个函数,你需要一个指向数组第一个元素的指针,而不是数组本身。
在 C 代码中,您将指针传递给数组的第一个元素,而数组保留在内存中。您可以做的是创建一个新数组,然后 return 一个指向它的指针。但是,请小心。如果你创建一个 auto 数组(在栈上创建),它只会存在于函数内部,所以 returned 指针将指向垃圾内存。
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
}
注意,在 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 中构建并 return 一个全新的列表:
def increment(mylist):
return [i + 1 for i in mylist]
这在 C 中不容易完成。惯用的方法是让调用者提供数组和大小(如上所述)或 return 动态分配的数组:
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;
}
并让调用者在通过转让所有权完成后释放 returned 数组。