C语言中函数调用、实参、形参的选择

本文详细介绍了函数调用与指针操作的四种不同组合方式,包括定义方式1与调用方式1、定义方式1与调用方式2、定义方式2与调用方式1以及定义方式2与调用方式2。通过具体代码示例,解释了每种组合方式的工作原理及输出结果,特别关注了参数传递和内存分配的影响。

两种定义方式,两种调用函数,有四种组合

即为定义方式1+调用方式1,定义方式1+调用方式2,定义方式2+调用方式1,定义方式2+调用方式2

定义方式1

void get_str( char* p )

{

p = (char * )malloc ( 1 + sizeof ( "testing")) ;

strcpy ( p , "testing") ;

}

定义方式2

void get_str ( char** p )

{

*p = ( char * )malloc  (1 + sizeof ("testing")) ;

strcpy (*p ,"testing");

}

 

调用方式1

int  main ()

{

char* ptr = NULL;

get_str ( ptr );

if ( ptr )

  printf ( "%s\n" , ptr ) ;

else

  printf ( "%p\n" , ptr )  ;  //输出指针的值

return  0 ;

}

调用方式2

int  main ()

{

char* ptr = NULL;

get_str ( &ptr ) ;

 if ( ptr )

printf ( "%s\n" , ptr ) ;

else

printf ( "%p\n" , ptr ) ;

return 0;

}

对于定义方式1,其函数首部为 void get_str (char* p) 。其中参数p 是指针参数,要求调用该函数时的实参应该为指针,可以使字符数组名(或者字符变量的地址,或者指针变量)。在调用方式1中,正是以指针变量ptr为实参,此时采用的是传值调用,其返回到调用函数main时,ptr的值并没有变化仍为NULL ,因此定义方式1+调用方式1,程序输出为000000.

而在调用方式2中是以指针变量ptr的地址为实参进行调用的,开始执行函数get_str时,指针p指向的目标为变量ptr。但是调用了函数malloc之后,重新为P指针赋值,即p指向了其他存储区(存储区内容为testing),但是目标变量ptr的内容没有改变,由于其采用的参数传递方式为传值调用,返回main函数时,ptr的值没有改变,定义方式1+调用方式2程序输出结果仍未000000

对于定义方式2,其函数首部为 void get_str(char** p) 。其中参数p是指向指针的指针参数,要求调用该函数时的实参为指针的地址。在调用方式1中,是以指针变量ptr为实参,这与形参要求的格式不匹配,所以定义方式1+调用方式2会出现运行错误

而在调用方式2中,是以指针变量ptr的地址为实参进行调用的,符合形参的匹配要求,在函数get_str(char** p)中,*p表示目标变量ptr,通过将malloc申请到的存储区域首地址复制给*p,即ptr指向所申请到的存储区的起始位置,串拷贝函数strcpy()运行之后,将字符串“testing”放入该村出去,最后返回到调用函数main()中,ptr指向的目标改变了,因此定义方式2+调用方式2程序运行结果为“testing

