Cpp:指针

2015/11/13 16:16更新:

为什么得到的喜欢数少于关注的人数呢?我分析了一下原因,难道是因为“喜欢”按钮在文章的最底部?!希望 @简叔 改进呀 =_=

下面是原文,,


指针跟迭代器类似,也可以对指针进行 解引用*) 和 自增++) 操作,其含义和迭代器类似。

指针用于指向对象,与迭代器类似,指针提供对其所指对象的间接访问。不同在于:指针指向单个对象,而迭代器只能访问容器内的元素。具体来说,指针保存的是另一个对象的地址:

string s("hello");
string *p = &s;        //指针 p 保存 s 的地址

& 是取地址符号,该符号只能用于左值。只有变量作为左值时,才能取其地址。

1、指针的定义和初始化


vector<int> *vp;    //指向 vector<int> 对象的指针
int *ip;            //指向 int 对象的指针
string *sp;         //指向 string 对象的指针

另一种风格的指针

int* ip;

但容易引起误解,会认为 int* 是一种类型。但下例只有 ip1 是指针,ip2 是普通的整型变量

int* ip1, ip2;

指针的取值

int val = 1024;
int *ip1 = 0;        //ip1 不指向任何对象
int *ip2 = &val;    //ip2 指向val
int *ip3;            //ip3 未初始化
ip1 = ip2;            //ip1 指向 val
ip2 = 0;            //ip2 不指向任何对象

避免使用未初始化的指针

初始化的约束

对指针初始化或赋值只能使用下列四种类型的值:

  • 0常量表达式(编译时能获得0值得const对象或字面值常量0);
  • 类型匹配的对象的地址;
  • 另一对象之后的下一地址;
  • 同一类型的另一有效指针;

举例:

int ival = 1;
double dval = 1.5;
int zero = 0;
const int czero = 0;
int *ip;
double *dp;

ip = ival;        //error
ip = zero;        //error
ip = czero;        //ok:编译时可以获得 0
ip = 0;            //ok:字面值常量 0
ip = &ival;        //ok

dp = ip;        //error:类型不匹配

当然 0 值还可以使用从C语言继承下来的预处理器变量 NULL,它在 cstdlib 头文件中定义,其值为 0。

int *ip = NULL;    //相当于 int *ip = 0;

NULL 不是标准库中定义的,所以不需要 std::

void*指针

void* 指针可以保存任何类型对象的地址:

double dval = 3.14;
double *dp = &dval;
void *vp = &dval;     //ok
vp = dp;                  //ok

但 void* 指针只允许有限的操作:

  • 与另一指针比较;
  • 向函数传递 void* 指针,或函数返回 void* 指针;
  • 给另一个 void* 指针赋值。

不允许 void* 指针操作所指向的对象。

3、指针的操作


*操作符

string s1("hello");
string s2("world");
string *sp = &s1;
cout << *sp << endl;    //解引用
sp = &s2;                    //改变指针所指对象
cout << *sp << endl;    //解引用
*sp = "hello world";    //改变所指的内容
cout << *sp << endl;    //解引用

输出结果:

hello
world
hello world

sp 的解引用可以获得 s 的值,因为 sp 指向 s,所以给 *sp 赋值可以改变 s 的值。

指针和引用的比较

虽然引用(reference)和指针都可以间接访问另一个值,但有区别:

  • 定义引用时没有初始化时错误的;
  • 赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,并不是与另一个对象关联;
  • 引用一经初始化,就始终指向同一个特定的对象

指针的例子

int ival1 = 1024, ival2 = 2048;
int *ip1 = &ival1, *ip2 = &ival2;
ip1 = ip2;        //ip1 此时指向 ival2

而引用的例子

int &r1 = ival1; int &r2 = ival2;
r1 = r2;        //将 ival2 赋给 ival1

上面的修改只会修改引用所关联的对象,并不会改变改变引用本身。并且修改后,两个引用还是指向原来关联的对象。

指向指针的指针

指针本身也是需要占内存的对象,所以指针也可以被指针访问。

int ival = 1024;
int *ip = &ival;
int **ipp = &ip;
cout << ival << endl;
cout << *ip << endl;
cout << **ipp << endl;

结果

1024
1024
1024

可以用三种方式输出ival的值。

最后举一个例子

int i = 10, j =10;
int *p1 = &i, *p2 = &j;
cout << *p1 << endl;
cout << *p2 << endl;
*p2 = *p1 * *p2;            //改变 p2 所指的内容
cout << *p1 << endl;
cout << *p2 << endl;
*p1 *= *p1;                //改变 p1 所指的内容
cout << *p1 << endl;
cout << *p2 << endl;

结果

10
10
10
100
100
100

4、使用指针访问数组


指针与数组密切相关。特别是在表达式中使用数组名时,改名字会自动转换为指向数组第一个元素的指针:

int val[] = {0, 1, 2, 3};
int *p = val;    //p 指向 val[0]
p = &val[3];    //p 指向 val[3]

指针的算术运算

上面的 p = &val[3]; 使用下标操作,也可以通过 指针的算术操作(pointer arithmetic) 来获取指定的地址:

p = val;            //p 指向 val[0]
int *p2 = p + 3;    //p2 指向 val[3]

指针的算术操作只有在计算过后的新指针还是指向同一数组的元素才算合法,且不能越界,比如上面 int *p2 = p + 3; 改成 int *p2 = p + 4; 就会出错,因为数组 val 的大小为 4,最大的下标为 3。

两个指针之间还可以做减法

ptrdiff_t n = p2 - p1;    //n = 3

p1p2 之间相差3个对象,所以 n = 3n 是标准库类型(library type) ptrdiff_t 类型。与 size_t 类型一样,ptrdiff_t 也是一种与机器相关类型,在cstddef头文件中定义。

允许在指针上加减 0,使指针保持不变。

解引用和指针算术操作之间的相互作用

在指针上加上一个整数值,其结果仍是指针。允许在这个结果上直接进行解引用操作,而不必先把它赋给一个新的指针:

int last = *(val + 3);    //相当于 val[3]

需要写括号,如果写成 int last = *val + 3; 则相当于 val[0] + 3

下标和指针

使用下标访问数组时,它实际上是使用下标访问指针:

int val[] = {0,1,2,3,4};
int i = val[0]    //val 指向数组 val[] 的第一个元素

另一个例子

int *p = &val[2];    //ok: p 指向第二个元素
int j = p[1];        //ok: p[1] 相当于 *(p + 1), j = val[3]
int k = p[-2];        //ok: p[-2] 相当于 val[0]

计算数组的超出末端指针

vector 类型提供的end操作将返回指向超出 vector 末端位置的一个迭代器。类似的,可以计算数组的超出末端指针的值:

const size_t arr_size = 5;
int arr[arr_size] = {1,2,3,4,5};
int *p = arr;
int *p2 = p + arr_size;    //ok:超出末端的指针

C++允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作。而计算数组超出末端之后或数组首地址之前的地址都是不合法的。

p2 不能解引用操作,但能与其他指针比较,或者用作指针算术表达式的操作数。

输出数组元素

const size_t arr_size = 5;
int arr[arr_size] = {1,2,3,4,5};
for (int *begin = arr, *end = arr + arr_size; begin != end; ++begin){
    cout << *begin << "," << endl;
}

指针是数组的迭代器。上面的程序与迭代器程序非常相似,事实上,内置类型具有标准库容器的许多性质,指针就是数组的迭代器。

5、指针与const限定符


两种类型:

  • 指向const对象的指针;
  • const指针;

指向const对象的指针

如果指针指向的是const对象,则不再能够使用指针来修改对象的内容。为了保证这个特性,C++语言强制要求指向const对象的指针也必须具有const特性:

const double *cdp;

cdp 是指向一个double类型const对象的指针,const限定了 cdp 指针所指向的对象,而并非 cdp 本身。即 cdp 不是const类型,在定义时不一定需要给它初始化;如果有需要的话,允许给 cdp 重新赋值,使其指向另一个const对象,但不能通过 cdp 修改所指对象的值:

