C++ 指针

什么是指针呢?指针就是一个变量。从过去的经验知道,如果将变量声明为某个数据类型,那么这个变量就可以存储这种数据类型。int变量可以存储整型,char变量可以存储字符,等等。那么指针可以存储什么呢?指针可以存储地址,地址就是操作系统用来表示RAM存储单元的二进制数,就好像个人的地址标识他的居住地一样。每个存储单元都有一个地址。内存是按字节编址的,这意味着每个字节的内存就有一个地址,但是每位内存没有。

指针用来存储变量的地址,或者数据类型是用来声明指针的对象的地址。例如,存储一个整型地址的指针的声明如下所示:

int *ptr;

而存储一个字符地址的指针的声明如下:

char *chptr;

声明的第一部分是变量或对象的数据类型,而对象的地址可由指针存储。接着是一个星号,指针的前面必须有一个星号。最后,就是为指针指定的名称。

通常,程序员按下述方式声明指针:

int* ptr;

他们将星号紧挨着数据类型,就好像数据类型和星号共同构成一个指针数据类型一样。编译器认可这种编码方式,但是就我个人而言,我不认同这种表示方法,因为它容易引起误解。为了在同一行中声明多个指针,则需要使用多个星号:

int *ptr, *ptr2;

另外,在同一行中还可以同时声明指针和其他变量,如下所示:

int *ptr, x, y, *ptr2, z;

在这个代码中,x、y 和 z 是整型变量,而ptr和ptr2是整型指针。

 

现在,我们讨论如何将地址赋予指针。取地址运算符&可以用来将变量的地址赋予指针。例如, &x 就是一个运算,它得到的是x的地址。大家是否还记得,在编程方面的第一门功课中讲到,定义为内存中某个位置的变量,它的值可由程序员改变?在那个时候,你或许还没有思考能够将变量看成某个位置的重要性。或许大家早已忘记了这一定义。如果这样,那么请大家准备改变这一想法。当在x上使用取地址运算符时,它实际上指的是内存中保存x的位置的地址。因此,在有上述声明的前提下,可以使用下述代码将x的地址存放在ptr中:

ptr = &x;

当ptr中存放内存中保存x的位置的地址时,我们就可以说ptr指向了x。 在下图中,指针以箭头表示。上述代码的结果如下图所示,而另一幅图显示了内存中的情况。

                         

 当谈论指针时,区分变量名称、位置、某位置处的值和位置的地址这些概念非常重要,下图描述了这些区别。

大家一定要记住变量就是位置。当在代码中使用位置时,它们的行为取决于它们位于代码的什么地方。如果它们出现在=赋值运算符的左边,那么它们的行为和出现在其他地方的行为大相径庭。如果它们没有出现在赋值运算符的左边,那么这个位置将被该位置上的值取代。例如,如果x为3,那么在表达式:

y = x + 5;

中,x被它的位置(或内存单元)上的值取代,即3,然后得到8并赋予y。但是,如果位置出现在赋值运算符的左边,它就不被该位置上的值取代。例如,如果y为10,那么上述表达式将不会替换为:

10 = x + 5;

如果真是如此,那就麻烦了!相反,上述表达式的行为完全不同:当在赋值左边使用位置时,位置仅仅只是位置(不是位置处的值)。因此,位置将所赋的结果值赋给赋值的右边。

取指针内容运算符*可以应用于指针。该运算的结果是一个位置。大多数运算的结果是某种类型的值。结果位置是解析地址的位置。因此,如果将x的地址赋予ptr,即:

ptr = &x;

那么*ptr将是为x保留的位置。

取指针内容运算符*看上去也用于指针的声明。但是,指针的声明中不使用运算符。这样只是声明指针所必需的表示方法。

既然取指针内容运算符*可以获得位置作为结果,所以它的行为取决于它在代码中的位置。请看下述示例:

int x, *ptr;

x = 3;

ptr = &x;

