C++基础精讲-05

文章目录

  • 1.构造函数初始化列表
    • 1.1 初始化列表的使用
    • 1.2 有参构造函数的默认值
  • 2.对象所占空间大小
    • 2.1 大小的计算
    • 2.2 内存对齐机制
  • 3. 析构函数
    • 3.1 基本概念
    • 3.2 总结
  • 4.valgrind工具集
    • 4.1 介绍
    • 4.2 memcheck的使用
  • 5. 拷贝构造函数
    • 5.1 拷贝构造函数定义
    • 5.2 浅拷贝/深拷贝
    • 5.3 拷贝构造函数的调用时机
    • 5.4 总结


1.构造函数初始化列表

1.1 初始化列表的使用

在构造函数体里对数据成员赋值,实际上是先调用数据成员的默认构造函数来创建对象,接着再对其进行赋值操作;而在初始化列表中初始化数据成员,是在对象创建的时候就直接完成初始化。

#include <iostream>
#include <string>
using std::endl;
using std::cout;
using std::string;
using std::cin;class Person
{
public:Person():_age(0),_name("张三"){cout << "我是无参构造函数" << endl;}Person(int age,string name):_age(age),_name(name){cout << "我是有参构造函数" << endl;}void print();
private:int _age;string _name;
};void Person::print()
{cout << "name=" << _name << ";age=" << _age << endl;
}int main(void)
{Person p1;p1.print();Person p2(10, "张万三");p2.print();return 0;
}

在这里插入图片描述

1.2 有参构造函数的默认值

构造函数的参数也可以按从右向左规则赋默认值,同样的,如果构造函数的声明和定义分开写,只用在声明或定义中的一处设置参数默认值,一般建议在声明中设置默认值。

class Person
{
public://有参构造函数参数默认值Person(int age=10 ,string name="张万一"):_age(age) ,_name(name){}void print();
private:int _age;string _name;
};void Person::print()
{cout << "name=" << _name << ";age=" << _age << endl;
}int main(void)
{Person p1(20);p1.print();return 0;
}

在这里插入图片描述

2.对象所占空间大小

2.1 大小的计算

成员函数并不影响对象的大小,对象的大小与数据成员有关

#include <iostream>
#include <string>
using std::endl;
using std::cout;
using std::string;
using std::cin;class Person
{
private:int _age;double _price;
};int main(void)
{Person p1;size_t  s1 = sizeof(Person);size_t s2 = sizeof(p1);cout << s1 << ";" << s2 << endl;return 0;
}

在这里插入图片描述

2.2 内存对齐机制

有时一个类所占空间大小就是其数据成员类型所占大小之和,有时则不是,这就是因为有内存对齐的机制。
1.什么是内存对齐:
存对齐指的是将数据存储在特定的内存地址上,使得数据的起始地址是其大小的整数倍。例如,一个int类型(通常为 4 字节)的变量,其起始地址通常是 4 的倍数。
2.原因
(1)提高访问效率:计算机的硬件设计使得在访问对齐的数据时更加高效。因为 CPU 通常按字长(如 4 字节或 8 字节)来访问内存,如果数据是对齐的,CPU 可以一次读取完整的数据;如果数据未对齐,可能需要多次访问内存才能获取完整的数据。
(2)硬件限制:某些硬件平台要求数据必须按特定的对齐方式存储,否则会抛出异常或导致性能下降。
对齐规则
(1)基本数据类型的对齐值:每种基本数据类型都有其默认的对齐值,通常等于该类型的大小。例如,char 类型的对齐值为 1 字节,int 类型的对齐值为 4 字节,double 类型的对齐值为 8 字节。
(2)结构体或类的对齐值:结构体或类的对齐值是其成员中最大对齐值。
(3)成员的存储位置:每个成员的起始地址必须是其对齐值的整数倍。如果前一个成员的结束地址不满足下一个成员的对齐要求,会在它们之间插入填充字节。
(4)结构体或类的总大小:结构体或类的总大小必须是其对齐值的整数倍。如果不满足,会在结构体或类的末尾插入填充字节。

3. 析构函数

3.1 基本概念

作用:析构函数是一种特殊的成员函数,在对象的生命周期结束时自动调用,用于释放对象所占用的资源,比如动态分配的内存、打开的文件、网络连接等。

