C++中的引用
标签: c++
1. 变量名回顾
+ 变量名实质上是一段连续存储空间的别名,是一个标号
+ 程序中通过变量来申请并命名内存空间
+ 通过变量的名字可以使用存储空间
2. c++的引用
引用是C++的概念,属于C++编译器对C的扩展。
int main()
{
int a = 0;
int &b = a; //int * const b = &a
b = 11; //*b = 11;
return 0;
}
/**
结论:请不要用C的语法考虑 b=11
*/
引用概念:
+ 在C++中新增加了引用的概念
+ 引用可以看作一个已定义变量的别名
+ 引用的语法:Type& name = var;
+ 引用做函数参数呢?(引用作为函数参数声明时不进行初始化)
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int &b = a;
//b是a的别名,请问c++编译器后面做了什么工作?
b = 11;
cout<<"b--->"<< a << endl;
printf("a: %d\n", a);
printf("b: %d\n", b);
printf("&a: %d\n", &a);
printf("&b: %d\n", &b); //对同一块内存空间可以取多个别名
system("pause");
return 0;
}
/**
b--->11
a: 11
b: 11
&a: 3798852
&b: 3798852
*/
结论:1. 普通引用在声明时必须用其它的变量进行初始化
为什么必须初始化? --> 结论:很像一个只读的常量
3. 引用的意义
1. 引用作为其它变量的别名而存在,因此在一些场合可以代替指针
2. 引用相对于指针来说具有更好的可读性和实用性
比较下面实现同样功能的代码:
#include <iostream>
using namespace std;
int swap(int &a, int &b)
{
int c = 0;
c = a;
a = b;
b = c;
}
int swap(int *a, int *b)
{
int c = 0;
c = *a;
*a = *b;
*b = c;
}
//引用和左值进行绑定时,将设计出高质量的程序
//引用像一个常量,能起到指针的作用
//引用和指针有关系吗?
//引用有内存空间
void main()
{
int a = 10;
int a1 = 20;
int &b = a; // type & 引用的名字 = 被引用的变量
char buf[100];
//在使用的时候,引用相当于变量的别名
b = 11; // *b = 11;
swap(a, b);
swap(&a, &b);
}
4. 关于引用
普通引用有自己的内存空间吗?回答是肯定的:
#include <iostream>
using namespace std;
struct Teacer {
int &a;
int &b;
};
int main()
{
printf("sizeof(Teacher) %d\n", sizeof(Teacer));
system("pause");
return 0;
}
/**
结果是:
sizeof(Teacher) 8
*/
我们可以得出结论:引用是一个有地址,引用是常量。
引用的本质:
+ 引用在C++中的内部实现是一个常指针
Type& name <--> Type* const name
+ C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
+ 从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏
请仔细对比间接赋值成立的三个条件:
1. 定义两个变量 (一个实参一个形参)
2. 建立关联,实参取地址传给形参: p = &a;
3. 利用*p形参去间接的修改实参的值
结论:引用在实现上,只不过是把间接赋值成立的三个条件的后两步合二为一。当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)。
5. 引用的细节问题
看下面的代码:
#include <iostream>
using namespace std;
int myf01()
{
int a;
a = 11;
return a;
}
int& myf02()
{
int a;
a = 11;
return a;
}
int& myf03()
{
int b;
b = 22;
return b;
}
void main()
{
int b1 = myf01();
printf("b1 = %d \n", b1);
int b2 = myf02(); //返回一个值,然后赋给b2
/**
还原现场是这样的:
b2 = p(p是a的别名),因此实现了赋值操作,执行完了这一步myf02()里面的变量才被清理
*/
printf("b2 = %d \n", b2);
int &b3 = myf03(); //b3是返回时的引用
/**
b3只是一个引用相当于int * const b3
b3并没有记录下a的值,只记住了地址,函数运行完成了之后myf03()里面的局部变量被清理了(也可能没有),有可能会出错
*/
printf("b3 = %d \n", b3); //b3是一个引用,c++编译器会帮我们执行*p操作
system("pause");
}
/**
结果如下:
b1 = 11
b2 = 11
b3 = 22
其实b3大多时候应该是一个未知的数,不过本人的64位机器上运行正常,这是因为还没有清理内存的缘故。
*/
如果函数里面的值生命周期比较长,结果会有所不同:
#include <iostream>
using namespace std;
int& myj01()
{
static int a; //a分配在全局区,生命周期和程序一致
a = 10;
return a;
}
void main()
{
int b1 = myj01();
printf("b1 = %d \n", b1);
int b2 = myj01(); //返回一个值,然后赋给b2
printf("b2 = %d \n", b2);
int &b3 = myj01(); //b3是返回时的引用
printf("b3 = %d \n", b3); //b3是一个引用,c++编译器会帮我们执行*p操作
system("pause");
}
/**
结果如下:
b1 = 10
b2 = 10
b3 = 10
原因也很简单,那就是函数myj01运行完后a并未被清理掉,因此不会出错。
*/
当然,也可以这样:
#include <iostream>
using namespace std;
int& myA(int &a)
{
a++;
return a;
}
void main()
{
int b = 10;
int b1 = myA(b);
int b2 = myA(b);
int b3 = myA(b);
printf("b1 = %d \n", b1);
printf("b2 = %d \n", b2);
printf("b3 = %d \n", b3); //b3是一个引用,c++编译器会帮我们执行*p操作
system("pause");
}
/**
结果如下:
b1 = 11
b2 = 12
b3 = 13
解释和前面的例子是一样的。
*/
我们需要特别注意下面的代码:
#include <iostream>
using namespace std;
struct AdvTeacher
{
char name[32];
int age;
};
void getTeacher01(AdvTeacher **p)
{
AdvTeacher *ptmp = (AdvTeacher *)malloc(sizeof(AdvTeacher));
ptmp->age = 30;
*p = ptmp;
}
//这个是结构体变量指针的引用,指针的引用
//p是t2的别名
void getTeacher02(AdvTeacher *&p) //和上面函数的作用一致
{
/**
&p <--> * const p
*/
p = (AdvTeacher *)malloc(sizeof(AdvTeacher));
p->age = 30;
}
//如果不加引用,那么myT3会拷贝给p,修改p和myT3没有任何关系
//加了引用,p是myT3的别名,修改p和修改myT3一样
void getTeacher03(AdvTeacher &p) //和上面函数的作用一致
{
p.age = 11;
}
void main()
{
AdvTeacher *t1 = NULL;
AdvTeacher *t2 = NULL;
getTeacher01(&t1);
getTeacher02(t2);
{
AdvTeacher myT3;
myT3.age = 10;
getTeacher03(myT3);
}
system("pause");
}
引用可以做左值:
#include <iostream>
using namespace std;
//static int a = 10;将a变成一个状态变量
//a初始化的时候为10,只会初始化一次
int& myg()
{
static int a = 10; //分配在全局区
printf("a = %d\n", a);
return a;
}
void main()
{
myg() = 11; //引用当做左值
myg();
system("pause");
}
/**
结果如下:
a = 10
a = 11
*/
常引用:
+ 常引用 Const int &e 相当于 const int * const e
+ 普通引用 int &e 相当于 int *const e
有两种方法对常引用进行初始化:
+ 让变量初始化const引用如
int a = 10;
const int &b = a;
+ 使用字面量初始化const引用
const int &c = 10;
虽然这在普通引用里不行,但在常引用里面行得通。当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名,使用常量对const引用初始化后将生成一个只读变量。
#include <iostream>
using namespace std;
struct AdvTeacher
{
char name[32];
int age;
};
//const &
void getTeacher(const AdvTeacher &p) //和上面函数的作用一致
{ //只能读,不能写
//p.age = 11; //常引用不能被修改
printf("age = %d \n", p.age);
}
//1. const引用的作用,让变量所指向的内存空间只读
//2. 给const引用初始化,有两种方法
//让变量初始化const引用
void main()
{
//int &a = 10; //10没法取地址,因此会出错
int b = 10;
const int &c = b;
//c = 11; //不能通过c去间接修改b
b = 12; //可以修改b的值
//const引用的第二种初始化方法
const int &d = 10; //对10另外分配一块内存空间
printf("&d = %d\n", &d);
system("pause");
}
/**
结果因人而异,不过肯定的是10分配了空间
&d = 1506876
*/
6. c++引用需要注意的地方
当函数返回值为引用时
+ 若返回栈内变量的引用,则不能用其成为其它引用的初始值,且不能将其作为左值使用。
+ 若返回静态变量或全局变量,可以成为其他引用的初始值,即可作为右值使用,也可作为左值使用。