1. 声明指针
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指 针存储其他变量地址之前,对其进行声明。
指针变量声明的一般形式为:
type *var-name;
-
type
是指针的基类型,它必须是一个有效的
C++
数据类型; -
var-name
是指针变量的名称。用来声明指针的星号
*
与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。
以下是有效的指针声明:
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
与大多数变量一样,除非对指针进行初始化,否则它包含的值将是随机的。您不希望访问随机的内存地址,因此将指针初始化为
NULL
。
NULL
是一个可以检查的值,且不会是内存地址:
int *ip = NULL;
未初始化的指针可能导致程序访问非法内存单元,进而导致程序崩溃。
2. 获取变量地址 &
2.1 & 作用于普通变量
每一个变量都有一个内存位置,每一个内存位置都定义了
&
运算符访问的地址,它表示了在内存中的一个地址。
#include <iostream>
using namespace std;
int main ()
{
int a;
char b[10];
cout << &a << endl; // 0x7ffe06ed398c
cout << &b << endl; // 0x7ffe06ed3990
return 0;
}
2.1 & 作用于数组
short int height[10]; //int型的数组(short int 每个数据2字节)
cout << "height "<< height << endl
<< "height+1 "<< height + 1 << endl
<< "&height[0] " << &height[0] << endl
<< "&height+1 "<< &height + 1<< endl
<< "height+9 "<< height+9 << endl
<< "height+10 " << height + 10 << endl;
输出结果:
height 0x7fffce72dfc0
height+1 0x7fffce72dfc2
&height[0] 0x7fffce72dfc0
&height+1 0x7fffce72dfd4
height+9 0x7fffce72dfd2
height+10 0x7fffce72dfd4
-
数组名
height
表示数组首元素地址; -
数组名
height+1
表示数组第二个元素地址; -
&height[0]
表示数组首个元素的地址,其与
height
是相等的; -
&height+1
表示先取数组开始的地址,然后再加 1 ,也就是说 c0 + 10*2 ,表示成 16 进制为 c0+14=d4; -
height+9
表示数组最后一个元素,c0+9*2= c0+12=d2; -
height+10
表示整个数组结束之后的第一个地址,与
&height+1
相等;
3. 使用指针存储地址
知道如何声明指针以及如何获取变量的地址,还知道指针是用于存储内存地址的变量。现在将它们联系起来,使用指针来存储用
&
获取的地址。
#include <iostream>
using namespace std;
int main ()
{
int age = 10;
int *page = &age;
cout << "age address is " << &age << endl; // 0x7ffc43a7b75c
cout << "page is " << page << endl; // 0x7ffc43a7b75c
int dogAge = 3;
page = &dogAge; // 将不同的内存地址赋给指针变量,让它指向不同的值
cout << "page is " << page << endl; // 0x7ffef77b240c
return 0;
}
4. 使用 * 访问指针指向的数据
指针的本质是变量,可以是各种数据类型,定义一个指针
*ip
,其中
ip
需要赋于一个地址(可以用
&
符号获取其他变量的地址再赋值给
ip
),而
*ip
是一个具体的值,即读取地址后获得的值。
如果有合法的指针
p
,要访问它包含的地址处存储的值,可使用
*p
。
#include <iostream>
using namespace std;
int main ()
{
int age = 10;
int *page = &age;
cout << "age address is " << &age << endl; // 0x7ffc43a7b75c
cout << "page is " << page << endl; // 0x7ffc43a7b75c
cout << "page point value is " << *page << endl; // 10
int dogAge = 3;
page = &dogAge; // 将不同的内存地址赋给指针变量,让它指向不同的值
cout << "page is " << page << endl; // 0x7ffef77b240c
cout << "page point value is " << *page << endl; // 3
return 0;
}
-
&
符号的意思是取地址,也就是返回一个对象在内存中的地址。 -
*
符号的意思是取得一个指针所指向的对象。 也就是如果一个指针保存着一个内存地址,那么它就返回在那个地址的对象。
简单点就是:
&
是取址,
*
是取值。
5. sizeof() 用于指针
指针是包含内存地址的变量。因此无论指针指向哪种类型的变量(整型、浮点型、字符型,还是其他的数据类型),其内容都是一个地址(一个代表内存地址的长的十六进制数)。
在特定的系统中,存储地址所需的字节数是固定的。因此,将
sizeof( )
用于指针时,结果取决于编译程序时使用的编译器和针对的操作系统,与指针指向的变量类型无关。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
#include <iostream>
using namespace std;
int main ()
{
cout << "sizeof(char) = " << sizeof(char) << endl;
cout << "sizeof(int) = " << sizeof(int) << endl;
cout << "sizeof(double) = " << sizeof(double) << endl;
cout << "sizeof(*char) = " << sizeof(char *) << endl;
cout << "sizeof(*int) = " << sizeof(int *) << endl;
cout << "sizeof(*double) = " << sizeof(double *) << endl;
return 0;
}
输出结果:
sizeof(char) = 1
sizeof(int) = 4
sizeof(double) = 8
sizeof(*char) = 8
sizeof(*int) = 8
sizeof(*double) = 8
输出表明,虽然
sizeof(char)
为 1 字节,而
sizeof(double)
为 8 字节,但
sizeof(char*)
和
sizeof(double*)
都是 8 字节。这是因为不管指针指向的内存单元是 1 字节还是 8 字节,存储指针所需的内存量都相同。
备注:使用的是 64 位编译器,并在 64 位系统上运行该程序,可能发现将
sizeof
用于指针的结果为 64 位,即 8 字节。
6. 使用 new 和 delete 动态地分配和释放内存
使用
new
来分配新的内存块。通常情况下,如果成功,
new
将返回指向一个指针,指向分配的内存,否则将引发异常。使用
new
时,需要指定要为哪种数据类型分配内存:
Type* Pointer = new Type;
需要为多个元素分配内存时,还可指定要为多少个元素分配内存:
Type* Pointer = new Type[numElements];
因此,如果需要给整型分配内存,可使用如下语法:
int* pointToAnInt = new int;
int* pointToNums = new int[10];
使用
new
分配的内存最终都需使用对应的
delete
进行释放:
Type* Pointer = new Type;
delete Pointer;
这种规则也适用于为多个元素分配的内存:
Type* Pointer = new Type[numElements];
delete[] Pointer;
注意:
-
对于使用
new[…]
分配的内存块,需要使用
delete[]
来释放;对于使用
new
为单个元素分配的内存,需要使用
delete
来释放。 -
不能将运算符
delete
用于任何包含地址的指针,而只能用于
new
返回的且未使用
delete
释放的指针。
#include <iostream>
using namespace std;
int main ()
{
int *a = new int;
*a = 10;
cout << "a is " << a << " *a is " << *a << endl;
int *b = new int[10];
cout << "b is " << b << " *b is " << *b << endl;
delete a;
delete[] b;
return 0;
}
7. 未初始化指针、NULL 指针
声明一个指针变量并不会自动分配任何内存。在对指针执行间接访问前 , 指针必须进行初始化、或者使它指向现有的内存、或者给它分配动态内存。
对未初始化的指针变量执行间接访问操作是非法的,而且这种错误常常难以检测。其结果常常是一个不相关的值被修改。这种错误是很难被调试发现的。
7.1 未初始化指针
下面这个代码段说明了一个极为常见的错误 :
int main()
{
int *a;
*a = 10;
cout << *a << endl;
return 0;
}
这个声明创建了一个名叫
a
的指针变量 , 后面那条赋值语句把 12 存储在
a
所指向的内存位置 。但是究竟 a 指向哪里呢 ? 我们声明了这个变量 , 但从未对它进行初始化, 所以我们没有办法预测 12 这个值将存储于什么地方 。
声明一个指向整型的指针不会 “ 创建 ” 用于存储整型值的内存空间 。
所以 , 如果程序执行这个赋值操作 , 会发生什么情况呢 ? 如果你运气好, a 的初始值会是个非法地址 , 这样赋值语句将会出错 , 从而终止程序 。 在
UNIX
系统上 , 这个错误被称为 “ 段违例(
Segmentation fault (core dumped)
) ” 或 “ 内存错误 (
memory fault
) ” 。 它提示程序试图访问一个并未分配给程序的内存位置 。
所以,在你对指针进行间接访问之前,必须非常小心,确保它们已被初始化 !
7.2 NULL指针
标准定义了
NULL
指针,它作为一个特殊的指针变量 ,表示不指向任何东西。要使一个指针变量为
NULL
, 你可以给它赋一个零值 。 为了测试一个指针变量是否为
NULL
,你可以将它与零值进行比较。
之所以选择零这个值是因为一种源代码约定 。 就机器内部而言 ,
NULL
指针的实际值可能与此不同 。在这种情况下 ,编译器将负责零值和内部值之间的翻译转换 。
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个
NULL
值是一个良好的编程习惯。赋为
NULL
值的指针被称为空指针。
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。
通过一个指针访问它所指向的地址的过程称为间接访问 (indirection) 或解引用指针 (dereferencing
thepointer) 。 这个用于执行间接访问的操作符是单目操作符 *
对指针进行解引用操作可以获得它所指向的值。但从定义上看,
NULL
指针并未指向任何东西。因此,对一个
NULL
指针进行解引用操作是非法的。在对指针进行解引用操作之前,你首先必须确保它并非
NULL
指针 。
int main()
{
int *a = NULL;
cout << *a << endl;
return 0;
}
程序会报错:
Segmentation fault (core dumped)
。
NULL
指针是一个定义在标准库中的值为零的常量。请看下面的程序:
#include <iostream>
using namespace std;
int main ()
{
int *a = NULL;
cout << " a is " << a << endl; // a is 0
return 0;
}
内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,可以使用 if 语句,如下所示:
if(ptr) /* 如果 ptr 非空,则完成 */
if(!ptr) /* 如果 ptr 为空,则完成 */
NULL
指针就是不指向任何东西的指针。它可以赋值给一个指针,用于表示那个指针并不指向任何值 。 对
NULL
指针执行间接访问操作的后果因编译器而异,两个常见的后果分别是返回内存位置零的值以及终止程序 。
8. 指针运算
有效的指针运算包括
- 相同类型指针之间的赋值运算 ;
- 指针同整数之间的加法或减法运算 ;
- 指向相同数组中元素的两个指针间的减法或比较运算 ;
- 将指针赋值为 0 或指针与 0 之间的比较运算 。
其他所有形式的指针运算都是非法的 , 例如
- 两个指针间的加法 、 乘法 、除法 、 移位或屏蔽运算 ;
-
指针同
float
或
double
类型之间的加法运算 ; -
不经强制类型转换而直接将指向一种类型对象的指针赋值给指向另一种类型对象的指针的运算 ( 两个指针之一是
void*
类型的情况除外 )。
可以对指针执行算术运算。可以对指针进行四种算术运算:
++
、
--
、
+
、
-
。
假设
ptr
是一个指向地址 1000 的整型指针,是一个 32 位的整数,对该指针执行下列的算术运算:
ptr++
在执行完上述的运算之后,
ptr
将指向位置 1004,因为
ptr
每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 个字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。
如果
ptr
指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。
在指针值上可以执行一些有限的算术运算。你可以把一个整型值加到一个指针上,也可以从一个指针减去一个整型值 。在这两种情况下 ,这个整型值会进行调整,原值将乘以指针目标类型的长度 。这样,对一个指针加 1 将使它指向下一个变量 , 至于该变量在内存中占儿个字节的大小则与此无关。
8.1 指针 ++ 和指针 – –
我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,因为数组是一个常量指针。
#include <iostream>
using namespace std;
int main ()
{
int a[3] = {1,2,3};
int *p = a;
for(int i=0; i<3; i++)
{
cout << " *p " << *p << endl;
p++;
}
cout << "#################" << endl;
int *q = &a[2];
for(int i=2; i>=0; i--)
{
cout << " *q " << *q << endl;
q--;
}
return 0;
}
输出结果:
*p 1
*p 2
*p 3
#################
*q 3
*q 2
*q 1
8.2 指针比较
指针可以用关系运算符进行比较,如
==
、
<
和
>
。如果
p1
和
p2
指向两个相关的变量,比如同一个数组中的不同元素,则可对
p1
和
p2
进行大小比较。
#include <iostream>
using namespace std;
int main ()
{
int a[3] = {1,2,3};
int *p = a;
int *q = &a[2];
while (p<=q)
{
if (p==q)
{
cout << "p==q: *p is " << *p << " *q is " << *q << endl;
}
if (p<q)
{
cout << "p<q: *p is " << *p << " *q is " << *q << endl;
}
if (p>q)
{
cout << "p>q: *p is " << *p << " *q is " << *q << endl;
}
p++;
q--;
}
return 0;
}
输出:
p<q: *p is 1 *q is 3
p==q: *p is 2 *q is 2
然而 , 指针运算只有作用于数组中其结果才是可以预测的。对任何并非指向数组元素的指针执行算术运算是非法的 ( 但常常很难被检测到 )。 如果一个指针减去一个整数后,运算结果产生的指针所指向的位置在数组第一个元素之前 , 那么它也是非法的。加法运算稍有不同, 如果结果指针指向数组最后一个元素后面的那个内存位置仍是合法 ( 但不能对这个指针执行间接访问操作 ) , 不过再往后就不合法了。
如果两个指针都指向同一个数组中的元素 , 那么它们之间可以相减 。 指针减法的结果经过调整( 除以数组元素类型的长度 ), 表示两个指针在数组中相隔多少个元素。如果两个指针并不是指向同一个数组的元素,那么它们之间进行相减就是错误的 。
任何指针之间都可以进行比较, 测试它们相等或不相等 。如果两个指针都指向同一个数组中的元素,那么它们之间还可以执行
<
、
< =
、
>
和
> =
等关系运算 ,用于判断它们在数组中的相对位置。 对两个不相关的指针执行关系运算,其结果是未定义的 。
警告总结:
- 错误地对一个未初始化的指针变量进行解引用。
-
错误地对一个
NULL
指针进行解引用 。 -
向函数错误地传递
NULL
指针。 - 未检测到指针表达式的错误,从而导致不可预料的结果 。
- 对一个指针进行减法运算,使它非法地指向了数组第 1 个元素的前面的内存位置。
如果指针并不指向任何有意义的东西 ,就把它设置为
NULL
。
9. 指针作用于数组
声明
int a[10];
定义了一个长度为 10 的数组
a
。换句话说,它定义了一个由 10 个对象组成的集合,这 10 个对象
存储在相邻的内存区域中,名字分别为a[0] 、a[1] 、… a[9] 。
a[i]
表示该数组的第
i
个元素。如果
pa
的声明为
int *pa;
则说明它是一个指向整型对象的指针,那么,赋值语句
pa = &a[0];
则可以将指针
pa
指向数组
a
的第
0
个元素, 也就是说,
pa
的值为数组元素
a[0]
的地址。
这样,赋值语句
x = *pa;
将把数组元素
a[0]
中的内容复制到变量
x
中。
如果
pa
指向数组中的某个特定元素,那么,根据指针运算的定义,
pa+1
将指向下一个元素,
pa+i
将指向
pa
所指向数组元素之后的第
i
个元索,而
pa-i
将指向
pa
所指向数组元素之前的第
i
个元素。因此,如果指针
pa
指向
a[0]
, 那么
*( pa+i)
引用的是数组元素
a[i]
的内容,
pa+i
是数组元素
a[i]
的地址,
*(pa+i)
引用的是数组元素
a[i]
的内容。
无论数组
a
中元素的类型或数组长度是什么,上面的结论都成立。“指针加1”就意味着,
pa+1
指向
pa
所指向的对象的下一个对象。相应地,
pa+i
指向
pa
所指向的对象之后的第
i
个对象。
下标和指针运算之间具有密切的对应关系。根据定义,数组类型的变量或表达式的值是该数组第
0
个元素的地址。执行赋值语
pa = &a[0];
后,
pa
和
a
具有相同的值。
因为数组名所代表的就是该数组最开始的一个元素的地址
,所以,
赋值语句
pa=&a[0]
也可以写成下列形式:
pa = a;
对数组元素
a[i]
的引用也可以写成
*( a+i)
这种形式。对第一次接触这种写法的人来说,可能会觉得很奇怪。在计算数组元素
a[i]
的值时,
C++
语言实际上先将其转换为
*( a+i)
的形式,然后再进行求值,因此在程序中这两种形式是等价的。
如果对这两种等价的表示形式分别施加地址运算符
&
, 便可以得出这样的结论:
&a[i]
和
a+i
的含义也是相同的。
a+i
是
a
之后第
i
个元素的地址。相应地,如果
pa
是一个指针,那么,在表达式中也可以在它的后面加下标
pa[i]
与
*(pa+i)
是等价的。简而言之,
一个通过数组和下标实现的表达式可等价地通过指针和偏移量实现。
但是,我们必须记住,数组名和指针之间有一个不同之处。指针是一个变量,因此,在
C/C++
语言中,语句
pa=a
和
pa++
都是合法的。但数组名不是变量,因此,类似于
a=pa
和
a++
形式的语句是非法的。
当把数组名传递给一个函数时,实际上传递的是该数组第一个元素的地址。在被调用函数中,该参数是一个局部变量,因此,数组名参数必须是一个指针,也就是一个存储地址值的变量。我们可以利用该特性编写
strlen
函数的另一个版本,该函数用于计算一个字符串的长度。
int strlen(char *s)
{
for(int n=0; *s != '\0'; s++)
{
n++;
}
return n;
}
因为
s
是一个指针,所以对其执行自增运算是合法的。执行
s++
运算不会影响到
strlen
函数的调用者中的字符串, 它仅对该指针在
strlen
函数中的私有副本进行自增运算。因此,类似于下面这样的函数调用:
strlen("hello, world"); /*字符串常量*/
strlen(array); /*字符数组array有100个元素*/
strlen(ptr); /* Ptr是一个指向char *类型对象的指针 */
都可以正确地执行。
在函数定义中,形式参数
char s[];
和
char *s;
是等价的。我们通常更习惯于使用后一种形式, 因为它比前者更直观地表明了该参数是一个指针。
指针和数组是密切相关的。事实上,指针和数组在很多情况下是可以互换的。例如,一个指向数组开头的指针,可以通过使用指针的算术运算或数组索引来访问数组。
#include <iostream>
using namespace std;
int main ()
{
int var[3] = {10,100,200};
int *p=var;
for (int i = 0; i < 3; i++)
{
//cout << *(var++) << endl; //编译错误,因为var是数组名(也是数组首元素地址),不是一个变量,不可修改其值
cout << *(var+i) << endl; //编译正确,因为没有对var进行赋值,只是通过与i相加访问变量地址
cout<< *(p++)<<endl; ///编译正确,p为指针变量可赋值
}
return 0;
}
输出结果:
10
10
100
100
200
200
把指针运算符
*
应用到
var
上是完全可以的,但修改
var
的值是非法的。这是因为
var
是一个指向数组开头的常量,不能作为左值。
由于一个数组名对应一个指针常量,只要不改变数组的值,仍然可以用指针形式的表达式。例如,下面是一个有效的语句,把 var[2] 赋值为 500:
*(var + 2) = 500;
10. 指针数组
可能有一种情况,我们想要让数组存储指向
int
或
char
或其他数据类型的指针。下面是一个指向整数的指针数组的声明:
int *ptr[MAX];
在这里,把
ptr
声明为一个数组,由
MAX
个整数指针组成。因此,
ptr
中的每个元素,都是一个指向
int
值的指针。
char *names[MAX]
是指针数组,它的本质是存储指针的数组,即存储
char
类型的指针的数组,数组内的每个元素都是一个指针,并且指向一个存储
char
类型的地址。
下面的实例用到了三个整数,它们将存储在一个指针数组中,如下所示
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr[MAX];
for (int i = 0; i < MAX; i++)
{
ptr[i] = &var[i]; // 赋值为整数的地址
}
for (int i = 0; i < MAX; i++)
{
cout << "Value of var[" << i << "] = ";
cout << *ptr[i] << endl;
}
return 0;
}
输出结果:
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
详解指针数组的结合过程:
int *ptr[3];
由于 C++ 运算符的优先级中,
*
小于
[]
,所以
ptr
先和
[]
结合成为数组,然后再和
int *
结合形成数组的元素类型是
int *
类型,得到一个叫一个数组的元素是指针,简称指针数组。
这相当于声明了一个数组 , 该数组有 3 个元素,其中每个元素都是一个指向整型对象的指针。
int *(ptr[3]);
这个和上面的一样,优先级顺序是
*
小于
()
,
()
等于
[]
。
ptr
先和
[]
结合成为数组,然后再和
int *
结合形成数组的元素类型是
int *
类型,得到一个叫一个数组的元素是指针,简称指针数组。
11. 数组指针
int (*ptr)[3];
这个就不一样了,优先级顺序是
*
小于
()
,
()
等于
[]
,
()
和
[]
的优先级一样,但是结合顺序是从左到右,所以先是
()
里的
*
和
ptr
结合成为一个指针,然后是
(*ptr)
和
[]
相结合成为一个数组,最后叫一个指针
ptr
指向一个数组,简称数组指针。
12. 指向指针的指针
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向
int
类型指针的指针:
int **var;
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,如下面实例所示:
#include <iostream>
using namespace std;
int main ()
{
int var;
int *ptr;
int **pptr;
var = 3000;
// 获取 var 的地址
ptr = &var;
// 使用运算符 & 获取 ptr 的地址
pptr = &ptr;
// 使用 pptr 获取值
cout << "var 值为 :" << var << endl;
cout << "*ptr 值为:" << *ptr << endl;
cout << "**pptr 值为:" << **pptr << endl;
return 0;
}
输出结果:
var 值为 :3000
*ptr 值为:3000
**pptr 值为:3000
13. 传递指针给函数
C++
允许您传递指针给函数,只需要简单地声明函数参数为指针类型即可。
#include <iostream>
using namespace std;
int get(int *p);
int main ()
{
int a = 1;
cout << "a 值为 :" << a << endl;
cout << "&a = " << &a << endl;
get(&a);
cout << "a 值为 :" << a << endl;
return 0;
}
int get(int *p)
{
cout << "p = " << p << endl;
*p = 10;
return *p;
}
输出结果:
a 值为 :1
&a = 0x7ffcb10db964
p = 0x7ffcb10db964
a 值为 :10
14. 从函数返回指针
C++
允许从函数返回指针。为了做到这点,必须声明一个返回指针的函数,如下所示:
int * func();
另外,
C++
不支持在函数外返回局部变量的地址,除非定义局部变量为
static
变量。
#include <iostream>
using namespace std;
int * get();
int main ()
{
int * ret=NULL;
ret = get();
for(int i=0; i<3; i++)
{
cout << "&ret is " << ret << endl;
cout << "i is " << i << " value is " << *ret << endl;
ret++;
}
return 0;
}
int * get()
{
static int a[3] = {1,2,3};
return a;
}
输出结果:
&ret is 0x601070
i is 0 value is 1
&ret is 0x601074
i is 1 value is 2
&ret is 0x601078
i is 2 value is 3
15 const 用于指针
通过将变量声明为
const
的,可确保变量的取值在整个生命周期内都固定为初始值。这种变量的值不能修改,因此不能将其用作左值。
指针也是变量,因此也可将关键字
const
用于指针。然而,指针是特殊的变量,包含内存地址,还可用于修改内存中的数据块。因此,
const
指针有如下三种。
- 指针包含的地址是常量,不能修改,但可修改指针指向的数据:
int daysInMonth = 30;
int* const pDaysInMonth = &daysInMonth;
*pDaysInMonth = 31; // OK! Data pointed to can be changed
int daysInLunarMonth = 28;
pDaysInMonth = &daysInLunarMonth; // Not OK! Cannot change address!
- 指针指向的数据为常量,不能修改,但可以修改指针包含的地址,即指针可以指向其他地方:
int hoursInDay = 24;
const int* pointsToInt = &hoursInDay;
int monthsInYear = 12;
pointsToInt = &monthsInYear; // OK!
*pointsToInt = 13; // Not OK! Cannot change data being pointed to
int* newPointer = pointsToInt; // Not OK! Cannot assign const to non-const
- 指针包含的地址以及它指向的值都是常量,不能修改(这种组合最严格):
int hoursInDay = 24;
const int* const pHoursInDay = &hoursInDay;
*pHoursInDay = 25; // Not OK! Cannot change data being pointed to
int daysInMonth = 30;
pHoursInDay = &daysInMonth; // Not OK! Cannot change address
将指针传递给函数时,这些形式的
const
很有用。函数参数应声明为最严格的
const
指针,以确保函数不会修改指针指向的值。这可禁止程序员修改指针及其指向的数据。
16. 指针常见错误
C++没有自动垃圾收集器对程序已分配但不能使用的内存进行清理。使用指针来管理内存资源时,程序员很容易犯错。
16.1 内存泄露
这可能是C++应用程序最常见的问题之一:运行时间越长,占用的内存越多,系统越慢。如果在使用
new
动态分配的内存不再需要后,程序员没有使用配套的
delete
释放,通常就会出现这种情况。
确保应用程序释放其分配的所有内存是程序员的职责。绝不能让下面这样的情形发生:
int* a = new int[5]; // initial allocation
// use a
...
// forget to release using delete[] a;
...
// make another allocation and overwrite
a = new int[10]; // leaks the previously allocated memory
16.2 指针指向无效的内存单元
使用运算符
*
对指针解除引用,以访问指向的值时,务必确保指针指向了有效的内存单元,否则程序要么崩溃,要么行为不端。这看起来合乎逻辑,但一个非常常见的导致应用程序崩溃的原因就是无效指针。
16.3 悬浮指针(也叫迷途或失控指针)
使用
delete
释放后,任何有效指针都将无效。为了避免再次使用已经
delete
的指针,很多程序员在初始化指针或释放指针后将其设置为
NULL
,并在使用运算符
*
对指针解除引用前检查它是否有效(将其与
NULL
比较)。
#include <iostream>
using namespace std;
int main ()
{
int *a = NULL;
cout <<a << endl; // 0
if(a)
{
cout <<"valid" << endl;
}
else
{
cout <<"invalid" << endl; // invalid
}
return 0;
}