分享

与LSGO一起学“9 引用(9.10 用值来传递对象)”

 老马的程序人生 2020-08-17

9.10 用值来传递对象

从前面几节我们了解了按地址传递与按值传递的区别,按地址传递可以修改原始变量的值,按值传递由于是传递的原始变量的拷贝,因此它不会修改原始变量的值。

假如仅仅是传递变量的话,采用指针或者引用这种按地址传递方式的优势不是很明显,但是假如是传递较大的对象的话,这种优势是相当的明显的。

这是因为,按值传递在向函数传递一个对象时,会像传递变量那样建立一个该对象的拷贝,而从函数返回一个对象时,也要建立这个被返回的对象的一个拷贝。

这样假如该对象的数据非常多时,这种拷贝带来的内存开销是相当可观的。比如说该对象拥有1000多个double型成员变量,每个double型变量占据8个字节,1000个就要占据8000个字节,每次通过值传递的方式给函数该对象,都要在栈中复制该对象,占用8000个字节的栈内空间,而返回该对象,又要在栈中复制一次,这样就又要占用8000个字节的内存空间。我们知道栈的内存只有2M大小,8000个字节占用8K,那么仅仅传递该对象就占用了栈内16K字节的空间。并且别的对象想要访问该对象的8000个数据成员的时候,也要同样采用复制的方式,那么系统的开销将无法估算了。

注:B = 字节,1KB = 1024B、1MB = 1024KB、1GB = 1024MB

然而,按值传递所付出的开销远不止如此,由于在传递过程中需要复制对象,因此会默认调用复制构造函数该函数的作用就是创建某个对象的临时拷贝。关于复制构造函数,将会在深入函数中做进一步讲解,这里你只需要知道,只要在栈中创建临时拷贝都会自动调用复制构造函数即可。

而当函数返回时,传递该对象时创建的该对象的拷贝会被删除,这时候又会自动调用该对象的析构函数来释放内存。假设返回的仍然是该对象,并且仍旧采用按值传递的方式,那么就又会调用复制构造函数建立一个该对象的临时拷贝,当该值被成功返回给调用程序后,然后再调用该对象的析构函数删除临时拷贝并释放内存(由于对象都在栈区创建,一切都由系统自动完成。)。

我们看到复制构造函数和析构函数一连被执行了两次,这无疑会增加系统的开销。我们用一个实例来演示一下按值传递一个对象的复制与删除过程。

//A.h

#pragma once

#include<iostream>

using namespace std;

class A

{

public:

    A();

    A(A&);//复制构造函数

    ~A();

};

//A.cpp

#include "A.h"

A::A()

{

    cout<<"执行构造函数创建一个对象"<<endl;

}

A::A(A&)

{

    cout<<"执行复制构造函数创建该对象的副本"<<endl;

}

A::~A()

{

    cout<<"执行析构函数删除该对象"<<endl;

}

//Program.cpp

#include<iostream>

#include"A.h"

using namespace std;

A func(A one)

{

    cout<<"&one:"<<&one<<endl;

    return one;

}

int main()

{

    A a;//在这一行定义了一个类A的对象a

    cout<<"&a:"<<&a<<endl;

    func(a);

    cout<<"Main finish"<<endl;

    return 0;

}

    A a;//在这一行定义了一个类A的对象a

创建一个对象会自动调用构造函数。

由于构造函数中加入了一条输出信息,所以我们看到输出“执行构造函数创建一个对象”。

   func(a);

接下来将对象a按值传递到func函数中,这时会自动调用复制构造函数创建对象a的一个副本,然后将这个副本传递到func函数中去。

所以我们看到输出了“执行复制构造函数创建该对象的副本”,以证明复制构造函数被调用。

A func(A one)

{

    cout<<"&one:"<<&one<<endl;

    return one;

}

当我们将这个副本传递到func函数中,func函数又将接收到的副本返回了。

由于返回方式也是按值返回,所以又会调用复制构造函数。

再次创建一个返回值one的副本。

