C语言的本质(16)——函数接口的传入参数与传出参数

本文探讨了C语言中函数接口如何通过指针参数进行传入和传出参数的传递。从传入参数示例到传出参数示例,再到Value-result参数的解析,详细阐述了不同类型的参数传递方式。同时,提到了二级指针作为参数时的情况,并区分了传出参数动态分配内存的两种场景。最后,讨论了返回值为指针的函数中传出内存的处理方式和调用者与实现者之间的责任划分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

如果函数接口有指针参数,既可以把指针所指向的数据传给函数使用(称为传入参数),也可以由函数填充指针所指的内存空间,传回给调用者使用(称为传出参数),例如strcpy的函数原型为

 

char *strcpy(char *dest, const char *src);

其中src参数是传入参数,dest参数是传出参数。有些函数的指针参数同时担当了这两种角色,如select函数。其函数原型为:

int select(int nfds, fd_set *readfds,fd_set *writefds,
        fd_set *exceptfds, struct timeval*timeout);

其中的fd_set *参数,既是传入参数又是传出参数,这称为Value-result参数。

 

传入参数示例:

假如我们实现一个函数,其参数通过地址来传入一个值,其原型如下:

void func(const unit_t *p);

其调用者与实现者之间的协议如下:

调用者

实现者

1、分配p所指的内存空间;

2、在p所指的内存空间中保存数据;

3、调用函数;

4、由于有const限定符,调用者可以确信p所指的内存空间不会被改变。

1、规定指针参数的类型unit_t *;

2、读取p所指的内存空间。

 

 

传出参数示例:

假如我们实现一个函数,其参数通过地址传出一个值,其原型如下:

 

void func(unit_t *p);

 

其调用者与实现者之间的协议如下:

调用者

实现者

1、分配p所指的内存空间

2、调用函数

3、读取p所指的内存空间

1、规定指针参数的类型unit_t *

2、在p所指的内存空间中保存数据

 

 

 

Value-result参数示例:

 

void func(unit_t *p);

其调用者与实现者之间的协议如下:

调用者

实现者

1、分配p所指的内存空间

2、在p所指的内存空间保存数据

3、调用函数

4、读取p所指的内存空间

1、规定指针参数的类型unit_t *

2、读取p所指的内存空间

3、改写p所指的内存空间

 

注意:由于传出参数和Value-result参数的函数接口完全相同,应该在文档中说明是哪种参数。

很多系统函数对于指针参数是NULL的情况有特殊规定:如果传入参数是NULL表示取缺省值,例如pthread_create(3)的pthread_attr_t *参数,也可能表示不做特别处理,例如free的参数;如果传出参数是NULL表示调用者不需要传出值,例如time(2)的参数。这些特殊规定应该在文档中写清楚。

下面是一个传出参数的完整例子:

 

/* populator.h */
#ifndef POPULATOR_H
#define POPULATOR_H
typedef struct {
    int number;
    char msg[20];
} unit_t;
extern void set_unit(unit_t *);
#endif

/* populator.c */
#include <string.h>
#include "populator.h"
void set_unit(unit_t *p)
{
    if (p == NULL)
         return; /* ignore NULL parameter */
    p->number = 3;
    strcpy(p->msg, "Hello World!");
}


/* main.c */
#include <stdio.h>
#include "populator.h"
int main(void)
{
    unit_t u;
    set_unit(&u);
    printf("number: %d\nmsg: %s\n", u.number, u.msg);
    return 0;
}


二级指针的参数:

二级指针也是指针,同样可以表示传入参数、传出参数或者Value-result参数,只不过该参数所指的内存空间应该解释成一个指针变量。用两层指针做传出参数的系统函数也很常见,比如pthread_join(3)的void **参数。下面看一个简单的例子。

 

二级指针做传出参数

 

/* redirect_ptr.h */
#ifndef REDIRECT_PTR_H
#define REDIRECT_PTR_H
extern void get_a_day(const char **);
#endif

 

