C++本质-引用
回顾指针、引出引用
在c语言中,使用指针,可以间接获取、修改某个变量的值
int main(){
int num = 10;
int *p = #
*p = 20;//改变指针的指向,这样就间接修改了num的值
return 0;
}
但是在C++中,使用引用(Reference)可以起到跟指针类似的功能
int main(){
int num = 10;
int &ref = num;//ref引用了变量num
ref = 20;//改变ref,就相当于改变num,也起到了间接修改num的功能
return 0;
}
定义
引用相当于变量的别名,后续对引用的任何操作,都相当于间接的对变量进行操作,引用变量时,用‘&’符号,而指针变量指向变量是,则用‘*’符号。下方是引用的一般说明例子
永远牢记:引用就是变量的别名,引用某个变量,可以理解为‘指向’某个变量!!!!!!!!!
int main(int argc, char **argv)
{
int age = 10;
int *P_age = &age;//这叫定义指针变量P_age,指向age的值
int &R_age = age;//这叫引用变量R_age,引用age的值
R_age = 20;//改变引用的值,就相当于 age = 20 因为R_age是age的别名
return 0;
}
引用的用法
1、可以利用引用初始化另一个引用,相当于某个变量的多个别名
int main(int argc, char **argv)
{
int age = 10;
int &R_age = age;//这叫引用变量R_age,引用age的值
R_age = 20;//改变引用的值,就相当于 age = 20 因为R_age是age的别名
//可以利用引用初始化另一个引用,相当于某个变量的多个别名
int &R_age_1 = R_age;//R_age_1也相当于age的别名
int &R_age_2 = R_age_1;//R_age_2同时也相当于age的别名
//从此,程序中对R_age和R_age_1和R_age_2操作,都相当于对age操作,也就是变量age有了多个别名
return 0;
}
2、通过程序实现两个数字的交换,从而体现出引用的方便之处
定义两个int型变量,int a = 10; int b = 20; 然后编写值交换函数,交换a和b的值
//值传递函数
void swap_val(int v1, int v2)
{
int temp = v1;
v1 = v2;
v2 = temp;
}
//指针传递函数,但编写很麻烦,调用也麻烦,还要加*和&
void swap_ptr(int *v1, int *v2)
{
int temp = *v1;
*v1 = *v2;
*v2 = temp;
}
//引用传递函数,编写方便,调用方便
void swap_ref(int &v1, int &v2)
{
int temp = v1;
v1 = v2;
v2 = temp;
}
int main(int argc, char **argv)
{
int a = 10;
int b = 20;
swap_val(a, b);
cout << "a=" << a << "b=" << b << endl;
//a=10 b=20 不会改变ab的值,因为我改变的是v1、v2的值,而v1、v2是局部变量,函数调用完之后自动销毁
swap_ptr(&a, &b);
cout << "a=" << a << "b=" << b << endl;
//a=20 b=10 因为改变的是地址的值,所以会改变地址指向的值
swap_ref(a, b);//进入引用函数,v1就是a的别名,改变v1就是改变a!!!恍然大悟
cout << "a=" << a << "b=" << b << endl;//a=20 b=10 交换了ab的值,而且函数编写和调用都非常简便
return 0;
}
对比上述几个交换函数,我们可以发现:引用传递的性质类似于指针传递,但是代码书写方式却类似于值传递。实际上,引用的能实现的功能,指针都可以实现,那为什么还要用‘引用’呢?(答案见第五节第一部分)
3、指针也可以被引用,叫做指向指针的引用
注意:引用某个变量时,变量是什么类型,这个引用就必须是什么类型
int main(int argc, char **argv)
{
//指针也可以引用!!!叫做指向指针的引用,但没有指向引用的指针
int value = 10;
int *p = &value;
int *&ref = p;//&ref左边是什么类型,这个引用就引用什么类型的变量,结果就是ref就是p的别名
*ref = 30;//相当于*p=30,因为ref从上一课开始已经是指针p的别名了;即赋值30给p指向的变量,即value
//通过引用,间接修改指针的指向
int height1 = 20;
ref = &height1;//相当于p=&height;即修改了指针的指向!
return 0;
}
4、在定义时,必须初始化,一旦指向了某个变量,就不可以改变
int main(int argc, char **argv)
{
int value = 10;
int height = 20;
int &ref = value;
&ref = height;//会报错,因为ref引用了value之后,就不可以再引用别的变量了
//引用定义时就必须初始化,所以下面的书写是不对的
int &ref;
&ref = value;
return 0;
}
5、重新赋值指针和引用,对所指向变量的影响
对指向源变量的指针重新赋值,不会改变原变量的值,对引用源变量的引用重新赋值,会改变原变量的值(对指针重新赋值是改变指针的指向,可不是对指针指向的内存区域重新赋值)
int main(int argc, char **argv)
{
int height = 10;
int temp = 30;
int *P_height = &height; //指针指向height
int &ref_height = height; //引用指向height
P_height = &temp;//对指针重新赋值,意思是改变指针的指向,故不会改变原变量的值
//这里注意一个很重要的问题,我对P_height赋值&temp,也就是让P_heught指向temp,height不会改变,
cout << height << endl;//10
ref_height = temp;//对引用重新赋值,就会改变原变量的值
cout << height << endl;//30
return 0;
}
引用与指针的比较
1、怎样理解引用比指针更安全?
指针这个东西,用好了非常强大,但是用不好,造成的后果也是很严重的。指针可以指向任何内存空间,还可以对其进行赋值(这里说的是对指向的存储空间赋值),这样就很危险。万一这块存储空间存放着比较重要的数据,如果可以随意更改的话,就会出事。而引用却不同,引用只能指向一个变量,不会像指针一样可以乱指,所以对比指针比较安全。
2、一些特性的比较
- 引用的代码书写更简单,而指针编写代码较为复杂
- 引用在创建的同时必须初始化,即必须引用一个有效的对象;而指针在定义时不必初始化,可以在定义后面的任何地方重新赋值
- 不存在引用=NULL,引用必须与合法的存储单元关联;而指针是可以=NULL的
- 引用一旦指向一个对象,它就不可以改变为另一个对象的引用(江湖人称“从一而终、忠贞不渝”);而指针在任何时候都可以改变指向。(注意:给引用赋值并不是改变引用与原变量的绑定关系,仅仅会改变原变量的值,引用依然是原变量的别名)
- 本质一些的东西:在代码层面,引用的用法和对象一样;但在汇编层面,引用一般都是通过指针来实现的,只不过编译器帮我们完成了转换
const引用的那些事
1、定义
一旦const引用一个对象,这个对象可以访问,但不能修改,即无法间接的修改原对象了。
引用可以用const修饰,这样就无法通过引用修改数据了,被称作常引用,那有什么意义呢?
一般这种const int &ref 通常会在函数的形参里出现,常引用的目的是:保证传进来的数据是只读的,即我只能读取到ref的数据,不可以对其进行修改。是有应用场合的!!!
2、const指针(非常重要)
用于牢记,const只修饰右边的部分,只要不单独修饰的部分,都可以更改!!!!
int *const p 和 int const *p的区别
int main(int argc, char **argv)
{
int height1 = 10;
int age1 = 20;
int * const p1 = &height1;//const修饰p,意味着p不可以被赋值,也就意味着指针p不可以更改指向
p1 = &age1;//报错
*p1 = 20;//不报错,这是因为const并没有修饰到*p,所以*p可以被赋值,也就意味着可以间接修改height值
cout << height1 <<endl; //20
int height2 = 10;
int age2 = 20;
int const *p2 = &height2;// const修饰*p2,意味着*p2无法赋值,但p2可以被赋值,
//也就意味着可以更改指向,但不能更改指向的数据
*p2 = 20;//报错
p2 = &age2;//不报错
return 0;
}
3、const引用的规则
const必须写在‘&’符号前面才能算常引用
int main(int argc, char **argv)
{
int height1 = 10;
int age1 = 20;
int & const ref1 = height1;//const修饰ref1,意味着ref1不可以更改指向,而引用本来就不可以更改指向
//int &ref1 = &height1;//与上行代码效果相同
ref1 = 20;//不会报错
//常引用
int const &ref2 = age1;//const修饰的是整个&ref2,所以引用变量此时不可以间接修改原变量的值
ref2 = 30;//报错
return 0;
}
4、const引用的用武之地
想要函数的形参既可以接收变量传入,也可以接受常量传入,这时候就要用到const引用
const引用作为函数形参时,可以接收“实参”的范围比较大,在面向对象中用处很多
//const引用作为函数形参
//这样的话,sum传常量和变量都可以了,这个非常重要!!!!
int sum(const int &v1, const int &v2)
{
return v1 + v2;
}
int main(int argc, char **argv)
{
int a = 10;
int b = 20;
//const引用作为函数形参数,实参可以是常量(const实参)也可以是变量(非const实参)
sum(a, b);//非const实参
sum(1, 2);//const实参
return 0;
}
const引用虽然不可以通过引用间接修改原变量的值,但是可以直接通过修改原变量的值,达到修改引用的指向
int main(int argc, char **argv)
{
//const引用虽然不可以通过引用修改所指向的值,但是依然可以修改变量达到修改所指向的值
int value_int = 10;
const int &ref_value_int = value_int;
ref_value_int = 20;//会报错!!!,因为const引用不允许通过修改引用来间接修改变量值
value_int = 20;//不管引用的事,我依然可以随意更改原变量,不会报错
cout << "ref_value_int=" << ref_value_int << endl;//20,此时引用指向的数值被改变了
return 0;
}
常引用const&,可以指向不同数据类型的变量。但如果此时修改原变量的值,引用指向的内容不会发生变化(为什么呢?)
int main(int argc, char **argv)
{
int value_int = 10;
const double &ref_value_double = value_int;//const double类型引用int型变量
value_int = 30;//改变原变量的值
cout << value_int << endl;//30
cout << ref_value_double << endl;//10,即使修改了原变量的值,引用依然指向初始值10
return 0;
}
因为:当const引用指向了不用数据类型的变量时,会产生一个匿名的临时变量,所以上面的代码有一些隐藏代码
int main(int argc, char **argv)
{
int value_int = 10;
int temp = value_int ;//temp = 10,这句是隐藏代码,编译器自动生成的
const double &ref_value_double = temp;//
value_int = 30;//即使改变原变量的值,也不会改变引用的指向
cout << value_int << endl;//30
cout << ref_value_double << endl;//10,即使修改了原变量的值,引用依然指向初始值10
return 0;
}
补充的小tips
1、如果查看引用所占字节数
我们通常的想法就是直接用sizeof(引用)来判断,但这是不准确的,这种问题,很考验本质的。首先我们要知道一件事,就是CPU架构在x86和x64环境下,指针存储字节是不一样的。在x86,指针占4个字节,在x64,指针占8个字节。(注意:引用和指针所占字节数是相同的)
struct test
{
int &ref;
};
int main(int argc, char **argv)
{
int height = 10;
int &ref_height = height; //引用指向height
cout << sizeof(ref_height) <<endl; //我们通常会这么干,但这是不准确的
//为什么不能这么测试引用所占字节数呢?
//因为sizeof(ref_height)里面的ref_height其实就是height,所得到的结果其实就是height的所占字节数
//也就是int类型所占的字节数,也就是4
//那如何,合理的测试引用所占用的字节数呢?可以定义一个结构体,结构体内只存放一个引用变量
//然后,可以通过sizeof结构体,来侧面证明一下引用和指针所占字节数是相同的
cout << sizeof(test) <<endl; //8
return 0;
}
2、引用的总结
- 引用的本质就是指针 ,只是编译器弱化了它的功能,所以引用就是弱化了的指针,也叫假指针
- 指针可以随意乱指,但引用只能指向一个内存地址,从一而终;
- 指针和引用的相同点就是都可以间接的访问与修改变量值
- 为了能接受各种各样的参数,我们通常使用常引用作为函数形参