指针就如同名字一样,其主要功能就是指向一个内存地址,然会对指定内存地址内容进行操作。
正是应为C有了指针,才使得C有了极大的灵活性,指针可谓是C的精华。

如何使用指针

首先先看看如何声明一个指针:

int *p;

注意,这里int *表示指针指向的内存地址中的内容为int类型,而不是说,创建一个int类型的指针。这里可以使用其他的变量类型。
同时这里绝对不能理解为创建一个大小为int类型的指针!!!在不同位数的CPU上,指针的大小是不同的。

指针的主要操作指令一共有两个,分别为*&。其中*为解引用地址,而&为取地址。下面通过实例来讲解如何使用:

#include <stdio.h>

int main(void){
        int a = 100;
        int *p = &a;

        printf("a = %d, &a = %p\n p = %p, *p = %d, &p = %p\n", a, p, &a, *p, &p);
        printf("sizeof(a) = %d, sizeof(p) = %d\n", sizeof(a), sizeof(p));

        return 0;
}
a = 100, &a = 0x7ffc14e8f24c
p = 0x7ffc14e8f24c, *p = 100, &p = 0x7ffc14e8f240
sizeof(a) = 4, sizeof(p) = 8

那么我们来看看第二个指令int *p = &a
这里可以看成两个步骤:

  1. 声明指针p
  2. 将变量a的地址赋值给a。

然后我们再看看printf中使用到变量操作:

  • 首先是变量a,我们可以进行两个操作:

    • 引用内容:获取变量内容,对应操作:a = 100
    • 取地址:获取变量的内存地址,对应操作:&a = 0x7ffc14e8f24c
扯一点题外话:通常我们所说的地址一般是虚拟地址,而非物理地址,其主要目的就是为了实现进程之间的隔离。但是这个和开发没有任何影响,无需担心。
  • 然后是指针p,这里我们可以对指针p执行三个操作:

    • 引用内容:获取变量内容,对应操作:p = 0x7ffc14e8f24c
    • 解引用:获取指针指向的内存地址的内容。对应操作:*p = 100
    • 取地址:获取指针p的内存地址,对应操作:&p = 0x7ffc14e8f240
注:对指针取地址会用于二级指针的使用,对应的还存在三级指针以及更大级别的指针,不过过高级别的指针并没有多大用处。

注意,这里我们就能看到,指针p的大小为8而不是int数据类型的大小,这一点是需要注意的。
当然指针最常见的用法,并不是像这个样例中用的,其实指针经常用在malloc函数、数组等方法。详情看下文介绍。

指针和数组

我们知道在学习数组的时候,创建数组的方法如下:

int arr[4] = {2, 4, 6, 8};

当我们需要使用引用数组中的一个数值时,一般的用法为:

printf("arr[1]=%d\n", arr[1]);
arr[1]=4

有没有想过,如果直接输入arr,而不填写和使用中括号,会如何呢:

printf("arr=%p\n", arr);
arr=0x7ffe51098000

这里我们就能看到,实际上数组也就是一个指针,对指针能进行的操作,数组也可以。同样的,对数组可以执行的操作,指针也可以。
不过需要注意的就是,对数组进行取地址操作,和直接传入数组,其值是相同,所以调用数组地址时,默认会不写取地址符&。如下:

printf("&arr=%p, arr=%p\n", &arr, arr);
&arr=0x7fff78289050, arr=0x7fff78289050

通常我们引用数组是通过中括号来实现的,不过我们也可以使用指针+数值的方式实现,如以下示例:

printf("*(arr+1)=%d, arr[1]=%d\n", *(arr+1), arr[1]);
*(arr+1)=4, arr[1]=5

arr+1的操作并不是说简单的内存地址+1,而是指内存地址向后+1格,而这1格的大小为之前声明的数据类型大小,也就是int。
需要注意的是,因为*的运算级高于+-,所以需要使用()优先计算arr+1。否则其输出结果将为3

指针在函数中的使用

