11.4 函数、数组和指针

C语言学习栏目目录

目录

1 使用指针形参

2 指针表示法和数组表示法


源码 VS2019环境

假设要编写一个处理数组的函数,该函数返回数组中所有元素之和,待处理的是名为marbles的int类型数组。应该如何调用该函数?也许是下面这样:

total = sum(marbles); // 可能的函数调用

那么,该函数的原型是什么?记住,数组名是该数组首元素的地址,所以实际参数marbles是一个储存int类型值的地址,应把它赋给一个指针形式参数,即该形参是一个指向int的指针:

int sum(int * ar); // 对应的函数原型

sum()从该参数获得了什么信息?它获得了该数组首元素的地址,知道要在该位置上找出一个整数。注意,该参数并未包含数组元素个数的信息。我们有两种方法让函数获得这一信息。第一种方法是,在函数代码中写上固定的数组大小:

int sum(int * ar) // 相应的函数定义
{
    int i;
    int total = 0;
    for (i = 0; i < 10; i++)  // 假设数组有10个元素
        total += ar[i];    // ar[i] 与 *(ar + i) 相同
    return total;
}

既然能使用指针表示数组名,也可以用数组名表示指针。另外,回忆一下,+=运算符把右侧运算对象加到左侧运算对象上。因此,total是当前数组元素之和。

函数定义有限制,只能计算10个int类型的元素。另一个比较灵活的方法是把数组大小作为第2个参数:

int sum(int * ar, int n)   // 更通用的方法
{
    int i;
    int total = 0;
    for (i = 0; i < n; i++)  // 使用 n 个元素
        total += ar[i];    // ar[i] 和 *(ar + i) 相同
    return total;
}

这里,第1个形参告诉函数该数组的地址和数据类型,第2个形参告诉函数该数组中元素的个数。

关于函数的形参,还有一点要注意。只有在函数原型或函数定义头中,才可以用int ar[]代替int * ar:

int sum (int ar[], int n);

int *ar形式和int ar[]形式都表示ar是一个指向int的指针。但是,int ar[]只能用于声明形式参数。第2种形式(int  ar[])提醒读者指针ar指向的不仅仅一个int类型值,还是一个int类型数组的元素。

注意 声明数组形参

因为数组名是该数组首元素的地址,作为实际参数的数组名要求形式参数是一个与之匹配的指针。只有在这种情况下,C才会把int ar[]和int * ar解释成一样。也就是说,ar是指向int的指针。由于函数原型可以省略参数名,

所以下面4种原型都是等价的:

int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);

但是,在函数定义中不能省略参数名。下面两种形式的函数定义等价:

int sum(int *ar, int n)
{
    // 其他代码已省略
}
int sum(int ar[], int n);
{
    //其他代码已省略
}

可以使用以上提到的任意一种函数原型和函数定义。

下程序清单演示了一个程序,使用 sum()函数。该程序打印原始数组的大小和表示该数组的函数形参的大小(如果你的编译器不支持用转换说明%zd打印sizeof返回值,可以用%u或%lu来代替)。

/************************************************************************
功能: 数组元素之和                                                                     
/************************************************************************/

// 如果编译器不支持 %zd,用 %u 或 %lu 替换它
#include <stdio.h>
#define SIZE 10
int sum(int ar[], int n);
int main(void)
{
	int marbles[SIZE] = { 20, 10, 5, 39, 4, 16, 19, 26,
	31, 20 };
	long answer;
	answer  = sum(marbles, SIZE);
	printf("The total number of marbles is %ld.\n", answer);
	printf("The size of marbles is %zd bytes.\n",
		sizeof marbles);
	return 0;
}

int sum(int ar[], int n)  // 这个数组的大小是?
{
	int i;
	int total  = 0;
	for (i  = 0; i  < n; i++)
		total  += ar[i];
	printf("The size of ar is %zd bytes.\n", sizeof ar);
	return total;
}

该程序的输出如下:

The size of ar is 4 bytes.
The total number of marbles is 190.
The size of marbles is 40 bytes.

注意,marbles的大小是40字节。这没问题,因为marbles内含10个int类型的值,每个值占4字节,所以整个marbles的大小是40字节。但是,ar才4字节。这是因为ar并不是数组本身,它是一个指向 marbles 数组首元素的指针。简而言之,在上程序清单中,marbles是一个数组, ar是一个指向marbles数组首元素的指针,利用C中数组和指针的特殊关系,可以用数组表示法来表示指针ar。

1 使用指针形参

函数要处理数组必须知道何时开始、何时结束。sum()函数使用一个指针形参标识数组的开始,用一个整数形参表明待处理数组的元素个数(指针形参也表明了数组中的数据类型)。但是这并不是给函数传递必备信息的唯一方法。还有一种方法是传递两个指针,第1个指针指明数组的开始处(与前面用法相同),第2个指针指明数组的结束处。下程序清单演示了这种方法,同时该程序也表明了指针形参是变量,这意味着可以用索引表明访问数组中的哪一个元素。

