• C++引用和指针


      引用为对象起了另外一个名字(注,引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字),引用类型引用另外一个类型。通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名。且引用必须被初始化(一般在初始化变量时,初始化会被拷贝到新建的对象中。然后定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法零引用重新绑定到另一个对象,因此引用必须初始化。

      定义一个引用后,对其进行的所有操作都是在与之绑定的对象上进行的:

    refVal=2;//把2赋给refVal指向的对象,此处即是赋给了iVal
    int ii=refVal;
    int &refVal2=refVal;//refVal2绑定到了那个与refVal绑定的对象上,智力就是绑定到了refVal
    int i=refVal;//利用refVal绑定的对象的值初始化变量i

      为引用赋值,实际上是把值赋给了与引用绑定的对象。获取引用的值,实际上是获取了与引用绑定的对象的值。同理,为引用作为初始值,实际上是以与引用绑定的对象作为初始值。引用本身不是一个对象,所以不能定义引用的引用。

    1引用的定义

      允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头:

    int i=1024,i2=2048;
    int &r=i,r2=i2;//r是一个引用,与i绑定在一起,r2是int
    int i3=1024,&ri=i3;//r3是int,ri是一个引用,与i3绑定在一起
    int &r3=i3,&r4=i2;//r3和r4都是引用

      引用只能绑定在对象上,而不能与字面值或者某个表达式的计算结果绑定在一起,而且存在两种例外情况,其他所有引用的类型都要和与之绑定的对象严格匹配。

      第一种例外,在初始化常量引用是允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为了一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式:

    int i=42;
    const int &r1=i; //允许将const int&绑定到一个普通int对象上
    const int &r2=42;//正确:r2是一个常量引用
    const int &r3=r2*2;正确:r3是一个常量引用
    int &r4=r1*2;//错误:r4是一个普通的非常量引用

    要理解这种例外情况的原因:,最简单的办法是弄清楚当一个常量引用被绑定到另外一种类型上时到底发生了什么:

    double dval=3.14;
    const int &ri=dval;

    此处ri引用了一个int型的数。对ri的操作应该是整数运算,但dval却是一个双精度浮点数而非整数。因此为了确保让ri绑定一个整数,编译器把上述代码变成了如下形式:

    cosnt int temp=dval;//由双精度浮点数生成一个临时的整数常量
    const int &ri=temp;//让ri绑定这个临时量

    在这种情况下,ri绑定了一个临时量对象。接下来探讨当ri不是常量时,如果执行了类似于上面的初始化过程将带来什么样的后果。如果ri不是常量,就允许对ri赋值,这样就会改变ri所引用对象的值。注意,此时绑定的对象是一个临时量而非dval。程序员既然让ri引用dval,就肯定想通过ri改变dval的值,否则干什么要给ri赋值呢?如此看来,既然大家基本上不会想着把引用绑定到临时变量上,C++语言也就把这种行为归为非法。

      第二种例外,存在继承关系的类是一个重要的例外:我们可以把基类的指针或引用绑定到派生类对象上。例如,我们可以用Quote&指向一个Bulk_quote对象,也可以把一个Bulk_quote对象的地址赋给一个Quote*.

      可以将基类的指针或引用绑定到派生类对象上有一层极为重要的含义:当使用基类的引用(或指针)时,实际上我们并不清楚该引用(或指针)所绑定对象的真实类型。该对象可能是基类的对象,也可能是派生类的对象。

    2.指针

      指针是指向另外一种类型的复合类型。与引用类似,指针也实现了对其他对象的间接访问。然而指针与引用相比又有很多不同点。其一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。其二,指针无须在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。

       定义指针类型的方法将声明符写成*d的形式,其中d是变量名。如果在一条语句中定义了几个指针变量,每个变量前面都必须有符号*:

    int *ip1,*ip2;//ip1和ip2都是指向int型对象的指针
    double dp,*dp2;//dp2是指向double型对象的指针,dp是double型对象

    获取对象的地址

      指针存放某个对象的地址,要想获取该地址,需要使用取操作符(&):

    int ival=42;
    int *p=&ival;//p存放变量ival的地址,或者说p是指向变量ival的指针

    第二条语句把p定义一个指向int的指针,随后初始化p令其指向变量ival的int对象。因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。

      除了接下来说道的两种另外情况,其他所有指针的类型都要和它所指向的对象严格匹配:

    double dval;
    double *pd=&dval;//正确:初始值是double型对象的地址
    double *pd2=pd;//正确:初始值指向double对象的指针
    int *pi=pd;//错误:指针pi的类型和pd的类型不匹配
    pi=&dval;//错误:试图把double型对象的地址赋给int型指针

    因为在声明语句中指针的类型实际上被用于指定它所指向对象的类型,所以二者必须匹配。如果指针指向了一个其他类型的对象,对该对象的操作将发生错误。

       第一种例外情况是允许一个指向常量的指针指向一个非常量对象:

    double dval=3.14;
    const double *cptr=&dval;//正确:但是不能通过cptr改变dval的值

      和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。

    注:可以这样想:所谓指向常量的指针或引用,不过是指针或引用“自以为是”罢了,他们觉得自己指向了常量,所以自觉的不去改变所致对象的值。

      第二种例外和引用一样,存在继承关系的类是一个重要的例外:我们可以把基类的指针或引用绑定到派生类对象上。例如,我们可以用Quote&指向一个Bulk_quote对象,也可以把一个Bulk_quote对象的地址赋给一个Quote*.

      可以将基类的指针或引用绑定到派生类对象上有一层极为重要的含义:当使用基类的引用(或指针)时,实际上我们并不清楚该引用(或指针)所绑定对象的真实类型。该对象可能是基类的对象,也可能是派生类的对象。

     指针值

      指针的值(即地址)应属下列四种情况之一:

      1.指向一个对象;

      2.指向紧邻对象所占空间的下一个位置;

      3.空指针,意味着指针没有指向任何对象;

      4.无效指针,也就是上述情况之外的其他值。

    试图拷贝或以其他方式访问无效指针的值都引发错误。编译器并不负责检查此类情况,这一点和试图使用未经初始化的变量是一样的。访问无效指针的后果无法预计,因此程序员必须清楚任意给定的指针是否是有效。

    利用指针访问对象

      如果指针指向了一个对象,则允许使用解引用符(操作符*)来访问该对象:

    int ival=42;
    int *p=&ival;//p存放变量ival的地址,或者说p是指向变量ival的指针
    cout << *p;//由符号*得到指针p所指的对象,输出42

    对指针解引用会得出所指的对象,因此如果给解引用的结果复制,实际上也就是给指针所指的对象赋值:

    *p=0;//由符号*得到指针p所指的对象,即可经由p为变量ival赋值
    cout << *p;//输出0

    空指针

      空指针不指向任何对象,在试图使用一个指针之前代码可以首先检查他是否为空。以下列出几个生成空指针的方法:

    int *p1=nullptr;//等价于int *p1=0
    int *p2=0;//直接将p2初始化为字面常量0
    //首先需要#include <cstdlib>
    int *p3=NULL;//等价于int *p3=0

    得到空指针最直接的方法就是用字面值nullptr来初始化指针,这也是C++11新标准刚刚引用的一种方法。nullptr是一种特殊类型的字面值,它可以被转换成其他的指针类型。另一种就如对p2的定义一样,可以通过将指针初始化为字面值0来生成空指针。

      把int变量直接赋给指针是错误的操作,即使int变量的值恰好等于0也不行。

    int zero=0;
    pi=zero;//错误:不能把int变量直接赋给指针

    说到这里:我建议大家初始化所有的指针,如若不然,会带来很大的问题的。

    赋值和指针

      指针和引用都能提供对其他对象的间接访问,然而在具体实现细节上二者有很大不同,期中最重要的一点就是引用本身并非一个对象。一旦定义了一个引用,就无法令其在绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。

      指针和它存放的地址之间就没有这种限制了,和其他变量(只要不是引用)一样,给指针赋值就是令它存放一个新的地址,从而指向一个新的对象:

    int i=42;
    int *pi=0;//pi被初始化,但没有指向任何对象
    int *pi2=&i;//pi2被初始化,存有i的地址
    int *pi3;//如果pi3定义于块内,则pi3的值是无法确定的
    pi3=pi2;//pi3和pi2指向同一个对象i
    pi2=0;//现在pi2不指向任何对象了

      有时候想搞清楚一条赋值语句到底是改变了指针的值还是改变了指针所指对象的值不太容易,最好的办法就是记住赋值永远改变的是等号左侧的对象。当写出如下语句时:

    pi=&ival;//pi的值被改变,现在pi指向了ival
    *pi=0;//ival的值被改变,指针pi并没有改变

    其他指针操作

      只要指针拥有一个合法值,就能将它用在条件表达式中。和采用算术值作为条件遵循的规则类似,如果指针的值是0,条件取false:

    int ival=1024;
    int *pi=0;//pi合法,是一个空指针
    int *pi2=&ival;//pi2是一个合法的指针,存放着ival的地址
    if(pi)    //pi的值是0,因此条件的值是false
        //.....
    if(pi2)    pi2指向ival,因此它的值不是0,条件的值是true
        //.....

      对于两个类型相同的合法指针,可以用相等操作符(==)或不想等操作符(!=)来比较它们,比较的结果是布尔类型。

    void*指针

      void*是一种特殊的指针类型,可用于存放任何对象的地址。一个void*指针存放这一个地址,这一点和其他指针类似。不同的是,我们对该地址中到底是个什么类型的对象并不了解:

    double obj=3.14,*pd=&obj;//正确:void*能存放任意类型对象的地址
    void *pv=&obj;//obj可以是任意类型的对象
    pv=pd;//pv可以存放任意类型的指针

    利用void*指针能做的事儿比较有限:拿它和别的指针比较、作为函数的输入或输出,或者赋给另外一个void*指针。不能直接操作void*指针所指的对象,因为我们并不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。

      概括说来,以void*的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象。

    指向指针的指针

       指针是内存中的对象,像其他对象一样也有自己的地址,因此允许把指针的地址再存放到另一个指针中。

      通过*的个数可以区分指针的级别。也就是说,**表示指向指针的指针,***表示指向指针的指针的指针,以此类推:

    int ival=1024;
    int *pi=&ival;//pi指向一个int型的数
    int **ppi=&pi;//ppi指向一个int型的指针

    此处pi是指向int型数的指针,而ppi是指向int型指针的指针。

      解引用int型指针会得到一个int型的数,同样,解引用指向指针的指针会得到一个指针。此时为了访问最原始的那个对象,需要对指针的指针做两次解引用:

    cout << "The value of ival
    "
            << "direct value: " <<ival << "
    "
            << "indirect value: " << *pi << "
    "
            << "doubly indirect value: " << **ppi
            << endl;

    该程序使用三种不同的方式输出了变量ival的值:第一种直接输出,第二种通过int型指针pi输出,第三种两次解引用ppi,取得ival的值。

    指向指针的引用

      引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用:

    int i=42;
    int *p;//p是一个int型指针
    int *&r=p;//r是一个对指针p的引用
    r=&i;//r引用了一个指针,因此给r赋值&i就是令p指向i
    *r=0;//解引用r得到i,也就是p指向的对象,将i的值改为0;

    理解r的类型到底是什么,最简单的方法是从右向左阅读r的定义。离变量名最近的符号(此例中是&r的符号&)对变量的类型有最直接的影响,因此r是一个引用。声明符的其余部分以确定r引用的类型是什么,此例中的符号*说明r引用的是一个指针。最后声明的基本数据部分指出r引用的是一个int指针。

  • 相关阅读:
    Flask--目录
    Linux相关目录
    Mac 提示错误”xcrun: error“
    subprocess模块
    压缩模块
    GitPython模块
    Ansible-ansible命令
    YAML语法
    Ansible-安装
    Ansible-概念
  • 原文地址:https://www.cnblogs.com/awy-blog/p/3585594.html
Copyright © 2020-2023  润新知