实用经验 25 理解指针的本质

指针是C/C++中最为重要的概念,因此也是C/C++区别于其他程序设计语言的主要特征。正确的使用指针类型数据,可以帮助我们有效的处理复杂的数据结构,直接处理内存地址。然而如果不恰当的使用指针则会带来安全性的问题。不正确的使用指针将会导致内存泄露、内存悬挂、野指针(不安全指向)等问题的出现,严重威胁到软件系统的稳定性和安全性。

C++标准规定:存放地址的变量称之为指针变量,变量的地址称之为变量的指针。所以我们可以看出,指针变量是一种特殊的变量,他不同于一般的变量,一般变量存放的是数据本身,而指针变量存放的是数据的地址。

一个变量有两种访问方式,他们是“直接访问”和“间接访问”,通过地址可以找到所需要的单元,因此这种访问是“直接访问”方式。另外一种访问是“间接访问”,它首先将欲访问单元的地址存放在另一个单元中。访问时,先找到存放地址的单元。从中取出地址。然后才能找到需访问的单元。再读或写该单元的数据。在这种访问方式中使用了指针。
在这里插入图片描述

图4-1 指针变量和变量内存关系

如图4-1所示。我们看这个例子,假设程序中声明1个int型变量nValue,其值为2。系统为变量nValue分配的首地址为0x012343456。同时声明一个int型指针变量pnValue(地址为0x065FDF40)。如果我们直接按照地址0x012343456找到nValue的存储单元对nValue进行访问,称之为直接访问。如果我们首先通过pnValue分配的地址找到pnValue。然后根据pnValue找到nValue变量在内存中的存储单元,从而对nValue进行访问。这种访问方式称之为“间接访问”。

说明

  • 指针变量也是变量,只是指针变量存储的是变量的指针。
  • 指针变量存放的是变量的指针,在32位系统中指针的宽度为32位(即4字节)。所以在32位系统中,指针变量的大小是4字节,无论是什么类型的指针变量。

定义一个指针变量的一般形式如下:

类型名称  *指针名称1, *指针名称2,.;

例如:

char *pszName; // 定义一个char型的指针
int  *pnOld;   // 定义一个int型指针

一个指针变量有两个属性,一个是它的值(即地址),另外一个是它的类型。我们先说一下“地址”。地址值是有大小范围的,其上下限是[0,2^n),即从“0”到“2的n次幂减1”,其中n是机器地址线的数量。通常这个n值不会与CPU的字长一致,但实际使用地址值时n都会取CPU字长为基准,在不同的硬件平台上,指针变量占用的内存单元数量与其地址值的范围大小成正比,也即指针变量占用n个内存位(bit)。

指针的另外一个属性是数据类型,实际上指针的这个类型表示指针所指向的变量的数据类型,而不是指针自身的类型。这个类型有两个作用:1、指示编译在解引用时从内从中读取几个字节,指针指向下一个元素内存跳变几个字节。2、指示编译在进行指针类型转换时如何进行类型检查和匹配。例如:

int  nValue = 0xFF00;
// 强制类型转换,确保类型匹配(第二点作用的体现,以通过类型检查)
char *pszValue = reinterpreter_cast<char *>(&a); 
// 指示编译器只获取一个字节数值(第一点作用的体现,以取出正确的数据)
cout << *p << endl; 

本例运行结果是0,即只取出了低十六进制位一个字节的数值。

C/C++中有两类特殊的指针。他们是空指针和void型指针。空指针是一种特殊的指针,他的值为0。C/C++语言中用符号常量NULL表示这个空值,并保证这个值不会是任何变量的地址。空指针可以给任何指针赋值。所以空指针一般用于判断指针是否合法。

void指针又称通用指针,void指针可指向任何的变量。C语言允许直接把任何变量的地址作为指针赋给通用指针。但有一点需要注意void*不能指向由const修饰的变量。

小心陷阱

  • 一个指针变量定义了,确保其初始化。以防此指针悬空变成野指针。
  • 如果一个指针变量被delete释放后,确保其被赋值为NULL。防止此指针变量变成野指针。
  • 如果一个函数形式参数可接收所有类型的指针,那请将此函数的形参声明为void*。如C语言内存操作系列函数(memmove、memcpy、memcpy)就是这类函数。
  • 禁止使用void *指针操纵其所指向的对象。因为void *操作对象时无法确定对象的类型。

同其他变量一样,指针变量也需要初始化。对指针进行初始化或赋值只能采用以下四种方式:

