C 指针


指针是一个变量,用于保存另一个变量的地址。


在64位机器上,指针变量在内存中占用8个字节。

指针声明是在数据类型后跟空格和一个*,后面指针变量名。例如int *p;表示p是一个指针,p变量的值是一个地址,该地址中存放的值为一个整数。

变量和地址

在程序运行过程中操作系统会在内存中分配存储空间给变量,变量存放在内存中的位置称为变量的地址。

内存可以想像成一个从上往下的由很多个连续的小盒子组成的柜子,每个小盒子中存放一个字节的数据,更改或得到变量的值需要访问一个小盒子或相邻的多个小盒子。例如char长度为一个字节,只占用一个小盒子。int长度为4个字节,要占用4个小盒子。

下面是一个int变量、int *指针和变量地址的例子:

#include <stdio.h>
int main(int argc, const char * argv[]) {
    int i = 3;
    int *p = &i;

    printf("Address of i : %p \n", p);  // 0x7fff5f7eeafc
    printf("Address of p : %p \n", &p); // 0x7fff5f7eeaf0
    printf("*p : %d \n", *p);   // 3

    return 0;
}

字符指针

C语言使用字符数组表示字符串,通常使用指针来操作数组,字符指针(Character Pointer)是最常用的指针。字符指针和字符数组的区别是字符数组中的元素可以更改,但通过字符指针更改字符数组中元素的结果是不可预测的。

下面是字符指针的例子:

#include <stdio.h>
int main(int argc, const char * argv[]) {
    char *p = "dog";
    char a[] = "cat";

    a[0] = 'C';
    //*p = 'f'; // runtime error: Bus error: 10

    printf("p : %s \n", p); // dog
    printf("a : %s \n", a); // Cat

    return 0;
}

在上面的例子中,语句char *p = "dog";中字符指针p指向字符串字面量"dog",但不能更改p指向的字符数组中的元素。

Null指针

null指针(null pointer)表示指针没有指向一个有效的对象。对null指针的间接引用(dereference)会导致运行时错误。

下面是null指针的例子:

#include <stdio.h>
int main(int argc, const char * argv[]) {
    char *p = NULL;
    printf("p : %p\n", p);  // 0x0

    //*p = 'a'; // runtime error: Segmentation fault: 11

    FILE *fp = fopen("not_exist.txt", "r");
    if (fp == NULL)
    //if (!fp)
        printf("打开文件失败! \n"); // 打开文件失败!

    return 0;
}

null指针在if判断中表示false。

Void指针

void指针(void pointer, void *)是无类型指针,可以指向任何数据类型。void指针指向的对象的大小和类型不知道,void指针不能被间接引用(dereference),void指针上也不能进行指针算术运算(pointer arithmetic)。

下面是void指针的例子:

#include <stdio.h>
int main(int argc, const char * argv[]) {
    char a[] = "apple";
    char *p = a;

    *p = 'A';
    printf("p : %s\n", p);  // Apple

    void *p2 = a;
    *(char *)p2 = 'b';
    //*p2 = 'b'; // error: incomplete type 'void' is not assignable

    printf("p2 : %s\n", p2);  // bpple

    return 0;
}

指针算术运算

指针算术运算(Pointer Arithmetic)包含4种:++、--、+、-。

指针运算会根据指针指向的对象的数据类型自动缩放,如果指针指向的数据类型为int,int长度为4,那么指针加1得到的结果值则增加4。

下面是指针算术运算的例子:

#include <stdio.h>
int main(int argc, const char * argv[]) {
    int a[] = {1, 2, 4, 8};
    int *p = a;

    printf("p : %p\n", p);  // 0x7fff5e76aad0

    p++;
    printf("p : %p\n", p);  // 0x7fff5e76aad4
    printf("a[1] : %d\n", *p);  // 2

    p += 2;
    printf("a[3] : %d\n", *p);  // 8


    void *p2 = a;
    p2++;
    printf("p2 : %p\n", p2);  // 0x7fff5e76aad1

    return 0;
}

指针数组

指针数组(Pointer Array)是一个元素为指针的数组。

下面是指针数组的例子:

#include <stdio.h>
int main(int argc, const char * argv[]) {
    char *colors[] = {"red", "blue"};

    for (int i = 0; i < 2; i++) {
        printf("Value of colors[%d] : %p. \n", i, colors[i]); // 0x10c9a4f74 0x10c9a4f78
        printf("\tcolors[%d] : %s. \n", i, colors[i]);  // red blue
    }

    return 0;
}

上面例子中数组colors中第一个元素保存的是字符串"red"的地址,第二个元素保存的是字符串"blue"的地址。

二级指针

二级指针(Pointer to Pointer)的声明如下:

char **pp;

二级指针的值为另一个指针的地址。二级指针常用来作为函数的参数,用来返回一个指针。

下面是二级指针的例子:

#include <stdio.h>
#include <stdlib.h>
int alloc(long len, char **pp) {
    char *p = malloc(len + 1);
    if(p == NULL)
        return 0;
    *pp = p;
    return 1;
}

int main(int argc, const char * argv[]) {
    char *p = "hello";
    char *p2;
    if(alloc(strlen(p), &p2))
        strcpy(p2, p);
    else
        printf("out of memory\n");

    printf("copy : %s \n", p);  // hello

    return 0;
}

在上面的例子中,在函数alloc中通过二级指针ppmain函数中的p2指针赋值。

函数指针

函数指针(Function Pointer)使用一种特殊的签名引用函数。

函数指针的声明如下:

return-type (*ptr-name)(type1,type2,…);

下面是函数指针的例子:

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main(int argc, const char * argv[]) {
    int (*fp)(int, int) = &add;

    int i = fp(1, 2);
    printf("1 + 2 = %d \n", i); // 3

    return 0;
}

指向数组的指针

指向数组的指针(Pointer to Array)的声明如下:

int (*p)[4];

上面语句声明变量p是一个指向包含4个int元素的数组的指针。

下面是指向数组的指针的例子:

#include <stdio.h>
int main(int argc, const char * argv[]) {
    int a[4] = {1, 2, 3, 4};
    int (*p)[4];

    p = &a;

    (*p)[3] = 8;

    printf("a[0] : %d. \n", (*p)[0]); // 1
    printf("a[3] : %d. \n", a[3]);    // 8

    return 0;
}