所以我们看到输出的还是复制构造函数调用的信息。

由于func函数的返回值没有赋给任何对象,因此这个返回的临时对象也就被丢失了。这时自动调用析构函数来释放这个临时对象所占用的内存。所以我们看到了这条析构函数执行的信息。

由于func函数已经结束了,因此在它内部拷贝的临时对象one也就被系统删除了。对象被删除会自动调用该对象的析构函数来释放内存。因此我们看到的是又一条析构函数在执行的信息。

最后,main函数结束,对象a的生命也结束了。这样就会调用析构函数来释放对象a的内存。因此我们看到了最后一条析构信息。

通过这个程序我们就可以观察到,将一个对象按值传递给一个函数,会调用两次复制构造函数和两次析构函数。

这样系统的开销是很大的,下一节我们就来解决这个开销的问题。


与LSGO一起学系列图文之 C++

第1章 初识C++

   1.1 C++简介

   1.2 C++的发展过程

   1.3 C++与C有什么不同

   1.4 学习C++之前需要先学C吗?

   1.5 C++与其他语言的区别

   1.6 Visual Studio 2010编译器

第2章 做一个简短的C++程序

   2.1 简单的屏幕输出小程序

   2.2 输出语句的使用

   2.3 std::介绍

   2.4 iostream与iostream.h的区别

   2.5 重名问题

   2.6 注释

   与LSGO一起学“C++上机小练习01”

第3章 初步了解函数

   3.1 一个简单的函数

   3.2 函数的参数

   3.3 函数的返回值、参数与变量

   3.4 函数的声明与定义

   3.5 局部变量

   3.6 全局变量

   与LSGO一起学“C++上机小练习02”

第4章 C++数据类型

   4.1 C++数据类型引入

   4.2 什么是变量

   4.3 变量及数据如何存储在内存上

   4.4 布尔型

   4.5 字符型

   4.6 双字节型

   4.7 整型概述

   4.8 为什么使用补码

   4.9 整型变量的定义

   4.10 浮点型变量

   4.11 常量

   4.12 枚举型常量

   与LSGO一起学“C++上机小练习03”

第5章 if语句与逻辑运算符

   5.1 什么是语句

   5.2 什么是块

   5.3 什么是表达式

   5.4 什么是运算符

   5.5 赋值运算符与数学运算符的联合

   5.6 什么是自加与自减

   5.7 表达式的优先级

   5.8 关系运算符

   5.9 if语句

   5.10 else语句

   5.11 else if语句

   5.12 if语句的嵌套

   5.13 带括号的嵌套语句

   5.14 逻辑“与”运算符

   5.15 逻辑“或”运算符

   5.16 逻辑“非”运算符

   5.17 逻辑运算符的优先级

   5.18 运算式的真假关系

   5.19 三目运算符

   5.20 三目运算符的优先问题

   5.21 三目运算符的使用问题

   5.22 三目运算符的型别问题

   5.23 三目运算符在字符型变量中的使用

   5.24 复杂的嵌套if语句

   5.25 逗号运算符

   与LSGO一起学“C++上机小练习04”

   与LSGO一起学“C++上机小练习05”

第6章 面向对象

   6.1 什么是面向对象程序语言

   6.2 面向对象程序语言的主要特征

   6.3 什么是类,对象和成员

   6.4 声明一个类

   6.5 命名习惯

   6.6 定义一个对象

   6.7 定义类或对象容易犯的错误

   6.8 共有与私有

   6.9 成员函数的声明和定义

   6.10 内联函数

   6.11 头文件与源文件

   6.12 const成员函数

   6.13 构造函数

   6.14 默认构造函数

   6.15 析构函数

   6.16 析构对象数组

   与LSGO一起学“C++上机小练习06”

   与LSGO一起学“C++上机小练习07”

   与LSGO一起学“C++上机小练习08”