析构函数语法
析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。 ~ClassName(){}

#include <iostream>
#include <cstring>// 使用命名空间 std
using namespace std;class Str {
private:char* str;
public:// 构造函数Str(const char* s) {//在堆上分配空间str = new char[strlen(s) + 1];strcpy(str, s);}// 析构函数~Str() {//堆内存的销毁if(nullptr!=str){ delete[] str;str = nullptr;cout << "析构函数" << endl;}}void printString() {cout << str << endl;}
};void test(void)
{Str sw("Hello, World!");sw.printString();// 函数结束,对象 sw 被销毁,析构函数自动调用
}int main(void) 
{test();return 0;
}

3.2 总结

  1. 对于全局对象整个程序结束时,自动调用全局对象的析构函数。
  2. 对于局部对象,在程序离开局部对象的作用域时调用对象的析构函数。
  3. 对于静态对象,在整个程序结束时调用析构函数。
  4. 对于 堆对象在使用 delete 删除该对象时,调用析构函数。

4.valgrind工具集

4.1 介绍

1.概念
Valgrind 是一款用于内存调试、性能分析和代码剖析的工具集,在 Linux 系统中广泛使用。
2.工具集中的主要工具
(1).Memcheck:这是 Valgrind 最常用的工具之一,主要用于检测内存错误。它可以检测出诸如内存泄漏、非法内存访问(如越界访问、使用未初始化的内存等)等问题。在开发过程中,这些内存错误可能会导致程序出现难以调试的崩溃或错误行为,Memcheck 能够帮助开发者快速定位和解决这些问题。
(2)Callgrind用于性能分析,它可以收集程序中函数调用的信息,包括函数的执行时间、调用次数等。通过分析这些数据,开发者可以找出程序中的性能瓶颈所在,从而有针对性地进行优化。
(3)Cachegrind:主要关注程序的缓存行为。它可以模拟 CPU 缓存,分析程序对缓存的使用情况,帮助开发者了解缓存命中率、缓存缺失等信息,以便优化程序的内存访问模式,提高缓存利用率,从而提升程序性能。
(4)Helgrind:用于检测多线程程序中的数据竞争问题。在多线程环境下,当多个线程同时访问共享数据且没有适当的同步机制时,就可能发生数据竞争,导致程序出现不确定的行为。Helgrind 能够检测出这些潜在的数据竞争情况,帮助开发者确保多线程程序的正确性和稳定性。
3.特点
(1)强大的检测能力:能够深入检测各种内存相关的问题和性能瓶颈,为开发者提供详细的错误信息和性能数据。
(2)易于使用:Valgrind 的命令行界面相对简单,只需指定要运行的程序以及相应的工具选项,就可以开始对程序进行分析。
(3)跨平台性:支持多种 Linux 平台,具有较好的移植性,方便在不同的系统环境中使用。
4.应用场景
(1)软件开发与调试:在开发过程中,及时发现和解决内存错误可以提高软件的稳定性和可靠性,减少后期维护成本。
(2)性能优化:通过分析性能数据和缓存行为,开发者可以采取针对性的优化措施,如优化算法、调整数据结构、改进内存访问模式等,以提高程序的运行效率。
(3)多线程程序开发:确保多线程程序的正确性和稳定性,避免数据竞争等问题导致的程序错误和异常。

4.2 memcheck的使用

sudo apt-get install valgrind       //安装//使用
valgrind --tool=memcheck ./a.out                 (简略)
valgrind --tool=memcheck --leak-check=full ./a.out	(详细)//在home目录下编辑.bashrc文件,改别名;方便使用vim ~/.bashrc    #打开并添加一下内存
alias memcheck='valgrind --tool=memcheck --leak-check=full --show-reachable=yes'
#保存退出  :wq
#生效
source ~/.bashrc

在这里插入图片描述

5. 拷贝构造函数

5.1 拷贝构造函数定义

1.语法格式

class ClassName {
public:// 拷贝构造函数ClassName(const ClassName& other) {// 初始化新对象的成员变量}
};

2.定义
该函数用一个已经存在的同类型的对象,来初始化新对象,即对对象本身进行复制 。在没有显式定义拷贝构造函数时,编译器自动提供了默认的拷贝构造函数。