这里的参数指针是const char **,有const限定符,却不是传入参数而是传出参数。

 

/* redirect_ptr.c */
#include "redirect_ptr.h"
 
static const char *msg[] ={"Sunday", "Monday", "Tuesday","Wednesday",
                            "Thursday","Friday", "Saturday"};
void get_a_day(const char **pp)
{
    static int i = 0;
    *pp = msg[i%7];
    i++;
}

/* main.c */
#include <stdio.h>
#include "redirect_ptr.h"
 
int main(void)
{
    const char *firstday = NULL;
    const char *secondday = NULL;
    get_a_day(&firstday);
    get_a_day(&secondday);
    printf("%s\t%s\n", firstday, secondday);
    return 0;
}

二级指针作为传出参数还有一种特别的用法,可以在函数中分配内存,调用者通过传出参数取得指向该内存的指针,一般来说,实现一个分配内存的函数就要实现一个释放内存的函数。

 

通过参数分配内存示例:

 

void alloc_unit(unit_t **pp);
void free_unit(unit_t *p);

其调用者与实现者之间的协议如下:

调用者

实现者

1、分配pp所指的指针变量的空间;

2、调用alloc_unit分配内存;

3、读取pp所指的指针变量,通过后者使用alloc_unit分配的内存;

4、调用free_unit释放内存。

1、规定指针参数的类型unit_t **;

2、alloc_unit分配unit_t的内存并初始化,为pp所指的指针变量赋值;

3、free_unit释放在alloc_unit中分配的内存

 

下面是一个通过二级指针参数分配内存的例子


/* para_allocator.h */
#ifndef PARA_ALLOCATOR_H
#define PARA_ALLOCATOR_H
typedef struct {
    int number;
    char *msg;
} unit_t;
 
extern void alloc_unit(unit_t **);
extern void free_unit(unit_t *);
 
#endif
/* para_allocator.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "para_allocator.h"
 
void alloc_unit(unit_t **pp)
{
    unit_t *p = malloc(sizeof(unit_t));
    if(p == NULL) {
           printf("out of memory\n");
           exit(1);
    }
    p->number = 3;
    p->msg = malloc(20);
    strcpy(p->msg, "Hello World!");
    *pp = p;
}
 
void free_unit(unit_t *p)
{
    free(p->msg);
    free(p);
}

/* main.c */
#include <stdio.h>
#include "para_allocator.h"
 
int main(void)
{
    unit_t *p = NULL;
 
    alloc_unit(&p);
    printf("number: %d\nmsg: %s\n", p->number, p->msg);
     free_unit(p);
    p = NULL;
    return 0;
}

二级指针参数如果是传出的,可以有两种情况:

第一种情况,传出的指针指向静态内存(比如上面的例子),或者指向已分配的动态内存(比如指向某个链表的节点);

第二种情况是在函数中动态分配内存,然后传出的指针指向这块内存空间,这种情况下调用者应该在使用内存之后调用释放内存的函数,调用者的责任是请求分配和请求释放内存,实现者的责任是完成分配内存和释放内存的操作。由于这两种情况的函数接口相同,我们在撰写文档或添加注释时应该说明是哪一种情况。

  

返回值是指针的情况

返回值显然是传出的而不是传入的,如果返回值传出的是指针,和通过参数传出指针的情况类似,也分为两种情况:

第一种是传出指向静态内存或已分配的动态内存的指针;

第二种是在函数中动态分配内存并传出指向这块内存的指针,这种情况通常还要实现一个释放内存的函数,所以有和malloc对应的free。由于这两种情况的函数接口相同,应该在文档中说明是哪一种情况。

 

返回指向已分配内存的指针示例:

 

unit_t *func(void);

其调用者与实现者之间的协议如下:

调用者

实现者

1、调用函数

2、将返回值保存下来以备后用

1、规定返回值指针的类型unit_t *