第7章 循环语句

   7.1 循环语句的老祖宗——goto语句

   7.2 while语句

   7.3 while语句的其他用法

   7.4 continue和break语句

   7.5 永不休止的while循环

   7.6 do……while循环

   7.7 for循环

   7.8 灵活的for循环

   7.9 条件为空的for循环

   7.10 嵌套的for循环

   7.11 switch语句

   7.12 switch语句常见错误

   7.13 switch的菜单功能

   与LSGO一起学“C++上机小练习09”

   与LSGO一起学“C++上机小练习10”

第8章 指针

   8.1 什么是地址

   8.2 用指针来保存地址

   8.3 空指针

   8.4 指针与变量类型

   8.5 用指针来访问值

   8.6 容易混淆的概念

   8.7 指针对数值的操作

   8.8 更换指针保存的地址

   8.9 为什么使用指针

   8.10 内存泄漏

   8.11 在堆中创建对象

   8.12 在堆中删除对象

   8.13 访问堆中的数据成员

   8.14 在构造函数中开辟内存空间

   8.15 对象在栈和堆中的不同

   8.16 this指针

   8.17 指针的常见错误

   8.18 指针的运算

   8.19 常量指针

   8.20 对指针的认知

第9章 引用

   9.1 什么是引用

   9.2 引用的地址

   9.3 引用就是别名常量

   9.4 引用对象

   9.5 空引用

   9.6 按值传递

   9.7 按指针(地址)传递

   9.8 按别名传递

   9.9 让函数返回多个值

   9.10 用值来传递对象

   9.11 用指针来传递对象

   9.12 用const指针来传递对象

   9.13 用引用来传递对象

   9.14 到底是使用引用还是使用指针

   9.15 引用和指针可以一块用

   9.16 引用容易犯的错误

   9.17 引用一个按值返回的堆对象

   9.18 引用一个按别名返回的堆对象

   9.19 在哪里创建就在哪里释放

第10章 深入函数

第11章 运算符重载

第12章 继承

第13章 虚函数

第14章 数组

第15章 链表

第16章 多态

第17章 类的特殊成员

第18章 字符串

第19章 代码重用

第20章 友元类与嵌套类

第21章 流

第22章 命名空间

第23章 模板

第24章 异常和错误处理

第25章 补充内容

   25.6 位运算

C++学习补充资料

   C++中inline函数

   C++中类的inline成员函数

   C++中混合运算的类型转换

   与LSGO一起学“C++上机小练习01”参考代码

   与LSGO一起学“C++上机小练习02”参考代码

   与LSGO一起学“C++上机小练习03”参考代码

   与LSGO一起学“C++上机小练习04”参考代码

   与LSGO一起学“C++上机小练习05”参考代码

   与LSGO一起学“C++上机小练习06”参考代码

   与LSGO一起学“C++上机小练习07”参考代码

   与LSGO一起学“C++上机小练习08”参考代码

   与LSGO一起学“C++上机小练习09”参考代码

   与LSGO一起学“C++上机小练习10”参考代码


通过微信学习的知识只能是碎片化的知识,作为新时代的我们希望能够构建自己的知识结构,使我们的知识体系化,系统化,以后在遇到碎片化的知识,我们做的只是融合到自己的知识结构中,故我们将推出“与LSGO一起学”系列课程,帮助大家来构建知识框架,初步规划有:

  1. “与LSGO一起学C++”;

  2. “与LSGO一起学C#”;

  3. LSGO一起学Matlab”;

  4. “与LSGO一起学数据结构”;

  5. “与LSGO一起学设计模式”;

  6. “与LSGO一起学可视化建模语言(UML)”;

  7. “与LSGO一起学线性代数”;

  8. “与LSGO一起学高等数学”

  9. “与LSGO一起学概率论与数理统计”;

  10. “与LSGO一起学抽象代数;

  11. “与LSGO一起学点集拓扑”

  12. “与LSGO一起学数字图像处理”;

  13. “与LSGO一起学智能计算”;

如果对这些内容感兴趣,可以一起来学习讨论。

我们的官网: www.lsgogroup.com

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多