*cdp = 2;    //error: *cdp might be const

把一个const对象的地址赋给一个普通的、非const对象的指针也会导致编译错误:

const double pi = 3.14;
double *dp = π            //error: dp is a plain pointer
const double *cdp = π    //ok: cdp is a pointer to const

不能使用 void* 指针保存const对象的地址,而必须使用const void*指针保存:

const int val = 2;
const void *cvp = &val;        //ok: cvp is const
void *vp = &val;            //error: val is const

允许将非const对象赋给指向const对象的指针,例如:

double dval = 3.14;
const double *cdp = &dval;    //ok: 但是不能通过指针 cdp 改变 dval 的值

尽管 dval 不是 const 对象,但任何企图通过指针 cdp 修改其值得行为都会导致错误。

事实上,也有办法通过指向const对象指针改变所指的非const对象的值:

double dval = 3.14;
const double *cdp = &dval;    //ok: 但是不能通过指针 cdp 改变 dval 的值
*cdp = 3.14159;                //error: 不能通过 cdp 改变所指对象的值
double *dp = &dval;            //ok:dp 可以指向非const对象
*dp = 3.14159;                //ok
cout << *cdp << endl;        //此时会输出:3.14159

可以这样理解指向const对象的指针:自以为指向const对象的指针。但并不能保证所指向的对象一定是const对象。

const指针

这种指针本身不能修改:

int ival = 0;
int *const icp = &ival;    //icp 是const指针

这样理解:icp 是指向int对象的const的指针。跟其他const对象类似,const指针的值不能修改,意思就是不能使 icp 指向其他对象。任何企图给const指针赋值的行为都会出错(即使是赋它本身的值也一样):

icp = icp;    //error: icp is const

并且 const指针在定义时必须初始化

const指针所指对象的值能否被该指针修改完全取决于该对象的类型,例如 icp 指向一个普通的非 const int 型的对象,则可以使用 icp 修改该对象的值:

*icp = 1;

指向const对象的const指针

const double pi = 3.14159;
const double *const cdcp = π

上面的意思是既不能修改 pi 的值,也不能修改 cdcp 所指的对象。

指针和typedef

在typedef中使用指针往往会带来意外的结果,下面是一个几乎所有初学者都会搞错的问题:请问 cstr 变量是什么类型?

typedef string *sp;
const sp cstr;

简单的回答是:const sp 类型的指针。进一步:const sp 所表示的真实类型是什么?可能会认为是

const string *cstr;    //error

但这是错误的,原因是:声明const sp时,const修饰的是 sp 类型,而 sp 是一个指针。所以等价于

string *const cstr;

理解const声明:

//s1 和 s2 都是const
string const s1;
const string s2;

用typedef写const类型定义时,const限定符加在类型前面容易引起误解

string s;
typedef string *sp;
//下面三种定义时等价的
const sp cstr1 = &s;    //容易误解
sp const cstr2 = &s;
string *const cstr3 = &s;

举例

    int i = -1;
    const int ic = i;                  //ok
    const int *pic = ⁣             //ok
    int *const cpi = ⁣            //error
    const int *const cpic = ⁣    //ok

END.


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,498评论 1 51
  • 307、setValue:forKey和setObject:forKey的区别是什么? 答:1, setObjec...
    AlanGe阅读 1,521评论 0 1
  • 话题:正确本身的价值 1+1=2,E=mc²两个式子同样都是正确的。可我们思考“正确”本身的价值时,第一个式子正确...
    卷卷皮阅读 248评论 1 3
  • 光泽照亮的地方背后暗黑色系蔓延的地方就会越多,我们唯有坚定自己的内心往前冲,往前走,不回头。 我们都知道努力的意义...
    阿俊xi阅读 364评论 0 1
  • 与其浪费时间在想有的没的,不如想方设法的去赚钱,去赚大钱!远离一切让人堕落、腐烂的东西!! 在赚钱的过程中,想要的...
    小Yan林阅读 115评论 0 0