2、返回一个指针

 

 

下面的例子演示返回指向已分配内存的指针

 

/* ret_ptr.h */
#ifndef RET_PTR_H
#define RET_PTR_H
 
extern char *get_a_day(int idx);
 
#endif
/* ret_ptr.c */
#include <string.h>
#include "ret_ptr.h"
 
static const char *msg[] = {"Sunday","Monday", "Tuesday", "Wednesday",
                            "Thursday","Friday", "Saturday"};
 
char *get_a_day(int idx)
{
    return msg[idx];
}
/* main.c */
#include <stdio.h>
#include "ret_ptr.h"
 
int main(void)
{
    printf("%s %s\n", get_a_day(0));
    return 0;
}


动态分配内存并返回指针示例:

 

unit_t *alloc_unit(void); voidfree_unit(unit_t *p);

其调用者与实现者之间的协议如下:

调用者

实现者

1、调用alloc_unit分配内存;

2、将返回值保存下来以备后用;

3、调用free_unit释放内存。

 

1、规定返回值指针的类型unit_t *;

2、alloc_unit分配内存并返回指向该内存的指针;

3、free_unit释放由alloc_unit分配的内存。

 

 

 

以下是一个完整动态分配内存并返回指针的例子

 

/* ret_allocator.h */
#ifndef RET_ALLOCATOR_H
#define RET_ALLOCATOR_H
 
typedef struct {
    int number;
    char *msg;
} unit_t;
extern unit_t *alloc_unit(void);
extern void free_unit(unit_t *);
 
#endif
/* ret_allocator.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "ret_allocator.h"
 
unit_t *alloc_unit(void)
{
    unit_t *p = malloc(sizeof(unit_t));
    if(p == NULL) {
           printf("out of memory\n");
           exit(1);
    }
    p->number = 3;
    p->msg = malloc(20);
    strcpy(p->msg, "Hello world!");
    return p;
}
 
void free_unit(unit_t *p)
{
    free(p->msg);
    free(p);
}
/* main.c */
#include <stdio.h>
#include "ret_allocator.h"
 
int main(void)
{
    unit_t *p = alloc_unit();
 
    printf("number: %d\nmsg: %s\n", p->number, p->msg);
    free_unit(p);
    p = NULL;
    return 0;
}


