vector的遍历可以使用下标或迭代器实现,同理,也可用下标或指针(pointer)来遍历数组.指针是指向某总类型对象的复合数据类型,是用于数
组的迭代器:指向数组中的一个元素.在指向数组元素的指针上使用解引用操作符*(dereference operator)和自增操作符++(increment
operator),与在迭代器上的用法类似.对指针进行解引用操作,可获得该指针所指对象的值.而当指针做自增操作时,则移动指针使其指向数组中
的下一个元素.在使用指针编写程序之前,我们需进一步了解一下指针.
4.2.1 什么是指针
对初学者来说,指针通常比较难理解.而由指针错误引起的调试问题连富有经验的程序员都感到头疼.然而,指针是大多数C程序的重要部分,而且
在许多C++程序中仍然受到重用.
指针的概念很简单:指针用于指向对象.与迭代器一样,指针提供对其所指对象的间接访问,只是指针结构更通用一些.与迭代器不同的是,指针用
于指向单个对象,而迭代器只能用于访问容器内的元素.
具体来说,指针保存的是另一个对象的地址:
string s("hello world");
string *sp=&s; //sp holds the address of s
第二条语句定义了一个指向string类型的指针sp,并初始化sp使其指向string类型的对象s.*sp中的*操作符表明sp是一个指针变量,&s中的&符
号是取地址(address-of)操作符,当此操作符用于一个对象上时,返回的是该对象的存储地址.取地址操作符只能用于左值,因为只有当变量用作
左值时,才能取其地址.同样地,由于用于vector类型、string类型或内置数组的下标操作和解引用操作生成左值,因此可对这两种操作的结果做
取地址操作,这样即可获取某一特定对象的存储地址.
------------------------------------------------------华丽的分割线----------------------------------------------------------
建议:尽量避免使用指针和数组
指针和数组容易产生不可预料的错误.其中一部分是概念上的问题:指针用于低级操作,容易产生与繁琐细节相关的(bookkeeping)错误.其他错
误则源于使用指针的语法规则,特别是声明指针的语法.
许多有用的程序都可不使用数组或指针实现,现代C++程序采用vector类型和迭代器取代一般的数组、采用string类型取代C风格字符串.
-----------------------------------------------------华丽,不要出界----------------------------------------------------------
4.2.2 指针的定义和初始化
每个指针都有一个与之关联的数据类型,该数据类型决定了指针所指向的对象的类型.例如,一个int型指针只能指向int型对象.
1.指针变量的定义
C++语言使用*符号把一个标识符声明为指针:
vector<int> *pvec; //pvec can point to a vector<int>
int *ip1,*ip2; //ip1 and ip2 can point to an int
string *pstring; //pstring can point to a string
double *dp; //dp can point to a double
-----------------------------------------------------------华丽的分割线----------------------------------------------
提示:理解指针声明语句时,请从右向左阅读.
-----------------------------------------------------------华丽-----------------------------------------------------------
从右向左阅读pstring变量的定义,可以看到
string *pstring;
语句把pstring定义为一个指向string类型对象的指针变量.类似地,语句
int *ip1,*ip2; //ip1 and ip2 can point to an int
把ip1和ip2都定义为指向int型对象的指针.
在声明语句中,符号*可用在指定类型的对象列表的任何位置:
double dp,*dp2; //dp2 is a pointer,dp is an object:both type double
该语句定义了一个double类型的dp对象以及一个指向double类型对象的指针dp2.
2.另一种声明指针的风格
在定义指针变量时,可用空格将符号*与其后的标识符分隔开来.下面的写法是合法的:
string* ps; //legal but can be misleading
也就是说,该语句把ps定义为一个指向string类型对象的指针.
这种指针声明风格极容易引起这样的误解:把string*理解为一种数据类型,认为在同一声明语句中定义的其他变量也是指向string类型对象的
指针.然而,语句
string* ps1,ps2; //ps1 is a pointer to string,ps2 is a string
实际上只把ps1定义为指针,而ps2并非指针,只是一个普通的string对象而已.如果需要在一个声明语句中定义两个指针,必须在每个变量标识符
前再加符号*声明:
string* ps1,*ps2; //both ps1 and ps2 are pointers to string
3.连续声明多个指针易导致混淆
连续声明同一类型的多个指针有两种通用的声明风格.其中一种风格是一个声明语句只声明一个变量,此时,符号*紧挨着类型名放置,强调这个
声明语句定义的是一个指针:
string* ps1;
string* ps2;
另一种风格则允许在一条声明语句中声明多个指针,声明时把符号*靠近标识符放置.这种风格强调对象是一个指针:
string *ps1, *ps2;
----------------------------------------------------------华丽的分割线---------------------------------------------------
提示:关于指针的声明,不能说哪种声明风格是唯一正确的方式,重要的是选择一种风格并持续使用
--------------------------------------------------------华丽,继续----------------------------------------------------------
我个人比较喜欢第二种,看起来清楚一点,将符号*紧贴着指针变量名放置.
4.指针可能的取值
一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一个对象;或者是0值.若指针保存0值,表明它不指
向任何对象.未初始化的指针是无效的,直到给该指针赋值后,才可使用它.下列定义和赋值都是合法的:
int ival=1024;
int *pi=0; // pi initialized to address no object
int *pi2=&ival; //pi2 initialized to address of ival
int *pi3; //ok,but dangerous,pi3 is uninitialized
pi=pi2; //pi and pi2 address the same object,e.g. ival
pi2=0; //pi2 now addresses no object
5.避免使用未初始化的指针.
很多运行时错误都源于使用了未初始化的指针.
就像使用其他没有初始化的变量一样,使用未初始化的指针时的行为C++标准中并没有定义使用未初始化的指针(爆寒,这句话翻译的我看不清楚
- -),它几乎总会导致运行时崩溃.然而,导致崩溃的这一原因很难发现.
对大多数的编译器来说,如果使用未初始化的指针,会将指针中存放的不确定值视为地址,然后操纵该内存地址中存放的位内容.使用未初始化的
指针相当于操纵这个不确定地址中存储的基础数据.因此,在对未初始化的指针进行解引用时,通常会导致程序崩溃.
C++语言无法检测指针是否未被初始化,也无法区分有效地址和由指针分配到的存储空间中存放的二进制位形成的地址.建议程序员在使用之前
初始化所有的变量,尤其是指针.
-------------------------------------------------------华丽的分割线又来了------------------------------------------------
实践告诉你:如果可能的话,除非所指向的对象已经存在,否则不要先定义指针,这样可避免定义一个未初始化的指针.
如果必须分开定义指针和其所指向的对象,则将指针初始化为0.因为编译器可检测出0值的指针,程序可判断该指针并未指向一个对象.
-------------------------------------------------------华丽的分割线,好饿----------------------------------------------------
6.指针初始化和赋值操作的约束
对指针进行初始化或赋值只能使用以下四种类型的值:
(1).0值常量表达式,例如,在编译时可获得0值的整型const对象或字面值常量0
(2).类型匹配的对象的地址.
(3)另一对象末的下一地址.
(4)同类型的另一个有效指针.
把int型变量赋给指针是非法的,尽管此int型变量的值可能为0.但允许把数值0火灾编译时可获得0值的const量赋给指针:
int ival;
int zero=0;
const int c_ival=0;
int *pi=ival; //error: pi initialized from int value of ival
pi=zero; //error:pi assigned int value of zero
pi=c_ival; //ok: c_ival is a const with compile-time value of 0
pi=0; //ok: directly initialize to literal constant 0
可以看出,除了直接使用0或在编译时值为0的const量外,还可以使用C++语言从C语言中继承下来的预处理器变量NULL,该变量在cstdlib头文件
中定义,其值为0.如果在代码中使用了这个预处理器变量,则编译时会自动被数值0替换.因此,把指针初始化为NULL等效于初始化为0值:
//cstdlib #defines NULL to 0
int *pi=NULL; //ok:equivalent to int *pi=0;
正如其他的预处理器变量一样,不可以使用NULL这个标识符给自定义的变量命名.
-----------------------------------------------------------华丽的分割线-------------------------------------------------
注解:预处理器变量不是在std命名空间中定义的,因此其名字应为NULL,而非std::NULL.
---------------------------------------------------------饿的华丽无比-------------------------------------------------
除了两种例外情况之外,指针只能初始化或赋值为同类型的变量地址或另一指针:
double dval;
double *pd=&dval; //ok: initializer is address of a double
double *pd2=pd; //ok:initializer is a pointer to double
int *pi=pd; //error: types of pi and pd differ
pi=&dval; //error:attempt to assign address of a double to int *
由于指针的类型用于确定指针所指对象的类型,因此初始化或赋值时必须保证类型匹配.指针用于间接访问对象,并基于指针的类型提供可执行
的操作,例如,int型指针只能把其指向的对象当作int型数据来处理,如果该指针确实指向了其他类型(如double类型)的对象,则在指针上执行的
任何操作都有可能出错
7.void*指针
C++提供了一种特殊的指针类型void*,它可以保存任何类型对象的地址:
double obj=3.14;
double *pd=&obj;
//ok: void* can hold the address value of any data pointer type
void *pv=&obj; //obj can be an object of any type
pv=pd; //pd can be a pointer to any type
void*表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型.
void*指针只支持几种有限的操作:
a.与另一个指针进行比较;
b.向函数传递void*指针或从函数返回void*指针;
c.给另一个void*指针赋值.
不允许使用void*指针操作它所指向的对象.
习题4.10 下面提供了两种指针声明的形式,解释宁愿使用第一种形式的原因:
int *ip;
int* ip;
第一种形式强调了ip是一个指针,在阅读的时候不容易被误解,尤其是当一个语句中同时定义多个指针的时候.
习题4.11 解释下列声明语句,并指出哪些是非法的,为什么?
a. int *ip;
b. string s,*sp=0;
c. int i;double* dp=&i;
d. int* ip,ip2;
e. const int i=o,*p=i;
f. string *p=NULL;
a合法.b合法.c非法,i跟dp两者类型不一致.d中定义了一个ip指针和一个整型变量ip2.e合法,定义了一个const int型对象和一个指向const
int型对象的指针p,i初始化为0,p也初始化为0.f合法,定义指向string对象的指针p,并将它初始化为0.
习题4.12 已知一指针p,你可以确定该指针是否指向一个有效的对象吗?如果可以,如何确定?如果不可以,说明原因.
无法确定某指针是否指向一个有效对象,因此在C++语言中,无法检测指针是否未被初始化,也无法区分一个地址是有效地址,还是由指针所分配
的存储空间中存放的不确定值的二进制位形成的地址
习题4.13 下列代码中,为什么第一个指针的初始化是合法的,而第二个则不合法?
int i=42;
void *p=&i;
long *lp=&i;
具有void*类型的指针可以保存任何类型对象的地址,因此p的初始化是合法的;而指向long型对象的指针不能用int型对象的地址来初始化,因此
lp的初始化不合法.
2075

被折叠的 条评论
为什么被折叠?



