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 }
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\n", t1.age);//33 35 36 printfT3(t1);//33,pT是形参,t1拷贝一份数据给pT 37 printf("t1.age:%d\n", 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.14; const 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引用初始化后,将生成一个只读变量