你好,C++(15)四两拨千斤——3.9 指向内存位置的指针

3.9  指向内存位置的指针

一天,两个变量在街上遇到了:

“老兄,你家住哪儿啊?改天找你玩儿去。”

“哦,我家在静态存储区的0x0049A024号,你家呢?”

“我家在动态存储区的0x0022FF0C号。有空来玩儿啊。”

在前面的章节中,我们学会了用int等数值数据类型表达各种数字数据,用char等字符数据类型表达文字数据,我们甚至还可以用结构体将多个基本数据类型组合形成新的数据类型,用以表达更加复杂的事物。除了这些现实世界中常见的数据之外,在程序设计当中,我们还有另外一种数据经常需要处理,那就是变量或者函数在内存中的地址数据。比如,上面对话中的0x0049A024和0x0022FF0C就是两个变量在内存中的地址。而就像对话中所说的那样,我们可以通过变量在内存中的地址便捷地对其进行读写访问,因而内存地址数据在程序中经常被用到。在C++中,表示内存地址数据的变量被称为指针变量,简称指针。

指针,是C++从C语言中继承过来的,它提供了一种简便、高效地直接访问内存的方式。特别是当要访问的数据量比较大时,比如某个体积比较大的结构体变量,通过指针直接访问变量所在的内存,要比移动复制变量来对其进行访问要快得多,可以起到四两拨千斤的效果。正确地使用指针,可以写出更加紧凑、高效的代码。但是,如果指针使用不当,就很容易产生严重的错误,并且这些错误还具有一定的隐蔽性,极难发现与修正,因而它也成为千千万万程序员痛苦的根源。爱恨交织,是程序员们对指针的最大感受,而学好指针,用好指针,也成为每个C++程序员的必修课。

3.9.1  内存空间的访问形式

指针是专门用来表示内存地址的,它的使用跟内存访问密切相关。为了更好地理解指针,我们先来看看C++中内存空间的访问形式。

在C++程序中,有两种途径可以对内存进行访问。一种是通过变量名间接访问。为了保存数据,通常会先定义保存数据的变量。定义变量也就意味着系统分配一定的内存空间用于存储某个数据,而变量名就成了这块内存区域的标识。通过变量名,我们可以间接地访问到这块内存区域,在其中进行数据的读取或者写入。

另外一种方式就是直接通过这些数据所在内存的地址,也就是通过指针来访问这个地址上的数据。

这两种都是C++中访问内存的方式,只是一个间接一个直接。打个不太恰当的比喻,比如我们要送一个包裹(数据)到某个地方(内存中的某块区域)去。按照第一种方式,我们说:送到亚美大厦(变量名)。而按照第二种方式,我们会说:送到科技路83号(内存地址)。虽然这两种方式表达形式不同,但实际上说的是同一件事。

在典型的32位计算机平台上,可以把内存空间看成是由很多个连续的小房间构成的,每个房间就是一个小存储单元,大小是一个字节(byte),而数据们就住在这些房间当中。有的数据比较小,比如一个char类型的字符,它只需要一个房间就够了。而有的数据比较大,就需要占用好几个房间。比如一个int类型的整数,其大小是4个字节,就需要4个房间才可以安置。为了方便地找到住在这些房间中的数据,房间都被按照某种规则进行了编号,这个编号,就是通常所说的内存地址。这些编号通常用一个32位的十六进制数来表示,比如上面例子中的0x0049A024、0x0022FF0C等如图3-6所示。

图3-6 住在内存中的数据

一旦知道某个数据所在的房间编号,就可以直接通过这个编号来对相应房间中的数据进行读写访问。就像上面的例子中把包裹直接送到科技路83号一样,我们也可以把数据直接保存到0x0022FF0C。

3.9.2  指针变量的定义

指针,作为一种表示内存地址的特殊变量,其定义的形式也有一定的特殊性:

数据类型* 变量名;

其中,我们用指针所表示地址上的数据类型来作为定义指针变量时用的数据类型。比如,我们要定义一个指针来表示某个int类型数据的地址,那么指针定义中的数据类型就是int。这个数据类型是由指针所指向的数据来决定的,可以是int、string和double等基本数据类型,也可以是自定义的结构体等复杂数据类型。简而言之,指针指向的数据是什么类型,就用这种类型作为指针变量定义时的数据类型。数据类型之后的“*”符号表示定义的是一个指针变量。“变量名”就是给这个指针指定的名字。例如:

