C++传值、传引用

C++的函数参数传递方式,可以是传值方式,也可以是传引用方式。传值的本质是:形参是实参的一份复制。传引用的本质是:形参和实参是同一个东西。

传值和传引用,对大多数常见类型都是适用的(就我所知)。指针、数组,它们都是数据类型的一种,没啥特殊的,因此指针作为函数参数传递时,也区分为传值和传引用两种方式。

e.g.

void fun_1(int a);    //int类型,传值(复制产生新变量)
void fun_2(int& a); //int类型,传引用(形参和实参是同一个东西)
void fun_3(int* arr); //指针类型,传值(复制产生新变量)
void func_4(int*& arr); //指针类型,传引用(形参和实参是同一个东西)

如果希望通过将参数传递到函数中,进而改变变量的值(比如变量是T a,T表示类型),则可以有这2种方式选择:

  1. 传a的引用:void myfun(T& a)
  2. 传a的地址的值:void myfun(T* a)

传值方式

这是最简单的方式。形参意思是被调用函数的参数/变量,实参意思是主调函数中放到括号中的参数/变量。传值方式下,形参是实参的拷贝:重新建立了变量,变量取值和实参一样。

写一段测试代码,并配合gdb查看:

test.cc

#include <iostream>
using namespace std; void swap(int a, int b){
int temp;
temp = a;
a = b;
b = temp;
cout << a << " " << b << endl;
} int main(){
int x = 1;
int y = 2;
swap(x, y);
cout << x << " " << y << endl; return 0;
}
➜  hello-cpp git:(master) ✗ g++ -g test.cc
➜ hello-cpp git:(master) ✗ gdb a.out
(gdb) b main
Breakpoint 1 at 0x4008fa: file test.cc, line 13.
(gdb) r
Starting program: /home/chris/work/hello-cpp/a.out Breakpoint 1, main () at test.cc:13
13 int x = 1;
(gdb) s
14 int y = 2;
(gdb) p &x
$1 = (int *) 0x7fffffffdc58
(gdb) p &y
$2 = (int *) 0x7fffffffdc5c
(gdb) s
15 swap(x, y);
(gdb) s
swap (a=1, b=2) at test.cc:6
6 temp = a;
(gdb) p &a
$3 = (int *) 0x7fffffffdc2c
(gdb) p &b
$4 = (int *) 0x7fffffffdc28
(gdb)

可以看到,实参x和y的值为1和2,形参a和b的值都是1和2;而x与a的地址、y与b的地址,并不相同,表明形参a和b是新建里的变量,也即实参是从形参复制了一份。这就是所谓的传值

传指针?其实还是传值!

test2.cc

#include <iostream>
using namespace std; void test(int *p){
int a = 1;
p = &a;
cout << p << " " << *p << endl;
} int main(void){
int *p = NULL;
test(p);
if(p==NULL){
cout << "指针p为NULL" << endl;
}
return 0;
}

这次依然用gdb调试(不用gdb也可以,直接看运行结果):

