指针基础概念
指针本质上也是一个变量,它也有类型。
我们以前讲的变量。
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)