C Primer Plus(6) 中文版 第10章 数组和指针 10.4 函数、数组和指针

发布于:2023-01-10 ⋅ 阅读:(207) ⋅ 点赞:(0)

10.4 函数、数组和指针
处理数组的函数,该函数返回数组中所有元素之和。
记住,数组名是该数组首元素的地址。所以函数的参数应该是一个指针。
函数从该参数获得了该数组元素首元素的地址,知道要在该位置上找出一个整数。注意,该参数并未包含数组元素个数的信息。我们有两种方法让函数获得这一信息。第一种方法是,在函数代码中写上固定的数组大小: 
int sum( int *ar ){ //相应的函数定义 
    int i;
    int total = 0;
    
    for( i = 0; i < 10; i++ ){ //假设数组有10个元素 
        total += ar[i];     //ar[i]与*(ar + i)相同 
    } 

既然能使用指针表示数组名,也可以用数组名表示指针。
该函数有限制,只能计算10个int类型的元素。另一个比较灵活的方法是把数组大小作为第2个参数:
int sum( int *ar, int n ){ //更通用的方法
    int i;
    int total = 0;
    
    for( i = 0; i < n; i++ ){
        total += 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 arr[]和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 ){
    //其他代码已省略 

可以使用以上提高的任意一种函数原型和函数定义。 
程序打印原始数组的大小和表示数组的函数形参的大小(如果你的编译器不支持用转换说明%zd打印sizeof返回值,可以用%u或%lu来代替)。
// sum_arr1.c -- sums the elements of an array
// use %u or %lu if %zd doesn't work
#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)     // how big an array?
{
    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;

/* 输出:

*/

sizeof ar的值是8。这是因为ar并不是数组本身,它是一个指向marbles数组首元素的指针。我们系统中用8字节存储地址,所以指针变量的大小是8字节(其他系统中地址的大小可能不是8字节)。简而言之,marbles是一个数组,ar是一个指向marbles数组首元素的指针,利用数组和指针的特殊关系,可以用数组表示法来表示指针ar。
10.4.1 使用指针形参
函数要处理数组必须知道何时开始,何时结束。sum()函数使用一个指针标识数组的开始,用一个整型形参表明待处理数组的元素个数(指针形参也表明了数组中的数据类型)。但是这并不是给函数传递必备信息的唯一方法,还有一种方法是传递两个指针,第1个指针指明数组的开始处(与前面用法相同),第2个指针指明数组的结束处。
指针作为函数的形参
/* sum_arr2.c -- sums the elements of an array */
#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;
}

/* use pointer arithmetic   */
int sump(int * start, int * end)
{
    int total = 0;
    
    while (start < end)
    {
        total += *start; // add value to total
        start++;         // advance pointer to next element
    }
    
    return total;

/* 输出:

*/ 

因为while循环的测试调教是一个不相等的关系,所以循环最后处理的一个元素是end所指向位置的前一个元素。这意味着end指向的位置实际上在数组最后一个元素的后面。C保证在给数组分配空间时,指向数组后面第一个位置的指针仍是有效的指针。这使得while循环的测试条件时有效的,因为start在循环中最后的值是end。注意,使用这种‘越界’指针的函数更为简洁:
answer = sump( marbles, marbles + SIZE );
如果end指向数组的最后一个元素而不是数组末尾的下一个位置,则必须使用下面的代码:
answer = sump( marbles, marbles + SIZE - 1 );
这种写法既不简洁也不好记,很容易导致编程错误。顺带一提,虽然C保证了marbles + SIZE有效,但是对marbles[SIZE](即存储在该位置上的值)未做任何保证,所以程序不能访问该位置。
还可以把循环体压缩成一行代码:
total += *start++;
一元运算符*和++的优先级相同,但结合律是从右往左,所以start++先求值,然后才是*start。也就是说,指针start先递增后指向。如果使用*++start,顺序则反过来,先递增指针,再使用指针指向位置上的值。如果(*start)++,则先使用start指向的值。再递增该值,而不是递增指针。这样,指针将一直指向同一个位置,但是该位置上的值发生了变化。虽然*start++的写法比较常用,但是
*(start++)这样写更清楚。
/* order.c -- precedence in pointer operations */
#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;

/* 输出:

*/

只有(*p3)++改变了数组元素的值,其他两个操作分别把p1和p2指向数组的下一个元素。
10.4.2 指针表示法和数组表示法
处理数组的函数实际上用指针作为参数,但是在编写这样的函数时,可以选择是使用数组表示法还是指针表示法。使用数组表示法,让函数是处理数组的这一意图更加明显。另外,许多其他语言的程序员对数组表示法更加熟悉。其他程序员可能更习惯使用指针表示法,觉得使用指针更自然。
至于C语言,ar[i]和*(ar + i)这两个表达式都是等价的。无论ar是数组名还是指针变量,这两个表达式都没问题。但是,只有当ar是指针变量时,才能使用ar++这样的表达式。
指针表示法(尤其与递增运算符一起使用时)更接近机器语句,因此一些编译器在编译时能生成效率更高的代码。然而,许多程序员认为他们的主要任务是确保代码正确、逻辑清晰、而代码优化应该留给编译器去做。