c++中的引用

在c语言中我们在变量前加上&符号修饰,表示取地址操作,&是取地址操作。但是在c++中引入了新的特性,使用&表示引用,引用相当别名,与原型共用同样的存储空间。

引用可用于函数传参,赋值操作等,使用引用可以避免值的拷贝,使代码更加简洁的同时,对于提高程序的性能有一定的作用。在c++中引用就是某一个变量或者原型目标的一个别名,对引用的操作和对原型的操作是一样的。

一、引用的定义

#include <iostream>
#include <stdio.h>
using namespace std;


int main()
{
    int a = 5;//定义一个原型变量
    int a1 = 6;
    int &b = a;//定义一个a变量的引用
    int &f = a;//定义一个a变量的引用,一个变量可以有多个引用
    //b = &a1;//不允许,引用一旦初始化成某个变量的引用后,地址已经确定,不能修改成其他变量的引用
    
    printf("a的值为:%d\n",a);
    printf("a的值为:%d\n", b);
    printf("a的地址为:%p\n", &a);//此处的&是取地址符的作用
    printf("b的地址为:%p\n", &b);//此处的&是取地址符的作用
    /*输出:
        a的值为 : 5
        a的值为 : 5
        a的地址为 : 00D5FA14
        b的地址为 : 00D5FA14*/

    //int &c = 6;//报错,引用不能初始化成常量的引用
    const int &d = 6;//允许,const 修饰的常量引用可以初始化成常量的引用
    return 0;
}

引用的几个使用规则:

 (1)一个变量可以取多个别名,即定义多个引用去引用同一个变量。

 (2).引用定义时必须初始化。

 (3)引用只能在初始化时引用一次,初始化后不能修改为引用其他变量。

 (4)const修饰的引用可以初始化成常量引用。

引用的本质:

从代码中输出的引用地址和变量地址可以看出,引用的本质是一个指针常量,指向的是变量的地址,因此引用所占用的内存空间和原型变量的空间地址相同。

我们可以这样理解:int &c = a; 实质上可以认为等价于 int *const c = a;

当然,引用和指针存在一些不同点:

  (1)从内存空间上来说,系统会为指针分配内存空间,而指针的值保存了指向的变量的地址。而系统不会为引用分配空间地址,引用的内存空间地址是变量的地址。

  (2)普通指针初始化之后还可以修改指向的对象,而且可以在定义之后进行赋值。引用在定义时候必须初始化,初始化之后不能修改所关联的变量。

  (3)引用是直接访问变量的空间内容,指针是间接访问。

二、引用的一些用途

1.引用作为函数参数

在c语言中,我们通常使用值或者指针作为形参类型,我们知道值传递在执行函数调用时需要做值的拷贝,而通过指针可以传递一个原型的地址,在函数内部直接通过指针访问数据。避免将数据全部压入栈中,提高程序执行的效率。在c++中处理支持c语言中的值传递和指针方式,还可以使用引用作为函数的参数,也可以在函数内部直接访问外部堆栈的变量内容,也是一种高效的方式,同时可以使代码更加的简洁。

#include <iostream>
#include <stdio.h>
using namespace std;

void func_swap_val(int &src,int &dst)
{
    int temp = dst;
    dst = src;
    src = temp;
}

int main()
{
    //引用作为函数的参数
    int src = 5, dst = 7;
    printf("原来变量值:src=%d,dst=%d\n", src, dst);
    func_swap_val(src, dst);
    printf("后来变量值:src=%d,dst=%d\n", src, dst);
    //输出
    //    原来变量值 : src = 5, dst = 7
    //    后来变量值 : src = 7, dst = 5
    return 0;
}

通过指针方式传参

#include <iostream>
#include <stdio.h>
using namespace std;
void func_swap_val_1(int *src, int *dst)
{
    int ptemp = *dst;
    *dst = *src;
    *src = ptemp;
}

int main()
{
 //指针作为函数的参数
    int src1 = 5, dst1 = 7;
    printf("原来变量值:src1=%d,dst1=%d\n", src1, dst1);
    func_swap_val_1(&src1, &dst1);
    printf("后来变量值:src1=%d,dst1=%d\n", src1, dst1);
    //输出
    //    原来变量值 : src = 5, dst = 7
    //    后来变量值 : src = 7, dst = 5
    return 0;
}

引用作为参数和指针作为参数的不同点:

(1)引用作为参数的时候,在内存中不会产生实参的副本,直接对实参进行操作,节省内存的同时避免了的拷贝。在c++中如果通过值传参,发生函数调用时,需要给形参去分配存储空间,形参从实参拷贝。如果传递的时一个类的对象,还会调用拷贝构造函数。因此如果参数需要传递的内容不是基础变量时,使用引用不仅可以提升空间效率和时间效率。

(2)使用对象的指针也能够达到等价于引用传参的效果,调用时操作系统需要给指针分配空间,同时如果需要修改指针指向的对象的内容,需要通过指针修改对象的内容。相对来说,更推荐使用引用。

2、常引用(const修饰引用)

const修饰引用,表示引用时常量,不能修改引用的值。

#include <iostream>
#include <stdio.h>
using namespace std;
int main()
{//常引用
    int f1 = 5;
    const int rf1 = f1;
    //rf1 = 6;//报错
    f1 = 6;

    return 0;
}

3、引用作为函数的返回值

声明方法:int &func();表示函数的返回值是一个int类型变量的引用。

同样的,使用引用作为返回值,在函数返回时,直接返回的是引用,而不是值,不需要分配空间和拷贝副本。