➜  hello-cpp git:(master) ✗ g++ -g test2.cc
➜ hello-cpp git:(master) ✗ gdb a.out
(gdb) b main
Breakpoint 1 at 0x4009e0: file test2.cc, line 11.
(gdb) r
Starting program: /home/chris/work/hello-cpp/a.out Breakpoint 1, main () at test2.cc:11
11 int *p = NULL;
(gdb) s
12 test(p);
(gdb) p p
$1 = (int *) 0x0
(gdb) p &p
$2 = (int **) 0x7fffffffdc58
(gdb) s
test (p=0x0) at test2.cc:4
4 void test(int *p){
(gdb) s
5 int a = 1;
(gdb) p p
$3 = (int *) 0x0
(gdb) p &p
$4 = (int **) 0x7fffffffdc18
(gdb)

可以看到,main()函数内和test()函数内,变量p的值都是0,也就是都是空指针;但是它们的地址是不同的。也就是说,形参p只是从形参p那里复制了一份值(空指针的取值),形参是新创建的变量。

直接运行程序的结果也表明了这一点:

➜  hello-cpp git:(master) ✗ ./a.out
0x7fff2a329e24 1
指针p为NULL

传引用

传值是C和C++都能用的方式。传引用则是C++比C所不同的地方。传引用,传递的是实参本身,而不是实参的一个拷贝,形参的修改就是实参的修改。相比于传值,传引用的好处是省去了复制,节约了空间和时间。假如不希望修改变量的值,那么请选择传值而不是传引用。

test3.cc

#include <iostream>
using namespace std; void test(int &a){
cout << &a << " " << a << endl;
} int main(void){
int a = 1;
cout << &a << " " << a << endl;
test(a);
return 0;
}

再次开gdb调试(依然是多此一举的gdb...直接运行a.out看结果就可以):

➜  hello-cpp git:(master) ✗ g++ -g test3.cc
➜ hello-cpp git:(master) ✗ gdb a.out
(gdb) b main
Breakpoint 1 at 0x4009af: file test3.cc, line 8.
(gdb) r
Starting program: /home/chris/work/hello-cpp/a.out Breakpoint 1, main () at test3.cc:8
8 int main(void){
(gdb) s
9 int a = 1;
(gdb) s
10 cout << &a << " " << a << endl;
(gdb) s
0x7fffffffdc44 1
11 test(a);
(gdb) s
test (a=@0x7fffffffdc44: 1) at test3.cc:5
5 cout << &a << " " << a << endl;
(gdb) s
0x7fffffffdc44 1
6 }
(gdb)

直接运行./a.out的结果:

➜  hello-cpp git:(master) ✗ ./a.out
0x7ffec97399e4 1
0x7ffec97399e4 1

显然,形参a和实参a完全一样:值相同,地址也相同。说明形参不是实参的拷贝,而是就是实参本身。

简单实践-实现swap()函数

swap()函数用来交换两个数字。根据前面一节的分析和测试,可以知道,既可以用传值的方式(也即传指针)来实现,也可以用传引用的方式来实现。

代码如下:

myswap.cc

#include <iostream>
using namespace std; void myswap_pass_by_reference(int& a, int &b){
int t = a;
a = b;
b = t;
} void myswap_pass_by_pointer_value(int* a, int* b){
int t = *a;
*a = *b;
*b = t;
} int main(){
int a=1, b=2;
cout << "originally" << endl;
cout << "a=" << a << ", b=" << b << endl; myswap_pass_by_reference(a, b);
cout << "after myswap_pass_by_reference" << endl;
cout << "a=" << a << ", b=" << b << endl; myswap_pass_by_pointer_value(&a, &b);
cout << "after myswap_pass_by_pointer_value" << endl;
cout << "a=" << a << ", b=" << b << endl; return 0; }

程序执行结果:

originally
a=1, b=2
after myswap_pass_by_reference
a=2, b=1
after myswap_pass_by_pointer_value
a=1, b=2

真的理解了吗?

其实出问题最多的还是指针相关的东西。指针作为值传递是怎样用的?指针作为引用传递又是怎样用的?

首先要明确,“引用”类型变量的声明方式:变量类型 & 变量名

“指针”类型的声明方式:基类型* 变量名

所以,“指针的引用类型”应当这样声明:基类型*& 变量名

这样看下来,不要把指针类型看得那么神奇,而是把它看成一种数据类型,那么事情就简单了:指针类型,也是有传值、传引用两种函数传参方式的。

指针的传值

void myfun(int* a, int n)

指针的传引用

void myfun(int*& arr, int n)

update

考虑这样一个问题:写一个函数,遍历输出一个一维数组的各个元素。

第一种方法,数组退化为指针,传值。同时还需要另一个参数来指定数组长度:

void traverse_1d_array(int* arr, int n){
...
}

缺点是需要指定n的大小。以及,传值会产生复制,如果大量执行这个函数会影响性能。

另一种方式,传入参数是数组的引用。想到的写法,需要事先知道数组长度:

void traverse_1d_array(int (&arr)[10]){
...
}

缺点是需要在函数声明的时候就确定好数组的长度。这很受限。

还有一种方法。使用模板函数,来接受任意长度的数组:

template <size_t size>
void fun(int (&arr)[size]){
...
}

这种使用模板声明数组长度的方式很方便,当调用函数时,编译器从数组实参计算出数组长度。也就是说,不用手工指定数组长度,让编译器自己去判断。这很方便啊。用这种方式,随手写一个2维数组的遍历输出函数:

template<size_t m, size_t n>
void traverse_array_2d(int (&arr)[m][n]){
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
cout << arr[i][j] << ",";
}
cout << endl;
}
}

总结一下

普通类型,以int a为例:

void myfun(int a)    //传值,产生复制
void myfun(int& a) //传引用,不产生复制
void myfun(int* a) //传地址,产生复制,本质上是一种传值,这个值是地址

指针类型,以int* a为例:

void myfun(int* a)   //传值,产生复制
void myfun(int*& a) //传引用,不产生复制
void myfun(int** a) //传地址,产生复制,本质上是一种传值,这个值是指针的地址

数组类型,以int a[10]为例:

void myfun(int a[], int n) //传值,产生复制
void myfun(int* a, int n) //传值,产生复制,传递的数组首地址
void myfun(int (&arr)[10]) //传引用,不产生复制。需要硬编码数组长度
template<size_t size> void myfun(int (&arr)[size]) //传引用,不产生复制。不需要硬编码数组长度

reference

http://www.cnblogs.com/dolphin0520/archive/2011/04/03/2004869.html

http://www.cnblogs.com/yjkai/archive/2011/04/17/2018647.html

http://bbs.csdn.net/topics/390362450

