C++:构造、析构、引用与拷贝构造

2023-10-31

构造函数


类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

类的数据成员多为私有的,要对它们进初始化,必须用一个公有函数来进行。同时这个函数应该在且仅在定义对象时自动执行一次。这个函数就是构造函数。它由系统自动调动,用户不可以调动。

构造函数是类的特殊的公有成员函数,有如下特征:

  • 函数名与类名相同,如:CGoods();

  • 无函数返回类型说明(注意:是没有而不是void,即什么也不写,不可写void。实际上构造函数有返回值,返回的是构造函数所创建的对象)

  • 系统自动调用。在程序运行时,当新的对象被建立,该对象所属的类的构造函数被系统自动调用,在该对象生存期中也只调用这一次,程序员不允许手动调动构造函数,例如a.CGoods("C#",76,78);是不允许的,构造函数它是对象创建时系统自动调用的

  • 可以重载,由不同的参数表区分重载

  • 构造函数可以在类中定义,亦可以在类外定义

    • 在类外定义构造函数时要加作用域限定符,如下:

    • CGoods::CGoods(char *name, int amount, float price)
      {
          strcpy(Name,name);
          Amount = amount;
          Price = price;
          Total_value = price * amount;
      }
      
  • 若类中没有给出构造函数,则C++编译器自动给出一个缺省的构造函数: 类名(void){},只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数;如果对象的数据成员全为公有的,也可以在对象名后加={},在花括号中顺序填入全体数据成员的初始值

注意:构造函数是在已经分配好的空间上初始化对象成员。给对象分配空间是在进入主函数时系统就分配了(静态创建的对象)或者new的时候运行时分配的(动态创建的对象)

补充:空间与对象

不同于C面向过程中内置类型的有空间即可操纵,面向对象中空间与对象是分离的,有空间不一定有对象,面向对象中有空间了,对象被构造函数初始化之后才可操纵。空间要回收的时候,必须先执行析构函数,再回收空间。Java有垃圾回收机制,程序员只需要关注对象就可以了,而C++里程序员既需要关注对象,还要关注空间。

扩充:C++类的默认函数

对于任何一个类,即便它没有任何函数,编译器将会自动地给它添加6个缺省函数,分别是构造函数、拷贝构造函数、重载=的函数、析构函数、重载&的函数(2个) (如果程序员显示地给出,编译器就不会添加),如下Empty类的6个默认函数:

class Empty
{
public:
	Empty() {}  //构造函数
    Empty(const Empty &e) {}  //拷贝构造函数
    Empty & operator=(const Empty &e)  //重载=的函数
 	{
    	return *this;
	}
    ~Empty() {}  //析构函数
 
 	Empty *operator&() { return this; }  //重载&的函数
 	const Empty * operator&() const { return this; }  //重载&的函数
}

构造函数的使用

创建对象:

CGoods Car1("Ford",30,9999);等效于 CGoods Car1 = CGoods("Ford",30,9999)

若不带参数:

CGoods Car1;

注意:定义不带参对象时,不能加括号,例如:CGoods Car1(),它定义的是一个返回值类型是CGoods类型的无参函数

析构函数


”生而不同,死而相同“:对象可由不同的重载构造函数来初始化,但是销毁的时候都用相同的析构函数

当一个对象被定义时,C++自动调用构造函数对该对象进行初始化,那么当一个对象的生命周期结束时,C++也会自动调用一个函数进行扫尾工作,这个特殊的成员函数就是析构函数。

析构函数有如下特点:

  • 析构函数名与类名相同,但在前面加上取反符号’~’,如:~CGoods()
  • 析构函数无函数返回类型
  • 析构函数不带任何参数
  • 一个类有一个也只有一个析构函数,这与构造函数不同
  • 析构函数可以缺省
  • 对象注销时,系统自动调用析构函数,但是程序员也可以在对象生存期没到时,手动调动析构函数,完成对象的自杀动作,如a.~Object();是可以的

