欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++修炼篇:02 关于引用不可不知的问题

程序员文章站 2024-01-01 17:07:58
...

文章向导

引用的出场
引用与三目运算符
特殊的引用(const引用)
引用的本质是什么?
强制类型转换中的const引用


一、引用的出场
  
  众所周知,变量是一段实际连续存储空间的别名,编程者可通过变量的名字来访问这片存储空间。而引用则是为变量(对象)所起的一个别名,操作这个别名就等同于操作这个变量(对象)。
  定义一个引用的语法为:Type& name = var; 但需注意的是定义时必须用同类型的变量进行初始化,即如下所示:
  

int a = 0;
int &b = a; //b为a的别名
a = 2; //操作b就是操作a

  一旦引用初始化完成,引用将和它的初始值对象一直绑定在一起。因此无法令引用重新绑定到另外一个对象上去。从这里也就可以知道:引用本身不是一个对象,它需要绑定一个已存在的对象实体,故不能定义引用的引用。
  
  下面是一些错误例子,想必可以帮助大家更好的理解引用的使用。

int &refval = 10; //error,引用类型的初始值必须是个对象
double dval = 3.14;
int &refval1 = dval; //error,引用类型的初始化值不是int类型
int &refval2; //error,引用类型初始化时必须绑定已存在的对象实体

二、引用与三目运算符

int a = 1;
int b = 2;

(a < b ? a : b) = 3; //OK, 返回a或b的引用,可作为左值使用
(a < b ? 1 : b) = 3; //ERROR, 返回1或b的值,不能作为左值

  以上展示了两种三目运算符的使用情况,但其中一条是错的,另一条则是对的。具体来说可归结为如下结论:
  

  • 当三目运算符的可能返回值都是变量时,返回的是变量的引用。
  • 当三目运算符的可能返回值有常量时,返回的是值,故不能作为引用。

三、特殊的引用(const引用)

  在上一篇博文中,笔者提到过const常量的概念(因为const对象一经创建后就不能再改变,故创建时必须进行初始化)。那么可否把引用绑定到const对象上呢?答案是肯定的,且我们把此称之为对常量的引用。
  但与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象。下面来看两个具体的例子:
  

[例1]
int a = 4;
const int &b = a;
int *p = (int*)&b;

b = 5; //ERROR, 对常量的引用不能去修改其所绑定的对象
*p = 5; //OK, 修改变量a的值
[例2]
const int &b = 1; //使用字面值常量对const引用进行初始化,编译器会为常量值分配空间,并将引用名作为这段空间的别名。
int *p = (int*)&b;

b = 5; //ERROR, 对常量的引用不能去修改其所绑定的对象
*p = 5; //OK, 修改变量a的值

  说道这儿,我们已经接触到const常量特性、const引用特性、使用字面常量值对const引用进行初始化三种情况。三者其实有着类似之处,但也有着比较重要的差异,下面用一综合实例来进行分析:
  

#include <stdio.h>

/*C++中的 const 常量特性:可能为 a 分配内存空间,但不一定使用其值*/
void Demo_one()
{
    printf("C++的 const 常量特性:\n");

    const int a = 0;
    int* p1 = (int*)&a;

    printf("a = %d, *p1 = %d\n", a, *p1); //0 0
    *p1 = 2;
    printf("a = %d, *p1 = %d\n", a, *p1); //0 2
}

/*C++中的 const 引用特性:b 虽然是 c 的别名,但 b 这个别名的操作是只读的*/
void Demo_two()
{
    printf("C++的 const 引用特性:\n");

    int c = 2;
    const int& b = c; //b 为 c 的 const 引用
    int* p2 = (int*)&b;

    printf("c = %d, b = %d, *p2 = %d\n", c, b, *p2); //2 2 2
    b = 5; //error, 对常量的引用不能去修改其所绑定的对象
    *p2 = 5;
    printf("c = %d, b = %d, *p2 = %d\n", c, b, *p2); //5 5 5
    c = 6;
    printf("c = %d, b = %d, *p2 = %d\n", c, b, *p2); //6 6 6
}

/*使用字面常量值对 const 引用初始化*/
void Demo_three()
{
    printf("使用常量对 const 引用初始化:\n");

    const int& c = 1;
    int* p = (int*)&c;

    printf("c = %d, *p = %d\n", c, *p); //1 1
    //c = 5; //error
    *p = 5;
    printf("c = %d, *p = %d\n", c, *p); //5 5
}

int main(int argc, char *argv[])
{
    Demo_one();
    printf("\n");
    Demo_two();
    printf("\n");
    Demo_three();
    return 0;
}

四、引用的本质是什么?

  引用在C++中的内部实现是一个指针常量(指向不变,但指向的内容可变),这点要与常量指针(指向可变,但指向的内容是常量不可修改)进行区分。
  理解了这一点后,我们就可以得出如下一种等价形式:

      Type &name; <=> Type * const name;
      
  因此C++编译器在编译过程中用指针常量来作为引用的内部实现,这也就意味着引用所占用的空间大小与指针相同。但从我们使用者的角度来观察,引用只是一个别名,C++帮我们很好的隐藏了引用的存储空间这一细节。

  下面这个例子则很好的说明了引用的本质特性,读者可用心体会。

#include <stdio.h>

struct TRef
{
    char& r;
};

int main(int argc, char *argv[])
{
    char c = 'c';
    char& rc = c;
    TRef ref = { c }; //==> 初始化: char& r = c;

    printf("sizeof(char&) = %d\n", sizeof(char&)); //1
    printf("sizeof(rc) = %d\n", sizeof(rc)); // <==> sizeof(c) = 1
    printf("sizeof(TRef) = %d\n", sizeof(TRef)); //8
    printf("sizeof(ref.r) = %d\n", sizeof(ref.r)); // <==> sizeof(c) = 1

    return 0;
}

  应注意到sizeof(char&)与sizeof(TRef)的大小差别!!!


五、强制类型转换中的const引用

  此部分所要谈及的是C++新式类型转换中的const_cast强制类型转换。该转换用于去除变量的只读属性,同时强制转换的目标必须是指针或引用。
  

#include<stdio.h>

int main()
{
    const int& j = 1; //使用字面常量值对const引用进行初始化
    int& k = const_cast<int&>(j);
    const int x = 2; //真正意义上的常量
    int& y = const_cast<int&>(x); //为x分配空间并将y作为其空间别名
    //int z = const_cast<int>(x); //Error,必须指是指针或引用

    k = 5;
    printf("k = %d\n", k); //5
    printf("j = %d\n", j); //5

    y = 8;
    printf("x = %d\n", x); //2, 取的是符号表中的值
    printf("y = %d\n", y); //8,取的是栈空间中的值
    printf("&x = %p\n", &x);
    printf("&y = %p\n", &y);

    return 0;
}

【测试结果】
C++修炼篇:02 关于引用不可不知的问题

参阅资料
primer c++ (第五版)
狄泰软件学院(C++深度解析教程)

上一篇:

下一篇: