指针

指针基础概念

指针本质上也是一个变量,它也有类型。

我们以前讲的变量。

int age = 12;

这个变量存储一个整数,这个变量的名字叫 age。变量其实是内存中的一块小区域。通常是占 n 个字节。

一个字节是 8 个位,8 bit。一个 bit 存储 0 或 1。

指针是 C 语言中最强大的工具之一,它也是最容易令人困惑的主题,所以一定要在开始时正确理解其概念。

在前面讨论内存时,谈到计算机如何为声明的变量分配一块内存。

在程序中使用变量名引用这块内存,但是一旦编译执行程序,计算机就使用内存位置的地址来引用它。

例如

int number = 5;

这条语句会分配一块内存来存储一个整数,使用 number 名称可以访问这个整数。值 5 就存储在这块区域中。

计算机用一个地址引用这块区域。存储这个数据的地址取决于所使用的计算机、操作系统和编译器。

在源代码中,这个变量名是固定不变的,但是地址在不同的系统上是不同的。

可以存储地址的变量称为指针。存储在指针中的地址通常是另一个变量。

指针 pnumber 中的地址是 number 第一个字节的地址。

变量 number 是一个值为 99 的整数变量。存储在 pnumber 中地址是 number 第一个字节的地址。

知道变量 pnumber 是一个指针是不够的,更重要的是,编译器必须知道它所指的变量类型。没有这个信息,根本不可能知道它占用多少内存,或者如何处理它所指向的内存的内容。

char 类型占一个字节,long 类型占四个字节,因此每个指针都和某个变量类型相关联,也就是用于指向该类型的变量。

所以,如果指针的类型是 int,就是指向 int 类型的变量。

float 类型的指针,就是指向 float 类型的变量。

一般给类型的指针写成 type*

类型名 void 表示没有指定类型,所以 void* 类型的指针可以包含任意类型的数据项地址。

以下语句可以声明一个指向 int 类型变量的指针:

int *pnumber;

pnumber 变量的类型是 int *,它可以存储任意 int 类型变量的地址。

该语句还可以写作:

int* pnumber;

这条语句创建了 pnumber 变量,但是没有初始化它,未初始化的指针是非常危险的。

比未初始化的普通变量要危险很多,所以应总是在声明指针时对它进行初始化。

如果不指向变量,可以设为 NULL。

int *pnumber = NULL;Code language: PHP (php)

NULL 是在标准库中定义的一个常量,对于指针它表示 0。

NULL 是一个不指向任何内存位置的值。

这表示使用不指向任何对象的指针,不会意外覆盖内存。

NULL 在头文件 stddef.h、stdlib.h、stdio.h、string.h、time.h、wchar.h 和 locale.h 中定义。

只要编译器不能识别 NULL,就应在源文件中包含 stddef.h 头文件。

如果用已声明的变量地址初始化 pointer 变量,可以使用寻址运算符 &。

例如:

int number = 99;
int *pnumber = &number;Code language: HTML, XML (xml)

注意 number 的声明必须在 pnumber 之前。否则代码不能编译。编译器需要先分配好空间,才能使用 number 的地址初始化 pnumber 变量。

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

int main(void)
{
    int number = 99;
    //int *pNumber;//不安全
    //int *pNumber = NULL;//可以指向一个NULL
    int *pNumber = &number;
    return 0;
}Code language: PHP (php)

指针的声明没有什么特别之处。可以用相同的语句声明一般的变量和指针。

例如:

double value, *pval, fnum;

要注意如下问题,很多人很容易犯错。

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

int main(void)
{
    int number, *pNumber;
    pNumber = &number;
    int age = *pNumber;
    return 0;
}Code language: HTML, XML (xml)

间接运算符与指针访问

使用间接运算符 * 可以访问指针所指的变量值。

这个运算符也称为取消引用运算符,因为它用于取消对指针的引用。

假设声明以下的变量:

int number = 15;
int *pointer = &number;
int result = 0;Code language: HTML, XML (xml)

pointer 变量含有 number 变量的地址,所以可以在表达式中使用它计算 result 的新值。如下:

result = *pointer + 5;

表达式 *pointer 等于存储在 pointer 中的地址的值。这是存储在 number 中的值 15。

所以 result 是 15+5 等于 20。

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

int main(void)
{
    int number = 15;
    int *pointer = &number;
    int result = 0;
    //result = number +5;
    result = *pointer + 5;
    printf("%d", result);
    return 0;
}Code language: PHP (php)

以下例子将声明一个变量和一个指针,然后输出它们的地址和它们所含的值。

#include <stdio.h>

