C++特性之引用 (Boolan)

C++特性之引用   (Boolan)程序员

本章内容:安全

1 引用的不一样用例ide

1.1 引用变量函数

1.2 引用数据成员spa

1.3 引用参数指针

1.4 引用做为返回值对象

1.5 使用引用仍是指针内存

1.6 右值引用ci


1 引用作用域

    在C++中,引用是变量的别名。全部对引用的修改都会改变被引用的变量的值。可将引用看成隐式指针,这个指针没有取变量地址和解除引用的麻烦。

也能够将引用看成原始变量的另外一种名称。能够建立单独的引用变量,在类中使用引用数据成员,将引用做为函数和方法的参数,也可让函数或者方法返回引用。


1.1 引用变量

    引用变量在建立时必须初始化,以下:

int ival = 3;
int &iRef = x;

赋值后,iRef就是ival的另外一个名称。使用iRef就是使用ival的当前值。对iRef赋值会改变ival的值。

没法在类外面声明一个引用而不初始化它:

int &emptyRef;  //编译出错

不能建立对未命名值(例如一个整数字面值)的引用,除非这个引用是一个const值。在下面的示例中,unnamedRef1将没法编译,由于这是一个针对常量的non-const引用。

这条语句意味着能够改变常量5的值,而这样作没有意义。因为unnamedRef2是一个const引用,所以能够运行,不能写成"unnamedRef2=7"。

int &unnamedRef1 = 5;        //编译出错
const int &unnamedRef2 = 5;  //正常运行

(1) 修改引用

    引用老是引用初始化的那个变量:引用一旦建立,就没法修改。这一规则致使了许多使人迷惑的语法。若是在声明一个引用时用一个变量"赋值",那么这个引用就指向这个变量。

然而,若是在此后使用变量对引用赋值,被引用变量的值就变为赋值变量的值。引用不会更新为指向这个变量。示例代码以下:

int x = 3,y = 4;
int &iRef = x;
iRef = y;//Changes value of x to 4. Doesn't make iRef refer to y.

若是试图在赋值时取y的地址,以绕过这一限制:

int x = 3,y = 4;
int &iRef = x;
iRef = &y;        //编译出错

上面的代码没法编译。y的地址是一个指针,但iRef声明为一个int的引用,而不是一个指针的引用。

    若是将一个引用赋值给另外一个引用时,只是修改了其指向的值,而不是修改所指向的引用变量。(在初始化引用以后没法改变引用所指的变量;而只能改变该变量的值。)

(2) 指针的引用和指向引用的指针

    能够建立任何类型的引用,包括指针类型。下面给出一个指向int指针的引用例子:

int *pVal;
int *&ptrRef = pVal;
ptrRef = new int;
*ptrRef = 5;

    这一语法有一点奇怪:你可能不习惯看到*和&彼此相邻。然而,该语义上很简单:ptrRef是pVal的引用,pVal是一个指向int的指针。修改ptrRef会更改pVal。指针的引用不多见,可是在某些场合下颇有用,在1.3节中会讨论这一内容。

    注意:

    (i)对引用取地址的结果与被引用变量取地址的结果是相同的。

    (ii)没法声明引用的引用,或者指向引用的指针。


1.2 引用数据成员

    类的数据成员能够引用,可是若是不是指向其余变量,引用就没法存在。所以,必须在构造函数初始化器(constructor initializer)中初始化引用数据成员,而不是在构造函数体

内。下面举例说明:

class MyClass
{
public:
    MyClass(int &iRef):m_ref(iRef) {}
private:
  int &m_ref;
};


1.3 引用参数

    C++程序员一般不会单独使用引用变量或者引用数据成员。引用常常用做函数或者方法的参数。默认的参数传递机制是值传递:函数接收参数的副本。修改这些副本时,原始的参数

保持不变。引用容许指定另外一种向函数传递参数的语义:按引用传递。当使用引用参数时,函数将引用做为参数。若是引用被修改,最初的参数变量也会修改。下面给出交换两个数的例子来讲明:

void swap(int &first, int &second)
{
    int temp = first;
    first = second;
    second = temp;
}

能够采用下面的方式调用这个函数:

int x = 5, y = 6;
swap(x, y);

    当使用x和y作参数调用函数swap()时,first参数初始化为x的引用,second参数初始化为y的引用。当swap()修改first和second时,x和y实际上也被修改了。

就像没法使用常量初始化普通引用变量同样,不能将常量做为参数传递给按引用传递参数的函数:

swap(3, 4);    //编译出错

(1) 指针转换为引用

    某个函数或者方法须要一个引用作参数,而你拥有一个指向被传递值的指针,这是一种常见的困境。在此状况下,能够对指针解除引用(dereferencing),将指针"转换"为引用。

这一行为会给出指针所指的值,随后编译器用这个值初始化引用参数。例如,能够这样调用swap():

int x = 5, y = 6;
int *px = &x;
int *py = &y;
swap(*px, *py);

(2) 按引用传递与按值传递

    若是要修改参数,并修改传递给函数或者方法的变量,就须要使用按引用传递。然而,按引用传递的用途并不局限于此。按引用传递不须要将参数副本复制到函数,在有些状况下

