38-c++-指针小结

xiaoxiao2021-03-01  16

4.8 指针、数组和指针算术

指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式。首先,我们来看一看算术。将整数变量加1后,其值将增加1;但将指针变量加1后,增加的量等于它指向的类型的字节数。将指向double的指针加1后,如果系统对double使用8个字节存储,则数组将增加8;将指向short的指针加1后,如果系统对short使用2个字节存储,则指针值将增加2。程序4.19演示了这种令人吃惊的现象,它还说明另一点;C++将数组名解释为地址。

程序4.19    addpntrs.cpp

//addpntrs.cpp——pointer addition #include<iostream> int main() { using namespace std; double wages[3] = {10000.0 , 20000.0 , 30000.0}; short stacks[3] = {3,2,1}; double * pw = wages; short * ps = &stacks[0]; cout<<"pw = "<<pw<<" , *pw = "<<*pw<<endl; pw=pw+1; cout<<"and 1 to the pw pointer\n"; cout<<"pw = "<<pw<<" , *pw = "<<*pw<<endl<<endl; cout<<"ps = "<<ps<<" , *ps = "<<*ps<<endl; ps=ps+1; cout<<"and 1 to the ps pointer\n"; cout<<"ps = "<<ps<<" , *ps = "<<*ps<<endl<<endl; cout<<"access two elements with array notation\n"; cout<<"stacks[0] = "<<stacks[0] <<", stacks[1] = "<<stacks[1]<<endl; cout<<"access two elements with array notation\n"; cout<<"access two elements with pointer notation\n"; cout<<"*stacks = "<<*stacks <<", *(stacks+1) = "<<*(stacks+1)<<endl; cout<<sizeof(wages)<<" = size of wages array\n"; cout<<sizeof(pw)<<" = size of pw pointer\n"; return 0; }

下面是该程序的输出:

4.8.1  程序说明

在多数情况下,C++将数组名解释为数组第1个元素的地址。因此,下面的语句将pw声明为指向double类型的指针,然后将它初始化为wages——wages数组中第1个元素的地址:

double * pw = wages;

和所有数组一样,wages也存在下面的等式:

wages = &wages[0] = address of first element of array

为表明情况确实如此,该程序在表达式&stacks[0]中显式地使用地址元算符来将ps指针初始化为stakes数组的第1个元算。

接下来,程序查看pw*pw的值。前者是地址,后者是存储在该地址中的值。由于pw指向第1个元素,因此*pw显示的值为第1个元素的值,即10000。接着,程序将pw1。正如前面指出的,这样数字地址值将增加8,这使得pw的值为第2个元素的地址。因此,*pw现在的值是20000——第2个元素的值。

此后,程序对ps执行相同的操作,这一次由于ps指向的是short类型,而short占用2个字节,因此将指针加1时,其值将增加2。结果是,指针也指向数组中下一个元素。

注意:将指针变量加1后,其增加的值等于指向的类型占用的字节数。

现在来看一看数组表达式stacks[1]C++编译器将该表达式看作是*(stacks + 1),这意味着先计算数组第2个元素大的地址,然后找到存储在那里的值。最后的结果便是stacks[1]的含义(运算符优先级要求使用括号,如果不使用括号,将给*stacks1,而不是给stacks1)。

从该程序的输出可知,*stacks + 1)和stacks[1]是等价的。同样,*(stacks + 2)stacks[2]也是等价的。通常,使用数组表示法时,C++都执行下面的转换:

arrayname[i] becomes * (arrayname + i);

如果使用的是指针,而不是数组名,则C++也将执行同样的转换:

pointername [i] becomes * (pointername + i);

因此,在很多情况下,可以相同的方式使用指针名和数组名。对于它们,可以使用数组方括号表示法,也可以使用解除引用运算符(*)。在多数表达式中,它们都表示地址。区别之一是,可以修改指针的值,而数组名是常量:

pointername = pointername + 1;

arrayname = arrayname + 1;

另一个区别是,对数组应用sizeof与那算符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度,即使指针指向的是一个数组。例如,在程序4.19中,pwwages指的是同一个数组,但对它们应用sizeof运算符得到的结果如下:

24 = size of wages array << displaying sizeof wages

4 = size of pw pointer << displaying sizeof pw

这种情况下,C++不会将数组名解释为地址

数组的地址

对数组取地址时,数组名也不会被解释为其地址。等等,数组名难道不被解释为数组的地址吗?不完全如此:数组名被解释为其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址:

