指针类型_的_函数形参

  无情的搬砖机器= =

一、指针作为函数参数

1.1 函数参数的传递方式

C语言的函数参数的传递方式有以下两种:

  1. 值传递:形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
  2. 指针传递:形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作

1.2 函数相关的知识

  1. 函数(return)返回值:实际一般是返回(处理过的)局部变量;且只能返回单个值;
  2. 指针作为函数参数,指针能使被调函数(变相)返回一个以上的结果。(直接对内存操作3改变多个实参)
  3. 函数内部修改外部变量的值,需要一级指针;
  4. 函数内部修改外部指针变量的值,需要二级指针;

所以要想直接对内存单元进行操控,用指针最直接,指针的功能很强大。

1.3 举例


经典例子1 :交换CET1 和 CET2 的值(一级指针交换值)

1
2
3
4
5
6
7
//Wrong
void swap_val(int a,intb)
{
int tmp = a;
a = b;
b = tmp;
}

错误原因:因为交换的是副本(形参),真品(实参)没改变的。


经典例子2:是在学习STM32的库函数的使用。当初刚接触库函数,对于函数初始化接口。

1
GPIO_Init(GPIOA, &GPIO_InitStructure);

分析:为什么要取初始化结构体变量的地址传递进库函数(&GPIO_InitStructure),而不是直接将结构体变量本身(GPIO_InitStructure)传递进去?


二、实际例子

2.1 两数值交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# include <stdio.h>
void Swap(int *p, int *q); //函数声明
int main(void)
{
int i = 3, j = 5;
Swap(&i, &j);
printf("i = %d, j = %d\n", i, j);
return 0;
}
void Swap(int *p, int *q)
{
int buf;
buf = *p;
*p = *q;
*q = buf;
return;
}

输出结果是:i = 5, j = 3

  此时实参向形参传递的不是变量 i 和 j 的数据,而是变量 i 和 j 的地址。其实传递指针也是拷贝传递,只不过它拷贝的不是内存单元中的内容,而是内存单元的地址,这就是天壤之别了。拷贝地址就可以直接对地址所指向的内存单元进行操作,即此时被调函数就可以直接对变量 i 和 j 进行操作了。有人会说:“被调函数用完就释放了,不就把 i 和 j 都释放了吗?”不是的,当函数调用完之后,释放的是 p 和 q,不是 i 和 j。p 和 q 中存放的是 i 和 j 的地址。所以 p 和 q 被释放之后并不会影响 i 和 j 中的值。前面讲过,修改指针变量的值不会影响所指向变量中的数据。只不过它们之间的指向关系没有了而已。

此外需要注意的是,形参中变量名分别为 p 和 q,变量类型都是 int* 型。所以实参 i 和 j 的地址,即 &i 和 &j 是分别传递给 p 和 q,而不是传递给 *p 和 *q。

函数参数传指针和传数据的区别

  综上所述,如果希望在另外一个函数中修改本函数中变量的值,那么在调用函数时只能传递该变量的地址。如果这个变量是普通变量,那么传递它的地址就可以直接操作该变量的内存空间。

  那么,是不是要定义一个指针变量指向它然后传递这个指针变量呢?不用多此一举。比如有一个“int i;”,如果想传递i的地址那就直接传递 &i 就行了,不用专门定义一个指针变量指向它,然后再传递这个指针变量。

如果要传递的变量本身就是一个指针变量怎么办?如果要操作该指针变量所指向的内存空间是不是要传递该指针变量的地址呢?

  指针变量本身就是地址,本身就是指向那个内存空间的,所以直接把它传过去就行了。除非你要改变那个指针变量里面存放的地址,即你要改变指针变量的指向,那么你就必须要传递指针变量的地址。
指针可以使得函数返回一个或者一个以上的值

2.2 数组中的n个元素的值分别减去20

需求分析:编写函数,要求将数组中的n个元素的值分别减去20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<stdio.h>
#define N 10

//函数前置声明
void traverseArray(int *pArr,int length);
void subArray(int a[],int length);
void subArray1(int * pArr,int length );

//数组元素的遍历
void traverseArray(int *pArr,int length)
{
int i;
for(i = 0;i<length;i++)
{
printf("%3d",*(pArr+i));
}
printf("\n");
return;
}

//将数组中所有元素都减去20
void subArray(int a[],int length)
{
int i ;
for(i = 0;i<length;i++)
{
a[i] = a[i] - 20;
}
return;
}

//将数组中所有元素都减去20
void subArray1(int * pArr,int length )
{
int i ;
for(i = 0;i<length;i++)
{
*(pArr + i) = *( pArr + i) - 20;
}
return ;
}
int main(void)
{
int a[N]={51,52,53,54,55,56,57,58,59,60};
printf("原来数组中的元素为:\n");
traverseArray(a,N);
printf("数组中元素第一次减去20后为:\n");
subArray(a,N);
traverseArray(a,N);
printf("数组中元素第二次减去20后为:\n");
subArray1(a,N);
traverseArray(a,N);
return 0;
}

数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响,这两个性质分别是:

  1. 不允许拷贝数组;
  2. 使用数组时通常会将其转换成指针。

  因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。
因此数组会被转换成指针(数组退化现象),所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针

尽管不能以值传递的形式传递数组,但是我们可以把形参写成类似数组的形式:

1
2
3
4
5
//尽管形式不同,但这三个printf函数是等价的
//每个函数都有一个const int*类型的形参
void print(const int*);
void print(const int[]);
void print(const int[10])//这里的维度表示我们期望数组含有多少元素,实际不一定

当编译器处理对printf函数的调用时,只检查传入的参数是否是const int*类型:

1
2
3
4
int i = 2;
int j[2] = {1,2};
print(&i); //正确,&i的类型是int*
print(j); //正确,j被转换成int*并指向j[0]

  如果我们传给printf函数的是一个数组,则实参自动地转换成指向首元素的指针,数组的大小对函数的调用没有影响。

三、指针作为函数参数的好处

  此外,传指针和传数据相比还有一个好处就是节约内存。我们知道,传数据拷贝的是内存单元的数据,如果数据很多的话拷贝过来都要为它们分配内存。而传指针的话只需要传递 4 字节的地址就行了。而且传数据非常消耗效率,为形参分配内存需要时间,拷贝需要时间,最后结束了返回还是需要时间。前面说过,return时系统会先自动创建一个临时变量来存放返回的值。所以传数据时很消耗效率,而传指针就是为了提高效率。

  事实上,在实际编程中我们都是传递指针!

往往只有满足下面这两个条件的时候我们才会直接传递数据而不是传递指针,而且这两个条件缺一不可:

  1. 数据很小,比如就一个 int 型变量。
  2. 不需要改变它的值,只是使用它的值。

此时不是不能用指针,当然也可以用指针,只是没有必要。

  以后在使用函数的时候,只要函数的参数不满足上面这两个条件,那么就用指针。此外需要注意的是,数组名本身就是地址,所以如果传递数组的话直接传递数组名就行了。接收的形参可以定义成数组(形式方面上而已)也可以定义为同类型的指针。

-------------本文结束感谢您的阅读-------------