注意:析构函数本身并不释放对象占用的内存空间,它只是在系统收回对象的内存空间之前执行扫尾工作

构造与析构顺序

对于如下代码

class Object
{
	int value;
public:
	Object(int x = 0) :value(x)
	{
		cout << "Create Object: " << value << endl;
	}
	~Object()
	{
		cout << "Destory Object: " << value << endl;
	}
};

void fun()
{
	Object a(1);
}

Object b(2);

int main()
{
	Object c(3);
	fun();
	return 0;
}

Object d(4);

执行结果为:

在这里插入图片描述

分析:

当程序编译链接通过时,形成可执行文件,运行可执行文件时,系统给该进程分配了四个空间:代码区.text、数据区.data、堆区.heap、栈区.stack
在这里插入图片描述

程序主函数执行时,先处理全局变量,放入.data区,所以先创建b和d;然后主函数接着执行,执行object c(3),创建c;再执行fun(),创建a,退出fun函数时,收回fun函数栈帧,所以局部变量对象a被销毁,对象a的析构函数被执行;再回到主函数接着执行return,主函数结束,主函数栈帧回收,主函数里的对象c被销毁;接着程序结束,给该进程分配的用户空间要回收,全局对象被依次销毁,所以打印出来的顺序就是2 4 3 1 1 3 4 2

构造函数与析构函数的this指针

构造函数和析构函数也有this指针

Object c(3);

其实就是

Object(&c,3);	

构造的时候把c这个对象传给this指针,构造函数就知道它要操纵的具体是哪个对象了

C++中除了静态函数和友元函数,其他所有类的成员函数都含有this指针

更多关于this指针的介绍,请参考我的另一篇博客:C++:类、对象、this指针、内联函数

用new创建对象

new是C++关键字中比较特殊的一个,它是一种运算符

Object *op = NULL;
op = new Object(10); 

new做了两个动作

  • 从堆区申请空间
  • 调动构造函数初始化对象

与C中malloc不同,malloc只申请空间

用delete删除对象

delete op;

delete做了两个动作

  • 调动对象的析构函数
  • 释放空间

引用


C++函数中参数的传递方式是传值。在函数域中为参数重新分配内存,而把实参的数值传递到新分配的内存中。它的优点是有效避免函数的副作用。

如果要求改变实参的值呢?如果实参是一个复杂的对象,重新分配内存会引起程序执行效率大大下降,怎么办呢?

在C++中有一种新的导出型数据类型——引用(reference),可以解决上面的难题,引用又称为别名。

引用&

就是别名/外号, 一旦引用初始化为某个变量,就可以使用引用名称直接使用该变量,就是给该变量起了个别名

引用不是定义一个新的变量,而是给一个已经定义的变量重新起一个别名,也就是C++系统不为引用类型变量分配内存空间。引用主要用于函数之间的数据传递。

引用的定义格式:类型 & 引用变量名 = 已定义过的变量名

例如:

double number;
double &newnum = number;//newnum是新定义的引用类型变量,newnum也就是number的别名

引用的特点

  1. 没有空引用
  2. 定义引用时必须进行初始化
  3. 没有二级引用,即没有引用的引用

引用的使用

C语言使用指针交换a,b

