网站首页 > 基础教程 正文
只为一份朦胧的美
在这个意象纷呈的季节
隔着一层薄薄的雾
感受你咫尺天涯的远
一、前言
本部分为C++重点概念理解系列中的第2节。本系列内容针对有一定基础的同学,新学习的同学关注"C++实战学习系列"或"C++学习刷题系列"。
二、主要内容
在C语言中,通过数据类型可以定义变量,也可以定义指针。因此,在定义函数的形参中,即可以是变量方式,也可以是指针方式。两者的区别在于:前者是按值传递数据,后者是按地址传递数据。那么,在C++语言中,为什么要引入引用?亦或引用是什么呢?老实的同学可能回答:引用是变量的别名。而深沉的同学会回答:使用方式像变量,但本质像指针,具体说嘛,@#¥%...
在C++重点概念理解系列的第二讲,我们慢慢揭开披在引用身上的神秘面纱!
2.1向函数传递数据的方式
在调用函数时,需要利用实参向函数传递数据。C语言可以采用按值传递和按指针传递两种方式。
1.按值传递数据,实现数据的单向传递
按值传递数据是一种最为常见的方式,如:
#include <iostream>
using namespace std;
int max(int x, int y) //按值传递数据
{
return x>y?x:y;
}
void main()
{
int a=10, b=20;
cout<<"a和b中的最大值为:"<<max(a,b) <<endl;
}
这个实例就是典型的按值传递数据:函数形参为整形变量a和b。因此,当执行函数调用时,会将实参变量a的值赋给形参变量x,将实参变量b的值赋给形参变量y。这一过程完全符合我们调用函数max处理数据的需求:将数据由调用函数传递给被调用函数这一单向需求。
存在的问题:当调用函数处理的数据具有双向需求时,这种按值传递数据的方式就存在明显的问题,如:
#include <iostream>
using namespace std;
void swap(int x, int y) //按值传递数据
{
int z=x; x=y; y=z;
}
void main()
{
int a=10, b=20;
cout<<"交换前的值:a="<<a<<",b="<<b<<endl;
swap(a, b);
cout<<"交换后的值:a="<<a<<",b="<<b<<endl;
}
输出结果:
交换前的值:a=10, b=20
交换后的值:a=10, b=20
我们调用函数swap的目的是想实现实参变量a和b内容的交换,然而实际并不能达到我们预期的结果。以按值传递数据的方式实现函数swap时,实参变量a与b和形参变量x与y是两组完全独立的变量,仅仅是在函数调用的那一时刻,将实参变量a和b的值分别复制给形参变量x和y。当赋值完成后,这两组变量没有任何关系。因此,在函数体内交换变量x和y的值,完全不影响到变量a和b的内容。
那么,怎样才能达到预期的双向数据传递需求呢?解决方案是采用按指针传递数据的方式。
2.按指针传递数据,实现数据的双向影响
#include <iostream>
using namespace std;
void swap(int *x, int *y) //按指针传递数据
{
int z=*x; *x=*y; *y=z;
}
void main()
{
int a=10, b=20;
cout<<"交换前的值:a="<<a<<",b="<<b<<endl;
swap(&a, &b);
cout<<"交换后的值:a="<<a<<",b="<<b<<endl;
}
输出结果:
交换前的值:a=10, b=20
交换后的值:a=20, b=10
当形参是指针变量x和y时,是将变量a和b的地址分别赋给指针x和y,即指针x和y分别指向变量a和b。在函数体内通过指针x和y交换数据,实际上交换的就是变量a和b的内容。因此,按指针传递数据,能达到双向传递数据的效果:调用函数将需要处理的数据传递给被调用函数,被调用函数对数据进行的修改也能反映给调用函数。(不理解指针的朋友,要关注即将推出的"C语言实战学习系列"或"C语言重点概念理解系列")
特别说明,按指针传递数据也能提高程序运行的效率,即不需要产生数据副本。但本讲主要是讲清楚C++引入引用的情景,以加深大家对引用本质的理解!
存在的问题:因为实参为地址,所以在实际项目中常常存在传递空地址(NULL)或无效地址的情况,给项目埋下极大的安全隐患!
例如:
#include <iostream>
using namespace std;
void swap(int *x, int *y) //按指针传递数据
{
int z=*x; *x=*y; *y=z;
}
void main()
{
int *pa, *pb;
swap(pa, pb); //传递无效地址
}
那么,能否让编译器地编译过程中保证不会发生传递空地址或无效地址的措施?
通过分析可知,当形参为指针时,调用函数的实参通有为两个形式:
其一:int int *pa, *pb; swap(pa, pb); //有可能发生空地址或无效地址
其二:int int a, b; swap(&a, &b); //不可能发生空地址或无效地址
其中,仅形式一可能发生传递空地址(NULL)或无效地址。原因在于实参也是一个指针,而指针本身就可能为空(NULL)或指向无效的存储空间。为什么形式二不会发生呢?原因在于只要存在变量的话,变量就一定具有一个存储空间,而存储空间一定会对应一个有效的地址。
至止,解决问题的关注点转变为:编译器能否只允许形式二的存在,而不允许形式一的存在?如果编译器能作如此保证的话,则问题就能完美解决!答案是肯定的,解决方案就是引用!
2.2引用的概念
引用的概念:引用是C++编译器自动间接引用的常量指针。可以如下几点深入理解:
1.引用在本质上是一个常量指针。因此,引用在定义时必须进行初始化,并且初始化后不能再指向其它的变量。
2.引用在使用方式上与变量的使用方式一样,感觉就像变量的别名一样。但这种表象是由编译器帮我们实现的。例如:
int m;
int &n = m;
n = 10;
当编译器进行编译时,会将代码解释成:
int m;
int * const n = &m;
*n = 10;
采用引用传递数据的代码如下:
#include <iostream>
using namespace std;
void swap(int &x, int &y) //按引用传递数据
{
int z=*x; *x=*y; *y=z;
}
void main()
{
int a=10, b=20;
cout<<"交换前的值:a="<<a<<",b="<<b<<endl;
swap(a, b);
cout<<"交换后的值:a="<<a<<",b="<<b<<endl;
}
由于形参是引用x和y,因此实参必须是变量的形式,即实参变量a和b。既然实参是变量a和b,则它们的地址就一定会存在。因此实质传递的地址就一定存在,并且有效!
上述代码,可以想象编译器会解释成如下形式:
#include <iostream>
using namespace std;
void swap(int * const x, int * const y)
{
int z=*x; *x=*y; *y=z;
}
void main()
{
int a=10, b=20;
cout<<"交换前的值:a="<<a<<",b="<<b<<endl;
swap(&a, &b);
cout<<"交换后的值:a="<<a<<",b="<<b<<endl;
}
最重要结论:当需要按地址传递数据时,尽量采用按引用方式进行!
2.3有奖问答
由上述可知,按指针传递数据时,可能会发生空指针或无效地址的情况,造成运行时的内存违规访问。而正常采用按引用传递数据可以解决这类问题。
有奖问题是:编译器欺骗我们,让我们认为引用就是变量的别名,从而隐藏引用的本质。那么,我们是否也能欺骗编译器,让按引用传递数据的方式照样会出错?请举出实例说明。
- 上一篇: C|函数指针和指针函数的联系与区别
- 下一篇: c语言-指针和引用的理解 c中指针和引用的区别
猜你喜欢
- 2024-10-09 探讨C++中引用变量的原理以及特点
- 2024-10-09 C++编程:函数参数类型-引用、指针、值
- 2024-10-09 C++ 引用,如何定义对象的常引用?linux C++第50讲
- 2024-10-09 深入浅出C++引用:理解与应用 c++引用的含义
- 2024-10-09 c++引用详解 c++引用怎么写
- 2024-10-09 c语言-指针和引用的理解 c中指针和引用的区别
- 2024-10-09 C++|指针,理解指针从为什么需要指针这种数据类型开始
- 2024-10-09 C|函数指针和指针函数的联系与区别
- 2024-10-09 const、指针、引用的关系 const与指针的关系
- 2024-10-09 C++ API设计:指针vs引用,应对空值、内存分配和可选参数选择之道
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)