- 引用铺垫:变量名
变量名实质上是一段连续存储空间的别名,是一个标号(门牌号),在程序中是通过变量来申请并命名内存空间的,通过变量的名字可以直接使用对应的内存空间。
对一段内存空间可以起除了变量名之外的其他别名,如果我们以“先入为主”的想法看待变量名和它对应的内存空间,那就是说:暂时地,这个变量名就是这段内存空间。
那么引用(对这段内存空间起了另外一个名字)的本质就是变量的别名。
- 在C++中增加了引用的概念:引用可以看做一个已定义变量的别名;
- 引用的语法: <类型名>& <别名> = <原变量名>;
- 引用做函數參數
-
普通的引用在声明时必须使用其他的变量进行初始化;
引用作为函数参数声明时不用进行初始化。
#include <iostream>
using namespace std;
void myswap(int &a, int &b)
{
int c = 0;
c = a;
a = b;
b = c;
}
int main()
{
int a = 10, b = 20;
cout << "a: " << a << "\tb: " << b << endl;
myswap(a, b);
cout << "a: " << a << "\tb: " << b << endl;
cout << "hello world.." << endl;
return 0;
}
- 复杂数据类型的引用
#include <iostream>
using namespace std;
struct Teacher
{
char name[64];
int age;
};
void printfT(Teacher *pT)
{
cout << pT->age<<endl;
}
void printT2(Teacher &pT)
{
cout << pT.age <<endl; // 因为是变量别名,就把引用看做一个变量
}
int main()
{
Teacher t1;
t1.age = 35;
printfT(&t1);
printT2(t1); // 引用做函数参数不用初始化,直接传过去
cout << "hello world ..." << endl;
return 0;
}
- 引用的意义
- 引用作为其他变量的别名存在,在一些场合可以直接代替指针使用;
- 引用相对于指针来说,对程序员具有更好的可读性(省去了输入星号*还有括号)和实用性;
- 引用的本质
- 5.1 引用在C++内部就是一个常指针——指向一个固定的内存空间
variableType &name <==> variableType * const name;
- 5.2 C++编译器在编译过程中使用常指针作用引用的内部实现,因此引用占用的空间大小和一个指针变量相同,通过代码的例子2有明确的演示
#include <iostream>
using namespace std;
struct Teacher
{
/***************例子2******************/
char name[64]; // 64byte
int age; // 4byte
double salary; //12byte
// 上面一共占80byte
int &a = age; // 8byte
// 再次测量共占空间88byte
};
int main()
{
int a = 10;
int &b =a;
printf("sizeof (Teache)i: %d\n", sizeof(Teacher));
/********例子1*****************************************/
printf("&a: %d\n", (int *)&a); /*********/
printf("&b: %d\n", (int *)&b); /*********/
/***************显示效果**********************************/
/***********&a: 1670882700
***********&b: 1670882700
*****************************************************/
/************a和b就是统一段内存空间的门牌号***********/
/*****************************************************/
cout << "hello world ..." << endl;
return 0;
}
- 5.3 从实用的角度,引用会让让人误会其只是一个别名,没有自己的内存空间,但其实这是C++为了实用性而做出的细节隐藏。
程序员只需要给出实参变量,编译器代替我们把定义指针和建立关联一起做掉了。
void func(int &a) | void func(int *const a)
{ | {
a = 5; | *a = 5;
} | }
- 使用引用
6.1 引用作为函数的返回值
结论:若函数内部的一个临时变量要作为引用返回,则这段代码是有风险的,尽量不要这么做。
还是到代码里面说问题。
#include <iostream>
using namespace std;
int& getAA2()
{
int a;
a = 10;
return a; //函数在这里试图返回一个栈上的变量,而且返回类型是引用
}
int main()
{
int a2 = 0;
a2 = getAA2();
int &a3 = getAA2();
printf("a2: %d\n", a2);
printf("a3: %d\n", a3);
cout << "Hello world..." << endl;
return 0;
}
运行结果:
- windows:
a2: 10
a3: 20655117
Hellod world...
请按任意键继续... - Ububtu:
Command terminated
Press ENTER or type command to continue
发现在g++编译器下代码无法通过编译,但是在VisualStudio 2015的编译器虽然通过,但是a3的值是一堆乱码。
首先说明,g++编译器在引用的上的检查比VS2015严格一些, 而且编译器提示:warning: reference to local variable ‘a’ returned 。
那么,VS2015中的乱码怎么出现?这需要绕一下来讲。
我们已经知道,引用的实质就是“常指针。”
在a2 = getAA2();这一次调用中,函数运行到return a;
a是一个int类型变量;
返回值类型为int&引用,则返回机制创建的副本变量为:int& tempVariable = a;(我们借助这个tempVariable可以获得较为直观的理解)
回到main函数初始的位置,a2 = getAA2(); 则即a2 = tempVarible; tempVariable是一个引用,本质是常指针,指向了存储函数的返回值的内存地址(这个地址是什么暂时不清楚,应该也不重要)。
在int& a3 = getAA2();这一次调用中,函数运行到return a;
a是一个int类型变量;
返回值类型为int&引用,则返回机制创建的副本变量为:int& tempVariable = a;(我们借助这个tempVariable可以获得较为直观的理解)
回到main函数初始的位置,int& a3 = getAA2(); 则a3中保存的实际是一个常指针,它指向局部变量a的地址,但是a现在已经被释放,原来地址中是一个随机的数值;
而在后面的打印指令中,编译器检测到要打印引用a3的值,那么自动转换为*(常指针),于是就取到了原来地址里面的随机数。
至于g++编译器,在检测到引用作为了右值之后,代码只要一执行函数对用的语句,就自动down掉了。
所以说一条保守的结论就是:不要使用引用对变量赋初始值。
当函数返回值为引用时:
若返回栈变量,1)不能作为其他引用的初始值;2)不能作为左值使用;
若返回静态变量或全局变量,既可作为右值使用,也可作为左值使用。
- 指针的引用
在这部分使用代码比较就可以看到使用指针引用的作用,它和C语言中的函数通过二级指针修改实参的内容做的是一样的事情。
struct Teacher
{
char name[64];
int age;
};
// 在被调用函数中获取资源
int getTeacher(Teacher **p)
{
int nRet = 0;
if (p == NULL) {
nRet = -1;
printf("func getTeacher():: detect that (p == NULL), error code: %d\n", nRet);
return nRet;
}
Teacher *tmp = NULL;
tmp = (Teacher *)malloc(sizeof(Teacher));
if (tmp == NULL) {
nRet = -2;
printf("func getTeacher():: detect that (tmp == NULL), error code: %d\n", nRet);
return nRet;
}
tmp->age = 33;
// *p是实参的地址 *实参的地址区间接地修改实参的值
*p = tmp;
return nRet;
}
int getTeacher2(Teacher * &myp)
{
// c++中指针引用作为函数的参数就是这种形式:类型 + &引用名
// Teacher *的意思就是说这是个Teacher类型的指针
int nRet = 0;
// 此处myp是实参的引用,给myp赋值就是给main函数中的pT1赋值
// 这里就是通过<指针的引用>达到了<二级指针>的效果
myp = (Teacher *)malloc(sizeof(Teacher));
if (myp == NULL) {
nRet = -1;
printf("func getTeacher2():: detects that (myp == NULL), error code: %d\n", nRet);
return nRet;
}
myp->age = 36;
return nRet;
}
可以看到,这两段函数实现的功能是一样的,但是代码量相比后者要比前者少一些。因此C++中的引用帮助程序员节省了一些时间和操作。
- 常引用
引用分为常引用和普通引用。
其中,普通引用的形式如下
int a = 10;
int &b = a;
常引用的形式则是
int x = 10;
const int &y = x;
常引用的作用是让变量拥有“只读”属性,不能用过引用去修改原来的变量的值。
常引用的初始化,有两种情况
1). 用变量初始化常引用
{
// 用变量去初始化常引用
int x1 = 30;
const int &y = x1;
}
2). 用常量去初始化常引用
{
const int a = r410; // c++编译器把a放在符号表中
int &m = 41; // 这个普通引用,引用的是一个字面量
// 这时,字面量只是一个值,它是没有内存空间的
// 但是引用的本质就是给内存起一个别名
// 所以上面的这行代码编译器不会编译通过,那么改怎么办?
// 答案:加一个const!
const int &m = 43;
// c++编译器是怎么工作的:
// 编译器会多分配一个内存空间,赋值43.再把这个内存空间起别名为:m
const int &m = 42;
}
---END