// 定义指针变量p,它可以记录某个int类型数据的地址
int* p;
// 定义指针变量pEmp,它可以记录某个Employee类型数据的地址
Employee* pEmp

最佳实践:选择合适的定义指针变量的方式

实际上,下面两种定义指针变量的形式都是合乎C++语法的:

int* p;
int *p;

这两种形式都可以编译通过,并表示相同的语法含义。但是,这两种形式所反映的编程风格和对代码阅读者所强调的意义不同。

“int* p”强调的是“p为一个指向int类型整数的指针”,这里,可以把int*看成为一种特殊的数据类型,而整个语句强调的是p为这种数据类型(int*)的一个变量。

“int *p”则是把*p当成一个整体,强调的是“这个指针指向的是一个int类型的整数”,而p就是指向这个整数的指针。

这两种形式没有对与错的区别,只有个人喜好的区别。本书推荐第一种形式,它把指针也当成是一种数据类型,定义指针变量的语句更加清晰明了,可读性更强。

特别地,当在一条语句中定义多个指针变量时,可能会让人混淆,例如:

// p是一个int类型的指针变量,而q实际上是一个int类型的变量
// 可能会让人误认为p和q都是int类型指针
int* p, q;
// 清楚一些:*p是一个整数,p是指向这个整数的指针,q也是一个整数
int *p, q;
// 定义两个指向int类型数据的指针p和q
int *p, *q;

在开发实践中,有这样一条编码规范:“一条语句只完成一件事情”。按照这条规范,只要我们分开定义p和q,就可以很好地避免上述问题。

如果我们确实需要定义多个相同类型的指针变量,我们也可以用typedef关键字将指针类型定义成新的数据类型,然后用这个新的数据类型定义多个指针变量:

// 将Employee指针类型定义成新的数据类型EMPointer
typedef Employee* EMPointer;
// 用EMPointer类型定义多个指针变量,这些变量都是“Employee*”类型
EMPointer pCAO,pCBO,pCCO,pCDO;

3.9.3  指针的赋值和使用

在定义得到一个指针变量之后,指针变量的值还是一个随机值。它所指向的可能是某个无关紧要的数据,但也可能是重要的数据或者程序代码,如果直接使用其后果是不可预期的。也许啥事儿没有,也许因此而引起地球毁灭。所以在使用指针之前,必须对其赋值进行初始化,将其指向某个有意义的合法内存位置。对指针变量进行赋值的语法格式如下:

指针变量 = 内存地址;

可以看到,对指针变量的赋值,实际上就是将这个指针指向某一内存地址,而这个内存地址上存放的就是这个指针想要指向的数据。我们知道,数据是用变量来表示的,获得变量的内存地址也就相当于获得这个数据所在的内存地址,进而也就可以用它对指针变量赋值了。在C++中,我们可以利用“&”取地址运算符,将它放在某个变量的前面,就可以获得这个变量所在的内存地址。例如:

// 定义一个整型变量,用以表示整型数据1003
int N = 1003;
// 定义整型指针变量pN,用“&”符号取得整型变量N的地址,
// 并将其赋值给整型指针变量pN
int* pN = &N;

这里,我们用“&”符号取得整型变量N的内存地址,这也就是1003这个整型数据所在的内存地址,然后将其赋值给整型指针变量pN,也就是将指针pN指向了1003这个数据。如图3-7所示。

图3-7  指针和指针所指向的数据

指针的初始化赋值最好是在定义指针的时候同时进行,比如上面的例子中,在定义指针pN的同时即取得变量N的内存地址赋值给它,从而使得指针在一开始就有一个合理的初始值,避免未初始化的指针被错误地使用。如果在定义指针时,确实没有一个合理的初始值,我们可以将其赋值为nullptr关键字,它表示这个指针没有指向任何内存地址,是一个空指针(null pointer),还不能使用。例如:

// 定义一个指针变量pN,赋值为nullptr表示它没有指向任何内存位置
// 这里只是定义变量,后面才会用到
int* pN = nullptr;

// …

// 判断pN是否指向了某个数据
// 如果pN的值不是nullptr初始值,就表示它被重新赋值指向了某个数据
if(nullptr != pN)
{
    // 使用pN指针访问它所指向的数据
}

