• C++对C语言的拓展(1)—— 引用


     1、变量名

    变量名实质上是一段连续存储空间的别名,是一个标号(门牌号);

    通过变量来申请并命名内存空间;

    通过变量的名字可以使用内存空间。

    2、引用的概念

    变量名,本身是一段内存的引用,即别名(alias)。引用可以看作一个已定义变量的别名

    引用的语法:Type & name = var;

    用法如下:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 int main(void)
     5 {
     6     int a = 10;//C编译器分配4个字节内存,a内存空间的别名
     7     int &b = a;//b就是a的别名
     8 
     9     a = 11;
    10     {
    11         int *p = &a;
    12         *p = 12;
    13         cout << a << endl;//12
    14     }
    15     b = 14;
    16     cout << "a=" << a << ",b=" << b << endl;//a=14,b=14
    17     return 0;
    18 }
    View Code

     3、规则

    (1)引用没用定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故类型与原类型保持一致,且不分配内存,与被引用的变量有相同的地址。

    (2)声明的时候必须初始化,一经声明,不可更改。

    (3)可对引用,再次引用,多次引用的结果是某一变量具有多个别名。

    (4)&符号前有数据类型时是引用,其它皆为地址。

     1 #include <iostream>
     2 using namespace std;
     3 
     4 int main(void)
     5 {
     6     int a,b;
     7     int &r = a;
     8     int &r = b;//error,不可更改原有的引用关系——规则(2)
     9     float &rr = b;//error,引用类型不匹配——规则(1)
    10     cout <<"&r="<< &r << ",&a=" << &a << endl;//变量与引用具有相同的地址——规则(1)
    11     int &ra = r;//可对引用再次引用,表示a变量有两个别名,分别是r和ra——规则(3)
    12 
    13     return 0;
    14 }

     4、引用作为函数参数

    普通引用在声明时必须用其它的变量进行初始化,引用作为函数参数声明时不进行初始化。

     1 #include <iostream>
     2 using namespace std;
     3 
     4 struct Teacher
     5 {
     6     char name[64];
     7     int age;
     8 };
     9 void printfT(Teacher *pT)
    10 {
    11     cout << pT->age << endl;
    12 }
    13 
    14 void printfT2(Teacher &pT)//pT是t1的别名,相当于修改了t1
    15 {
    16     pT.age = 33;
    17     cout << pT.age << endl;
    18 }
    19 
    20 void printfT3(Teacher pT)//pT和t1是两个不同的变量
    21 {
    22     cout << pT.age << endl;
    23     pT.age = 45;//只会修改pT变量,不会修改t1变量
    24 }
    25 
    26 int main(void)
    27 {
    28     Teacher t1;
    29     t1.age = 35;
    30 
    31     printfT(&t1);//35
    32 
    33     printfT2(t1);//33,pT是t1的别名
    34     printf("t1.age:%d
    ", t1.age);//33
    35 
    36     printfT3(t1);//33,pT是形参,t1拷贝一份数据给pT
    37     printf("t1.age:%d
    ", t1.age);//33
    38 
    39     return 0;
    40 }

    5、引用的意义

    (1)引用作为其它变量的别名而存在,因此在一些场合可以代替指针

    (2)引用相对于指针来说具有更好的可读性实用性

    c++中引入引用后,可以用引用解决的问题避免用指针来解决。

     1 #include <iostream>
     2 using namespace std;
     3 
     4 struct student
     5 {
     6     int age;
     7     char name[64];
     8 };
     9 void swap1(int *a, int *b) 
    10 {
    11     int temp;
    12     temp = *a;
    13     *a = *b;
    14     *b = temp;
    15 }
    16 
    17 void swap2(int &a, int &b)
    18 {
    19     int temp;
    20     temp = a;
    21     a = b;
    22     b = temp;
    23 }
    24 
    25 void printS1(struct student s)//子拷贝方式:student s=s1;结构体整个值拷贝的动作
    26 {
    27     cout << s.age << " " << s.name << endl;
    28 }
    29 
    30 void printS2(struct student *sp)//指针方式
    31 {
    32     cout << sp->age << " " << sp->name << endl;
    33 }
    34 
    35 void printS3(struct student &sp)//引用方式:student &s=s1;
    36 {
    37     cout << sp.age << " " << sp.name << endl;
    38 }
    39 
    40 int main(void)
    41 {
    42     int a = 10;
    43     int b = 20;
    44     swap1(&a, &b);
    45     cout << "a=" << a << ",b=" << b << endl;//a=20,b=10
    46 
    47     int c = 100;
    48     int d = 200;
    49     swap2(c, d);
    50     cout << "c=" << c << ",d=" << d << endl;//c=200,d=100
    51 
    52     student s1 = { 10,"zhang3" };
    53     printS1(s1);//10 zhang3
    54     printS2(&s1);//10 zhang3
    55     printS3(s1);//10 zhang3
    56     return 0;
    57 }

     6、引用的本质

     引用做参数传递时,编译器会替我们将实参取地址给引用,即:int &a = main :: &b;//a是b的引用;

    当对引用进行操作赋值时,编译器帮我们隐藏*操作,即:cout<<a其实是cout<<*a;*被编译器隐去了。

    思考一:C++编译器定义引用时,背后做了什么工作?

    引用所占大小与指针相同;常量要初始化,引用也要初始化,引用可能是一个常量;综上两点,引用可能是一个常指针

    • 当我们去研究引用的时候,可以将引用当作一个常指针去研究;
    • 当使用引用编程时,就把引用理解为变量的别名就可以了。

    思考二:普通引用有自己的空间吗?

    (1)引用在C++中的内部实现是一个常指针:Type & name  <===>  Type* const name;

    (2)C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同;

    (3)从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间,这是C++为了实用性而做出的细节隐藏。

    间接赋值的三个必要条件:

    • 定义两个变量(一个实参,一个形参);
    • 建立关联,实参取地址传给形参;
    • *p形参去间接的修改实参的值。

    引用在实现上,只不过是把间接赋值成立的三个必要条件的后两个合二为一了。

    当实参传给形参引用的时候,只不过是C++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)。

    7、引用作为函数的返回值(引用当左值)

    (1)当函数返回值为引用时,若返回栈变量,不能成为其它引用的初始值(不能作为左值使用);

     1 #include <iostream>
     2 using namespace std;
     3 
     4 int get1()
     5 {
     6     int a;
     7     a = 10;
     8     return a;
     9 }
    10 
    11 int& get2()//返回值为引用
    12 {
    13     int a;
    14     a = 10;
    15     return a;
    16 }
    17 
    18 int main(void)
    19 {
    20     int a1 = 0;
    21     int a2 = 0;
    22 
    23     a1 = get1();//值拷贝
    24     a2 = get2();//将一个引用赋给一个变量,会有拷贝操作,可以理解为:编译器类似做了如下隐藏操作,a2=*(get2())
    25     int &a3 = get2();//将一个引用赋给另一个引用作为初始值,由于是栈的引用,内存非法
    26     
    27     cout << a1 << endl;//10
    28     cout << a2 << endl;//10
    29     cout << a3 << endl;//10
    30     cout << a3 << endl;//不一定为10
    31 
    32     return 0;
    33 }

    (2)当函数返回值为引用时,若返回静态变量或全局变量,可以成为其它引用的初始值(可作为右值使用,也可作为左值使用)

     1 #include <iostream>
     2 using namespace std;
     3 
     4 int get1()
     5 {
     6     static int a;
     7     a = 10;
     8     return a;
     9 }
    10 
    11 int& get2()//返回值为引用
    12 {
    13     static int a;
    14     a = 10;
    15     return a;
    16 }
    17 
    18 int main(void)
    19 {
    20     int a1 = 0;
    21     int a2 = 0;
    22 
    23     a1 = get1();//值拷贝
    24     a2 = get2();//将一个引用赋给一个变量,会有拷贝操作,可以理解为:编译器类似做了如下隐藏操作,a2=*(get2())
    25     int &a3 = get2();//将一个引用赋给另一个引用作为初始值,由于是静态区域,内存合法
    26     
    27     cout << a1 << endl;//10
    28     cout << a2 << endl;//10
    29     cout << a3 << endl;//10
    30     cout << a3 << endl;//10
    31 
    32     return 0;
    33 }

    (3)引用作为函数返回值,如果返回值为引用可以当左值,如果返回值为普通变量不可以当左值。

     1 #include <iostream>
     2 using namespace std;
     3  
     4 int get1()//返回值为普通变量,函数当左值,返回的是变量的值
     5 {
     6     static int a=10;
     7     return a;
     8 }
     9 
    10 int& get2()//返回值为引用,返回的是变量本身
    11 {
    12     static int a = 10;
    13     return a;
    14 }
    15 
    16 int main(void)
    17 {
    18     int c1 = get1();//函数当右值
    19     cout << "c1=" << c1 << endl;
    20     
    21     int c2 = get2();//函数返回值是一个引用,并且当右值
    22     cout << "c2=" << c2 << endl;
    23     
    24    //get1()=100;//error,函数当左值
    25     get2() = 100;//函数返回值是一个引用,并且当左值
    26 
    27     c2 = get2();
    28     cout << "c2=" << c2 << endl;
    29 
    30     return 0;
    31 }

    8、指针引用

    指针是一个存放地址的变量,而指针引用指的是这个变量的引用,即对指针的引用,众所周知C++中如果参数不是引用的话会调用参数对象的拷贝构造函数,所以如果有需求想改变指针所指的对象(换句话说,就是要改变指针里面存的地址),就要使用指针引用。

     1 #define _CRT_SECURE_NO_WARNINGS
     2 #include <iostream>
     3 using namespace std;
     4 
     5 struct teacher
     6 {
     7     int id;
     8     char name[64];
     9 };
    10 
    11 int get_mem(struct teacher** tpp)
    12 {
    13     struct teacher *tp = NULL;
    14     tp = (struct teacher*)malloc(sizeof(struct teacher));
    15     if (tp == NULL)
    16     {
    17         return -1;
    18     }
    19     tp->id = 100;
    20     strcpy(tp->name, "li4");
    21 
    22     *tpp = tp;//tpp是实参的地址,*实参的地址去间接的修改实参的值
    23     return 0;
    24 }
    25 
    26 void free_teacher(struct teacher **tpp)
    27 {
    28     if(tpp==NULL)
    29     {
    30         return ;
    31     }
    32     struct teacher *tp = *tpp;
    33     if (tpp != NULL)
    34     {
    35         free(tp);
    36         *tpp = NULL;
    37     }
    38 }
    39 
    40 int get_mem2(struct teacher* &tp)//指针的引用做函数参数
    41 {
    42     tp = (struct teacher*)malloc(sizeof(struct teacher));//给tp赋值,相当于给main函数中的tp赋值
    43     if (tp == NULL)
    44     {
    45         return -1;
    46     }
    47     tp->id = 300;
    48     strcpy(tp->name, "wang5");
    49     return 0;
    50 }
    51 
    52 void free_teacher2(struct teacher *&tp)
    53 {
    54     if (tp != NULL)
    55     {
    56         free(tp);
    57         tp = NULL;
    58     }
    59 }
    60 
    61 int main(void)
    62 {
    63     struct teacher *tp = NULL;
    64     get_mem(&tp);//C语言中的二级指针
    65     cout << "id=" << tp->id << ",name=" << tp->name << endl;//id=100,name=li4
    66     free_teacher(&tp);
    67 
    68     get_mem2(tp);//指针的引用
    69     cout << "id=" << tp->id << ",name=" << tp->name << endl;//id=300,name=wang5
    70     free_teacher2(tp);
    71     return 0;
    72 }

     9、const引用

     const引用可以防止对象的值被随意修改。

    (1)const对象的引用必须是const的,将普通引用绑定到const对象是不合法的,原因:既然对象是const的,表示不能被修改,引用当然也不能修改,必须使用const引用。

    const int a=1;
    
    int &b=a;//这种写法是不合法的,编译不过。

    (2)const引用可使用相关类型的对象(常量,非同类型的变量或表达式) 初始化。这个是const引用与普通引用最大的区别。

    即:

    const int &a=2//是合法的;
    
    double a=3.14const int &b=a;//也是合法的
     1 #define _CRT_SECURE_NO_WARNINGS
     2 #include <iostream>
     3 using namespace std;
     4 
     5 int main(void)
     6 {
     7     //普通引用
     8     int a = 10;
     9     int &b = a;
    10     cout << "b=" << b << endl;//b=10
    11 
    12     //const引用
    13     const int c = 20;
    14     const int &d = c;//如果想对一个常量进行引用,必须是一个const引用
    15     cout << "c=" << c << endl;//x=20
    16     cout << "d=" << d << endl;//y=20
    17 
    18     //const引用
    19     int x = 100;
    20     const int &y = x;//相反,如果一个普通变量,用一个const引用接收是可以的
    21     cout << "x=" << x << endl;//x=100
    22     cout << "y=" << y << endl;//y=100
    23     
    24     x = 21;
    25     //y = 22;//error,常引用限制为只读,不能通过y去修改x的值
    26     cout << "x=" << x << endl;//x=21
    27     cout << "y=" << y << endl;//y=21
    28     
    29     return 0;
    30 }

    10、const引用的原理 

    const引用的目的是,禁止通过修改引用值来改变被引用的对象。const引用的 初始化特性较为微妙,可通过如下代码说明:

     1 #define _CRT_SECURE_NO_WARNINGS
     2 #include <iostream>
     3 using namespace std;
     4 
     5 int main(void)
     6 {
     7     double val = 3.14;
     8     const int &ref = val;
     9     double &ref2 = val;
    10     cout << "ref=" << ref<<",ref2="<<ref2 << endl;//ref=3,ref2=3.14
    11 
    12     val = 4.14;
    13     cout << "ref=" << ref << ",ref2=" << ref2 << endl;//ref=3,ref2=4.14
    14 
    15     return 0;
    16 }

    上述输出结果为 ref=3,ref2=3.14 和 ref=3,ref2=4.14。因为 ref 是 const 的,在初始化的过程中已经给定值,不允许修改。而被引用的对象是 val,是非 const 的,所以 val的修改并未影响ref的值,而 ref2 的值发生了相应的改变。

    那么,为什么非 const 的引用不能使用相关类型初始化呢?实际上,const引用 使用相关类型对象初始化时发生了如下过程:

    int temp = val;
    const int &ref = temp;

     如果 ref 不是 const 的,那么改变 ref 值,修改的是 temp,而不是 val。期望对 ref 的赋值会修改 val 的程序员会发现 val 实际并未修改。

    结论:

    1)const int & e 相当于 const int * const e

    2)普通引用 相当于 int *const e

    3)当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名

    4)使用字面量对const引用初始化后,将生成一个只读变量

  • 相关阅读:
    寻找重复数
    除自身以外数组的乘积
    汇总区间
    Atlas 分表功能
    Atlas 读写分离 & Atlas + MHA 故障自动恢复
    MHA 的 Binlog Server & VIP 漂移
    MHA 高可用介绍
    MySQL 主从复制(下)
    MySQL 基础面试题
    MySQL 主从复制(上)
  • 原文地址:https://www.cnblogs.com/yuehouse/p/9774862.html
Copyright © 2020-2023  润新知