C++传值、传引用的更多相关文章

  1. js中 函数参数的 传值/传引用 问题

    如果  传入function的参数是  (数值.字符串.布尔值) 此时是以 传值 的方式 进行. 如果  传入function的参数是  (数组.对象.其他函数) 此时是以 传引用 的方式 进行. 1

  2. Python参数传递(传值&传引用)

    # 测试参数是传值还是传引用def test(arg): print("test before") print(id(arg)) arg[1]=30 # 测试可变对象 # arg[ ...

  3. 【转载】Java是传值还是传引用

    1. 简单类型是按值传递的 Java 方法的参数是简单类型的时候,是按值传递的 (pass by value).这一点我们可以通过一个简单的例子来说明: /* 例 1 */ /** * @(#) Te ...

  4. Java中的值传递和地址传递(传值、传引用)

    首先,不要纠结于 Pass By Value 和 Pass By Reference 的字面上的意义,否则很容易陷入所谓的“一切传引用其实本质上是传值”这种并不能解决问题无意义论战中.更何况,要想知道 ...

  5. 从一次面试经历谈PHP的普通传值与引用传值以及unset

    关于这个概念一般都会在PHP的第一堂课说变量的时候给介绍,并且我以前还给其他PHPer介绍这个概念.但是作为一个工作一段时间的PHPer的我,竟然在面试的时候一下子拿不定主意最后还答错了,很觉得丢脸( ...

  6. php普通传值和引用传值 (相当通俗易懂的一篇讲解)

    首先,要理解变量名存储在内存栈中,它是指向堆中具体内存的地址,通过变量名查找堆中的内存; 普通传值,传值以后,是不同的地址名称,指向不同的内存实体; 引用传值,传引用后,是不同的地址名称,但都指向同一 ...

  7. PHP变量的传值和引用

    问题: 1.PHP变量的存储.取值方式如何? 2.变量赋值时,普通传值和引用传值分别是什么意思?有何区别? 3.unset被赋值的变量会对两种赋值后原值和新值的影响?   变量的存储.取值形式: 变量 ...

  8. PHP的普通传值与引用传值以及unset

    首先,要理解变量名存储在内存栈中,它是指向堆中具体内存的地址,通过变量名查找堆中的内存; 普通传值,传值以后,是不同的地址名称,指向不同的内存实体; 引用传值,传引用后,是不同的地址名称,但都指向同一 ...

  9. java集合中的传值和传引用

    在学习java集合过程中发现了传值和传引用的区别: 我们来看下面两句话 ●java集合就像一种容器,我们可以把多个对象(实际上是对象的引用),丢进该容器.(来自疯狂java讲义) ●当使用Iterat ...

  10. Go语言的传值与传引用

    Go语言里的传值与传引用大致与C语言中一致,但有2个特例,map和channel默认传引用,也就是说可以直接修改传入的参数,其他的情况如果不用指针的话,传入的都是参数的副本,在函数中修改不会改变调用者 ...

随机推荐

  1. Linux命令之ln

    ln命令(是l不是i注意了) 用处:建立链接,分为软链接和硬链接 用法:下面讲 示例: 一.硬链接 我想给profile文件建立一个硬链接(硬链接就是把这个文件复制一份的意思) 硬链接hard建好了, ...

  2. 在tomcat集群环境下redis实现分布式锁

    上篇介绍了redis在集群环境下如何解决session共享的问题.今天来讲一下如何解决分布式锁的问题 什么是分布式锁? 分布式锁就是在多个服务器中,都来争夺某一资源.这时候我们肯定需要一把锁是不是 , ...

  3. CM记录-Hadoop 分布式文件系统HDFS(登录、配置、监控)

    1.登录(浏览器输入ip地址:7180,登录用户名和登录密码即可) 2.CM主界面(各个组件,监控图表,绿色代表运行正常.黄色代表运行不良,需要关注根据实际情况调整,红色代表故障,需要排查问题) 3. ...

  4. GYM 101173 F.Free Figurines(贪心||并查集)

    原题链接 题意:俄罗斯套娃,给出一个初始状态和终止状态,问至少需要多少步操作才能实现状态转化 贪心做法如果完全拆掉再重装,答案是p[i]和q[i]中不为0的值的个数.现在要求寻找最小步数,显然要减去一 ...

  5. .NET面试题系列(十)委托与事件

    委托 有了委托的存在,使得方法可以作为参数传递给另一个方法. int Max(int x,int y) { return x>y?x:y; } int Min(int x,int y) { re ...

  6. JacobMathType

    JACOB是一个 Java到微软的COM接口的桥梁.使用JACOB允许任何JVM访问COM对象,从而使JAVA应用程序能够调用COM对象,;MathType 是由美国Design Science公司开 ...

  7. IDAPython安装

    转载:All Right   (本人没有测试过) 关于IDAPython的安装教程网上的资料非常少,也不是很详细,我费了好长时间才装好,现在和大家分享一下. 注意事项 下面几点关系到安装是否成功 ID ...

  8. Linux - sort 排序

    -t # 指定排序时所用的栏位分隔字符 -n # 依照数值的大小排序 -r # 以相反的顺序来排序 -f # 排序时,将小写字母视为大写字母 -d # 排序时,处理英文字母.数字及空格字符外,忽略其他 ...

  9. Java SE之反射技术[Class](三)

    /** * * @author Zen Johnny * */ package com.cpms.test; import java.lang.reflect.Field; import java.u ...

  10. flash GC

    所有应用程序都要管理内存.应用程序的内存管理包括用于确定何时分配内存,分配多少内存,何时将内容放入回收站,以及何时清空回收站的准则.MMgc是 Flash Player用于几乎所有内存分配工作的通用内 ...