*******************************************************
原文链接:http://www.cplusplus.com/doc/tutorial/
指针与数组
数组的概念和指针的概念是十分紧密的。事实上,一个数组的表示符相当于它的第一个元素的地址,就像指针是它指向的第一个元素的地址一样,因此事实上他们是相同的概念。例如,设想这样两个声明:
int numbers [20];
int * p;
|
下面的赋值会是有效的:
p = numbers;
|
那之后,
p
和
numbers
会相等,并且会有相同的性质。唯一的不同就是我们可以改变指针
p
的值使它指向另一个地方,而
numbers
将一直指向被定义的那
20
个
int
型元素的第一个。
因此,不像一个普通的指针,如
p
,
numbers
是一个数组,而一个数组可以被认为是一个“常指针”。因此,下面的分配会是无效的:
numbers = p;
|
因为
numbers
是一个数组,所以它的操作就像一个常指针,我们不能向一个常量赋值。
由于变量的性质,下面例子中所有包含指针的表达式都是非常有效的:
// more pointers
#include <iostream>
using namespace std;
int main ()
{
int numbers[5];
int * p;
p = numbers; *p = 10;
p++; *p = 20;
p = &numbers[2]; *p = 30;
p = numbers + 3; *p = 40;
p = numbers; *(p+4) = 50;
for (int n=0; n<5; n++)
cout << numbers[n] << ", ";
return 0;
}
|
10, 20, 30, 40, 50,
|
在关于数组那一章我们多次使用中括号(
[]
)来指明我们想引用的那个元素在数组中的索引。当然,这些中括号操作符
[]
也是一种间接取值操作符,称为“偏移操作符”。它们对它们前面的变量间接取值就像
*
那样,但是它们还要把中括号内数字加到要被间接取值的地址上。例如:
a[5] = 0;
// a [offset of 5] = 0
*(a+5) = 0;
// pointed by (a+5) = 0
|
这两个表达式是等价的,并且在
a
是一个指针或
a
是一个数组的情况下都是有效的。
指针的初始化
当声明指针的时候我们可能会想明确的指出我们想要它们指向的变量:
int number;
int *tommy = &number;
|
这些代码的行为和如下的是等价的:
int number;
int *tommy;
tommy = &number;
|
当一个指针被初始化时,我们一直分配给它,它将指向的那个值的引用
(tommy)
,从来不是被指向的那个值
(*tommy)
。你必须考虑在声明一个指针时,星号(
*
)只是代表它是一个指针,它不是间接取值操作符(虽然都使用相同的符号:
*
)。记住,它们是一个记号的两种不同功能。因此,我们必须小心不要将上面的代码混淆为:
int number;
int *tommy;
*tommy = &number;
|
那时不正确的,如果你想一下就会知道这种情况是没有意义的。
就像在数组中的情况,编译器允许我们在指针被声明时将其初始化为指向常量这种特殊情况:
char * terry =
"hello"
;
|
在这种情况下,内存空间被保留来存储“
hello
”,然后一个指向这个内存块的首个字符的指针被赋给
terry
。如果我们设想:“
hello
”被存储在从地址
1702
开始内存区,我们可以这样描绘前面的声明:
指出
terry
含有的是值
1702
而不是
’h’
或“
hello
”是非常重要的,虽然
1702
实际就是它们两个的地址。
指针
terry
指向一个字符序列,并且被像它是一个数组(记住:一个数组就像一个常指针)那样读。例如,我们可以通过这两个表达式之一来访问数组的第五个元素:
*(terry+4)
Terry[4]
|
两个表达式都有一个值
’o’
(数组的五个元素)。
指针的算术运算
在指针上的算术运算操作同普通整形数据类型上的有一点点不同。首先,对于指针只有加法和减法是允许的,其它的对于指针没有任何意义。但是即使是加法和减法,根据指针指向的数据类型的大小的不同也有不同的行为。
当我们看不同的基本数据类型的时候,我们看到一些类型被其它的类型占有更多
/
更少的内存空间。例如,在整型数这种情况中,
char
占
1B
(字节),
short
占
2B
,
long
占
4B
。
设想我们有三个指针:
char *mychar;
short *myshort;
long *mylong;
|
并且我们知道他们分别指向内存地址
1000
,
2000
和
3000
。
此时如果我们写:
mychar++;
myshort++;
mylong++;
|
如你的预期,
mychar
将带有值
1001
。然而,
myshort
带的值将是
2002
,而
mylong
将是
3004
。这是由于当我们把
1
加到一个指针上时,我们是使其指向同它定义的类型
,
类型相同的接下来的元素,因此被指向类型的以字节计的大小被加到了这个指针上。
这同样适用于一个指针加上
/
减去一个数字的情况。如果我们像如下这样写将得到完全相同的结果:
mychar = mychar + 1;
myshort = myshort + 1;
mylong = mylong + 1;
|
自增(
++
)和自减(
--
)操作符都有被间接取值操作符高的优先级,但当被用于后缀的时候,都有一个特殊的行为(表达式被用为改变前的值计算)。因此,下面的表达式可能会引起混乱:
*p++
|
因为
++
比
*
有更高的优先级,这个表达式等价于
*(p++)
。因此,它做的是增加
p
的值(因此
p
现在指向下一个元素),但是因为
++
被用作后缀整个表达式的值被计算为被原始引用(在增加前指针指向的地址)指向的值。
注意同:
(*p)++
的不同。
这里,表达式是应该被这样计算:被
p
指向的值被增加
1
。
p
(指针本身)的值不会被修改(被修改的是被这个指针指向的值)。
如果我们写:
*p++ = *q++;
|
由于
++
被
*
有更高的优先级,
p
和
q
都被增加,但由于两个增量操作符(
++
)都被用作后缀而不是前缀,被赋给
*p
的值是
*q
——这里的
p
,
q
都是增加前的。然后都被增加。它大体上等价于:
*p = *q;
++p;
++q;
|
想往常一样,我推荐你使用小括号
()
来避免意外的结果,并且使得代码更易读。
指向指针的指针
C++
允许使指针指向指针,这样,指针的定义可以扩展为:指向数据(或者甚至指向其他的指针)。为了达到这个目的,我们只需为他们的声明中的每层引用加一个星号(
*
):
char a;
char * b;
char ** c;
a =
'z'
;
b = &a;
c = &b;
|
这样,设想为每个变量随机选择的内存位置为
7230
,
8092
和
10502
,可以被描述为:
每个变量的值被写在了每个框内;框下则分别是他们在内存中的地址。
在这个例子中的新事是变量
c
,
c
可以被用于三个不同层次的间接使用,它们的每一个都和一个不同值相联系:
- c拥有类型 char** 和值 8092
- *c拥有类型 char* 和值 7230
- **c 拥有类型 char 和值 'z'
void
指针
指针的
void
类型是一种特殊的指针类型。在
C++
中,
void
表示省略类型,因此
void
指针是指向没有类型的值的指针(因此也是未定长度和未定间接取值性质)。
这就允许
void
指针指向任何数据类型,从一个整型或浮点型值到一个字符串。但是在交换他们的时候有一个大的限制:被他们指向的数据被能被直接的间接取值(这是符合逻辑的,因为我们没有间接取值得类型),由于这个原因,我们在对它间接取值之前,将通常不得不把
void
指针的类型改变为一些其它的指向具体数据类型的指针类型。这通过使用强制类型转换(
type-casting
)来完成。
它的一个用途可能是向一个函数传递泛型形参:
// increaser
#include <iostream>
using namespace std;
void increase (void* data, int size)
{
switch (size)
{
case sizeof(char) : (*((char*)data))++; break; case sizeof(int) : (*((int*)data))++; break;
}
}
int main ()
{
char a =
'x'
;
int b = 1602;
increase (&a,sizeof(a));
increase (&b,sizeof(b));
cout << a <<
", "
<< b << endl;
return 0;
}
|
y, 1603
|
sizeof
是
C++
中内置一个操作符,它返回它参数的以字节计的大小。对于非动态数据类型这个值是一个常量。因此,例如。
sizeof(char)
是
1
,因为
char
类型是一个字节长的。
Null
指针
一个
null
指针是一个任意类型的普通指针,不同是它拥有一个特殊的值,而这个值表明它不指向任何有效的引用或内存地址。这个值是正数值
0
通过强制类型转换为任意指针类型所得的结果。
int * p;
p = 0;
// p has a null pointer value
|
不要把
null
指针同
void
指针弄混。一个
null
指针是任意指针都可以带有一个值,意味着它
”
不
指向任何地方
”
;而一个
void
指针是一种特殊的指针类型,它可以指向没有特定类型的地方。一个(
null
)指的是指针自身存储的值,另一个(
void
)指的是它指向的数据的类型。
指向函数的指针
C++
允许指向函数的指针的操作。指向函数的指针的典型应用是把一个函数作为一个实参传递给另一个函数,因为这些不能被按间接取值传递(
since these cannot be passed dereferenced
)。为了声明一个指向函数的指针我们需要像声明这个函数的原型那样来声明它,只不过这个函数的名字被括在小括号
()
中,并在名字前插入一个星号(
*
):
// pointer to functions
#include <iostream>
using namespace std;
int addition (int a, int b)
{ return (a+b); }
int subtraction (int a, int b)
{ return (a-b); }
int (*minus)(int,int) = subtraction;
int operation (int x, int y, int (*functocall)(int,int))
{
int g;
g = (*functocall)(x,y);
return (g);
}
int main ()
{
int m,n;
m = operation (7, 5, addition);
n = operation (20, m, minus);
cout <<n;
return 0;
}
|
8
|
在这个例子中,
minus
是一个全局的指向带有两个
int
型形参的函数的指针。它被立即赋了值,指向函数
subtraction
,所有的都在单独的一行里:
int (* minus)(int,int) = subtraction;
|