• 指针、引用和句柄


    1.指针与引用

    (1)指针是对象在内存中的地址;

    (2)引用是对象的别名,其实质就是功能受限但是安全性更高的指针;

    (3)句柄是指针的指针,句柄实际上是一个数据,是一个Long (整长型)的数据。句柄是一个标识符,是拿来标识对象或者项目的,它就象我们的姓名一样。Windows是一个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化了。如果地址总是如此变化,我们该到哪里去找该对象呢?为了解决这个问题,Windows操作系统为各应用程序腾出一些内存储地址,用来专门登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的。Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。

    引用与指针的区别:  
    常常有人问引用与指针的区别,可能是指针和引用在功能上的相似,而是他们混淆这两个概念, 
    现在总结以下二者的区别,希望大家能彻底弄清这两个概念根本性的差别: 
    引用,一个变量的别名,为什么引入别名呢?原因是我们想定义一个变量,他共享另一个变量的 
    内存空间,使用别名无疑是一个好的选择。变量是什么?是一个内存空间的名字,如果我们给这个 
    内存空间在起另外一个名字,那就是能够共享这个内存了,引用(别名)的由此而来。 
    指针,指向另一个内存空间的变量,我们可以通过它来索引另一个内存空间的内容,本身有自己的 
    内存空间。 
    二者区别:(1)引用访问一个变量是直接访问,而指针是间接访问。 
    (2)引用是一个变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间。 
    (3)引用在开始的时候就绑定到了一个内存空间(开始必须赋初值),所以他只能是这个 
    内存空间的名字,而不能改成其他的,当然可以改变这个内存空间的值. 
    例如 
    int i = 3,j = 4; 
    int &x = i;//成为i的别名 
    x = j;//不能否认x仍然引用i,并没有成为j的别名,只是修改了x和j共享的内存空间的值. 
    这点与指针不同,指针在任何时刻都可以改变自己的指向 
    ====================================================================================

    你无法让引用与其指示物分离。

    和指针不同,一旦引用和对象绑定,它无法再被重新指向其他对象。引用本身不是一个对象(它没有标识;当试图获得引用的地址时,你将的到它的指示物的地址;记住:引用就是它的指示物 )。

    从某种意义上来说,引用类似 int* const p 这样的const指针(并非如 const int* p这样的指向常量的指针)。不管有多么类似,请不要混淆引用和指针;它们完全不同。


    何时该使用引用, 何时该使用指针?

    尽可能使用引用,不得已时使用指针。

    当你不需要“重新指向(reseating)”时,引用一般优先于指针被选用。这通常意味着引用用于类的公有接口时更有用。引用出现的典型场合是对象的表面,而指针用于对象内部。

    上述的例外情况是函数的参数或返回值需要一个“临界”的引用时。这时通常最好返回/获取一个指针,并使用 NULL指针来完成这个特殊的使命。(引用应该总是对象的别名,而不是被解除引用的 NULL 指针)。

    注意:由于在调用者的代码处,无法提供清晰的的引用语义,所以传统的 C 程序员有时并不喜欢引用。然而,当有了一些 C++经验后,你会很快认识到这是信息隐藏的一种形式,它是有益的而不是有害的。就如同,程序员应该针对要解决的问题写代码,而不是机器本身。

    引用和指针

    ★ 相同点: 
    1. 都是地址的概念; 
    指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

    ★ 区别: 
    1. 指针是一个实体,而引用仅是个别名; 
    2. 引用使用时无需解引用(*),指针需要解引用; 
    3. 引用只能在定义时被初始化一次,之后不可变;指针可变; 
    引用“从一而终” ^_^ 
    4. 引用没有 const,指针有 const,const 的指针不可变; 
    5. 引用不能为空,指针可以为空; 
    6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof指针”得到的是指针本身(所指向的变量或对象的地址)的大小; 
    typeid(T) == typeid(T&) 恒为真,sizeof(T) ==sizeof(T&) 恒为真, 
    但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。 
    7. 指针和引用的自增(++)运算意义不一样;

    ★ 联系 
    1. 引用在语言内部用指针实现(如何实现?)。 
    2. 对一般应用而言,把引用理解为指针,不会犯严重语义错误。引用是操作受限了的指针(仅容许取内容操作)。

    C++中,指针参数和引用的区别.

     

    从概念上讲。指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。

    而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。

    在C++中,指针和引用经常用于函数的参数传递,然而,指针传递参数和引用传递参数是有本质上的不同的:

    指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。

    而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

    引用传递和指针传递是不同的,虽然它们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将影响不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。

    为了进一步加深大家对指针和引用的区别,下面我从编译的角度来阐述它们之间的区别:

    程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。

    最后,总结一下指针和引用的相同点和不同点:

    ★相同点:

    ●都是地址的概念;

    指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。

    ★不同点:

    ●指针是一个实体,而引用仅是个别名;

    ●引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;

    ●引用没有const,指针有const,const的指针不可变;

    ●引用不能为空,指针可以为空;

    ●“sizeof引用”得到的是所指向的变量(对象)的大小,而“sizeof指针”得到的是指针本身的大小;

    ●指针和引用的自增(++)运算意义不一样;

    ●引用是类型安全的,而指针不是(引用比指针多了类型检查 
    举个最简单的例子

     

    void tmp(int* b) 

      if (b==NULL) 
      { 
      b=new int; 
    *b=200; 

    else 
      { 
      *b=100; 
      } 

    void main() 

      int a=0; 
      tmp(&a);

    int* c=NULL; 
      //c=new int; 
      tmp(c); 
    return; 
    }

    在main中有两种调用tmp函数的方法  第一个是通过引用,因为引用不可以为空,所以就是直接定义一个int.然后用引用的方法传地址过去. 
    第二种方式 是指针.假如指针c在调用tmp前 ,是指向null且未分配空间,那么在tmp中会给分配空间,然后赋值.但是tmp调用完之后 c将不是tmp中赋的值. 
    OK,那么我们就明白了.指针在传递时,其实就是将指针的地址copy过去的.注意,这里是copy.如果你在被调函数中修改内存指向的值,那么函数调用结束回到主调函数中,修改是有效的,也就是修改成功.这就相当于,在被调函数中,申明了一个int*的变量,其值就是传递进来的int*. 
    int* b=int*c; 
    那么在被调函数的堆栈中修改自己定义的int*b是 当然也是不会影响到int*c的地址. 
    总来说,引用其实也是地址,C语言中的取地址符就是& 
    那么,如果你想在被调函数中申请空间,那么就用引用,若是调用之前申请好空间,只是在被调函数中修改其指向的值那么引用和指针都OK. 
    所以,推荐使用引用.

    C++指针使用方法解惑 

        在下列函数声明中,为什么要同时使用*和&符号?以及什么场合使用这种声明方式? 

          void func1( MYCLASS *&pBuildingElement );      
    论坛中经常有人问到这样的问题。本文试图通过一些实际的指针使用经验来解释这个问题。 
    仔细看一下这种声明方式,确实有点让人迷惑。在某种意义上,"*"和"&"是意思相对的两个东西,把它们放在一起有什么意义呢?。为了理解指针的这种做法,我们先复习一下C/C++编程中无所不在的指针概念。我们都知道MYCLASS*的意思:指向某个对象的指针,此对象的类型为MYCLASS。
       void func1(MYCLASS *pMyClass);         
    // 例如:
          MYCLASS* p = new MYCLASS;       func1(p);      
          上面这段代码的这种处理方法想必谁都用过,创建一个MYCLASS对象,然后将它传入func1函数。现在假设此函数要修改pMyClass:
          void func1(MYCLASS *pMyClass)       {          DoSomething(pMyClass);          pMyClass = // 其它对象的指针       }   
          第二条语句在函数过程中只修改了pMyClass的值。并没有修改调用者的变量p的值。如果p指向某个位于地址0x008a00的对象,当func1返回时,它仍然指向这个特定的对象。(除非func1有bug将堆弄乱了,完全有这种可能。) 
    现在假设你想要在func1中修改p的值。这是你的权利。调用者传入一个指针,然后函数给这个指针赋值。以往一般都是传双指针,即指针的指针,例如,CMyClass**。
          MYCLASS* p = NULL;       func1(&p);              void func1(MYCLASS** pMyClass);       {          *pMyClass = new MYCLASS;          ……       }     
    调用func1之后,p指向新的对象。在COM编程中,你到处都会碰到这样的用法--例如在查询对象接口的QueryInterface函数中:
          interface ISomeInterface {             HRESULT QueryInterface(IID &iid, void** ppvObj);            ……         };         LPSOMEINTERFACE p=NULL;          pOb->QueryInterface(IID_SOMEINTERFACE, &p);         
        此处,p是SOMEINTERFACE类型的指针,所以&p便是指针的指针,在QueryInterface返回的时候,如果调用成功,则变量p包含一个指向新的接口的指针。 
      如果你理解指针的指针,那么你肯定就理解指针引用,因为它们完全是一回事。如果你象下面这样声明函数:
          void func1(MYCLASS *&pMyClass);       {           pMyClass = new MYCLASS;             ……       }       
          其实,它和前面所讲得指针的指针例子是一码事,只是语法有所不同。传递的时候不用传p的地址&p,而是直接传p本身:     
          MYCLASS* p = NULL;       func1(p);       
        在调用之后,p指向一个新的对象。一般来讲,引用的原理或多或少就象一个指针,从语法上看它就是一个普通变量。所以只要你碰到*&,就应该想到**。也就是说这个函数修改或可能修改调用者的指针,而调用者象普通变量一样传递这个指针,不使用地址操作符&。 
      至于说什么场合要使用这种方法,我会说,极少。MFC在其集合类中用到了它--例如,CObList,它是一个CObjects指针列表。
          class CObList : public CObject {           …           // 获取/修改指定位置的元素           CObject*& GetAt(POSITION position);           CObject* GetAt(POSITION position) const;       };       
    这里有两个GetAt函数,功能都是获取给定位置的元素。区别何在呢? 
    区别在于一个让你修改列表中的对象,另一个则不行。所以如果你写成下面这样:
          CObject* pObj = mylist.GetAt(pos);       
    则pObj是列表中某个对象的指针,如果接着改变pObj的值:
          pObj = pSomeOtherObj;      
    这并改变不了在位置pos处的对象地址,而仅仅是改变了变量pObj。但是,如果你写成下面这样:
          CObject*& rpObj = mylist.GetAt(pos);       
        现在,rpObj是引用一个列表中的对象的指针,所以当改变rpObj时,也会改变列表中位置pos处的对象地址--换句话说,替代了这个对象。这就是为什么CObList会有两个GetAt函数的缘故。一个可以修改指针的值,另一个则不能。注意我在此说的是指针,不是对象本身。这两个函数都可以修改对象,但只有*&版本可以替代对象。  
      在C/C++中引用是很重要的,同时也是高效的处理手段。所以要想成为C/C++高手,对引用的概念没有透彻的理解和熟练的应用是不行的。
    2.指针与句柄(MFC)
     初识句柄(handle)是在学习MFC的时候,当时给出的定义“句柄-资源的标识"
    句柄并没有什么神奇之处,不管哪种句柄,实际都是一个整数。它标识一种资源,如窗口、位图等等。就象你找一个人,必须知道它的地址一样,如果你要操作一种资源,必须先获得句柄。
          “取窗口句柄()”并不是只能取出窗口的句柄,所有窗口控件,如编辑框、标签等都可以用本命令取出自己的句柄,如:

         编辑框1.取窗口句柄()
         标签1.取窗口句柄()

    控件的句柄同样,任何控件都有它自身的特有属性,句柄也就指它的特有属性(包括共性)。好像人有男女之分,要找男人一定是找有特有性别的人,也就是能区分不是女人的特性的人。

        “句柄”(handle)
          handle的本意是把柄,把手的意思。是你与操作系统打交道的东东。举个通俗的例子,比如你考上了大学,入学后,学校(操作系统)会给你一个学生证号。 注意,这个号码是学校指定的,你无法自选。有了这个号码(学生证,假设一证多用)享受学校提供的服务:如你就可以去图书馆借书,去食堂吃饭,去教室上课等 等。但你不能到食堂里买啤酒,因为学校不允许这种服务。而在计算机中系统提供的服务就是API调用,
             你有了HANDLE,就可以理直气壮地向系统提出调用API的服务.而指针的权力就大多了,有了指针你可以到处去喝酒,打架,学校(操作系统)管不着,所 以句柄和指针的区别在于句柄只能调用系统提供的服务。而句柄虽然是一个能相互区别的号码,但与我们普通的ID号又有区别,普通的ID号是可以由程序员自己 定义的,而句柄不行,它是对象生成时系统指定的,是为了区别系统中存在的各个对象,这个句柄不是由程序员赋给的。


    所 谓 句柄实际上是一个数据,是一个Long (整长型)的数据。句 柄是WONDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI 对象等等。WINDOWS句柄有点象C语言中的文件句柄。 从上面的定义中的我们可以看到,句柄是一个标识符,是拿来标识对象或者项目的,它就象我们的姓名一样,每个人都会有一个,不同的人的姓名不一样,但是,也 可能有一个名字和你一样的人。从数据类型上来看它只是一个16位的无符号整数。应用程序几乎总是通过调用一个WINDOWS函数来获得一个句柄,之后其他 的WINDOWS函数就可以使用该句柄,以引用相应的对象。

    如 果想更透彻一点地认识句柄,我可以告诉大家, 句柄是一种指向指针的指针。 我们知道,所谓指针是一种内存地址。应用程序启动后,组成这个程序的各对象是住留在内存的。如果简单地理解,似乎我们只要获知这个内存的首地址,那么就可 以随时用这个地址访问对象。但是,如果您真的这样认为,那么您就大错特错了。我们知道,Windows是一个以虚拟内存为基础的操作系统。在这种系统环境 下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化了。如果地址总是如此变化,我 们该到哪里去找该对象呢? 为了解决这个问题,Windows操作系统为各应用程序腾出一些内存储地址,用来专门登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本 身是不变的。Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知 道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时(Unload)又释放给系统。 句柄地址(稳定)→记载着对象在内存中的地址────→对象在内存中的地址(不稳定)→实际对象 本质:WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的,相反的,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。 但是必须注意的是程序每次从新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确不一样的。假如我们把进入电影院看电影看 成是一个应用程序的启动运行,那么系统给应用程序分配的句柄总是不一样,这和每次电影院售给我们的门票总是不同的一个座位是一样的道理。

    1、 句柄所指的可以是一个很复杂的结构,并且很有可以是与系统有关的,比如说上面所说的线程的句柄,它指向的就是一个类或者结构,他和系统有很密切的关系,当 一个线程由于不可预料的原因,而终止时在系统就可以回它所占用的资料,如CPU,内存等等,反过来想可以知道,这个句柄中的某一些项,是与系统进行交互 的。由于Windows系统,是一个多任务的系统,它随时都可能要分配内存,回收内存,重组内存。 2、指针它也可以指向一个复杂的结构,但是通常是用户定义的,所以的必需的工作都要用户完成,特别是在删除的时候。 但在VC++6.0中也有一些指针,它们都是处理一些小问题才用的,如最常见的字符的指针,它也是要用户处理的如果你动态分配了内存;但是Cstring 就不要用户处理了,它其实是VC++中的一个类,所以的操作都由成员函数完成,产生(分配)由构造函数,删除(回收)由析构函数完成。 3.句柄是标号,指针是内存地址

    句 柄和指针根本就不是一回事,基本上没有共同的地方。我对句柄的定义是:. 句柄是系统内部受保护的数据结构的标志或者说索引. 我的解释: 当你的应用程序或者系统建立内核对象,用户对象,GUI对象的时候实际上这些对象是一些内存数据结构(这个就不要我解释了吧)显然这些对象需要受到保护, 不能让用户随便修改访问,否则系统很容易崩溃所以提供了一个机制来保护性的访问这些对象,那就是API句柄是这些对象的标记,或者说是索引在内存中往往会 存在一张句柄表,一般至少有如下结构 索引(也就是句柄) 指针(指向内存对象数据结构) 其他项 1 ox???????? 2 ox???????? 3 ox???????? 4... 系统采用API,查询句柄表,取得句柄所对应的指针,这个指针才是真正的指针,用它可以访问修改受保护的内存. . 假如句柄是指针 . 1 你自己输出一些句柄的值来看,句柄一般都是一些很小的整数值, 比如1,2,3,4...... 如果是指针的话,它起不是指向受保护的区域?岂不是NULL指针??? 2 句柄如果是指针的话,那么用户岂不可以直接访问内存对象了?那系统还有什么稳定性,安全性? 3 你把得到的句柄,比如hwnd做hwnd++,hwnd--等运算,你看错不错 其他理由我都不想说了 如果有人说看到句柄的定义为指针(我没有看到过) 那你就去当指针吧,反正指针也是32位的值,和无符号整数没有任何区别.

    附注:获得窗口句柄三种方法

    1.HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName)

    HWND FindWindowEx(HWND hwndParent, HWND hwndChildAfter,LPCTSTR lpClassName, LPCTSTR lpWindowName)

    2.HWND WindowFromPoint(POINT& Point)//获得当前鼠标光标位置的窗口HWND

    3.BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam)

    BOOL CALLBACK EnumChildWindows(HWND hWndParent, WNDENUMPROC lpEnumFunc,LPARAM lParam) 
    BOOL CALLBACK EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam) 
    BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)

    指针 句柄之间的转换

    a.由指针获得句柄 
    CWnd * pWnd; 
    CWnd HWnd; 
    HWnd = pWnd->GetSafeHWnd();

    b.由句柄得到指针:
    CWnd* pWnd=FromeHandle(hMyHandle); 
    pWnd->SetWindowText("Hello World!"); 
    or CWnd* pWnd; pWnd->Attach(hMyHandle);

    MFC类中有的还提供了标准方法,比如Window 句柄 : 
    static CWnd* PASCAL FromHandle( HWND hWnd ); 
    HWND GetSafeHwnd( ) const;

    对于位图: 
    static CBitmap* PASCAL FromHandle( HBITMAP hBitmap ); 
    static CGdiObject* PASCAL FromHandle( HGDIOBJ hObject ); 
    HGDIOBJ GetSafeHandle( ) const;

    下面是对句柄的生动比喻:

    csdn上有人说过:牧童遥指杏花村。牧童的手为指针,杏花村的牌子为句柄,杏花村酒店为对象的实例.
    句柄就是烤叉,用烤炉烤过鸭,鸡,牛,羊,狗么?炉子里的东西是看不见,摸不到的,但你能用叉子去控制,

    至于叉子上的是什么,你放进去前应该记住。呵呵

    句柄有时是指针,有时是索引,但他绝对是一把钥匙,内核句柄110的钥匙,GDI句柄是您的钥匙,只对您有效。

    单从概念上讲,句柄指一个对象的标识,而指针是一个对象的首地址。从实际处理的角度讲,即可以把句柄定义为指针,又可以把它定义为同类对象数组的索引,这两种处理方法都有优缺点,至于选用哪种方式,完全应该看实际需要,这可以说是一种程序设计上的技巧。那种单纯认为句柄是指针或索引的想法都是机械的、不确切的。

    其实,在Windows中类似的处理是很多的、很灵活的。再具个相似的例子:我们知道,在Windows中有个函数叫做CallWindowProc。故名思义,它的作用就是向指定的窗口过程传递一个消息。你也许会想,既然我已经有了窗口过程的指针,为什么我不可以直接通过这个指针调用该函数(这是C语言的内建功能)?事实上,在Win16中确实可以这么做,因为GetWindowLong返回的确实是该函数的指针。但在Win32下,GetWindowLong返回的并不是该函数的指针,而是一个包含函数指针的数据结构的指针(MSDN上说返回的是一个窗口函数地址或它的句柄,就是指的这种情况)。该数据结构是可变的,但只要你使用CallWindowProc来调用的话是不会出错的。这里我们又看到使用句柄处理带来的好处。(补充说明一点:微软在这里之所以这么处理,是为了解决16位/32位以及ANSI/UNICODE的转化问题)

  • 相关阅读:
    CentOS 6.5环境实现corosync+pacemaker实现DRBD高可用
    通达OA2008优化前端web为lnmp环境及后续优化
    CentOS 6.5环境使用ansible剧本自动化部署Corosync + pacemaker环境及corosync常用配置详解
    利用mycat实现基于mysql5.5主从复制的读写分离
    登录服务器windows2008出现:远程桌面服务当前正忙,因此无法完成您尝试执行的任务。请在几分钟后重试。其他用户应该仍然能够登录
    CentOS 6.5使用Corosync + pacemaker实现httpd服务的高可用
    ansible的安装部署及简单应用
    centos6.7安装系统后看不到网卡无法配置IP的解决办法
    Error: Cannot retrieve metalink for repository: epel. Please verify its path and try again
    centos6环境创建局域网http方式的yum源
  • 原文地址:https://www.cnblogs.com/gaoxiangde/p/4379882.html
Copyright © 2020-2023  润新知