张银峰的编程课堂

指针入门

指针是一种复合数据类型;定义数组时使用下标运算符[]与其它单一变量进行类型区别;类似的,指针变量使用'*'号加以区别。

// 定义整型指针pi,赋值为0,表示它不指向(持有)任何整型对象。
int *pi = 0;

// 定义字符型指针,赋值为NULL(其实就是0),表明它未指向任何char型对象。
// '*'号紧邻char,强调pc是一个char型指针。(重点在于强调类型)
char* pc = NULL;

// pf是一个float型指针,pf2则一个float型变量,因为它前面没有'*'号。
// '*' 号与类型和变量名分开,突出自身。
// 未初始化的变量值是不确定的,指针pf也一样。
float * pf, pf2;

示例中,*号的位置代表着不同的编码风格,使用哪种完全取决于个人喜好。正如注释所写,从代码审阅的角度讲,不同风格的强调的侧重点不同。变量存在一种从右向左的读法,方法是从变量名(或最后一个修饰词)开始向左阅读,对于第一个变量,可以读作:pi是一个变量,这个变量是一个指针,指针类型是int。连接起来就是:pi是一个指针变量,pi是一个指向整型的指针变量。程序没有为pf进行初始化,是因为我们确信后面会给它进行正确的赋值,如果不确定能做到这一点,建议在定义指针时就初始化为NULL。

取地址

指针变量的值是一段内存区域的地址,为指针赋值,是通过取地址运算符(&)实现的,称为指向了某个变量或地址。

int a = 5;
int *pa = &a;     // pa指向了a

double b = 3.0;
double *pb = &b;  // pb指向了b

定义一个变量就代表要分配一定字节的内存。

  • 对于变量a,系统会分配sizeof(int)个字节的空间以便容纳一个数值。
  • 对于指针pa,系统会分配sizeof(int*)个字节的空间以便存储一个地址。

ZYF

把变量的空间想象成一个盒子,这个盒子有一个编号,此编号就是内存地址。指针的指向(即被赋值)就代表把盒子的地址交给指针的空间存储,即指针的值是盒子的地址。由此可知,指针pa、pb的值有两个特性:

  • 首先,pa、pb的值是一个地址,即指针的值是一个地址。
  • 指针的值与被指向的变量地址是相同的。(呃,这两条分明是把猫叫了个咪。)
    pa           =        &a             |  语句
-----------------------------------------|----------
                                         |
   0x001f2a              0x901b30        |  内存地址
+------------+        +------------+     |
| 0x901b30 --|------->|     5      |     |  值
+----|-------+        +-----|------+     |
     |                      |            |
     v                      v            |
  值为地址                值为数值        |  值的含义

可以使用%p来打印一个指针的值,地址值通常用16进制表示,为了更清楚的表明这一点,我们在输出值前面加上了0x标志。注意观察结果,pa与&a的值是一样的,表示它们指向同一个值,pb同理。

#include <stdio.h>

int main()
{
    int a;
    int b = 5;

    int *pa = &a;
    int *pb = &b;

    printf("&a = 0x%p\n", &a);
    printf("pa = 0x%p\n", pa);

    printf("\n");

    printf("&b = 0x%p\n", &b);
    printf("pb = 0x%p\n", pb);

    return 0;
}

glimix.com

解引用

指向操作让指针持有一个合法的地址值。如果将自己想像成一个指针变量,被指向的对象想像为盒子,指向相当于我们拎起了盒子,盒子的提手就是该盒子的地址,即地址是彼此的连接;盒子中的东西就是你持有的值,至于这个东西是什么,就需要打开盒子去查看。这个打开的动作对应到指针变量上,就叫做解引用指针,通过解引用操作符(*)来实现的。

#include <stdio.h>

int main()
{
    int a;
    int b = 5;
    int *pa, *pb;

    // 让指针指向一个地址,相当于拥有一个盒子。
    pa = &a;
    pb = &b;

    // *pa: 相当于打开盒子查看值
    // 由于a没有初始化,因此它是一个随机值。
    printf("a = %d\n*pa = %d\n\n", a, *pa);

    // *pb: 术语化的读法就是:解引用指针pb。
    // b初值为5,因此打开盒子pb后你会看到数字5躺在里面。
    printf("b = %d\n*pb = %d\n", b, *pb);

    return 0;
}

glimix.com

使用未初始化的变量,是一种不确定行为。对于指针变量,如果没有确切的地址可以赋值,则可以将其赋值为NULL,以表示指针指向的地址无效,赋为NULL值的指针被称为空指针。

NULL指针是一个定义在标准库中的值为零的常量。

#define NULL ((void *)0)

在大多数操作系统上,程序不允许访问地址为0的内存,即对空指针进行间接访问是非法的。如果指针为空,则作为条件表达式时求值为假。

#include <stdio.h>

int main()
{
    int *pa = NULL;
    double *pd = 0; // 等同于NULL,但建议使用NULL。

    printf("pa: 0x%p\n", pa);

    if (pa)
        printf("pointer");
    else
        printf("pa is NULL");

    // 对空指针进行解引用是非法的
    int b = *pa;

    return 0;
}

glimix.com

ZYF