<think>首先,用户的问题是:“我想理解函数传入参数传出参数的区别 请问函数参数传递方式 传入参数传出参数的区别”。我需要基于提供的引用内容来回答。 关键引用: - 引用[1]:解释了形式参数(形实际参数(实)。形是定义函数时的参数,实是调用函数时提供的参数。传递方式是值传递,实的值复制给形。 - 引用[2]:强调在C语言中,值传递是单向的,形的改变不影响实。 - 引用[3]:通过代码示例展示了值传递:在函数内部交换形的值,但实不变。 - 引用[4]:提到了地址传递(指针传递),通过传递变量的地址,函数可以直接修改实的值。 用户的问题聚焦于“传入参数传出参数”的区别。在编程中: - 传入参数(Input parameters):函数接收这些参数用于计算或操作,但不修改它们(在值传递中)。 - 传出参数(Output parameters):函数通过它们返回额外的值,通常通过指针或引用传递,允许函数修改实的值。 在C语言中: - 值传递:传入参数,实的值被复制到形函数内部对形的修改不影响实。 - 地址传递:通过指针,可以同时作为传入传出参数函数可以读取修改实的值。 回答结构: 1. **清晰结构**:逐步解释,确保回答真实可靠。 2. **使用中文**:整个回答用中文。 3. **LaTeX格式**: - 行内数学表达式用$...$,例如变量名。 - 独立公式用$$...$$并单独成段。 4. **添加引用标识**:在引用的内容末尾自然地添加引用标识,如[^1]。 5. **生成相关问题**:在回答结束后,添加“§§相关问题§§”部分,列相关问题。 核心内容: - 定义传入参数传出参数。 - 解释不同传递方式:值传递(传入) vs. 地址传递(传入传出)。 - 使用引用中的例子来支持。 最终回答框架: 1. 介绍传入参数传出参数的概念。 2. 解释值传递(传入参数)。 3. 解释地址传递(传出参数)。 4. 总结区别。 5. 添加相关问题。</think>### 函数参数传递方式:传入参数传出参数的区别 在编程中,理解函数参数传递方式对编写高效代码至关重要。传入参数(input parameters)传出参数(output parameters)的区别主要体现在参数如何被函数使用修改。下面我将逐步解释,基于值传递地址传递两种常见方式,并结合实际示例说明。 #### 1. **传入参数(Input Parameters)** - **定义**:传入参数函数接收的输数据,函数使用这些值进行计算或操作,但**不会修改原始实的值**。 - **传递方式**:主要通过值传递(pass by value)。 - 在值传递中,实的值被复制到形的独立存储单元中。形占用不同内存空间,因此函数内部对形的修改不影响实[^1][^2]。 - 数学表达式:如果实为 $a$,形为 $b$,则 $b = a$(复制值),但 $b$ 的改变不影响 $a$。 - **示例**(C语言值传递): ```c #include <stdio.h> void swap(int x, int y) { // x, y 是形传入参数) int temp = x; x = y; y = temp; // 交换形值,但不影响实 printf("函数内: x=%d, y=%d\n", x, y); } int main() { int a = 1, b = 2; swap(a, b); // a, b 是实 printf("主函数: a=%d, b=%d\n", a, b); // 输: a=1, b=2(未改变) return 0; } ``` 在此例中,$a$ $b$ 的值被复制到 $x$ $y$,函数内部交换 $x$ $y$ 不影响 $a$ $b$[^3]。 #### 2. **传出参数(Output Parameters)** - **定义**:传出参数用于函数向调用者返回额外结果,函数**直接修改实的值**。 - **传递方式**:主要通过地址传递(pass by reference)或指针传递。 - 实的地址被传递给形(指针变量),函数通过地址访问并修改实的原始存储单元[^4]。 - 数学表达式:如果实地址为 $\&c$,形为指针 $p$,则 $p$ 指向 $c$ 的内存,$*p$ 的修改直接影响 $c$。 - **示例**(C语言地址传递): ```c #include <stdio.h> void swap(int *p1, int *p2) { // p1, p2 是指针形(可传入传出) int temp = *p1; *p1 = *p2; *p2 = temp; // 通过指针修改实值 } int main() { int c = 1, d = 2; swap(&c, &d); // 传递cd的地址 printf("c=%d, d=%d\n", c, d); // 输: c=2, d=1(值被交换) return 0; } ``` 这里,$c$ $d$ 的地址传入函数通过指针 $p1$ $p2$ 直接修改其值,实现“传出”效果[^4]。 #### 3. **关键区别总结** - **修改权限**: - 传入参数函数只能读取值,不能修改原始实(单向传入)[^2][^3]。 - 传出参数函数可读取并修改原始实(双向传递)[^4]。 - **内存操作**: - 值传递(传入):形在栈上创建新存储单元,函数结束释放[^3]。 - 地址传递(传出):形指向实的原始内存单元,直接操作[^4]。 - **用途**: - 传入参数:适用于输数据无需修改的场景(如计算平方:$f(x) = x^2$)。 - 传出参数:适用于需要返回多个结果的场景(如交换变量或更新状态)。 #### 4. **其他语言中的表现** - Python:默认是“对象引用传递”,但不可变对象(如整数)表现为值传递(传入),可变对象(如列表)可修改(类似传出)[^1]。 - Java:类似C语言,基本类型值传递(传入),对象引用可修改内容(传出)。 通过理解这些区别,您可以更灵活地设计函数,避免意外修改数据。如果您有特定语言的问题,可以提供更多细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尹成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值