<think> 嗯,用户想了解C语言函数中实参形参的使用。这个问题其实很基础但非常重要,是理解函数调用的关键概念。 用户提供的引用材料已经相当面了,涵盖了实参形参的定义、区别、内存关系等核心内容。不过用户可能更需要一个系统化的解释,特别是实际使用时容易混淆的点。 用户可能刚接触C语言函数,ta的困惑点可能在于: - 为什么修改形参不影响实参 - 什么时候需要传递指针 - 普通变量和数组作为参数时的区别 从引用内容看,特别值得强调的有: 1) 形参是函数定义时的占位符(引用1),实参是调用时传递的具体值(引用2,3) 2) 两者内存空间独立(引用4,5),这个特性常导致初学者困惑 3) 数组作为参数时实际传递的是指针(引用5) 用户可能没意识到但实际编程中会遇到的问题是:当需要修改实参时,必须传递指针。这点在字符串处理、结构体修改等场景特别关键。 考虑到用户的学习阶段,解释时应该: - 用简单代码示例说明基本概念 - 重点突出值传递和地址传递的区别 - 提醒常见错误场景(比如试图在函数内修改普通形参影响实参) 用户似乎更关注实际应用而非纯理论,所以准备用具体场景说明:比如交换两个变量的函数为什么必须用指针,数组参数如何传递等实用技巧。 </think>好的,我们来系统地讲解一下C语言中函数实参(Actual Argument)和形参(Formal Parameter)的使用,这是理解函数调用机制的核心概念。 ## 核心概念 1. **形参 (Formal Parameter):** * **定义位置:** 在**函数定义**时,写在函数名后面的括号 `()` 中。 * **作用:** 它们是函数内部的**局部变量**,用于**接收**调用该函数时传递进来的值。你可以把它们看作是函数体内部用于处理数据的**占位符**或**标签**。 * **生命周期:** 在函数**被调用时创建**(分配内存),在函数**执行完毕返回时销毁**(释放内存)。 * **初始化:** 形参的值在函数调用时,由对应的**实参**进行**初始化**(本质上是**值拷贝**)。 * **示例:** ```c // 函数定义: max 是函数名,num1 和 num2 是形参 int max(int num1, int num2) { int result; if (num1 > num2) result = num1; else result = num2; return result; } ``` 2. **实参 (Actual Argument):** * **定义位置:** 在**函数调用**时,写在函数名后面的括号 `()` 中。 * **作用:** 它们是传递给函数的**实际数据值**(可以是常量、变量、表达式或其他函数的返回值)。这些值用于**初始化**对应的形参。 * **生命周期:** 实参本身(如果是变量)的生命周期由其定义的作用域决定,与函数调用无关。传递给函数的是它**当前值的副本**。 * **传递方式:** C语言默认使用**值传递 (Pass by Value)**。这意味着传递给函数的是实参值的一个**拷贝**,函数内部操作的是这个拷贝,**不会直接影响原始实参变量**(除非使用指针)。 * **示例:** ```c int main() { int a = 100, b = 200; int ret; // 函数调用: max 是函数名,a 和 b 是实参(此处是变量) ret = max(a, b); // 函数调用: 10 和 20 是实参(此处是常量),max(a, b) 是实参(此处是函数返回值) ret = max(10, max(a, b)); return 0; } ``` ## 关键区别与联系 | 特性 | 形参 (Formal Parameter) | 实参 (Actual Argument) | | :----------- | :--------------------------------------- | :------------------------------------------ | | **定义位置** | 函数定义时 | 函数调用时 | | **本质** | 函数内部的局部变量 | 传递给函数的具体数据值 | | **作用** | 接收实参传递来的值,作为函数内部操作的标签 | 提供实际的值给对应的形参进行初始化 | | **内存空间** | 函数被调用时在栈上分配独立空间 | 自身在内存中有独立空间(如变量) | | **联系** | 形参的值在函数调用时由**对应位置的实参**进行**初始化** | 实参的值被**拷贝**一份用于初始化对应的形参 | | **修改影响** | 修改形参的值**不影响**其对应的原始实参变量 | 函数内部无法直接修改原始实参变量(值传递) | ## 重要特性:值传递 (Pass by Value) * C语言函数参数传递的**默认方式**是**值传递**。 * **过程:** 当进行函数调用时,计算实参表达式(如果有)的值,然后将这个值的**副本**(而不是变量本身的内存地址)传递给函数,用于初始化对应的形参。 * **结果:** * 函数内部对形参变量的任何修改(如赋值)都**只发生在这个副本上**。 * 函数内部的这些修改**不会**影响调用函数中原始实参变量的值。 * **示例说明:** ```c #include <stdio.h> // 函数定义: 尝试交换两个形参的值 void swap(int x, int y) { int temp = x; x = y; y = temp; printf("Inside swap: x = %d, y = %d\n", x, y); // 输出交换后的形参值 } int main() { int a = 10, b = 20; printf("Before swap: a = %d, b = %d\n", a, b); swap(a, b); // 函数调用: 传递 a 和 b 的值 (10 和 20 的副本) printf("After swap: a = %d, b = %d\n", a, b); // a 和 b 的值未改变! return 0; } ``` **输出:** ``` Before swap: a = 10, b = 20 Inside swap: x = 20, y = 10 // swap函数内部交换了形参x和y的副本 After swap: a = 10, b = 20 // main函数中的a和b保持不变 ``` ## 如何修改实参? - 使用指针 (Pass by Address/Reference) * 如果你**确实需要**在函数内部修改调用函数中的原始变量(实参),你需要使用**指针**。 * **原理:** 传递实参变量的**地址**(指针)给函数。函数通过形参(指针变量)接收这个地址,然后通过**解引用**操作符 `*` 来访问和修改该地址所指向的内存单元(即原始实参变量)。 * **示例:** ```c #include <stdio.h> // 函数定义: 接收指向int的指针 void real_swap(int *px, int *py) { int temp = *px; // 解引用px获取px指向的值(a的值) *px = *py; // 解引用py获取py指向的值(b的值),赋给px指向的内存(a) *py = temp; // 将temp的值赋给py指向的内存(b) printf("Inside real_swap: *px = %d, *py = %d\n", *px, *py); } int main() { int a = 10, b = 20; printf("Before real_swap: a = %d, b = %d\n", a, b); real_swap(&a, &b); // 传递a和b的地址 printf("After real_swap: a = %d, b = %d\n", a, b); // a 和 b 的值被成功交换 return 0; } ``` **输出:** ``` Before real_swap: a = 10, b = 20 Inside real_swap: *px = 20, *py = 10 After real_swap: a = 20, b = 10 ``` ## 数组作为参数 * 当数组作为实参传递给函数时,情况比较特殊: * 实际传递的是数组**首元素的地址**(一个指针),而不是整个数组的副本(效率原因)。这**不是值传递**,而是**地址传递**。 * 函数内部的形参通常声明为指针(如 `int *arr`)或者带有大小的数组(如 `int arr[]` 或 `int arr[10]`),但编译器都会将其视为指针。 * 因为传递的是地址,所以函数内部通过这个指针修改数组元素的值,会**直接影响**原始数组。 * **示例:** ```c #include <stdio.h> // 函数定义: 形参可以写成 int arr[] 或 int *arr void modifyArray(int arr[], int size) { for (int i = 0; i < size; i++) { arr[i] *= 2; // 修改数组元素,会影响原始数组 } } int main() { int numbers[] = {1, 2, 3, 4, 5}; int len = sizeof(numbers) / sizeof(numbers[0]); modifyArray(numbers, len); // 传递数组名(即首元素地址) for (int i = 0; i < len; i++) { printf("%d ", numbers[i]); // 输出: 2 4 6 8 10 } return 0; } ``` ## 总结与最佳实践 1. **理解初始化与拷贝:** 函数调用时,实参的值被**拷贝**用于初始化对应的形参。 2. **牢记值传递默认:** 默认情况下(非指针、非数组),函数内部对形参的修改**不影响**实参。 3. **需要修改实参时用指针:** 如果函数需要改变调用者变量的值,必须将变量的**地址**(使用 `&` 运算符)作为实参传递,函数形参需声明为对应的**指针类型**,并在函数内部通过**解引用** `*` 操作符来访问和修改。 4. **数组名即地址:** 传递数组给函数时,传递的是首元素地址,函数内部通过指针操作数组元素会直接影响原数组。通常需要额外传递数组大小。 5. **形参实参匹配:** 函数调用时,实参的**数量、类型和顺序**必须与函数定义中的形参**严格匹配**,否则会导致编译错误或未定义行为。 理解实参形参的关系,特别是值传递和指针传递的区别,是掌握C语言函数的关键一步,对于编写正确、高效的程序至关重要[^1][^2][^3][^4][^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值