引用

简单的来说,所谓引用就是给已定义变量重新起一个名字(起一个外号)。

创建一个引用变量

C++使用 &符号 作为引用(和取地址符是同一个符号)

int a ;
int & b = a;

上述语句就是给 int类型的变量a起了一个b的别名。
我们发现,当& 在等号左边(我们也可以叫其为 “左值”)的时候是作为引用出现的。当& 符号在等号右边(同样也可以称其为 “右值”)0的时候是作为取地址符出现的。


我们在使用引用的时候要注意几个要点:

1. 必须在声明引用时进行初始化。
int a;
int & b;
b = a;//不能这样做!

更加直白的说。和const指针很像(虽然还并没有说道const指针,可以暂时了解一下,等知道const指针后,再回来看看)
一旦引用和某个变量关联起来,就会一直在这个变量身边。
站在编译器的角度来理解就是:
int & b = a;//这是你写的语句
在编译器眼中:
int * const ptr = &a;//这是编译器中的语句
这个 *ptr 就和 b 是一样的。

2.修改了引用的值,变量的值也跟着修改。

简单的理解就是:我们给这个变量仅仅是起了另外一个名字,它还是它。无论是a还是b都是那个变量自己。

实际上,这个通过引用起的外号和原来的名字是指向同一块空间
image.png

3.引用必须指向一块合法的空间(不可以用NULL引用)。
void test()
{
   // int &ref = 10;//引用了 不合法的内存
    const int &ref = 10;//加入const,编译器处理方式:int tmp = 10; const int &ref = tmp;
    //tmp临时空间我们看不到
    //ref = 10;
    int* p = (int*)&ref;//通过指针找到这个内存空间
    *p = 10000;。//修改
    cout<<"ref = "<<ref<<endl;
}

以上 const int &ref = 10;是常量的引用。
如果加入了const就无法修改值。
尤其是void showValue(const int &val);如果编译器发现值被改变了,就会报错

4.引用不能重名

数组的引用

int arr[10];
for(int i = 0; i < 10; i++)
{
    arr[i] = i;
}
//给数组其别名的方式
int (&pArr)[10] = arr;

我们可以用上述的方式来给数组也起一个别名。

这里有一点需要注意:没有引用的数组!
这样做是不合法的,也是没有意义的。

有关引用的应用(*重点)

这里主要是说明引用和函数是如何配合使用的。

1. 将引用作为函数的参数

在这里要先回忆一下 函数的传值方式:

  1. 值传递
  2. 指针传递
  3. 引用传递(C++新增)

有关值传递,应该大学学C语言的时候老师就应该强调过了,在这里请回忆起来值传递为什么不能交换值的原因。做一个比喻:老师把复习的资料传给学生,学生们把资料修改了。但是老师那里的那份资料并没有被修改。
所以我们为了交换值,引入的指针传值,因为是对地址的操作所以值成功的交换了。
这里的引用传递,其本质还是对地址的操作,所以也是可以交换值的。

//值传递
void mySwap01(int a ,int b)
{
    int tmp = a ;
    a = b;
    b = tmp;
}
//地址传递
void mySwap(int* a ,int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
//引用传递
void mySwap03(int &a,int &b)//&a = a,&b=b
{//可以避免使用指针
    int tmp = a ;
    a = b;
    b = tmp;
}

其实有的时候,引用传值的还是有限制的。

int square(int &a)
{  //计算平方
    return a*a;
}

以上程序,如果我们在main中调用的时候
int b = square(x + 5)
其实这样做是不允许的,因为x+5并不是一个变量,这里的参数a不过只是一个别名。但是编译器不一定会报错,但是编译器会指出这一点:
Warning:Temporary used for parameter 'a' in call to square(int &)
我们可以解释一下编译器是怎么帮你渡过这个难关的:其实,这里编译器给你建立了一个临时变量,并将其初始化表达式 x+5 的值。然后,a成为了该临时变量的引用。这样就对这种“不检点的行为”睁一只眼闭一只眼。(其实编译器为你做了不少好事)
有关编译器为程序员创建临时变量的事情,详情参见《C++ Primer Plus》这里就不详细描述了。

2. 将引用用于结构体

引用的引入主要是为了这种用户自定义的类型。
在这个部分,要介绍三个点:

1.使用指向结构体的引用
2.将引用作为函数返回值
3.使用函数调用来访问结构体的成员

请看以下程序:

struct sys
{
    char a[10];
    char b[20];
    int c;
};
const sys & func(sys & s)
{
    s.c++;
    return s;
}

int main()
{
    sys s1 = 
    {
        "hello!",
        "world!",
        1
    };//给结构体赋值
    
    func(s1);
    
    sys copy;
    copy = func(s1); 
    
    return 0;
}

1.使用指向结构体的引用
func(s1);
该函数调用结构体s1 按引用传递 传给func()函数

2.将引用作为返回值
通常,返回机制将返回值复制到临时存储区中,随后调用程序将访问这片存储区。但是,如果是返回引用的话就不需要这个临时存储区,函数实际上是直接访问返回值。
通常,引用将指向传递给函数的引用,因此调用函数实际上直接访问了自己的一个变量。
copy = func(s1);
如果函数func()返回一个结构体,sys的内容被复制到一个临时的存储区中,然后返回存储单元的内容 复制给copy中。但是,由于是返回一个指向s1的引用,这种情况下s1的内容就被直接复制到copy中,也不需要临时存储空间。
这就是引用的优点:更高效
总结一下就是:返回引用的函数实际上被引用变量的别名
但是这里我们需要注意的是:避免返回当函数终止时已经不存在的内存单元,如果需要的话使用 new 动态开辟空间,记得 delete 或者 delete[] 释放。

3.使用函数调用来访问结构体的成员
如果我们想输出s1的值,我们可以这样做:
cout << func(s1).c
其实以上的语句 和 以下语句等价;
func(s1);
cout << s1.c << endl;

3.有关指针的引用

指针的引用这个概念是针对双指针而提出的。

struct Person
{
  int Age; 
};
void allocatMemory(Person ** p)// **p 具体的Person对象, *p 对象的指针, p指针的指针
{
    *p = (Person*)malloc(sizeof(Person));
    (*p)->Age = 100;
}
//利用指针引用来开辟空间
void allocateMemoryByRef(Person* &p)
{
    p = (Person*)malloc(sizeof(Person));
    p->Age = 1000;
}

这样对指针进行引用可以避免混淆。


Horizon
3 声望1 粉丝

今日潜修默进,是为来日天下行走