C语言学习笔记(三五)

C语言学习地三十五天,今天学习指针。

5.2 指针与函数参数
由于C语言是以传值的方式将参数值传递给被调用函数,因此,被调用函数不能直接修改主调函数中变量的值。例如,排序函数可能会使用一个名为swap的函数来交换两个次序颠倒的元素。但是,如果将swap函数定义为下列形式:

    void swap(int x, int y){ /* 错误定义的函数 */
        int temp;

        temp = x;
        x = y;
        y = temp;
    }

则下列语句无法达到该目的。

    swap(a, b);

这是因为,由于参数传递采用传值方式,因此上述的swap函数不会影响到调用它的例程中的参数a和b的值。该函数仅仅交换了a和b的副本的值。
那么,如何实现我们的目标呢?可以使主调程序指向所要交换的变量的指针传递给被调用函数,即:

    swap(&a, &b);

由于一元运算符&用来取变量的地址,这样&a就是一个指向变量a的指针。swap函数的所有参数都声明为指针,并且通过这些 指针间接访问它们指向的操作数。

    void swap(int *px, int *py) { /* 交换*px和*py */
        int  temp;

        temp = *px;
        *px = *py;
        *py = temp;
    }

指针参数使得被调用函数能够访问和修改主调函数中对象的值。我们来看这样一个例子:函数getint接受自由格式的输入,并执行转换,将输入的字符流分解成整数,且每次调用得到一个整数。getint需要返回转换后得到的整数,并且,再到达输入结尾时要返回文件结束标记。这些值必须通过不同的方式返回。EOF(文件结束标记)可以用任何值表示,当然也可用一个输入的整数表示。

可以这样设计该函数:将标识是否达到文件结尾的状态作为getint函数的返回值,同时,使用一个指针参数存储转换后得到的整数并传回给主调函数。函数scanf的实现就采用了这种方法。
下面的循环语句调用getint函数给一个整型数组赋值:

    int n, array[SIZE], getint(int *);

    for (n = 0; n < SIZE && getint(&array[n]) != EOF; n++)
        ;

每次调用getint时,输入流中的下一个整数将被赋值给数组元素array[n],同时,n的值将增加1。请注意,这里必须将array[n]的地址传递给函数getint,否则函数getint将无法把转换得到的整数传回给调用者。
该版本的getint函数在到达文件结尾时返回EOF,当下一个输入不是数字时返回0,当输入中包含一个有意义的数字时返回一个正值。

    #include <ctype.h>
    #include <stdio.h>

    int main() {
        int n, getint(int *);

        getint(&n);
        printf("n=%d", n);
    }

    int getch(void);
    void ungetch(int);

    /* getint函数:将输入中的下一个整型数赋值给*pn */
    int getint(int *pn) {
        int c, sign;

        while (isspace(c = getch())) /* 跳过空白符 */
            ;
        if (!isdigit(c) && c != EOF && c != '+' && c != '-') {
            ungetch(c); /* 输入不是一个数字 */
            return 0;
        }
        sign = (c == '-') ?  -1 : 1;
        if (c == '+' || c == '-')
            c = getch();
        for (*pn = 0; isdigit(c); c = getch())
            *pn = 10 * *pn + (c - '0');
        *pn *= sign;
        if (c != EOF)
            ungetch(c);
        return c;
    }

5.3 指针与数组
通过数组下标所能完成的任何操作都可以通过指针来实现。一般来说,用指针编写的程序比用数组下标编写的程序执行速度快,但另一方面,用指针实现的程序理解起来稍微困难一些。
    声明

    int a[10];

定义了一个长度为10的数组a。换句话说,它定义了一个由10个头对象组成的集合,这10个对象存储在相邻的内存区域中,名字分别为a[0]、a[1]、...、a[9]。
a[i]表示该数组的第i个元素。如果pa的声明为

    int *pa;

则说明它时一个指向整型对象的指针,那么,赋值语句

    pa = &a[0];