int main(void)
{
    int number = 0;                 /* A variable of type int initialized to  0 */
    int *pointer = NULL;            /* A pointer that can point to type int     */
    number = 10;
    printf("\nnumber的地址: %p", &number);            /* Output the address */
    printf("\nnumber的值: %d\n\n", number);           /* Output the value   */

    pointer = &number;              /* Store the address of number in pointer   */

    printf("pointer的地址: %p", &pointer);            /* Output the address */
    printf("\npointer的大小: %d bytes", sizeof(pointer)); /* Output the size   */
    printf("\npointer的值: %p", pointer);  /* Output the value (an address) */
    printf("\n*pointer指向的值: %d\n", *pointer);       /* Value at the address */
    return 0;
}Code language: PHP (php)
  • 同时也是乘法符号,但是编译器不会混淆,因为出现的位置是不一样的,而且编译器是可以理解的。

可以通过指针 pnumber 访问 number 的内容,所以可以在算术语句中使用取消引用的指针。

例如:

*pnumber += 25;

上述语句将变量 pnumber 所指向的地址中的值增加 25。星号 * 表示访问 pnumber 变量所指向的内容。这里它是变量 number 的内容。

变量 pnumber 能存储任何 int 变量的地址。这表示可以用下面的语句改变 pnumber 的变量。

int value = 999;
pnumber = &value;Code language: HTML, XML (xml)

重复之前的语句:

*pnumber += 25;

该语句操作的新的变量 value。value 的新值是 1024。这表示指针可以包含同一类型的任意变量的地址。

所以,可以使用一个指针变量可以改变其它任何变量的值,只要它们的类型和指针相同。

#include <stdio.h>

int main(void)
{
    int value = 900;
    int *pnumber = NULL;
    int number = 90;
    pnumber = &number;
    printf("%d\n", *pnumber); //90
    *pnumber += 10;
    printf("%d\n", number); //100

    pnumber = &value;
    *pnumber += 10;
    printf("%d\n", value); //110? 910?
    printf("%d\n", *pnumber); //910
    return 0;
}Code language: PHP (php)

自己动手做一下下面的题目:

#include <stdio.h>

int main(void)
{
    long num1 = 0;
    long num2 = 0;
    long *pnum = NULL;

    pnum = &num1; //pnum的值是num1的地址
    *pnum = 2;   //num1:2
    ++num2;   //num2:1
    num2 += *pnum;   //num2:3

    pnum = &num2;  //pnum的值是num2的地址
    ++*pnum;   //num2:4                                    /* Increment num2 indirectly */

    printf("\nnum1 = %ld  num2 = %ld  *pnum = %ld  *pnum + num2 = %ld\n",
           num1, num2, *pnum, *pnum + num2); //2 4 4 8
    return 0;
}Code language: PHP (php)

指针与输入

下面例子演示指针如何用于键盘输入。

前面使用 scanf() 输入数值。使用 & 运算符获取接收输入的变量的地址,并用作传给函数的参数。有了一个含有地址的指针后,只需使用这个指针的名字作为参数。

下面是以前的写法:

#include <stdio.h>

int main(void)
{
    int value = 0;
    scanf("%d", &value);
    printf("\n%d", value);
    return 0;
}Code language: PHP (php)

看下面例子:

#include <stdio.h>

int main(void)
{
    int value = 0;
    int *pvalue = NULL;

    pvalue = &value;                    /* Set pointer to refer to value  */

    printf("Input an integer: ");
    scanf(" %d", pvalue);              /* Read into value via the pointer */

    printf("\nYou entered %d\n", value);      /* Output the value entered */
    return 0;
}Code language: PHP (php)

pvalue 和 &value 是相同的,用任何一个作为 scanf 的第二个参数都是可以的,它们都表示 value 的地址。

我们的代码:

#include <stdio.h>

int main(void)
{
    int value = 0;
    int *pValue = NULL;
    pValue = &value;
    scanf("%d", pValue);
    printf("\n%d", value);
    return 0;
}Code language: PHP (php)

假定创建如下指针:

int *pvalue = NULL;Code language: PHP (php)

如前面说的,NULL 是 C 语言中是特殊的常量。它相当于数字 0 的指针。

NULL 常常定义为 ((void*)0)。

给指针赋予 0 时,就等于将它设为 NULL。

所以编写如下语句:

int *pvalue = 0;

因为 NULL 等于 0,如果要测试指针 pvalue 是否是 NULL,可以编写如下语句:

if (!pvalue)
{
    //指针为空
}Code language: JavaScript (javascript)

当然也可以这样:

if (pvalue == NULL)
{
    //指针为空
}Code language: PHP (php)

我们的代码:

#include <stdio.h>

int main(void)
{
    int *pvalue = NULL;
    int *pv = 0;
    if (!pvalue)
    {
        //空指针
    }
    if (pvalue == NULL)
    {
        //空指针
    }
    return 0;
}Code language: PHP (php)

常量指针

声明指针时,可以使用 const 关键字指定,该指针指向的值不能改变。

看下面例子:

long value = 9999L;
const long *pvalue = &value;Code language: HTML, XML (xml)

这时候 pvalue 所指向的值声明为 const,编译器就会检查语句有没有试图修改 pvalue 指向的值。所以下面语句是错误的:

*pvalue = 8888L;

也就是不能通过 pvalue 的指针来修改它所指向的值。

但是可以通过 value 来修改。

下面语句是可以的:

value = 7777L;

虽然不能通过 pvalue 来修改它所指向的值。

但是 pvalue 本身保存的值(地址)是可以改变的。

比如下面:

long number = 8888L;
pvalue = &number;Code language: HTML, XML (xml)

这样 pvalue 的值又被改为了 number 的地址。仍然不能使用指针改变它指向的值。也就是如果希望:

*pvalue = 8888L;

这样,那也是不行的。

一句话总结:

指向常量的指针,可以改变指针中的地址,但是不能改变地址指向的值。

我们的代码:

#include <stdio.h>

int main(void)
{
    long value = 9999L;
    value = 123L;
    const long *pValue = &value;
    *pValue = 456L; //指针是const 不能通过它来修改指向的变量
    printf("%d", value);
    return 0;
}Code language: PHP (php)

我们的例子:

#include <stdio.h>

int main(void)
{
    long value = 9999L;
    value = 123L;
    const long *pValue = &value;
    //*pValue = 456L;//指针是const 不能通过它来修改指向的变量
    value = 456L;
    printf("%d", value);

    long number = 1000L;
    pValue = &number;
    //*pValue = 456L;//虽然指针指向了number 但是也是不能通过它来修改

    return 0;
}Code language: PHP (php)

当然,也可以使指针中存储的地址不能改变。

此时,在指针声明中使用 const 关键字的方式,但是有点不同。

下面语句可以使指针总是指向相同的对象:

int count = 43;
int *const pcount = &count; //定义一个常量指针Code language: JavaScript (javascript)

第二条语句声明并初始化了 pcount,指定该指针存储的地址不能改变。

编译器会检查代码是否无意中把指针指向其他地方,所以下面语句会在编译时生成一条错误信息。

int item = 34;
pcount = &item; //错误 尝试改变指针存储的地址值Code language: HTML, XML (xml)

但是使用 pcount 仍可以改变 pcount 指向的值:

*pcount = 235;

这条语句通过指针引用了存储在 count 中的值,并将其改为 235。

#include <stdio.h>

int main(void)
{
    long value = 9999L;

    long * const pValue = &value;
    *pValue = 100L;
    printf("%d", value);

    long number = 12L;
    pValue = &number; //出错 指针的值不能改变
    return 0;
}Code language: PHP (php)

当然还可以直接使用 count 来改变它的值。

可以创建一个常量指针,它指向一个常量值。也就是结合上一节课的。

int item = 25;
const int *const pitem = &item;Code language: JavaScript (javascript)

这时候 pitem 是一个指向常量的常量指针。所以所有的信息都固定不变。不能改变存储在 pitem 中的地址,也不能使用 pitem 改变它指向的内容。

但仍可以直接修改 item 的值。

如果希望所有的信息都固定不变,可以把 item 也指定为 const。

#include <stdio.h>

int main(void)
{
    long value = 9999L;

    const long * const pValue = &value;
    //*pValue = 100L; //不能改变指针指向的变量的值
    printf("%d", value);

    long number = 12L;
    //pValue = &number; //出错 指针的值不能改变

    printf("\n%d", *pValue);
    return 0;
}Code language: PHP (php)

指针命名规范

随着程序越来越大,就越难记住哪一个是一般变量,哪一个是指针,因此,最好使用 p 作为指针名的第一个字母。

如果严格遵守这个命名方法,那就很容易记住了。

#include <stdio.h>

int main(void)
{
    long value = 9999L;
    long * pValue = &value;

    *pValue = 100L;
    printf("%d", value);
    printf("\n%d", *pValue);

    long number = 111L;
    pValue = &number; //看到p前缀的变量 就知道这是一个指针
    return 0;
}Code language: PHP (php)

Previous:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注