(1)使用NULL指针进行初始化。因为NULL可以给任何指针变量初始化。
(2)使用类型匹配的对象的地址。
(3)另一个对象之后的下一个地址。
(4)同一类型的另一个有效的指针。

int iVal ;
int iZero = 0;
const int piVal = NULL;
int *pi = iVal;    // error: 用一个整型变量初始化pi是错误的
pi = iZero;        // error: 用整型值0初始化pi是错误的
pi = piVal;        // ok:   通过const int 指针初始化int指针是合法的。
pi = NULL;         // ok:   直接通过恒值0初始化int指针是合法的。
pi = &iVal;        // ok:   使用int型对象地址初始化int型指针是合法的。

说明

  • 由于指针的类型用于确定指针所指向的对象的类型,因此初始化或赋值时必须保证类型匹配。
  • 指针用于间接访问对象,并基于指针的类型提供可执行的操作。也就是说int*指针必须指向int型变量,不能指向double或其他。否则基于指针进行的间接操作都是未定义的。
  • 绝对不能把一个整型值,赋值给一个指针(NULL除外)。

指针变量支持解引用,算术操作等基本操作。解引用操作符返回对象的左值,利用这个功能可实现指针所指向的对象的值。

char *pszBye= “good bye”;
printf("pszBye = %s", pszBye);
*pszBye = 'H';
printf("pszBye = %s", pszBye);

算术操作可实现指针的移动,如果指针指向一个数组,通过指针的算术操作在指向数组某个元素的指针上加上(或减去)一个整型数值,就可以计算出指向数组另外一个元素的指针值。

int  ia[] = {1, 2, 3, 4};
int *pa = ia;       // pa 指向ia[0]
int *pa2 = pa + 4;  // pa2指向ia[4]

小心陷阱

  • 指针算术操作仅支持两种形式。第一种形式:指针 +/- 整数,如上述实现指针的移动。第二种形式:指针 – 指针,这种形式只有当两个指针都指向同一类型的元素时,才允许一个指针减去另一个指针。
  • 绝不允许出现 指针 + 指针 这样的算术运算。不能对void *指针进行算术操作,因为void *类型为空类型,指针无法判定到底跳动几个字节。

最后,我们讨论const修饰指针。const指针有两种类型,一种是指向const对象的指针,另外一种是const指针。指向const对象的指针const修饰对象,不允许用指针修改指针所指向的对象。const指针中的const修饰指针,通过指针可实现所指向对象的修改,但不允许指针的值发生变化。

对于指向const对象的指针,通常把一个const对象的地址赋给一个普通的,非const对象的指针会导致编译错误。例如:

const double dValue = 3.14;
double *pdPtr = &dValue; // 错误的初始化方式,不允许把const对象指针赋给非const指针。
double  dVal = 23;
const double *pcdPtr = &dVal;// 正确的赋值方式,但不允许修改dVal的值。

同样不能使用void *指针保存const对象的地址,而必须使用const void *类型的指针才能保存const对象的地址。但允许把一个非const对象的地址赋值于一个指向const对象的指针。

说明

  • 指向const对象指针有两种形式,第一种为:const 类型 *指针;第二种为:类型 const *指针。在C/C++中这两种形式是等价的。
  • 请注意这么一点:不能保证指向const对象的指针所指向的对象一定不可修改。如果你把指向const的指针理解为“自以为指向const的指针”,这样也许更好理解一点儿。

const指针顾名思义就是指指针的值不能修改。和其他const量一样,const指针必须在定义时就进行初始化。const指针不能保证其所指向的对象是否可通过const指针修改。它的声明形式为:

类型  * const 指针;

还有一种指针就是指向const对象的const指针。这种指针即是const指针,又是指向const对象的指针。它的声明形式为:

const 类型 *const 指针;

总结

如何区分const指针,指向const对象的指针,指向const对象的const指针,是C++初学者都会遇到的难题。在C/C++中遵循如下准则:如果const在*的左边说明此指针为指向const对象的指针,如果const出现在*的右边说明此指针为const指针。

小心指针和typedef的陷阱,时刻明白typedef不是字符串替换。例如:

typedef string *pstring;
const pstring pcstr;

这里pctr为一个const指针,而非指向const对象的指针。

请谨记

  • 指针指地址,指针变量指存放地址的变量。指针的类型并不是指针本身的类型,而是指指针所指对象的类型。
  • 使用const修饰指针,const出现的位置的不同,指针的属性也不同。如果const位于*左方,则说明指针指向的对象为const对象。如果const位于*的右方,则说明指针为const指针。
  • 在32为系统下,指针变量的大小是4字节。无论此指针变量是什么类型。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值