#include <iostream>
#include <cstring>namespace myspace1
{using std::endl;using std::cout;using std::cin;using std::string;
}using namespace myspace1;class Person
{
public://构造函数Person():_x(0),_y(0) {cout << "我是无参构造函数" << endl;}Person(int x, int y) :_x(x), _y(y){cout << "我是有参构造函数" << endl;}//析构函数~Person(){cout << "我是析构函数" << endl;}//拷贝构造函数Person(const Person& r):_x(r._x),_y(r._y){cout << "我是拷贝构造函数" << endl;}void print(){cout << "x=" << _x << ";y=" << _y << endl;}private:int _x;int _y;
};void test()
{Person p1(3, 4);//这里调用拷贝构造函数Person p2 = p1;         //==Person p2(p1)p1.print();p2.print();
}int main(void)
{test();
}

在这里插入图片描述

5.2 浅拷贝/深拷贝

浅拷贝默认拷贝构造函数执行的是浅拷贝,只复制对象的成员变量的值。对于包含指针成员的对象,浅拷贝会导致多个对象的指针成员指向同一块内存区域,当其中一个对象被销毁时,释放该内存会导致其他对象的指针成为悬空指针,引发未定义行为。
深拷贝:为了避免浅拷贝带来的问题,需要显式定义拷贝构造函数,实现深拷贝。深拷贝会为新对象的指针成员分配新的内存空间,并将原对象指针所指向的内容复制到新的内存中。

错误案例

#include <iostream>
#include <cstring>namespace myspace1
{using std::endl;using std::cout;using std::cin;using std::string;
}using namespace myspace1;class Person
{
public://构造函数Person():_x(0),_name(new char[strlen("张三")+1]()){strcpy(_name, "张三");cout << "我是无参构造函数" << endl;}Person(int x, const char *name) :_x(x), _name(new char[strlen(name) + 1]()){strcpy(_name, name);cout << "我是有参构造函数" << endl;}//析构函数~Person(){delete[] _name;cout << "我是析构函数" << endl;}void print(){cout << "x=" << _x << ";name=" << _name << endl;}private:int _x;char* _name;
};void test()
{Person p1(20, "张万三");//这里调用拷贝构造函数Person p2 = p1;         //==Person p2(p1)p1.print();p2.print();
}int main(void)
{test();
}

在这里插入图片描述
如果是默认的拷贝构造函数,p2会对p1的_name进行浅拷贝指向同一片内存;p2被销毁时,会调用析构函数,将这片堆空间进行回收;p1再销毁时,析构函数中又会试图回收这片空间,出现double free问题

所以需要我们显示定义拷贝构造函数

#include <iostream>
#include <cstring>namespace myspace1
{using std::endl;using std::cout;using std::cin;using std::string;
}using namespace myspace1;class Person
{
public://构造函数Person():_x(0),_name(new char[strlen("张三")+1]()){strcpy(_name, "张三");cout << "我是无参构造函数" << endl;}Person(int x, const char *name) :_x(x), _name(new char[strlen(name) + 1]()){strcpy(_name, name);cout << "我是有参构造函数" << endl;}//析构函数~Person(){delete[] _name;cout << "我是析构函数" << endl;}//拷贝构造函数Person(const Person& r):_x(r._x), _name(new char[strlen(r._name) + 1]){strcpy(_name, r._name);cout << "我是拷贝构造函数" << endl;}void print(){cout << "x=" << _x << ";name=" << _name << endl;}private:int _x;char* _name;
};void test()
{Person p1(20, "张万三");//这里调用拷贝构造函数Person p2 = p1;         //==Person p2(p1)p1.print();p2.print();
}int main(void)
{test();
}

在这里插入图片描述
所以,如果拷贝构造函数需要显式写出时(该类有指针成员申请堆空间),在自定义的拷贝构造函数中要换成深拷贝的方式,先申请空间,再复制内容

5.3 拷贝构造函数的调用时机

1.当使用一个已经存在的对象初始化另一个同类型的新对象时

void test()
{Person p1(20, "张万三");Person p2 = p1;         //==Person p2(p1)p1.print();p2.print();
}

2.当函数参数(实参和形参的类型都是对象),形参与实参结合时(实参初始化形参);

void fun(Person p)     //避免多余的复制,节省空间:fun(Person &p)  使用引用
{p.print();
}void test()
{Person p1(20, "张三");fun(p1);
}

