将变量作为参数传递并获得所需的返回值与传递指针基本相同吗?

Posted

技术标签:

【中文标题】将变量作为参数传递并获得所需的返回值与传递指针基本相同吗?【英文标题】:Is passing variables as arguments & getting desired return value essentially same as passing pointers? 【发布时间】:2013-04-21 23:54:45 【问题描述】:

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

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 可以模拟与使用指针作为参数时相同的效果?它们是否基本相同,只是选择和样式/语法的问题?

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

【问题讨论】:

请阅读更多关于指针的内容,这是一个非常强大的工具,而且不仅仅是语法问题。 我认为您的意思是第一个示例中的foo(&foobar) 通过使用指针,您不会返回,因为您正在传递参数的地址并且它被修改了。但是使用方法,一旦它们退出函数,它们就会离开堆栈,因此你需要返回值。 两者都有一些风格,但第二种方法完全避免了您自己的代码遭受NULL指针取消引用的机会。对于较大的对象(例如,structs),前一种方式往往更快,因为它不必复制内存来推入堆栈。 【参考方案1】:

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

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

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

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

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

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

【讨论】:

【参考方案2】:

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

在案例 1 中,您将 main 内的 foobar 的地址传递给函数 foo。这个foobar的地址被存储到函数foobar参数变量中,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 中,情况有所不同。您将foobar 的值传递给foo,它保存在变量bar 中。在foo 中,您无法直接访问对象foobar。你可以对foo 中的值做任何你想做的事情,它不会影响foobar。当您返回某个值时,foobar = foo(foobar); 语句会将返回的值分配给 foobar,这是 foobar 更新的时间。

       +--------+                                    
       |   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,后来被解释为一个地址。我建议阅读更多关于指针的内容,它是一个强大的工具。

【讨论】:

【参考方案3】:

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

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

在您的示例中,如果您希望将return of foo()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 语言最优雅的特性之一,一旦你知道如何使用它们,它就是一个非常有用且不可或缺的工具。

【讨论】:

【参考方案4】:

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

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

C 中的一个常见习语是:

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

这意味着,您必须 pass-the-object-by-reference 到该函数。这就是你的方法#1 行之有效的地方,经验丰富的程序员知道你想用它做什么。

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

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

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

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

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

【讨论】:

【参考方案5】:

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

return 样式可以接受常量参数。

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

按地址传递样式(也称为“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。

【讨论】:

【参考方案6】:

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

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);


请参阅我的回答 here 以获得更详细的示例。

【讨论】:

C中没有call by referencecall by address,只有call by value @SheerFish ya call by referenece 是 C++ 功能。但是,call by address 在 C 中:Sample.C void swap(int* a,int* b) int main() int a = 56,b=59; swap(&amp;a,&amp;b); 执行良好。

以上是关于将变量作为参数传递并获得所需的返回值与传递指针基本相同吗?的主要内容,如果未能解决你的问题,请参考以下文章

指针作为函数参数传递的注意事项

go语言中将函数作为变量传递

数组名作函数参数时,实参与形参变量之间的数据传递是?

基本概念之将引用作为函数的参数有哪些特点?

结构体作为函数参数值传递的问题

---数组和指针的常见用法