3

我尝试了两种将值传递给函数的方法,它们似乎都具有相同的效果。

1) Passing pointers

int foo(int *bar){
    *bar = doSomething();
}

int main(void){
    int foobar = 0;

    foo(&foobar);
}

2) Passing variables

int foo(int bar){
    //do stuff
    return bar;
}

int main(void){
    int foobar = 0;

    foobar = foo(foobar);
}

那么这是否意味着使用适当return的 i 可以模拟与使用指针作为参数时相同的效果?它们是否基本相同,只是选择和样式/语法的问题?

我听说指针会使代码复杂化,因此作为一般经验法则,应该避免指针。

4

6 回答 6

3

C 的一半是指针,直到你掌握了指针的概念,你才知道 C 是什么。

并不总是可以只使用返回值。事实上,很多 API 只使用返回值作为成功/错误指示代码,而真正的返回值放在指针参数中。例如,网络库可能具有以下用途:

if (AcceptClient(&ClientInfo) == SUCCESS) { ... }

指针参数也用于多个返回值:

if (GetConfigString(&StrData,&StrLen) == OK) { ... }

在 C 中编程时不能真正避免使用指针。但是,在支持指针抽象的语言中,即管理指向许多不同对象的指针并且对程序员隐藏了复杂性,使用抽象确实比使用普通指针更好它减少了处理指针时可能出现的很多问题。例如,在 Object Pascal 中有动态数组、ansistrings、widestrings 等。它们都是指针抽象。不需要处理存储(解除)分配、元素访问、范围检查等。一切都由编译器注入的代码处理。因此,即使发生错误(例如访问有效索引之外的元素),也很容易跟踪错误。

于 2013-04-28T04:41:23.240 回答
2

在这个特定示例中,两者具有相同的效果:它们都更新 的值foobar,但它们更新的方式完全不同。

在案例 1 中,您将foobarinside的地址传递main给函数foo。这个地址foobar被存储到bar函数的参数变量中foo,并bar指向foobar。因为现在已经有了 的地址,foobar而对象的生命周期还没有结束,所以可以foobar直接访问 的存储,并对其进行修改。

       +--------+                                    
       |   55   |     +------call foo(&foobar)-----+
       +--------+     |                            |                              
int    | foobar |     |                            V
       +--------+     |    +--points to--------+--------+
       | 0x1234 |-----+    |                   | 0x1234 |
       +--------+<---------+                   +--------+
                                 bar (  int *  |   bar  |        )
                                               +--------+
                                               | 0xabcd |
                                               +--------+
                             At this moment bar holds the address of foobar
                             *bar = whatever will assign whatever to the address
                             which is held by bar, and thus changing the value
                             of foobar in main

情况2,情况不同。您将 的值传递foobarfoo在变量中保存的值bar。在foo您没有直接访问该对象的情况下foobar。你可以对里面的值做任何你想做的事情foo,它不会影响foobar. 当您返回某个值时,foobar = foo(foobar);语句将返回的值分配给foobarthisfoobar更新时。

       +--------+                                    
       |   55   |-------------call foo(foobar)-----+
       +--------+                                  |                              
int    | foobar |<-+                               V
       +--------+  |                           +--------+
       | 0x1234 |  |                           |   55   |
       +--------+  |                           +--------+
                   |             bar (  int *  |   bar  |        )
                   |                           +--------+
         foobar = foo(foobar);                 | 0xabcd |
                   |                           +--------+
                   |             {
                   |               //change bar
                   |
                   +-------------- return bar;
                                 }

                              Here bar does not point to anything, it just has
                              copy of the value of foobar. Whatever is returned
                              by foo will be assigned in foobar in main

需要注意的一点是,当您传递地址时,实际上会将一个值复制到bar,该值稍后会被解释为地址。我建议阅读更多关于指针的信息,它是一个强大的工具。

于 2013-04-28T06:47:16.520 回答
2

它们可能看起来相同,但实际上并不相同!即使两者都用于pass-by-value传递参数,但它们的使用方式却大不相同。当您将指针作为参数传递给函数时,无论您通过取消引用该函数体中的这些指针来执行什么操作反映在这些指针指向的变量的原始值中,即使这些passed pointers可能是在 中声明的“原始”指针的副本。您会main()看到,多个指针可以指向一个变量,因此变量的值可以是通过取消引用这些指针中的任何一个来更改。

但是,当您将变量作为参数而不是指针传递时,对这些变量值的所有操作都不会反映在“原始”变量的值中。这些更改仅限于copies作为参数传递的那些更改。