3.当函数的返回值是对象,执行return语句时

//避免多余的复制,节省空间
//Person &fun1()
//{
//    //返回的是引用,要注意返回对象的声明周期(返回全局变量/变量前加  static)
//    return 
//}
Person fun2()
{//Person(20, "李四"); 匿名对象;在这行代码执行完毕后,该临时对象就会立即调用析构函数销毁。Person p3(30, "王强");return p3;          //发生复制
}

5.4 总结

1.默认情况下,c++编译器至少为我们写的类增加3个函数
(1).默认构造函数(无参,函数体为空)
(2).默认析构函数(无参,函数体为空)
(3).默认拷贝构造函数,对类中非静态成员属性简单值拷贝
如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
如果用户定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会提供默认拷贝构造
2.构造/析构
(1)构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
(2)析构函数主要用于对象销毁前系统自动调用,执行一些清理工作。
3.浅拷贝/深拷贝
(1)一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。
(2)当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/76600.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

文章记单词 | 第28篇(六级)

一&#xff0c;单词释义 shirt /ʃɜːt/ n. 衬衫&#xff1b;衬衣commonly /ˈkɒmənli/ adv. 通常地&#xff1b;一般地&#xff1b;普遍地pick /pɪk/ v. 挑选&#xff1b;采摘&#xff1b;捡起&#xff1b;选择&#xff1b;n. 选择&#xff1b;鹤嘴锄&#xff1b;精华com…

安装低版本Pytorch GPU

网上很多教程都是自动安装&#xff0c;不指定版本&#xff0c;其实有大问题。而且torch、torchvision、torchaudio的版本必须是对应&#xff0c;所以一旦版本不对&#xff0c;就可能会出现各种问题。 其实Pytorch官网就已经给出了安装低版本的教程 登入Pytorch官网 点击previo…

2025认证杯挑战赛B题【 谣言在社交网络上的传播 】原创论文讲解(含完整python代码)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了认证杯数学中国数学建模网络挑战赛第一阶段B题目谣言在社交网络上的传播完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半…

并发编程--互斥锁与读写锁

并发编程–互斥锁与读写锁 文章目录 并发编程--互斥锁与读写锁1. 基本概念2. 互斥锁2.1 基本逻辑2.2 函数接口2.3示例代码12.4示例代码2 3. 读写锁3.1 基本逻辑3.2示例代码 1. 基本概念 互斥与同步是最基本的逻辑概念&#xff1a; 互斥指的是控制两个进度使之互相排斥&#x…

亲手打造可视化故事线管理工具:开发全流程、难点突破与开发过程经验总结

亲手打造可视化故事线管理工具&#xff1a;开发全流程、难点突破与开发过程经验总结 作为还没入门的业余编程爱好者&#xff0c;奋战了2天&#xff0c;借助AI开发一款FLASK小工具&#xff0c;功能还在完善中&#xff08;时间轴可以跟随关联图缩放&#xff0c;加了一个用C键控制…

网络攻防技术-虚拟机安装和nmap端口扫描

文章是博主上实验课做的实验和心得体会&#xff0c;有些高深的地方我可能也比较一知半解&#xff0c;欢迎来交流。全文参考课程所习得&#xff0c;纯粹梳理知识点和分享&#xff0c;如有不妥请联系修改。 文章侧重实验部分&#xff0c;也会讲述实验相关的理论知识。理论后期如果…

中断的硬件框架

今天呢&#xff0c;我们来讲讲中断的硬件框架&#xff0c;这里会去举3个开发板&#xff0c;去了解中断的硬件框架&#xff1a; 中断路径上的3个部件&#xff1a; 中断源 中断源多种多样&#xff0c;比如GPIO、定时器、UART、DMA等等。 它们都有自己的寄存器&#xff0c;可以…

动手学深度学习:手语视频在VGG模型中的测试

前言 其他所有部分同上一篇AlexNet一样&#xff0c;所以就不再赘诉&#xff0c;直接看VGG搭建部分。 模型 VGG是第一个采取块进行模块化搭建的模型。 def vgg_block(num_convs,in_channels,out_channels):layers[]for _ in range(num_convs):layers.append(nn.Conv2d(in_ch…

信息学奥赛一本通 1498:Roadblocks | 洛谷 P2865 [USACO06NOV] Roadblocks G