则可以将指针pa指向数组a的第0个元素,也就是说,pa的值为数组元素a[0]的地址。
这样,赋值语句

    x = *pa;

将把数组元素a[0]中的内容赋值到变量x中。
如果pa指向数组中的某个特定元素,那么,根据指针运算的定义,pa+1将指向下一个元素,pa+i将指向pa所指向数在元素之后的第i个元素,而pa-i将指向pa所指向数组元素之前的第i个元素。因此,如果指针pa指向a[0],那么*(pa+1)引用的是数组元素a[1]的内容,pa+i是数组元素a[i]的地址,*(pa+i)引用的是数组元素a[i]的内容。
无论数组a中的元素的类型或数组长度是什么,上面的结论都成立。“指针加1”就意味着,pa+1指向pa所指向的对象的下一个对象。相应地,pa+i指向pa所指向的对象之后的第i个对象。
下标和指针运算之间具有密切的对应关系。根据定义,数组类型的变量或表达式的值是该数组第0个元素的地址。执行赋值语句:

    pa = &a[0];

后,pa和a具有相同的值。因为数组名所代表的就是该数组最开始的一个元素的地址,所以,赋值语句pa=&a[0]也可以写成下列形式:

    pa = a;

对数组元素a[i]的引用也可以写成*(a+i)这种形式。在计算数组元素a[i]的值时,C语言实际上先将其转换为*(a+i)的形式,然后再进行求值,因此程序中者两种形式是等价的。如果对这两种等价的表示形式分别施加地址运算符&,便可以得出这样的结论:&a[i]和a+i的含义也时相同的。a+i是a之后第i个元素的地址。相应地,如果pa是一个指针,那么,再表达式中也可以在它的后面加下标。pa[i]与*(pa+i)是等价的。简而言之,一个通过数组和下标实现的表达式可等价地通过指针和偏移量实现。
但是,数组名和指针之间有一个不同之处。指针是一个变量,因此,在C语言中,语句pa=a和pa++都是合法的。但数组名不是变量,因此,类似于a=pa和a++形式的语句是非法的。
当把数组名传递给一个函数时,实际上传递的时该数组第一个元素的地址。在被调用函数中,该参数是一个局部变量,因此,数组名参数必须是一个指针,也就是一个存储地址值的变量。我们可以利用该特性编写strlen函数的另一个版本,该函数用于计算一个字符串的长度。

    /* strlen函数:返回字符串s的长度 */
    int strlen(char *s){
        itn n;

        for(n = 0; *s != '\0'; s++)
            n++;
        return n;
    }

因为s是一个指针,所以对其执行自增运算是合法的。执行s++运算不会影响到strlen函数的调用者中的字符串,它仅对该指针在strlen函数中的私有副本进行自增运算。因此,类似于下面这样的调用:

    strlen("hello, world"); /* 字符串常量 */
    strlen(array);  /* 字符数组array有100个元素 */
    strlen(ptr);    /* ptr是一个指向char类型对象的指针 */

都可以正确地执行。
在函数定义中,形式参数

    char s[];

    char *s;

是等价的。我们通常更习惯于使用后一种形式,因为它比前者更直观地表明了该参数是一个指针。
也可以将指向子数组起始位置的指针传递给函数,这样,就将数组的一部分传递给了函数。例如,如果a是一个数组,那么下面两个函数调用

    f(&a[2])

    f(a+2)

都将把起始与a[2]的子数组的地址传递给函数f。在函数f中,参数的声明形式可以为

    f(int arr[]) {,,,}

    f(int *arr) {...}

对于函数f来说,它并不关心所引用的是否只是一个更大数组的部分元素。
如果确信相应的元素存在,也可以通过下标访问数组第一个元素之前的元素。类似于p[-1]、p[-2]这样的表达式在语法上都是合法的,它们分别引用位于p[0]之前的两个元素,当然,引用数组边界之外的对象是非法的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值