在您的示例中,如果您希望将 ofreturn of foo()或 of的值分配给doSomething()bar且不打算更改bar' 的值之外的变量,那么只需要将变量作为参数传递,而不是指针。

这是一些说明它的简单代码:

//Passing variables as arguments

#include<stdio.h>

int foo(int bar){
    bar=303;

}

int main(void)
{
    int foobar = 0;
    printf("The value of foobar before function call is %d\n",foobar);
    foo(foobar);
    printf("TThe value of foobar after function call is %d",foobar);
}

输出The value of foobar before function call is 0

       `The value of foobar after function call is 303`

//Passing pointers to variables as arguments

#include<stdio.h>
int doSomething();

int foo(int *bar){
    *bar = doSomething();
}

int main(void){
    int foobar = 0;
    printf("The value of foobar before function call is %d\n",foobar);
    foo(&foobar);
    printf("TThe value of foobar after function call is %d",foobar);
}

int doSomething()
{
    return 303;
}

输出The value of foobar before function call is 0

       `The value of foobar after function call is 0`

最后,关于指针使代码变得复杂,好吧,指针是 C 语言最优雅的特性之一,一旦你知道如何使用它们,它就是一个非常有用且不可或缺的工具。

于 2013-04-28T04:55:50.367 回答
1

是的,任何一种风格都有效。各有利弊。

return样式可以采用常量参数。

int foo(int bar);   foo(42);  // works
void foo(int *bar); foo(42);  // nope
void foo(int *bar); foo(&42); // still nope

pass-by-address 样式(也称为“out-parameters”)减轻了字符串函数(例如)的内存管理,并让它们返回错误代码。

int strcpy(char *src, char *dst) { /* nice simple while loop */ }
char *strcpy(char *src)          { /* welcome to malloc hell */ }

此外,在使用结构时,按地址传递可以节省堆栈空间和复制开销。

struct quux { /* lots and lots of stuff */ };
struct quux foo(int bar) { ...; return s; /* have to fit a struct quux on stack */ }
int foo(int bar, struct quux *result) { /* write stuff into *result */; }

一般来说,return尽可能地首选,并且只在必要时使用 out-parameters。

于 2013-04-28T04:41:49.823 回答
1

它不仅仅是这里的“风格问题”讨论。编写代码是为了让机器工作,但 99.9% 的“现实世界”代码是为人类理解而编写的。

人类理解单词、句子、习语等。

C 中的常见习语之一是:

“如果一个函数必须更改/更改对象的内容(变量、结构变量、联合变量等),则按值将句柄传递给对象。”

这意味着,您必须pass-the-object-by-reference使用该功能。这就是你的方法#1 运作良好的地方,经验丰富的程序员知道你想用它做什么。

另一方面,另一个成语说:

“如果函数表现得像一个纯粹的数学函数,其中f(x)总是等于y,然后按值传递对象本身,并使用返回值”(不确定我是否框架得当)

这意味着,对于每个x,如果 的值f(x)从不改变,则使用方法#2。

这准确地传达了其他程序员您正在尝试做的事情。我希望这个解释有所帮助。

无论如何,这两种方法都做同样的工作,并且输出是相同的。

于 2013-04-28T05:15:04.873 回答
1

你们都是一样的。你可以使用任何东西。但是,您的第一个代码必须是

int foo(int *bar){
    *bar = doSomething();
}

int main(void){
    int foobar = 0;

    foo(&foobar);
}

因为 bar 是指针类型,它需要变量的地址作为参数。因此 foobar 将是一个变量,而 &foobar 将是指针。

通常,这些东西将被称为按值调用,按地址调用。请参阅 wiki 按值调用和按地址调用。

//Call by Address
    void SwapIntAddr(int* ptmp1, int* ptmp2) 
    {
        int ptmp;
        ptmp  = *ptmp1;
        *ptmp1 = *ptmp2;
        *ptmp2 = ptmp;
    }

//Call by Value

    void SwapIntVal(int tmp1, int tmp2) 
    {
         int tmp;
         tmp  = tmp1;
         tmp1 = tmp2;
         tmp2 = tmp;
    }


int main(){
  int a = 3, b= 5;

  SwapIntVal(a,b); // This will have no effect
  printf("%d %d\n",a,b);

  SwapIntAddr(&a,&b); // This will have effect
  printf("%d %d\n",a,b);

}

有关更详细的示例,请参阅我的答案。

于 2013-04-28T04:37:47.043 回答