指针在函数中的使用就非常的简单。
需要明确注意的是,指针一般而言用于以下两种情况,其他情况下一般直接传入变量引用值即可:

  • 需要给函数传入大量参数时,可以直接写在数组或struct,然后再将指针传入函数即可。
  • 函数的返回值可能不止一个,return指令无法满足需求,需要传入指针,直接让函数修改变量值。或是函数返回的也是一个指针,需要给予一个参数来得到返回的指针指向的内存大小。

例如我们经常使用到的scanf参数,就需要传入指针,这里直接使用&取地址符即可。

scanf("%d", &a);

或者是在一些刷算法的网站里,函数需要使用指针返回结果,并且需要得到该变量的大小。

#include <stdio.h>
#include <stdlib.h>

int *ra(int *size){
        int *p = (int *)malloc(sizeof(int) * 2);
        p[0] = 1;
        p[1] = 2;
        *size = 2;
        return p;
}

int main(void){
        int size=0;
        int *p = ra(&size);
        for(int i=0; i<size; i++){
                printf("%d ", p[i]);
        }
        printf("\n");

        return 0;
}
1 2

这里的malloc函数详细的用法和原理以后再说,其大致功能就是开辟一段指定大小的内存空间,这段内存空间并不会因为一些其他操作而被覆盖,也不会被自动回收,需要调用函数free才能被释放。

二维数组 + 二级指针

二维数组+二级指针=二维数组指针,讲起来有点复杂,先讲解一个实例再来说说如何使用吧.
我们知道再执行C程序的时候都需要输入参数,而这些参数实际上都会以二级指针的形式传递给main函数,如下:

#include <stdio.h>

int main(int argc, char **argv){
        printf("&argv=%p\n", &argv);
        for(int i=0; i<argc; i++){
                printf("argv[%d]=%p: %s\n", i, argv[i], argv[i]);
        }
        return 0;
}

这里的执行命令为:./a.out options arg2
那么其返回结果就为:

&argv=0x7fff6f4054a0
argv[0]=0x7fff6f4067ad: ./a.out
argv[1]=0x7fff6f4067b5: options
argv[2]=0x7fff6f4067bd: arg2

其在内存中的分布大致如下:
二级指针
注:字符串其实就是有字符组成的数组,所以这里可以将指针argv理解为一个二维数组指针。
还有一点需要注意的是,字符串需要以\0结尾,所以每个字符串的大小应该是字符数量+1。

那么这里我们再说以下如何创建一个二维数组指针。在C中,不同的创建手法会生成不同的指针:

  1. 指向数组的指针int (*p)[2],其语义为:创建一个指向一维数组的指针,每个一维数组内含2个元素。
  2. 内含指针数组int *p[2],其语义为:创建一个内含2个元素的数组,该数组的内容为指针。

这两者的主要区别就在于()的使用,默认[]的优先级高于*,所以会造成语义上的不同。
不过这里我们还需要注意的就是int **pint (*p)[2]的区别,虽然我在前面有说过指针和数组可以理解为同一种东西,但是在使用时还是不同之处的。这两个同样为二级指针,但是在相互赋值的时候是会报出警告的,如下:
警告
从警告上,我们也就能看出,C编译器将int **int (*)[2]区分为两个不同类型的指针,这并不影响程序的编译,但是会在使用时出现未知问题。
这里我就列举以下可以进行赋值操作的指针:

源指针目的指针
int c2int (*p)][3]
int *p[10]char **p
int (*p)[3]int (*p)[3]
int **pint **p

那么这里来看以下实例:

#include <stdio.h>
#include <stdlib.h>

int main(void){
        int arr[2][3] = {{1, 2, 3}, {5, 6, 4}};
        int (*p)[3] = arr;

        for(int i=0; i<2; i++){
                for(int j=0; j<3; j++){
                        printf("%d ", p[i][j]);
                }
        }

        return 0;
}
1 2 3 5 6 4

本文经「原本」原创认证,作者乾坤盘,访问yuanben.io查询【56H6L91Y】获取授权信息。

最后修改:2020 年 06 月 10 日 08 : 29 PM
如果觉得我的文章对你有用,请随意赞赏