C++传值、传引用

简介: C++传值、传引用C++的函数参数传递方式,可以是传值方式,也可以是传引用方式。传值的本质是:形参是实参的一份复制。传引用的本质是:形参和实参是同一个东西。传值和传引用,对大多数常见类型都是适用的(就我所知)。

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

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
C++string类作为形参传值,实参与形参的变化
C++string类作为形参传值,实参与形参的变化
C++string类作为形参传值,实参与形参的变化
|
存储 Java C++
基于堆栈内存详析 Java函数形参是传值还是引用? | C++指针与Java引用的区别 | C++引用、指针等之间的区别 | C++与Java类的实例化的区别
基于堆栈内存详析 Java函数形参是传值还是引用? | C++指针与Java引用的区别 | C++引用、指针等之间的区别 | C++与Java类的实例化的区别
|
C++
Makefile第三讲:终端传值给Makefile、Makefile传值给C++代码
摘要 终端传值给Makefile,咋传?只需在终端输入以下命令,那么就可以在Makefile文件中放心大担的使用$(abcde)这个变量了,它的值为BBB Makefile   fun.h #include class Test { public: void static display(const char *str); }; fun.
3208 0
|
C++
C++:传值与传地址
    参数的传递方式有两种:传值和传地址。        传值 传值方式是将实参的值复制到形参中,因此实参和形参是两个不同的变量,各有各的存储空间,函数形参可以看做是函数的局部变量。
1122 0
|
11天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
51 18
|
11天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
37 13
|
11天前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
37 5
|
11天前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
27 5
|
11天前
|
Serverless 编译器 C++
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
33 4