#include <iostream>
#include <stdio.h>
using namespace std;
int total = 110;//定义一个全局变量
int &get_total_1();//声明函数,返回值类型是引用
int get_total_2();//声明函数,返回值是int类型的值

int &get_total_1()
{
    return total;
}

int get_total_2()
{
    int b = total;
    return b;
}int main()
{//引用作为函数的返回值
    int &t1 = get_total_1();//注意的是,引用作为返回值,如果返回一个局部临时变量的引用,可能出错甚至出现严重后果
    int t2 = get_total_2();
    printf("t1=%d,t2=%d\n", t1, t2);

    return 0;
}

使用引用作为函数返回值需要注意的问题:

  (1)不能返回局部变量的引用,否则程序运行调用函数时可能出现未知的后果。

  (2)不要返回在函数中new申请的变量,除非是为全局变量或者和调用层同等作用域的变量申请的,如果是new出来的局部变量,同样面临未知的问题。如果临时变量没有初始化成为实际的值,那么引用的值为空。

  (3)允许返回类的成员的引用,但建议使用的是const修饰引用,防止类的成员被修改。

4、引用作为函数返回值并作为左值使用

函数返回值是一个引用,并给这个引用赋值。

#include <iostream>
#include <stdio.h>
using namespace std;
int total = 110;//定义一个全局变量
int &get_total_1();//声明函数,返回值类型是引用
int get_total_2();//声明函数,返回值是int类型的值

int &get_total_1()
{
    return total;
}

int get_total_2()
{
    int b = total;
    return b;
}int main()
{//引用作为函数的返回值
    int &t1 = get_total_1();//注意的是,引用作为返回值,如果返回一个局部临时变量的引用,可能出错甚至出现严重后果
    int t2 = get_total_2();
    printf("t1=%d,t2=%d\n", t1, t2);
    //输出
    //    t1=110,t2=110

    get_total_1() = 100;
    printf("total=%d\n", total);
    //输出
    //    total = 100;

    return 0;
}

 

5、引用和c++的多态

c++中多态是指在不同继承关系的类对象中调用同一个函数,产生不同的行为。

例如:在一对继承关系的两个类,这两个类中都有一个成员函数,函数名、参数和返回值都一样,但是函数的内部处理逻辑不一样,然后我们通过调用函数实现了不同类的对象执行不同的操作。父类对象调用函数时执行的是父类的成员函数去处理,子类调用函数时主席那个的是子类的成员函数。

构成多态需要满足条件:

  (1)调用函数的对象必须是指针或者引用。

  (2)被调用的函数必须是虚函数,而且完成虚函数函数体的重写。

#include <iostream>
#include <stdio.h>
using namespace std;
class Human 
{
public:
    virtual void eat(int val)
    {
        cout << "humam need to eat " << val << endl;
    }
};

class ChineseHumam:public Human
{
    virtual void eat(int val)
    {
        cout << "ChineseHumam need to eat " << val << endl;
    }
};

void hunman_eat(Human &obj)
{
    obj.eat(110);
}

int main()
{
    Human n1;
    ChineseHumam n2;
    hunman_eat(n1);
    hunman_eat(n2);
    //输出
    /*humam need to eat 110
    ChineseHumam need to eat 110*/
    return 0;
}

从实例我们不难看出,同样是调用human_eat函数,传递的是Hunman对象和ChineseHuman对象,调用的eat函数不一样。

需要注意的是:

  (1)调用的同名、同参数和同返回值函数是human_eat。

  (2)被调用的成员函数必须是虚函数,因此函数需要用关键字virtual修饰。

  (3)调用函数的形参必须是基类对象,这里因为派生类只能给基类赋值,而基类不能给派生类赋值。所以函数的形参类型是基类类型。

  (4)调用成员函数必须是指针或者引用,因为派生类改变了虚表,这个虚表就属于派生对象了,赋值的时候只会把基类的成员给过去,虚表指针不会给到派生类。所以在调用成员函数的时候会发生语法检查,如果满足多态条件,触发需要虚表中的虚函数地址。不满足则直接用基类对象调用基类的函数。

此处主要是说明引用引用于多态中,对于多态的更多原理,将在专题中学习。

6、指针的引用

我们利用指针传递参数时,实质是将指针的副本传递给了函数,是指针的值的传递。但我们在函数内部修改形参的指针的值时,反向外部调用的实参的指针对应的值没有被改变。

#include <iostream>
#include <stdio.h>
using namespace std;
int total = 110;//定义一个全局变量
void set_val(int *p)
{
    p = &total;
}

int main()
{
    int tv = 55;
    printf("tv=%d\n", tv);
    set_val(&tv);
    printf("tv=%d\n", tv);
    //输出
    //    tv = 55
    //    tv = 55

    return 0;
}

从实例我们可以发现,函数set_val把形参指针的值修改了,但是对于外部而言,值依然不变,因为函数内部修改的形参指针是实参的副本。

如果我们通过指针的引用作为形参,则类似于二级指针的方式。

#include <iostream>
#include <stdio.h>
using namespace std;
int total = 110;//定义一个全局变量

void set_val(int *p)
{
    p = &total;
}

void set_val_1(int *&p)
{
    p = &total;
}

int main()
{
    int tv = 55;
    printf("tv=%d\n", tv);
    set_val(&tv);
    printf("tv=%d\n", tv);
    //输出
    //    tv = 55
    //    tv = 55
    int *ptv = &tv;
    printf("*ptv=%d\n", *ptv);
    set_val_1(ptv);
    printf("*ptv=%d\n", *ptv);
    //输出
    //    *ptv = 55
    //    * ptv = 110

    return 0;
}

 

posted on 2020-09-04 18:32  cycit  阅读(492)  评论(0)    收藏  举报

导航