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引用初始化后,将生成一个只读变量

原文地址:https://www.cnblogs.com/yuehouse/p/9774862.html