/************************************************************************
功能: 数组元素之和                                                                     
/************************************************************************/
#include <stdio.h>
#define SIZE 10
int sump(int* start, int* end);
int main(void)
{
	int marbles[SIZE] = { 20, 10, 5, 39, 4, 16, 19, 26,	31, 20 };
	long answer;
	answer  = sump(marbles, marbles  + SIZE);
	printf("The total number of marbles is %ld.\n", answer);
	return 0;
}
/* 使用指针算法 */
int sump(int* start, int* end)
{
	int total  = 0;
	while (start  < end)
	{
		total += *start; // 把数组元素的值加起来
		start++;      // 让指针指向下一个元素
	}
	return total;
}

指针start开始指向marbles数组的首元素,所以赋值表达式total += *start把首元素(20)加给total。然后,表达式start++递增指针变量start,使其指向数组的下一个元素。因为start是指向int的指针,start递增1相当于其值递增int类型的大小。

注意,sump()函数用另一种方法结束加法循环。sum()函数把元素的个数作为第2个参数,并把该参数作为循环测试的一部分:

for( i = 0; i < n; i++)

而sump()函数则使用第2个指针来结束循环:

while (start < end)

因为while循环的测试条件是一个不相等的关系,所以循环最后处理的一个元素是end所指向位置的前一个元素。这意味着end指向的位置实际上在数组最后一个元素的后面。C保证在给数组分配空间时,指向数组后面第一个位置的指针仍是有效的指针。这使得 while循环的测试条件是有效的,因为 start在循环中最后的值是end(在最后一次while循环中执行完start++;后,start的值就是end的值)。注意,使用这种“越界”指针的函数调用更为简洁:

answer = sump(marbles, marbles + SIZE);

因为下标从0开始,所以marbles + SIZE指向数组末尾的下一个位置。如果end指向数组的最后一个元素而不是数组末尾的下一个位置,则必须使用下面的代码:

answer = sump(marbles, marbles + SIZE - 1);

这种写法既不简洁也不好记,很容易导致编程错误。顺带一提,虽然C保证了marbles  +  SIZE有效,但是对marbles[SIZE](即储存在该位置上的值)未作任何保证,所以程序不能访问该位置。

还可以把循环体压缩成一行代码:

total += *start++;

一元运算符*和++的优先级相同,但结合律是从右往左,所以start++先求值,然后才是*start。也就是说,指针start先递增后指向。使用后缀形式(即start++而不是++start)意味着先把指针指向位置上的值加到total上,然后再递增指针。如果使用*++start,顺序则反过来,先递增指针,再使用指针指向位置上的值。如果使用(*start)++,则先使用start指向的值,再递增该值,而不是递增指针。这样,指针将一直指向同一个位置,但是该位置上的值发生了变化。虽然*start++的写法比较常用,但是*(start++)这样写更清楚。程序清单下的程序演示了这些优先级的情况。

/************************************************************************
功能: 指针运算中的优先级                                                                     
/************************************************************************/

#include <stdio.h>
int data[2] = { 100, 200 };
int moredata[2] = { 300, 400 };
int main(void)
{
	int* p1, * p2, * p3;
	p1  = p2  = data;
	p3  = moredata;
	printf(" *p1 = %d, *p2 = %d,  *p3 = %d\n", *p1, *p2, *p3);
	printf("*p1++ = %d, *++p2 = %d, (*p3)++ = %d\n", *p1++, *++p2, (*p3)++);
	printf(" *p1 = %d, *p2 = %d,  *p3 = %d\n", *p1, *p2, *p3);
	return 0;
}

 下面是该程序的输出:

*p1 = 100, *p2 = 100,  *p3 = 300
*p1++ = 100, *++p2 = 200, (*p3)++ = 300
*p1 = 200, *p2 = 200,  *p3 = 301

只有(*p3)++改变了数组元素的值,其他两个操作分别把p1和p2指向数组的下一个元素。

2 指针表示法和数组表示法

从以上分析可知,处理数组的函数实际上用指针作为参数,但是在编写这样的函数时,可以选择是使用数组表示法还是指针表示法。如程序清单1所示,使用数组表示法,让函数是处理数组的这一意图更加明显。另外,许多其他语言的程序员对数组表示法更熟悉,如FORTRAN、Pascal、Modula-2或BASIC。其他程序员可能更习惯使用指针表示法,觉得使用指针更自然,如程序清单2所示。至于C语言,ar[i]和*(ar+1)这两个表达式都是等价的。无论ar是数组名还是指针变量,这两个表达式都没问题。但是,只有当ar是指针变量时,才能使用ar++这样的表达式。

指针表示法(尤其与递增运算符一起使用时)更接近机器语言,因此一些编译器在编译时能生成效率更高的代码。然而,许多程序员认为他们的主要任务是确保代码正确、逻辑清晰,而代码优化应该留给编译器去做。

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值