会带来两面的好处:

    (i)效率:复制较大的对象或者结构须要较长的时间。按引用传递只是把指向对象或者结构的指针传递给函数。

    (ii)正确性:并不是全部对象都容许按值传递,即便容许按值传递的对象,也可能不支持正确的深度复制(deep copying)。(若是须要深度复制,动态分配内存的对象必须提供自定

义复制构造函数。)

    若是要利用好这些好处,但不想修改原始对象,可将参数标记为const,从而实现按常理引用传递参数。按引用传递的这些优势意味着,只有在参数是简单的内建类型(int或double

),且不须要修改参数的状况下才应该使用按值传递。其余状况下都应该按引用传递。


1.4 引用做为返回值

    可让函数或者方法返回一个引用,这样作的主要做用是提升效率。返回对象的引用不是返回整个对象能够避免没必要要的复制。固然,只有涉及的对象在函数终止以后仍然存在的

状况才能使用这一技巧。(若是变量的做用域局限于函数或者方法,例如:堆栈中分配的变量,在函数结束时会被销毁。这个时候绝对不能返回这个变量的引用。)

    返回引用的另外一个缘由是但愿将返回值直接赋为左值(lvalue)(赋值语句在左边)。一些重载的运算符一般会返回引用。


1.5 使用引用仍是指针

    在C++中,引用有可能被认为是多余的:几乎全部使用引用能够完成的任务均可以用指针来代替完成。例如,能够这样编写swap()函数:

void swap(int *first, int *second)
{
    int temp = *first;
    *first = *second;
    *second = temp;
}

    然而,这些代码不如使用引用版本那么清晰:引用可使程序整洁并易于理解。此外,引用比指针安全:不可能存在无效的引用,也不须要显式地解除引用,所以不会遇到像指针

那样的解除引用问题。

    大多数状况下,应该使用引用而不是指针。对象的引用甚至能够像指向对象的指针那样支持多态性。只有在须要改变所指地址时,才须要使用指针,由于没法改变引用所致的对像

。例如,动态分配内存时,应该将结果存储在指针而不是引用中。须要使用指针的第二种状况是可选参数。例如,指针参数能够定义为带默认值nullptr的可选参数,而引用参数不能这样定义。

    还有一种方法能够判断使用指针仍是引用做为参数和返回类型:考虑谁拥有内存。若是接受变量的代码负责释放相关对象的内存,必须使用指向对象的指针,最好是智能指针,这

是传递拥有权的推荐方式。若是接受变量的代码不须要释放内存,那么应该使用引用。

    注意:若是不须要改变所指的地址,就应该使用引用而不是指针。

1.6 右值引用

    在C++中,左值(lvalue)是能够获取其地址的一个量,例如一个有名称的变量。因为常常出如今赋值语句的左边,所以称其为左值。另外一方面,全部不是左值的量都是右值(rvalue)

,例如常量值,临时对象或者临时值。一般右值位于赋值运算符的右边。

    右值引用是一个对右值(rvalue)的引用。特别地,这是一个当右值是临时对象时使用的概念。右值引用的目的是提供在涉及临时对象时能够选用的特定方法。因为知道临时对象会

被销毁,经过右值引用,某些涉及复制大量值的操做能够经过简单的复制指向这些值的指针来实现。

    函数能够将&&做为参数说明的一部分(例如 type&&name),来指定右值引用参数。一般,临时对象被看成const type&,但当函数重载使用了右值引用时,能够解析临时对象,

用于该重载。下面的示例说明了这一点。代码首先定义了两个incr()函数,一个接受左值引用;另外一个接受右值引用:

// Increment value using lvalue reference parameter.
void incr(int &value)
{
    cout << "increment with lvalue reference" << endl;
    ++value;
}
// Increment value using rvalue reference parameter.
void incr(int &&value)
{
    cout << "increment with rvalue reference" << endl;
    ++value;
}

    可使具备名称的变量做为参数调用incr()函数。因而a是一个具备名称的变量,所以调用接受左值引用的incr()函数。调用完incr()后,a的值将是11。

int a = 10, b = 20;
incr(a);//调用incr(int &value);

    还能够用表达式做为参数来调用inrc()函数。此时没法使用接受左值引用做为参数的incr()函数,由于表达式a+b的结果是临时的,这不是一个左值。在此状况下,会调用右值引用

版本。因为参数是一个临时值,当incr()函数调用结束后,会丢失这个增长的值。

incr(a + b); //将调用incr(int &&value);

    字面量也能够做inrc()调用的参数,此时一样会调用右值引用版本,由于字面量不能做为左值。

incr(3);    //将调用incr(int &&value);

    若是删除接受左值引用的incr()函数,使用名称的变量调用incr(),例如:incr(b),此时会致使编译错误,由于右值引用参数(int &&value)永远不会与左值(b)绑定。以下所示可

以使用std::move()将左值转换为右值,强迫编译器调用incr()的右值版本。当incr()调用结束后,b的值为21。

incr(std::move(b));    //将调用incr(int &&value);

    右值引用并不局限于函数的参数。能够声明右值引用类型的变量,并对其赋值,尽管这种用法并不常见。查下看以下代码:

int &i = 2;//invalid:reference to a constant
int a = 2, b = 3;
int &j = a + b;//invalid:reference to a temporary

    使用右值引用后,下面的代码彻底合法:

int &&i = 2;
int a = 2, b = 3;
int &&j = a + b;

    前面示例中单独使用右值引用的状况不多见。

相关文章
相关标签/搜索