在这一段代码之后,如果不在赋值运算符的左边使用*ptr, 那么它仅是一个变量(位置),提供在结果位置处存储的值。在表达式:

y = *ptr + 5;

中,运算*ptr得到保存x的位置。因此,运算*ptr将被值3(之前赋给x的值)取代。如果*ptr出现在赋值运算符的左边,如表达式:

*ptr = 10 + z;

中所示,那么它就只是赋值左边的一个变量(位置):它只被用作一个位置,而10 + z的结果将存储在为x保留的位置上。但是这里要注意:通过指针,x的值可以由前面的代码修改。如果z为5,那么x的值将变更为15,而且如果执行下述代码:

cout << x << endl;

将输出15。下图描述了这段代码的执行过程。

 

在使用指针的过程中需要牢记以下几个方面:

(1)变量就是位置

(2)当在地址上应用取指针内容运算符时,结果就是该地址的位置;取指针内容运算符可以应用于指针(因为指针存储地址),也可应用于能生成地址结果的表达式。

(3)根据位置是出现在赋值的左边还是代码的其他地方,位置的行为不同。如果位置出现在赋值的左边,那么它就是位置。如果出现在代码的其他地方,那么它使用的是该位置处的值。

如果对一个没有存储位置地址的指针使用取指针运算符*,编译器无法捕捉这种错误;该错误是一个运行时错误。因此,为了避免发生运行时错误,请一定要确保在使用取指针内容运算符时,应用取指针内容运算符的指针已经赋予了一个地址。

在编写代码时,有时候会将指针初始化为NULL,如下代码所示:

float *ptr = NULL;

在iostream文件中,NULL定义为0,它是一个不能赋值的位置的地址。如果将指针设为NULL,那么当执行在这个指针上应用取指针内容运算符的代码时会提示一个运行时错误。今后,在编写代码的过程中,如果无法确保某个指针是否赋予了地址,则可以通过下述方式测试指针:

if ( ptr != NULL)

  *ptr = 3.14;

通过使用这样的代码可以帮助防止“指针中没有存储有效地址”这样的运行时错误。

当在类函数中使用NULL时,要注意NULL定义在iostream头文件中这一事实。所以类实现文件中的顶部应该包含#include <iostream>以及using namespace std;这样的代码。

在数据结构中,指针几乎总是用来存放数组的地址或者对象的地址。我们首先讨论数组,对象留在后面。为了声明一个用来存放数组地址的指针,我们仅需用数组的数据类型声明它即可。例如,为了声明一个存放整型数组的地址的指针,则需要采用下述形式声明:

int *ptrToArray;

这与以前使用的表示法完全相同。原因在于指针是用来存储数组第一个元素的地址,而在这种情况下即是一个整型数。在存储数组第一个元素的过程中,指针实际上存储的是整个数组的地址。

下面我们讨论数组,这里有可能比过去的习惯做法要稍微深入一点。首先,在单独使用数组的名称时,数组名称存储的是数组的地址(即数组第一个元素的地址)。因此,对于下述整型数组:

int num[5];

可以用下述代码将num赋予指针:

ptrToArray = num;

因为num存储的是数组的地址,而现在地址已经赋予了ptrToArray,所以ptrToArray存储的也是数组的地址。当ptrToArray存放了数组的地址时,就可以说ptrToArray指向数组。

那么num是指针吗?它存储地址,但是意识到数组的名称并不是指针非常重要。num不是指针的原因在于指针定义为一个存储地址的变量。名称“变量”暗含着它的值是可以改变的。但是我们不能在num中存储一个不同的地址;如果试图去这样做,编译器就会报错。如果愿意,在ptrToArray中可以存储一个不同的地址,所以它是指针。

注:本文摘自《C++类和数据结构》一书,(美)Jeffrey S. Childs,推荐新手去看看这本书。

原文地址:https://www.cnblogs.com/shihui142857/p/2829526.html