【题目链接】 ybt 1498&#xff1a;Roadblocks 洛谷 P2865 [USACO06NOV] Roadblocks G 【题目考点】 1. 图论&#xff1a;严格次短路径 严格次短路的路径长度必须大于最短路的路径长度。 非严格次短路的路径长度大于等于最短路的路径长度。 【解题思路】 每个交叉路口是一…

Arm CPU安全通告:基于TrustZone的Cortex-M系统面临多重故障注入攻击

安全之安全(security)博客目录导读 目录 一、概述 二、致谢 三、参考文献​​​​​​Black Hat USA 2022 | Briefings Schedule 四、版本历史 一、概述 Arm注意到BlackHat 2022大会官网发布的演讲摘要《糟糕..&#xff01;我又一次故障注入成功了&#xff01;——如何突…

【频域分析】包络分析

【频域分析】包络分析 算法配置页面 可以一键导出结果数据 报表自定义绘制 获取和下载【PHM学习软件PHM源码】的方式 获取方式&#xff1a;Docshttps://jcn362s9p4t8.feishu.cn/wiki/A0NXwPxY3ie1cGkOy08cru6vnvc

ElMessage

以下是关于 ElMessage 的详细说明和使用方法&#xff1a; 什么是 ElMessage ElMessage 是 Element Plus 提供的一个全局消息提示组件&#xff0c;用于在页面上显示短暂的消息提示。它可以用于显示成功、警告、错误等不同类型的消息。 基本用法 1. 引入 ElMessage 在使用 E…

全面解析 KaiwuDB 数据库的数据类型

在现代数据库管理系统中&#xff0c;数据类型的选择至关重要。它不仅决定了数据存储的效率&#xff0c;还影响到查询的速度和数据的一致性。KaiwuDB&#xff0c;作为一款开源的分布式数据库&#xff0c;提供了多种数据类型&#xff0c;以适应不同的业务需求和存储要求。本文将全…

【计网】网络交换技术之分组交换(复习自用,重要1)

复习自用的&#xff0c;处理得比较草率&#xff0c;复习的同学或者想看基础的同学可以看看&#xff0c;大佬的话可以不用浪费时间在我的水文上了 另外两种交换技术可以直接点击链接访问相关笔记&#xff1a; 电路交换 报文交换 一、分组交换的定义 1.定义 分组交换&#x…

C++ STL及Python中等效实现

一. STL 概述 STL 包含以下核心组件&#xff1a; 容器&#xff08;Containers&#xff09;&#xff1a;存储数据的结构&#xff0c;如数组、链表、集合等。迭代器&#xff08;Iterators&#xff09;&#xff1a;用于遍历容器的接口&#xff0c;类似指针。算法&#xff08;Alg…

python-63-前后端分离之图书管理系统的Flask后端

文章目录 1 flask后端1.1 数据库实例extension.py1.2 数据模型models.py1.3 .flaskenv1.4 app.py1.5 运行1.6 测试链接2 关键函数和文件2.1 请求视图类MethodView2.2 .flaskenv文件3 参考附录基于flask形成了图书管理系统的后端,同时对其中使用到的关键文件.flaskenv和函数类M…

蓝桥杯真题——好数、R格式

目录 蓝桥杯2024年第十五届省赛真题-好数 【模拟题】 题目描述 输入格式 输出格式 样例输入 样例输出 提示 代码1&#xff1a;有两个案例过不了&#xff0c;超时 蓝桥杯2024年第十五届省赛真题-R 格式 【vector容器的使用】 题目描述 输入格式 输出格式 样例输入…

Python中NumPy的索引和切片

在数据科学和科学计算领域&#xff0c;NumPy是一个功能强大且广泛使用的Python库。它提供了高效的多维数组对象以及丰富的数组操作函数&#xff0c;其中索引和切片是NumPy的核心功能之一。通过灵活运用索引和切片操作&#xff0c;我们可以轻松访问和操作数组中的元素&#xff0…

设计模式:策略模式 - 消除复杂条件判断的利器

一、什么是策略模式&#xff1f; 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;它将一组算法或业务逻辑封装为独立的策略类&#xff0c;使这些策略可以互换使用&#xff0c;并通过上下文类动态选择合适的策略。 核心思想 • 将不同的行…

LeetCode hot 100—不同路径

题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; …