1. 引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间, 使用方式和普通变量相同, 当原变量来对待 。比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
底层实现: 通过指针来实现,
2. 形式: 类型+& + 新名称 = 新值
void test(){
int a = 10;
int b = -1;
// ra引用, 是变量a的别名, a所代表的空间有两个名字, 代表还是原变量本身
int& ra = a;
ra = 20;// a 和 ra 都为 20
// 引用在定义之后就不会再改变指向了, 始终指向本体
int& rra = a;
rra = b; // a变为-1, 修改了值
}
注意: 引用类型必须和引用实体是同种类型的
3. 引用特性
void Test2()
{
int a = 10;
// int& ra; // 该条语句编译时会出错
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);
}
引用相当于是一个const指针, 引用的底层就是通过指针来实现的, 指向的空间不能变, ==> 类型* const --> 指向在定义时确定, 不会变, 从一而终(int* const) --> const的指针
而 const 类型* const --> 表示内容和指向都不能变(const int* const a = 5;)
void test3(){
// 常引用: 常引用可以指向const变量, 常量, 临时变量
const int c = 30;
const int& rc = c;
//rc = 100; // const引用的内容不能变 --> 常引用
const int& r1 = 10;
double d = 2.3;
ra = d; // a = 2
double& rd = d;
// int& rd2 = d; // 类型不一致
const int& rd2 = d;// 发生了隐式类型转换 生成了一个int的临时变量2, 这里的rd2不是d(double)的别名, 指向的是一个临时变量, 不是d, 因为临时变量具有常性,
int* pa = &a;
//const int& rpa = pa;// 当两个类型不支持隐式类型转换时 加了const也不行
}
4. 使用场景
// 交换两个数值的内容
void Swap(int* array, int i, int j){
// Swap(array, 2, 3) i和j相当于是地址, 索引 传的是数组
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
// 不仅传地址, 还传值,
void swap(int* a, int* b){
int tmp = *a;
*a = *b;
*b = tmp;
}
// 1. 引用做参数
// 这里的a, b为局部变量, 但是指向的是外部的本体, 局部变量交换内容也就相当于本体交换了
// 底部实现其实和swap是一样的(传指针一样) , 都是先取地址, 但是和传指针不同的是引用只传地址, 没有传内容(值),所以快
void swap2(int& a, int& b){
int tmp = a;
a = b;
b = a;
}
void test4(){
int a = 10;
int b = 20;
swap(&a, &b);
swap2(a, b);
}
// 2. 引用做返回值(不能返回临时变量(不能反悔生命周期小于函数生命周期的变量)
int& Add(int& ra){
return ++ra;
}
int& Add(int a, int b){
int c = a + b;
return c; // c为一局部变量, Add栈帧调用完了 之后就销毁了, 下一次再调其他的函数, ret指的就不是c了
}
void test5(){
int a = 10;
int& ra = Add(a);// 11
ra += 10;// 21
}
注意: 如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
5 传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
传值和指针在作为传参以及返回值类型上效率相差很大。相比之下, 引用比传值上效率更高,
6 引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
引用和指针的不同点:
// 对引用操作就是对本体操作, 对copy操作不会影响本体
struct B{
int _a;
int _b;
int _c;
};
void test6(){
B b; // 结构体变量
B& rb = b;// &b 和 &rb 的值是一样的
B* pb = &b;
B copy = b;// 结构体的副本, 克隆(双胞胎) ©值和&b不一样 另一个不同的空间
b._a = 10;
rb._a = 20;// 编译器在底层会翻译成指针的操作 rb->_a = 20
pb->_a = 40;// 这三个改的都是同一个空间的内容
// 这是另外一个新的空间
copy._a = 30;// 只影响copy中的内容, 不会影响b的内容
// rb->_a = 10; 不能这样写, 是错的, 只是底层实现
}
void test7(){
// 指针和引用的区别,
// 1. 引用在定义时必须初始化, 指针可以不用初始化,
int a = 10;
int& ra = a;
int* pa;
// 2. 只有一级引用, 没有多级引用, 指针可以有多级指针
int& ra2 = a;
int** pa2 = &pa;
int*** pa3 = &pa2;
// 3. 引用的自加是实体内容自加, 而指针自加是地址的偏移
ra++; // a的内容加1
pa++; // 向后偏移4个字节的空间
// 4. sizeof(引用)表实体的大小, sizeof(指针)表示指针的大小
B b;
B& rb = b;
B* pb = &b;
cout << "引用大小:" << sizeof(rb) << endl;// 求b的大小 = 12
cout << "指针大小:" << sizeof(pb) << endl;// 求指针的大小 = 4
// 5. 有空指针 , 但是没有空引用
//int& ra3 = NULL;
int* pc = NULL;
// 6. 引用更加安全, 不会有空异常
}
7. 引用的价值:
C++98中的指针空值
在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。NULL实际是一个宏,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:
void f(int){
cout<<"f(int)"<<endl; }
void f(int*){
cout<<"f(int*)"<<endl; }
int main(){
f(0);// f(int)
f(NULL); // f(int)
f((int*)NULL); // f(int*)
return 0;
}
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
注意: