C++ 第七章 指针、数组与引用 - 7.2 指针

第七章 指针、数组与引用

7.2 指针

对于类型T来说,T是表示“指向T的指针”的类型。换句话说,T类型的变量能存放T类型对象的地址。例如:

char c = ‘a’;
char* p = &c;		//p存放着c的地址,&是取地址运算符

这两条语句的图形化表示是:
在这里插入图片描述
对指针的一个基本操作是解引用(dereferencing),即引用指针所指的对象。这个操作也称为间接取值(indirection)。解引用运算符是个前置一元运算符,对应的符号是*。例如:

char c = ‘a’;
char* p = &c;		//p存放着c的地址,&是取地址运算符
char c2 = *p;		//c2 == ‘a’; *解引用运算符

指针p所指的对象是c,c中存储的值是 ‘a’,因此我们把*p赋给c2等价于给c2赋值 ‘a’。

当指针指向数组中的元素时,C++允许对这类指针执行某些算术运算(见7.4节)。

指针的具体实现应该与运行程序的机器的寻址机制同步。大多数机器支持逐字节访问内存,其他机器则需要从字中抽取字节。很少有机器能直接寻址到一个二进制位。因此,能独立分配且用内置指针指向的最小对象是char类型的对象。有一点请读者注意:bool占用内存空间至少和char一样多(见6.2.8节)。如果想把更小的值存得更紧密,可以使用位逻辑操作(见11.1.1节)、结构中的位域(见8.2.7节)或者bitest(见34.2.2节)。

符号*在用作类型名的后缀时表示“指向”的含义。如果我们想表示指向数组的指针或者指向函数的指针,需要使用稍微复杂一些的形式:

int* pi;			//指向int的指针
char** ppc;			//指向字符指针的指针
int* ap[15];		//ap是一个数组,包含15个指向int的指针
int(*fp)(char*);	//指向函数的指针,该函数接受一个char*实参,返回一个int
int* f(char*);		//该函数接受一个char*实参,返回一个指向int的指针

6.3.1节解释了如何声明一个指针,§ iso.A则包含完整的语法信息。

指向函数的指针很有用,相关内容将在12.5节讨论;20.6节将介绍指向类成员的指针。

7.2.1 void*

在某些偏向底层的代码中,我们偶尔需要在不知道对象确切类型的情况下,仅通过对象在内存中的地址存储或传递对象。此时,我们会用到void*。void*的含义是“指向未知类型对象的指针”。

除了函数指针(见12.5节)和指向类成员的指针(见20.6节),指向其他任意类型对象的指针都能被赋给一个void类型的变量。此外,一个void能被赋给另一个void*,两个void能比较是否相等,我们还能把void显式地转换成其他类型。因为编译器事实上并不清楚void所指的对象到底是什么类型,所以对它执行其他操作可能不太安全并且会引发编译器错误。要想使用void,我们必须把它显式地转换成某一特定类型的指针。例如:

void f(int* pi)
{
    
    
	void* pv = pi;							//ok:发生了从int*到void*的隐式类型转换
	*pv;									//错误:不允许解引用void*
	++pv;									//错误:不允许对void*执行递增操作(所指的对象尺寸未知)

	int pi2 = static_cast<int*>(pv);		//显式转换回int*

	double* pd1 = pv;						//错误
	double* pd2 = pi;						//错误
	double* pd3 = static_cast<double*>(pv);	//不安全(见11.5.2节)
}

一般情况下,如果某个指针已经被转换成(强制类型转换)指向一种与实际所指对象类型完全不同的新类型,则使用转换后的指针是不安全的行为。例如,某个机器可能假定double沿着8字节边界分配内存,如果指向int的pi分配内存的方式与之不同,将造成意想不到的后果。这种显式类型转换既不安全也不自然,我们在设计static_cast(见11.5.2节)的时候考虑到了这一点:static_cast的字面形式比较特殊,一旦出现了与之有关的错误,程序员很容易定位。

void*最主要的用途是当我们无法假定对象的类型时,向函数传递指向该对象的指针;它还用于函数返回未知类型的对象。要想使用这样的对象,必须先进行显式类型转换。

用到void*指针的函数通常位于系统的最底层,这些函数的作用大多是操作硬件资源。例如:

void* my_alloc(size_t n);		//从特定的堆上分配n个字节的内存空间

在系统的较上层代码中很少用到void*,一旦出现了你就要认真核实是不是存在设计上的错误。当用于优化的目的时,void*能隐藏在类型安全的接口(见27.3.1节)中。

函数指针(见12.5节)和指向类成员的指针(见20.6节)不能被赋给void*。

7.2.2 nullptr

字面值常量nullptr表示空指针,即不指向任何对象的指针。我们可以把nullptr赋给其他任意指针类型,但是不能赋给其他内置类型:

int* pi = nullptr;
double* pd = nullptr;
int i = nullptr;		//错误:i不是指针

nullptr只有一个,它可以用于任意指针类型,C++并没有为每种指针类型各设计一个空指针。

在nullptr被引入之前,人们使用数字0表示空指针。例如:

int* x =0;		//x的值是nullptr

任何对象都不会分配到地址0上,0(所有位全0的模式)是nullptr最常见的表示形式。0本身是一个int,但是标准类型转换规则(见10.5.2.3节)允许我们把0当成是一个指针常量或指向成员的类型的常量。

在原来的代码中,很多人习惯于定义一个宏NULL来表示空指针。例如:

int* p = NULL;	//使用宏NULL

然而,在不同的具体实现中NULL的定义有所差别;例如,NULL可能是0,也可能是0L。在C语言中,NULL通常是(void*)0,这种用法在C++中是非法的(见7.2.1节):

int* p = NULL;	//错误:不能给void*赋给int*

使用nullptr的好处很多,首先它的可读性更强,其次当一组重载函数既可以接受指针也可以接受整数时(见12.3.1节),用nullptr能够避免语义混淆。

猜你喜欢

转载自blog.csdn.net/qq_40660998/article/details/121794561
今日推荐