可以用“&”获得一个数据的内存地址,反过来,我们也可以用“*”获得一个内存地址上的数据。“*”称为指针运算符,也称为解析运算符。它所执行的是跟“&”运算符完全相反的操作。如果把它放在一个指针变量的前面,就可以取得这个指针所指向内存地址上的数据。例如:

// 输出pN指向的内存地址0x0016FA38
cout<<pN<<endl;
// 通过“*”符号获取pN所指向内存地址上的数据“1003”并输出
// 等同于cout<<N<<endl;
cout<<*pN<<endl;
// 通过指针修改它所指向的数据
// 等同于N = 1982;
*pN = 1982;
// 输出修改后的数据“1982”
cout<<*pN<<endl;

通过“*”运算符可以取得pN这个指针所指向的数据变量N,虽然“N”和“*pN”的形式不同,但是它们都代表内存中的同一份数据,都可以对这个数据进行读/写操作,并且是等效的。

特别地,如果一个指针指向的是一个结构体类型的变量,与结构体变量使用“.”符号引出成员变量不同的是,如果是指向结构体的指针,则应该用“->”符号引出其成员变量。这个符号,多像一个指针。例如:

// 定义一个结构体变量
Employee  Zengmei;
// 定义一个指针,并将其指向这个结构体变量
Employee* pZengmei = &Zengmei;
// 用“->”运算符,引用这个结构体指针变量的成员变量
pZengmei->m_strName = "Zengmei";
pZengmei->m_nAge = 28;

最佳实践:尽量避免把两个指针指向同一变量

当指针变量被正确赋值指向某个变量后,它也就成为了一个有效的内存地址,也可以用它对另外一个指针赋值。这样,两个指针拥有相同的内存地址,指向同一内容。例如:

// 定义一个整型变量
int a = 1982;
// 得到变量a的内存地址并赋值给指针pa
int* pa = &a;
// 使用pa对另外一个指针pb赋值
int* pb = pa;

在这里,我们用已经指向变量a的指针pa对指针pb赋值,这样,pa和pb的值是相同的,都是变量a的地址,也就是说,两个指针指向了同一个变量。

值得特别指出的是,虽然两个指针指向同一变量在语法上是合法的,可是在实际的开发中,却是应当尽量避免的。稍不留意,这样的代码就会给人带来困扰。继续上面的例子:

// 输出pa指向的数据,为1982
cout<<*pa<<endl;
// 通过pb修改它所指向的数据为1003
*pb = 1003;
// 再次输出pa指向的数据,变为了1003
cout<<*pa<<endl;

如果我们仅仅看这段程序的输出,一定会感到奇怪:为什么没有通过pa进行任何修改,而前后两次输出的内容却不同?如果我们结合前面的代码,就会明白,pa和pb指向的是同一个变量a,当我们通过指针pb修改变量a后,再通过pa来获得变量a的数据,自然就是更新过后的了。表面上看起来没有通过pa对变量a作修改,而pb却早已暗渡陈仓,偷偷地将变量a的数据做了修改。在程序当中,是最忌讳这种偷偷摸摸的行为的,因为一旦这种行为导致了程序运行错误,将很难被发现。所以,应尽量避免两个指针指向同一变量,就如同一个人最好不要取两个名字一样。

时间: 2024-07-31 06:20:03

你好,C++(15)四两拨千斤——3.9 指向内存位置的指针的相关文章

(转载)你好,C++(15)四两拨千斤——3.9 指向内存位置的指针

你好,C++(15)四两拨千斤——3.9 指向内存位置的指针 3.9  指向内存位置的指针 一天,两个变量在街上遇到了: “老兄,你家住哪儿啊?改天找你玩儿去.” “哦,我家在静态存储区的0x0049A024号,你家呢?” “我家在动态存储区的0x0022FF0C号.有空来玩儿啊.” 在前面的章节中,我们学会了用int等数值数据类型表达各种数字数据,用char等字符数据类型表达文字数据,我们甚至还可以用结构体将多个基本数据类型组合形成新的数据类型,用以表达更加复杂的事物.除了这些现实世界中常见的

金融篇-保证金制度(四两拨千斤)

期货市场中有一种叫做保证金制度,这种制度有以小博大,四两拨千斤的功效.简单的说,就是你可以用100元的钱,买到1000元的东西.有点不好理解了吧,在说清楚点,他的规则是这样的,比如,有一张黄豆的期货合约,每斤黄豆10元,数量为1000斤,这张合约的价值就是10*1000=10000元,按理说,如果你要买这张合约,你就需要10000元,你身上现在只有1000元,但是你明明知道在过一段时间,黄豆价格就要上涨,这就让你看到钱也赚不到.现在有了保证金制度后,你可以这样去做,把你的1000元到期货市场开个