short tell [10];

cout << tell <<endl;

cout << &tell << endl;

从数字上说,这两个地址相同;但从概念上说,&tell[0](tell)是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。因此,表达式tell+1将地址值加2,而表达式&tell+2将地址加20。换句话说,tell是一个short指针(*short),而&tell是一个这样的指针,即指向包含20个元素的short数组(short(*)[20])。

您可能会问,前面有关&tell的类型描述是如何来的呢?首先,您可以这样声明和初始化这种指针:

short  (*pas) [20] = &tell;

如果省略括号,优先级规则将使得pas先于[20]结合,导致pas是一个short指针数组,它包含20个元素,因此括号是必不可少的。其次,如果要描述变量的类型,可将声明中的变量名删除。因此,pas的类型为short(*)[20]。另外,由于pas被设置为&tell,因此*pastell等价,所以(*pas)[0]tell数组的第一个元素。

总之,使用new来创建数组以及使用指针来访问不同的元素很简单。只要把指针当作数组名对待即可。然而,要理解为何可以这样做,将是一种挑战。要想真正了解数组和指针,应认真复习它们的相互关系。

4.8.2 指针小结

刚才已经介绍了大量指针的知识,下面对指针和数组做一总结。

1. 声明指针

要声明指向特定类型的指针,请使用下面的格式:

typeName * pointerName;

下面是一些示例:

double * pn;

char *pc;

其中,pnpc都是指针,而double *char *是指向double的指针和指向char的指针。

2. 给指针赋值

应将内存地址赋给指针。可以对变量名应用&运算符,来获得被命名的内存的地址,new运算符返回未命名的内存和地址。

下面是一些示例:

double * pn ;

double * pa ;

char * pc ;

double bubble = 3.2 ;

pn = &bubble ;

pc = new char;

pa = new double [30] ;

3. 对指针解除引用

对指针解除引用意味着获得指针的值。对指针应用解除引用或间接值运算符(*)来解除引用。因此,如果像上面的例子中那样,pn是指向bubble的指针,则*pn是指向的值,即3.2

下面是一些示例:

cout<<*pn;

*pc = S;

另一种对指针解除引用的方法是使用数组表达法,例如,pn[0]*pn是意义的。决不要对未被初始化为适当地址的指针解除引用。

4.区分指针和指针所指向的值

如果pt是指向int的指针,则*pt不是指向int的指针,而是完全等同于一个int类型的变量。pt才是指针。

下面是一个示例:

int * pt = new int ;

* pt = 5;

5.数组名

在多数情况下,C++将数组名视为数组的第一个元素的地址。

下面是一个示例:

int tacos[10];

一种例外情况是,将sizeof运算符用于数组名时,此时将返回整个数组的长度(单位为字节)。

6.指针算术

C++允许将指针和整数相加。加1的结果等于原来的地址值加上指向的对象占用的总字节数。还可以将一个指针减去另一个指针,获得两个指针的差。后一种运算将得到一个整数,仅当两个指针指向同一个数组(也可以指向超出结尾的一个位置)时,这种运算才有意义;这将得到两个元素的间隔。

下面是一些示例:

int tacos [10] = {5,2,8,4,1,2,2,4,6,8};

int * pt = tacos ;

pt = pt + 1;

int *pe = &tacos[9];

pe = pe - 1;

int diff = pe - pt ;

7. 数组的动态联编和静态联编

使用数组声明来创建数组时,将采用静态联编,即数组的长度在编译时设置:

int tacos[10];

使用new[]运算符创建数组时,将采用动态联编(动态数组),即将在运行时为数组分配空间,其长度也将在运行时设置。使用完这种数组后,应使用delete[]释放其占用的内存:

int size;

cin>>size;

int * pz = new int [size];

...

delete [] pz;

8. 数组表示法和指针表示法

使用方括号数组表示法等同于对指针解除引用:

tacos[0] means * tacos means the value at address tacos

tacos[3] means * (tacos + 3) means the value at address tacos + 3

数组名和指针变量都是如此,因此对于指针和数组名,既可以使用指针表示法,也可以使用数组表示法。

下面是一些示例:

int * pt = new int [10];

*pt = 5;

pt[0] = 6;

pt[9] = 44;

int coats[10];

*(coats + 4) = 12;

转载请注明原文地址: https://www.6miu.com/read-3200022.html

最新回复(0)