全文目录
概述
众所周知C++参数传递有三种,分别问值传递、指针传递、引用传递。其实这只是三种表现形式罢了,归根结底其实本质上就只有一种,那就是实参初始化形参。我曾经一度以为传参实际上就是替换,彼时还是个没完全理解赋值和初始化真谛的萌新(虽然现在也没出新手村,哈哈)。
值传递(pass-by-value)
什么是变量?
在讨论值传递之前我们现在自问下什么是变量?有人说变量不是很简单么,比如说
int a; // a 就是个int类型变量,只是a没有初始化罢了
a = 1; // 此时 a 被赋值,已有初始值, 但这并不是初始化
int b = 2; // b被声明同时被初始化
int c = b; // 用 b 来初始化 c
那么变量是什么呢?变量就是内存单元的名字,这块内存单元存放的数据可以通过变量名来获取,称作变量的值。可以通过取地址(&)运算符来获取内存单元的地址,也就是变量的地址。
int main()
{
int a;
a = 1;
int b = 2;
int c = b;
std::cout << "&a: " << &a << std::endl
<< "&b: " << &b << std::endl
<< "&c: " << &c << std::endl;
}
/*
* (以下是输出部分)
* &a: 00AFF978
* &b: 00AFF96C
* &c: 00AFF960
*/
我们可以从输出的部分得知三个变量的地址是不同的,用一个变量去初始化另一个变量这两个变量的地址是不同的,将这个引申到函数参数值传递的情况。
值传递的例子
#include <iostream>
void FuncByValue(int nValue)
{
nValue++;
std::cout << "执行中 FuncByValue(int) 形参地址: " << &nValue << ", 形参自增之后的值:" << nValue << std::endl;
}
int main()
{
int nTestValue = 1;
std::cout << "调用前 FuncByValue(int) 实参地址: " << &nTestValue << ", 实参值:" << nTestValue << std::endl;
FuncByValue(nTestValue);
std::cout << "调用后 FuncByValue(int) 实参地址: " << &nTestValue << ", 实参值:" << nTestValue << std::endl;
}
/*
* (以下是输出部分)
* 调用前 FuncByValue(int) 实参地址: 003BFA90, 实参值:1
* 执行中 FuncByValue(int) 形参地址: 003BF9A4, 形参自增之后的值:2
* 调用后 FuncByValue(int) 实参地址: 003BFA90, 实参值:1
*/
由上例可知,实参nTestValue在调用函数前后地址并没有变化,形参和实参的地址不相同,说明了形参是实参的一份拷贝,是一个独立的变量,两者包含了不同的内存单元。 实参初始化形参后,形参的值改变了,在函数调用结束时实参的值并没有发生相应的变化,即通过值传递参数的方式,形参的改变并不能影响实参的改变。
指针传递(pass-by-pointer)
什么是指针?
我对指针的印象来源于我的大学C语言老师,当时在上指针那一章节的时候他在黑板上写了一句: 指针 = 地址 ,写完之后他把书一扔,然后说:“没了,我已经教完了”。当时听完直接愣住了(内心os 啊?),这个我预习的时候可不一样,指针可是很难的啊,怎么就一句话就没了。老师是幽默风趣的,也是渊博的,这点我从未怀疑,他平时上课经常性的上一半就开始讲他的故事,包括他的一些风流史(诸如去某地见女网友啥的),他是一个挺有意思的一个人,也正因为他这种教学风格也让我愿意学习,而不是换个地方玩手机。言归正传,指针 = 地址 这句确实无敌, 指针的全称是指针变量,只是简称指针罢了,指针变量跟整型变量等各种变量没有本质上的差别(都是变量肯定是本质上都一样,不然如何归类到变量!)。指针变量和常规变量不同的地方在于他们的类型和值的含义。我们来浅析一下:
int main()
{
int nTestValue = 1;
int* pValue = nullptr;
pValue = &nTestValue;
std::cout << "nTestValue的地址: " << &nTestValue
<< ",nTestValue的值: " << nTestValue << std::endl
<< "pValue的值: " << pValue
<< ", pValue所指向的值: " << *pValue << std::endl
<< "pValue本身的地址值: " << &pValue << std::endl;
delete pValue;
pValue = nullptr;
}
/*
* (以下是输出部分)
* nTestValue的地址: 0077FA54, nTestValue的值: 1
* pValue的值: 0077FA54, pValue所指向的值: 1
* pValue本身的地址值: 0077FA48
*/
首先我们来套用“指针 = 地址” 这个概念性公式:
int* pValue = nullptr; 声明了一个整型指针变量 pValue
pValue = &nTestValue; 把整型变量nTestValue的地址赋值给 pValue
后面一句是不是就是概念“指针 = 地址”的完美诠释。这样就可以理解为指针里面包含了一个内存单元的地址值,这个内存单元本身是有数据存在的。被变量 nTestValue 命名的这块内存单元,pValue 指向了它,换句话说 pValue 不管你这块内存单元以前叫啥名字,在我这我只要知道了你的地址就行了,我(pValue)通过间接寻址可以获取/修改这一内存单元的数据,这也是指针的灵活性的体现。
对于整型变量nTestValue 来说,通过变量名 (nTestValue )可以访问该内存单元的数据 1,通过取地址符号(&nTestValue )可以获取变量 nTestValue 的地址;而对于指针变量 pValue 来说,通过间接寻址运算符(*pValue )来获取内存单元的数据1,即返回的是该地址的变量的值,通过变量名(pValue)来获取pValue所包含的地址,指针变量也是变量同样也可以用取地址符来获取pValue本身的地址,即&pValue。
指针传递的例子
#include <iostream>
void FuncByPoint(int* pValue)
{
(*pValue)++;
std::cout << "执行中 FuncByPoint(int*) 形参地址: " << pValue << ", 形参自增之后的值:" << *pValue;
pValue++;
std::cout << ", 指针自增之后的地址: " << pValue << std::endl;
}
int main()
{
int nTestValue = 1;
int* pValue = &nTestValue;
std::cout << "调用前 FuncByPoint(int*) 实参地址: " << pValue << ", 实参值:" << *pValue << std::endl;
FuncByPoint(pValue);
std::cout << "调用后 FuncByPoint(int*) 实参地址: " << pValue << ", 实参值:" << *pValue << std::endl;
delete pValue;
pValue = nullptr;
}
/*
* (以下是输出部分)
* 调用前 FuncByPoint(int*) 实参地址: 006FF778, 实参值:1
* 执行中 FuncByPoint(int*) 形参地址: 006FF778, 形参自增之后的值:2, 指针自增之后的地址: 006FF77C
* 调用后 FuncByPoint(int*) 实参地址: 006FF778, 实参值:2
*/
由上例可知实参 pValue 的值在传入函数前后均未发生改变,形参和实参的值是一样的(此处指的是地址),指针传递的是一个地址值,相当于用一个指针去初始化另一个指针,形参和实参都指向同一个内存单元。形参的指针自增后,地址值发生了变化,但是不影响实参的地址,因为这是两个指针,所以说明指针传递本身也是一种值传递,只不过传递的不是寻常的值而是地址而已。通过间接寻址运算符来修改指针所指向的内存单元的数据,虽然这一步操作时发生在函数体内的(形参完成的),但是内存单元里面的数据确实是已经被修改了,所以在函数体结束之后修改依然有效,这就是为什么调用后实参的数据也被修改了的原因。因为实参pValue 从始至终都指向 nTestValue所命名的内存单元,只是这个内存单元内的值被函数形参的指针修改了。通过指针传递参数的方式,形参的改变并不会影响实参的改变(注意指针是地址),但改变形参指向的内存单元的数据时,实参所指的内存单元的数据也会发生改变。
引用传递(pass-by-reference)
什么是引用?
一句话概括 “引用 = 别名”。引用就是给一个变量取别名,C/C++ 可以通过typedef给表达式取一个类型别名,但两者还是有区别的,引用是个别名但同样也是个变量,且是一种类型;通过typedef定义的只是一个类型别名,此处并不是产生一个新的类型,大多数情况下是用来简写复杂的类型。
int main()
{
int nTestValue = 1;
int nOtherValue = 2;
int& nRefValue = nTestValue;
nRefValue = nOtherValue;
}
上面例子运行没问题,是不是有点诧异?造成诧异的原因可能是因为理解错了!!!首先引用声明时必须要初始化,引用只会在这个时候发生绑定,且只发生一次绑定不可更改绑定,其他任何时候都只是给引用变量赋值罢了。执行完之后变量 nTestValue 的值也是 2,nRefValue = nOtherValue;相当于是 nTestValue = nOtherValue;
引用和指针的异同
- 引用声明时必须初始化(声明即绑定),指针可以不初始化(野指针,不安全操作)
- 引用绑定后不可修改,指针可以随意改变指向
- 引用不能为空,指针可以为空
- 引用的创建和销毁不会调用对象的构造和析构函数(引用的高效性)
- 引用底层是用指针来实现的
个人浅析(感兴趣的可以另行深入研究):
(1). 引用声明时为什么必须初始化?因为引用是给另一个变量取别名,如果在声明时不绑定被取名对象变量何来引用呢?引用概念使然。指针只要在使用前赋值就行了。
(2). 引用绑定后就不能修改了,这是为什么?个人理解指针是弱绑定关系,引用是强绑定关系,如果可以修改的话,那还要指针干什么。(这段解释有点牵强了,不知道有没有懂哥有更充分的依据,评论区告知!)
(3). 依旧回归引用的概念。
(4). 这个是引用最重要的特性。产生了新的对象时构造函数才会被调用,引用的创建并不会产生新的对象,所以就不会调用构造函数。同理引用的销毁也不会删除被引用的对象,也就不会调用析构函数
(5). 在汇编代码中,两者处理方式都是一样的, 引用可以理解为需要初始化的指针常量,仅通过结果逆向推导出来的,并未深究。
引用传递的例子
#include <iostream>
void FuncByReference(int& nValue)
{
nValue++;
std::cout << "执行中 FuncByReference(int&) 形参地址: " << &nValue << ", 形参自增之后值:" << nValue << std::endl;
}
int main()
{
int nTestValue = 1;
int& nRefValue = nTestValue;
std::cout << "调用前 FuncByReference(int&) 实参地址: " << &nRefValue << ", 实参值:" << nRefValue << std::endl;
FuncByReference(nRefValue);
std::cout << "调用后 FuncByReference(int&) 实参地址: " << &nRefValue << ", 实参值:" << nRefValue << std::endl;
}
/*
* (以下是输出部分)
* 调用前 FuncByReference(int&) 实参地址: 00AFF8AC, 实参值:1
* 执行中 FuncByReference(int&) 形参地址: 00AFF8AC, 形参自增之后值:2
* 调用后 FuncByReference(int&) 实参地址: 00AFF8AC, 实参值:2
*/
由上例可知实参 nRefValue 在传入函数前后地址均未发生改变,说明形参和实参共用同一块内存单元。在函数体内对形参nValue自增的操作相对于是对实参 nRefValue 自增,在函数运行结束之后,实参的值依然被改变了。即通过引用传递参数的方式,形参的改变能影响实参的改变。
写在最后
小结
1. 通过值传递参数的方式,形参的改变并不能影响实参的改变
2. 通过指针传递参数的方式,形参的改变并不会影响实参的改变(注意指针是地址),但改变形参指向的内存单元的数据时,实参所指的内存单元的数据也会发生改变。
3. 通过引用传递参数的方式,形参的改变能影响实参的改变
因本人水平有限,错误之处在所难免,欢迎评论区交流指正。
标签:变量,int,C++,参数传递,引用,实参,nTestValue,浅析,指针 From: https://blog.csdn.net/weixin_41403580/article/details/136810011