四两拨千斤

1 public void hazardAlertJob(){ 2 Map<String,Map<String,Object>> alertTranNoMaps=new HashMap<String,Map<String,Object>>(); 3 EmConfig config=emConfigMapper.select(); 4 Long startTranNo=0L; 5 if(config!=null&&config.getHazar

四两拨千斤,一招搞定数字格式化问题

问题描述 今天在处理一个数字的格式显示问题时,遇到下面的一些需求: 可以显示千分符 可以显示百分比 可以显示小数位数 如下图,如果勾选的话就使能该项设置. 百分比和小数位数比较好解决,百分比只需要在源数字后面加两个零再加上百分号:小数只需要在小数点后面加零即可.最主要的是千分符的处理,好,就来说千分符. 千分符问题 数字千分符的处理有很多处理方案,最主要的有下面几种. 方法一:循环遍历 思路: 将数字转换成字符串,然后倒序遍历取值,每取3个字符插入一个,逗号,直到遍历到第一个字符. 代码很简单,

借力用力,一两拨千斤——抛弃程序员天生的自负

工作了几年,辗转了几个公司,最近一直在思考一个问题:如何借力用力,一两拨千斤.程序员,总是有一种自负:总感觉自己做的系统比他人好,自己写的代码比人优雅,然后自己重新实现类似的系统.编写类似的代码,遇到了类型的问题.甚至比别人多踩了一些大坑,才恍然大悟原来他人为何要那么做.系统设计.软件实现,其实是一个权衡.妥协的过程. 各种设计,各种实现都有实现者的权衡考虑,一个有责任.有思考力的设计者都会有自己的设计.实现初衷.取舍.我们不应该先入为主地做判断.这种程序员的自负情节是要不得的. 这跟借力用力.

C++对象模型——指向Member Function的指针 (Pointer-to-Member Functions)(第四章)

4.4 指向Member Function的指针 (Pointer-to-Member Functions) 取一个nonstatic data member的地址,得到的结果是该member在 class 布局中的byte位置(再加1),它是一个不完整的值,须要被绑定于某个 class object的地址上,才可以被存取. 取一个nonstatic member function的地址,假设该函数是nonvirtual,则得到的结果是它在内存中真正的地址.然而这个值也是不全然的,它也须要被绑定

C++ Primer 第四版读书笔记(三)之数组与指针

C++语言提供了两种类似于vector和迭代器类型的低级复合类型--数组与指针.与vector类型相似,数组也可以保存某种类型的一组对象:而它们的区别在于,数组的长度是固定的.数组一经创建,就不允许添加新的元素.指针则可以像迭代器一样用于遍历和检查数组中的元素. 现代C++程序应尽量使用vector和迭代器类型,而避免使用低级的数组和指针.设计良好的程序只有在强调速度时才在类实现的内部使用数组和指针. 数组是C++语言中类似于标准库vector类型的内置数据结构.与vector类似,数组也是一种

转载:C++中两个类中互相包含对方对象的指针问题

原文链接:http://www.cnblogs.com/hanxi/archive/2012/07/25/2608068.html 前几天很不爽,因为C++中两个类中互相包含对方对象的指针编译时提示某一个类未定义...所以我就想啊想,这样也对,我的头文件都有#ifndef的,包含了一次就不能再包含了,以为就实现不了这样的功能,于是就改了设计方案: class A { public: A(B* pB):m_pB(pB) { } private: B* m_pB; }; class B { publ

2016.2.15 四旋翼相关资料

--------2016.2.15--------最近在玩儿四旋翼,所以相关的资料我会贴上来供需要的朋友参考,如果遇到了我们趟过的地雷就能够方便一些. 机架:F450我们之后由于要将NVIDIA的开发板和飞行器连在一起,所以从机架的选择上我们要慎重考虑,之前为了方便选的是F330,但是DJI正版的F330停产了,所以taobao的F330进行测试,经过测试发现F330相当脆弱,机架非常容易在后写撞击中折断,所以如果上面搭载NVIDIA的开发板的话,那么这样开发的成本太高,一旦炸机可能机毁板子亡,