void Swap_c(int *pa, int *pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

C++使用引用交换a,b

void Swap_cpp(int &a, int &b)//在函数中对a,b的交换,就是对实参的交换
{
	int tmp = a;
	a = b;
	b = tmp;
}

C中使用指针间接交换了a,b;而C++使用引用直接操纵实参进行了交换

补充:&的使用

  1. 位运算与:数字之间:1 & 0 --> 0
  2. 取地址符:变量名前:&a
  3. 引用:类型与标识符中间:int &c = a;

引用的本质

对于如下代码:

void fun(int &ap)
{
    int *p = &ap;
    ap = 100;
}
int main()
{
    int x = 10;
    int &p = x;//p就是x的别名
    p = 200;
    fun(x);
    fun(p);
    return 0;
}

编译的时候,编译器会把引用改写成常指针,上述代码就变成如下代码:

void fun(int * const ap)//const修饰ap指针自身,ap自身不可变,但*ap可变
    					//const修饰直接右边,基本类型对const透明
{
    int *p = ap;
    *ap = 100;
}
int main()
{
    int x = 10;
    int * const p = &x;
    *p = 200;
    fun(&x);
    fun(p);
    return 0;
}

在这里插入图片描述

从逻辑角度看,引用就是别名,指针是地址;从底层来看,引用就是常性的指针

注意:函数内局部变量的地址不能作为返回值

对于如下代码:编译的时候都会报警告

int *funp()
{
	int a = 0;
	return &a;
}
int & funy()
{
	int x = 100;
	return x;
}
int * funarr()
{
	int arr[10] = { 0 };
	arr[0] = 124;
	return arr;
}

在这里插入图片描述

函数执行完毕后函数栈就被释放了,不能再对函数内局部变量的地址进行操作,因而不能返回局部变量的地址,可以返回局部变量的值,函数返回局部变量值时实际上是返回变量值的拷贝。作为局部变量,在栈区存储,虽然在函数调用结束后所在内存会被释放回收掉,但返回值会有一个拷贝副本,供调用者使用

变量的生存期不受函数生存期影响,才能返回其地址,如动态创建的(堆区)、全局的、静态的

关于函数返回局部变量的更多介绍

拷贝构造函数


拷贝构造函数是一种特殊的构造函数,其形参为本类的对象引用。拷贝构造函数的功能是用一个已经存在的对象的数据成员去初构造同类型的新对象的数据成员

同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行拷贝是完全可行的。这个拷贝过程只需要拷贝数据成员,而函数成员是共用的。在建立对象时可用同一类的另一个对象作为参数来构造该对象,这时所用的构造函数称为拷贝构造函数

对于CGoods类,缺省拷贝构造函数形式如下:

CGoods(const CGoods & cgd) 
{
    Strcpy(Name,cgd.Name);
    Price = cgd.Price;
    Amount = cgd.Amount;
    Total_value = cgd.Total_value;
}

使用拷贝构造:

int main()
{
    CGoods x("C++", 22, 97);
    
    CGoods a(x);//用x的数据成员去初始化新对象a的数据成员
}

注意:拷贝构造函数不是把一个对象赋值给一个空间,面向对象思维里对象是对象,空间是空间,拷贝构造函数仍然是构造函数,只不过参数是同类型的另一个对象

拷贝构造函数的应用场景

1.当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参拷贝到新对象中

如下代码:fun函数的形参是类的对象,在car所指定的空间中构造一个对象时系统就要用到拷贝构造函数

CGoods fun(CGoods car)//函数的形参是类的对象,在car所指定的空间中构造一个对象
{
    CGoods("C#",12);
    return c1;
}
int main()
{
    CGoods x("C++",22,97);
    CGoods a;
    a = fun(x);
}

2.当函数的返回值是类的对象,函数执行完成返回调用者时使用。理由也是要建立一个临时对象,再返回调用者

如上代码中,fun函数的返回值是类的对象,在主函数中用a来接收fun函数的返回值,系统不能把c1直接赋值给a,因为局部对象是在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一个无名临时对象,该临时对象的生存期只在函数调用处的表达式中并且该临时对象是只读的。即return对象时,实际上是调用拷贝构造函数把该对象的值拷入临时对象,主函数调用者使用的是该临时对象。如果返回的是局部变量,处理过程类似,只是不调用构造函数,只创建一个临时变量副本。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++:构造、析构、引用与拷贝构造 的相关文章

  • 格式说明符%02x

    我有一个简单的程序 include
  • 是否需要销毁运算符删除的形式才能真正销毁对象?

    C 20 添加了破坏形式operator delete区别于std destroying delete t范围 它导致delete表达式在调用之前不再销毁对象operator delete 目的是在显式调用对象的析构函数和释放内存之前 允许
  • 使用 ADAL v3 使用 ClientID 对 Dynamics 365 进行身份验证

    我正在尝试对我们的在线 Dynamics CRM 进行身份验证以使用可用的 API 我能找到的唯一关于执行此操作的官方文档是 https learn microsoft com en us dynamics365 customer enga
  • 如何使用 openSSL 函数验证 PEM 证书的密钥长度

    如何验证以这种方式生成的 PEM 证书的密钥长度 openssl genrsa des3 out server key 1024 openssl req new key server key out server csr cp server
  • C# 中的 Stack<> 实现

    我最近一直在实现递归目录搜索实现 并且使用堆栈来跟踪路径元素 当我使用 string Join 连接路径元素时 我发现它们被颠倒了 当我调试该方法时 我查看了堆栈 发现堆栈内部数组中的元素本身是相反的 即最近 Push 的元素位于内部数组的
  • strlen() 编译时优化

    前几天我发现你可以找到编译时strlen使用这样的东西 template
  • 如何在 C# 中将 Json 转换为对象

    我想将 Json 转换为 C 中的对象 这里的 Json 是 值 e920ce0f e3f5 4c6f 8e3d d2fbc51990e4 如何使用 Object 问题看似愚蠢 但其实并不那么愚蠢 我没有简单的 Json 我有 IEnume
  • 防止控制台应用程序中的内存工作集最小化?

    我想防止控制台应用程序中的内存工作集最小化 在Windows应用程序中 我可以这样做覆盖 SC MINIMIZE 消息 http support microsoft com kb 293215 en us fr 1 但是 如何在控制台应用程
  • Unity手游触摸动作不扎实

    我的代码中有一种 错误 我只是找不到它发生的原因以及如何修复它 我是统一的初学者 甚至是统一的手机游戏的初学者 我使用触摸让玩家从一侧移动到另一侧 但问题是我希望玩家在手指从一侧滑动到另一侧时能够平滑移动 但我的代码还会将玩家移动到您点击的
  • Libev,如何将参数传递给相关回调

    我陷入了 libev 中争论的境地 通常 libev 在类似的函数中接收包 接收回调 没关系 但是实际操作中 我们需要派遣一个亲戚 写回调 根据收到的包裹处理具体工作 例如 S RECV MSG pstRecvMsg S RECV MSG
  • 来自嵌入图像的 BitmapSource

    我的目标是在 WPF 窗口上重写 OnRender 方法中绘制图像 someImage png 它是嵌入资源 protected override void OnRender System Windows Media DrawingCont
  • 测量进程消耗的 CPU 时钟

    我用 C 语言编写了一个程序 它是作为研究结果创建的程序 我想计算程序消耗的确切 CPU 周期 精确的循环次数 知道我怎样才能找到它吗 The valgrind tool cachegrind valgrind tool cachegrin
  • 条件类型定义

    如果我有一小段这样的代码 template
  • wordexp 失败时我们需要调用 wordfree 吗?

    wordexp 失败时我们需要调用 wordfree 吗 在某些情况下 调用 wordfree 似乎会出现段错误 例如 当 wordfree 返回字符串为 foo bar 的错误代码时 这在手册页中并不清楚 我已经看到在某些错误情况下使用了
  • 如何防止 Blazor NavLink 组件的默认导航

    从 Blazor 3 1 Preview 2 开始 应该可以防止默认导航行为 https devblogs microsoft com aspnet asp net core updates in net core 3 1 preview
  • 我们可以通过指针来改变const定义的对象的值吗?

    include
  • 当Model和ViewModel一模一样的时候怎么办?

    我想知道什么是最佳实践 我被告知要始终创建 ViewModel 并且永远不要使用核心模型类将数据传递到视图 这就说得通了 让我把事情分开 但什么是Model 和ViewModel一模一样 我应该重新创建另一个类还是只是使用它 我觉得我应该重
  • 使用 gcc 时在头文件中查找定义的好方法是什么?

    在使用 gcc 时 有人有推荐的方法在头文件中查找定义吗 使用 MSVC 时 我只需右键单击并选择 转到定义 这非常好 我使用过 netbeans gcc 它确实有代码帮助 包括到定义的超链接 所以这是一种选择 但是 我想知道是否有任何其他
  • ContentDialog Windows 10 Mobile XAML - 全屏 - 填充

    我在项目中放置了一个 ContentDialog 用于 Windows 10 上的登录弹出窗口 当我在移动设备上运行此项目时 ContentDialog 未全屏显示 并且该元素周围有最小的填充 在键盘上可见 例如在焦点元素文本框上 键盘和内
  • 嵌入式linux编写AT命令

    我在向 GSM 模块写入 AT 命令时遇到问题 当我使用 minicom b 115200 D dev ttySP0 term vt100 时它工作完美 但我不知道如何在 C 代码中做同样的事情 我没有收到任何错误 但模块对命令没有反应 有

随机推荐

  • voronoi图编程构造_可视化编程真的有那么糟糕?

    作者 Anton Livaja 译者 弯月 责编 屠敏 以下为译文 我想告诉你 如果使用恰当 可视化编程和是图解推理是一个非常强大的工具集 也就是说 只有当可视化编程扎根于数学和计算机科学并建立坚实的基础 才能发挥良好的作用 为了降低编程的
  • 《职场情绪稳定:内在的力量与策略》

    近期发生的新闻热点 如大规模裁员 创业公司倒闭 公共卫生事件等 让公众更加关注稳定情绪和心理健康的问题 在职场中 我们常常遇到各种挑战和压力 如何保持稳定的情绪成了一个重要的话题 首先 让我们分享一些工作中可能引发我们情绪波动的事情 我曾经
  • IT项目管理七

    Tony Prince 和他的团队正在做一个娱乐和健康方面的项目 他们被要求修改现有的成本估计 以便能有一个可靠的评价项目绩效的基线 你的进度和成本目标是在6个月内在200 000美元的预算下完成项目 1 作业一 准备和打印一页类似于图7
  • 求n个数的最小公倍数(C语言)

    Problem Description 求n个数的最小公倍数 Input 输入包含多个测试实例 每个测试实例的开始是一个正整数n 然后是n个正整数 Output 为每组测试数据输出它们的最小公倍数 每个测试实例的输出占一行 你可以假设最后的
  • java项目 畅购商城 购物车

    第10章 购物车 学习目标 能够通过SpringSecurity进行权限控制 掌握购物车流程 掌握购物车渲染 微服务之间的认证访问 1 SpringSecurity权限控制 用户每次访问微服务的时候 先去oauth2 0服务登录 登录后再访
  • 网易游戏(互娱)游戏研发一面&二面(已收到offer)

    简单来讲下上周面网易互娱的心得 因为我不是走内推而是直接怼笔试的 所以上周才有了笔试结果然后被告知面试 我面的岗位是游戏研发工程师 初级 一面 40分钟左右 开始是简单的自我介绍 C 关于C 问的比较简单 因为我跟面试官说我主要学的是Jav
  • 风格回调函数 vs c++风格虚基类

    http www cnblogs com raymon archive 2012 08 28 2660876 html 风格回调函数 vs c 风格虚基类 关于接口定义和调用的对比 c 中也很常用回调函数 比如MFC中 既可以用回调函数的方
  • APP移动端自动化基础及appium环境搭建

    目录 APP移动端自动化测试基础 主流移动端自动化工具 Appium介绍 Appium工作原理 Appium环境搭建 安装前准备工具 安装Android SDK 配置环境变量 安装Python client 安装夜神模拟器 mumu模拟器
  • 一文一图搞懂OSI七层模型

    什么是OSI 所谓的OSI 是由国际化标准组织 ISO 针对开放式网路架构所制定的电脑互连标准 全名是开放式通讯系统互连参考模型 Open System Interconnection Reference Model 简称OSI模型 该模型
  • Air780E

    目录 Air780E编译指南 准备工作 下载源码 注意 需要两个库 准备工具 工具链下载 开始编译 常见编译问题 Air780E编译指南 https wiki luatos com develop compile Air780E html
  • 全面深入彻底理解Python切片操作【原创】

    全面深入彻底理解Python切片操作 原创 我们基本上都知道Python的序列对象都是可以用索引号来引用的元素的 索引号可以是正数由0开始从左向右 也可以是负数由 1开始从右向左 在Python中对于具有序列结构的数据来说都可以使用切片操作
  • 系统权限-数据权限案例分析

    文章目录 前言 一 数据权限 三 源代码下载 四 数据库权限设计图 五 数据权限前台界面 六 数据权限服务端 6 1 aop 拦截 数据范围 6 2 数据实现层ServiceImpl 埋点 七 总结 7 1设计思路 7 2 缺陷 前言 传统
  • TestNG单元测试框架-常用注解介绍以及testng和Junit的区别【杭州多测师_王sir】【杭州多测师】...

    一 TestNG单元测试框架 常用注解介绍 testng学习网址 https www jc2182 com testng testng environment html 1 Before类别和After类别注解按照如下循序执行 Before
  • Java实体类中封装其他实体类并引用

    在Java开发过程中有很多情况是二个表及以上的联合操作 这是需要分清楚表的主次关系 在引用的时候有人会把二个表的数据全都封装在一个实体类中 然后在对这个实体类进行操作 但如果是三个表呢 四个表呢 还都封装在一个实体类吗 这样被封装的实体类的
  • C++ #ifndef、#define、#endif作用

    在C 项目中 ifndef define endif非常常见 接下来就来简单说一下它们的作用 作用 防止头文件被重复引用 防止被重复编译 简介 ifndef 它是if not define的简写 是宏定义的一种 确切的说是预处理功能 宏定义
  • 邮件附件乱码小技巧

    经常有人收到一些Internet邮件 里面有一个附件 例如文件名叫 我的WORD文档 doc 可是用WORD打开后 提示错误或者乱码 遇到这种情况可以用以下步骤解决 1 重命名 把 我的WORD文档 doc 改名字为 我的WORD文档 uu
  • 基于BCM53262交换芯片平台的Linux操作系统移植(三)之配置文件修改

    2018 05 09 10 49 zhoulinhua 2018 05 10 一 单板类型支持 1 修改at91sam9x5ek defconfig定制软件匹配当前单板 buildroot at91 configs at91sam9x5ek
  • windows往磁盘拷文件,拒绝访问

    1 首先确保该盘的权限 全部设置成完全 2 设置完后 如果还会遇到问题 客户端没有所需的特权 方法 cmd以管理权限打开 输入 icacls c setintegritylevel M
  • 视锥裁剪

    背景 视锥体 frustum 是指场景中摄像机的可见的一个锥体范围 它有上 下 左 右 近 远 共6个面组成 在视锥体内的景物可见 反之则不可见 为提高性能 只对其中与视锥体有交集的对象进行绘制 我们计算出视锥体六个面的空间平面方程 将点坐
  • C++:构造、析构、引用与拷贝构造

    构造函数 类的构造函数是类的一种特殊的成员函数 它会在每次创建类的新对象时执行 类的数据成员多为私有的 要对它们进初始化 必须用一个公有函数来进行 同时这个函数应该在且仅在定义对象时自动执行一次 这个函数就是构造函数 它由系统自动调动 用户