C++类与对象(中)第一篇

目录

前言:

类的六个默认成员函数

构造函数

析构函数

拷贝构造函数

拷贝场景一:函数参数类型为类类型对象

拷贝场景二:利用已存在的对象创建新对象

拷贝场景三:函数返回值类型为类类型对象


前言:

编译器编译类的详细步骤:

  1. 先识别类名;
  2. 识别类中有哪些成员变量;
  3. 识别类中有哪些成员函数;
  4. 编译器对成员函数进行预处理,加上隐藏的this指针;

因而成员变量类域的前后顺序程序编译没有影响;

类的六个默认成员函数

//空类中什么都没有吗?
class Date
{};
//任何类在什么都不写时,编译器会自动生成6个默认成员函数;
//默认成员函数:用户没有显示实现,编译器会生成的成员函数称为默认成员函数;

构造函数

对于学生类,创建一个学生对象,这个学生就应该具有姓名、年龄 、学号等,但是这些数据成员没有初始化,该学生的姓名 、年龄、 学号等数据成员的值将为随机值或0;此时这个学生对象无任何意义!创建对象时,C++自动初始化对象的工作专门由该类的构造函数完成;

构造函数的作用在创建对象,系统自动调用初始化数据成员构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

构造函数的特点:

  1. 类名即为函数名;
  2. 没有返回值(函数名前什么也不写,也没有void);
  3. 类创建对象时,编译器自动调用构造函数;
class Date
{
private:int _year;int _month;int _day;
public://构造函数Date()//函数名为类型名&&返回值没有{_year = 1;_month = 1;_day = 1;}void print(){cout << _year << "-" << _month << "-" << _day << endl;}
};
int main()
{Date d;//创建对象d时自动调用构造函数Date()d.print();//只调用了print()函数return 0;
}

运行结果:

构造函数的特点:

4.  构造函数支持函数重载 ;

注:函数重载(同一作用域中的同名函数,同名函数的参数个数 参数类型 类型顺序不同);

class Date
{
private:int _year;int _month;int _day;
public:Date(){//声明_year = 2000;_month = 12;_day = 18;}Date(int year, int month, int day){_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "-" << _day << endl;}
};int main()
{Date d1;d1.print();Date d2(2023, 12, 18);d2.print();return 0;
}

 运行结果:

默认构造函数:

  1.  用户在类中没有显示定义构造函数, 编译器自动生成的构造函数,叫默认构造函数;
  2.  无参构造函数也可以叫默认构造函数;
  3.  全缺省也可以叫默认构造函数;

可以不传参数可以调用构造函数,都可以叫默认构造函数

三个默认构造函数不能同时存在,只能存在一个默认构造函数

class Date
{
private:int _year;int _month;int _day;
public:Date()//无参的构造函数--->默认构造函数{_year = 2021;_month = 12;_day = 18;}Date(int year=2023, int month=10, int day=18)//全缺省参数的构造函数--->默认构造函数{_year = year;_month = month;_day = day;}  //默认构造函数只能存在一个;void print(){cout << _year << "-" << _month << "-" << _day << endl;}
};
int main()
{Date d1;d1.print();return 0;
}

 运行结果:

类中用户没有显示定义构造函数,C++编译器自动生成一个无参的默认构造函数;当用户显示定义构造函数时,C++编译器不再自动生成

用户没有显示定义, 编译器自动生成默认构造函数,默认构造函数初始化数据成员时,对于内置类型(int float dobule ...... 指针)的数据,不做任何处理内置类型的数据为随机值

class Date
{
private:int _year;int _month;int _day;
public://用户未定义默认构造函数,编译器自动生成默认构造//对于Date类中的内置类型不做任何处理void print(){cout << _year << "-" << _month << "-" << _day << endl;}
};
int main()
{Date d1;d1.print();return 0;
}

监视窗口:

 用户没有显示定义, 编译器自动生成默认构造函数,默认构造函数初始化数据成员时,对于自定义类型(struct class union)的数据,调用自定义类型中的默认构造函数

class Time
{
private:int hour; int minute;int second;
public://显示定义构造函数Time(){cout << "Time()" << endl;int hour = 0;int minute = 0;int second = 0;}
};class Date
{
private:int _year;int _month;//内置类型--->_year _month _dayint _day;Time _t;//自定义类型----> _t
public:void print(){cout << _year << "-" << _month << "-" << _day << endl;}
};
int main()
{Date d;d.print();return 0;
}

运行结果:

由于默认构造函数对于内置类型成员不做任何处理,为弥补这种缺陷,C++11允许在类中声明时给缺省值

class Date
{
private:int _year = 2023;int _month = 12;//内置类型成员声明时给缺省值int _day = 18;
public:void print(){cout << _year << "-" << _month << "-" << _day << endl;}};
int main()
{Date d;d.print();return 0;
}

运行结果:

析构函数

析构函数的作用是在对象生命周期结束时自动调用,用于完成对象中资源的清理工作,例如释放内存、关闭文件等操作;

析构函数的特性:

  1. 析构函数的函数名:  ~ 类名
  2. 析构函数无参无返回值;
  3. 一个类只能有一个析构函数,若未显式定义,系统会自动生成默认的析构函数;

      ( 注意:析构函数不能重载

     4. 对象生命周期结束时,C++编译系统系统自动调用析构函数;

class Stack
{
private:int* _a;int _top;int _capacity;
public://默认构造函数Stack(int capacity=4){cout << "Stack()" << endl;_a = (int*)malloc(sizeof(int)* capacity);if (_a == nullptr){perror("malloc failed:");exit(-1);}_top = 0;_capacity = capacity;}//析构函数~Stack() //函数名:~Stack && 没有参数{cout << "~stack()" << endl;free(_a);_a = nullptr;_top = 0;_capacity = 0;}
};int main()
{Stack st;//创建对象时调用默认构造函数return 0;
}//对象st出作用域时自动调用析构函数

运行结果:

析构函数对于内置类型成员不做任何处理;对于自定义类型中的成员,清空哪个类的对象中的数据就调用哪个类的析构函数;

class Time
{
private:int _hour;int _minute;int _second;
public:~Time(){cout << "~Time" << endl;}
};
class Date
{
private:int _year;int _day; //内置类型: _year _month _dayint _month;Time _t; //自定义类型: _t
};
int main()
{Date d;return 0;
}

运行结果:

如果类中没有申请资源时,用户可以不显示定义析构函数,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类;
class Stack
{
private:int* _a;int _top;int _capacity;
public://默认构造函数Stack(int capacity=4){cout << "Stack()" << endl;_a = (int*)malloc(sizeof(int)* capacity);if (_a == nullptr){perror("malloc failed:");exit(-1);}_top = 0;_capacity = capacity;}//用户显示实现析构函数~Stack() {cout << "~stack()" << endl;free(_a);_a = nullptr;_top = 0;_capacity = 0;}
};int main()
{Stack st1;Stack st2;return 0;
}

由于main()函数建立函数栈帧时先创建st1对象,再创建st2对象,所以当main()函数函数栈帧销毁时,先对st2开辟的空间调用析构函数,再对st1开辟的空间调用析构函数;

拷贝构造函数

拷贝构造函数的作用:利用已经存在的对象创建一个新对象时(拷贝对象),就会调用新对象的拷贝构造函数初始化新对象的成员变量

拷贝构造函数的特征:

  1. 拷贝构造函数是构造函数的重载形式(拷贝构造函数是一个特殊的构造函数);
  2. 拷贝构造函数的参数是同类型的对象并且参数只有一个而且是类类型对象的引用;

拷贝场景一:函数参数类型为类类型对象

func()函数传参时将类对象d1作为实参传递给形参d,而传参的本质是拷贝,因为形参和实参空间是独立的,但数据内容是相同的;对象(结构体)可以传值调用,也可以传址调用;

class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}};
void func(Date d)
{d.Print();
}
int main()
{Date d1(2023, 12, 20);func(d1);return 0;
}

结构体作为实参,采用传值传参,结构体多大,传参时函数栈帧开辟的空间就有多大; 而且形参的改变并不能影响实参(空间是独立的);C++中,为了传参时不发生拷贝,可以采取传址传参传址传参只是获取到实参的地址,不会发生拷贝;还可以让引用作为func()函数的形参,因为引用只是实参的别名,形参和实参共用同一块内存空间,所以也不会发生拷贝

 浅拷贝/值拷贝: 拷贝构造函数对象按内存存储按字节序完成拷贝;

通过监视窗口查看日期类对象d1与拷贝对象d

假设极端场景下,不得不采用传值传参时,会发生什么?

class Stack
{
private:int* _a;int _capacity;int _top;
public://构造函数Stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (nullptr == _a){perror("malloc failed: ");}_capacity = capacity;_top = 0;}//析构函数~Stack(){free(_a);_capacity = _top = 0;_a = nullptr;}
};
void func2(Stack st)
{//...
}
int main()
{Stack st1;func2(st1);return 0;
}

运行结果:

原因如下:

由于是值拷贝,对象st1的成员变量_a与对象st的成员变量_a数值相同,那么这两个指针指向同一块内存空间,然而C++的析构函数在对象销毁时自动调用,func2()函数调用结束后,调用析构函数释放对象st中的成员变量_a所指向的空间,当主函数(main())调用结束后,调用析构函数释放对象st1中的成员变量_a所指向的空间,造成同一块空间的二次释放;

如何解决值拷贝所出现的问题?

C++规定,自定义类型的对象拷贝时,调用拷贝构造函数实现深拷贝;

拷贝场景二:利用已存在的对象创建新对象

class Date
{
private:int _year;int _month;int _day;
public:
//构造函数Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
//假设日期类的拷贝构造函数如下Date(Date d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}};
int main()
{Date d1(2023, 12, 20);//调用构造函数Date d2(d1);//调用拷贝构造函数 d1-已存在的对象 d2-拷贝对象return 0;
}

为什么使用拷贝构造函数使用传值方式编译器直接报错

因为会引发无穷递归调用;

原因如下:

 如何解决传值调用所引发的无穷递归问题?

 采用引用作为拷贝构造函数的形参

返回到拷贝场景一,浅拷贝对于栈类,对于同一块空间释放两次,如何解决?
class Stack
{
private:int* _a;int _capacity;int _top;
public://构造函数Stack(int capacity = 4){_a = (int*)malloc(sizeof(int)* capacity);if (nullptr == _a){perror("malloc failed: ");}_capacity = capacity;_top = 0;}//拷贝构造函数//this指向st stt为st1的别名Stack(Stack& stt){//开辟与st1一样大的空间this->_a = (int*)malloc(sizeof(int)*stt._capacity);if (nullptr ==this->_a){perror("malloc failed:");exit(-1);}//拷贝指向的资源memcpy(this->_a, stt._a, sizeof(int)*stt._top);this->_top = stt._top;this->_capacity = stt._capacity;}//析构函数~Stack(){free(_a);_capacity = _top = 0;_a = nullptr;}
};
void func2(Stack st)
{//...
}
int main()
{Stack st1;func2(st1);return 0;
}

运行结果:

监视窗口:

用户未显示定义拷贝构造函数,编译器会生成默认的拷贝构造函数;

默认的拷贝构造函数对内置类型成员完成值拷贝,对于自定义类型成员调用其拷贝构造函数完成拷贝;

编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝;

拷贝场景三:函数返回值类型为类类型对象

class Stack
{
private:int* _a;int _capacity;int _top;
public://构造函数Stack(int capacity = 4){_a = (int*)malloc(sizeof(int)* capacity);if (nullptr == _a){perror("malloc failed: ");}_capacity = capacity;_top = 0;}//拷贝构造函数//this指向st stt为st1的别名Stack(Stack& stt){//开辟与st1一样大的空间this->_a = (int*)malloc(sizeof(int)*stt._capacity);if (nullptr ==this->_a){perror("malloc failed:");exit(-1);}//拷贝指向的资源memcpy(this->_a, stt._a, sizeof(int)*stt._top);this->_top = stt._top;this->_capacity = stt._capacity;}//析构函数~Stack(){free(_a);_capacity = _top = 0;_a = nullptr;}
};Stack func()
{Stack st;return st;//传值返回,返回谁?-->返回时先将局部变量st保存于寄存器中;//局部变量st出作用域销毁,因此返回的是st的拷贝;所以此处调用拷贝构造函数;
}
int main()
{func();return 0;
}

当局部变量st被static修饰,此时st存放于静态区,出作用域并不会被销毁,可以考虑返回类型为传引用返回,共用同一块内存空间,不会调用拷贝构造函数,少了一次拷贝;

Stack& func()
{static Stack st;return st;//传值返回,返回谁?-->返回时先将局部变量st保存于寄存器中;//局部变量st出作用域销毁,因此返回的是st的拷贝;所以此处调用拷贝构造函数;
}
int main()
{func();return 0;
}

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

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

相关文章

推箱子地图库1-49关

推箱子地图库1-49关 49关 local WALL1--{"墙","墙 "}4 10287 local DEST2--{"目的地",""}1 4001100 10157 local BOX3--{"箱子","&#xffe5;"} 2 2000801 local PLAYER4--{"玩家","&&a…

influxdb-cluster集群部署

一.部署环境 * InfluxDB集群节点数&#xff1a;mate服务至少3个节点&#xff0c;节点数越多&#xff0c;集群性能越高。 * 操作系统&#xff1a;支持的操作系统包括Linux、Windows和MacOS。 * CPU&#xff1a;至少2核4线程&#xff0c;主频越高越好。 * 内存&#xff1a;至少8…

【已解决】Redis序列化反序列化不一致 - String类型值多了双引号问题

在项目中使用spring 的RedisTemplate从redis中获取数据的时候&#xff0c;发现字符串的value多了双引号。如下图所示&#xff1a; 产生的原因可以分一下几个方面&#xff1a; 一、采用的序列化对象不同 多服务之间调用时候&#xff0c;序列化服务A(向redis中写数据的)和反序…

【翼韵】数据上传沟通、决策、试错

韵达德邦来说说&#xff0c;翼达、翼韵、翼德、翼邦的小记录 翼达同学&#xff1a;沟通成本好大&#xff01; 翼韵同学&#xff1a;决策成本很大&#xff01; 翼德同学&#xff1a;试错成本更大&#xff01; 翼邦同学&#xff1a;你们加起最大&#xff01; QY成本沟通成本33%决…

Win7如何修改MAC地址

MAC地址&#xff0c;又叫做物理地址、硬件地址&#xff0c;是用来定义网络设备的位置&#xff0c;一般情况下&#xff0c;MAC地址在网卡中是固定的&#xff0c;但不排除有人手动去修改自己的MAC地址。win7如何修改MAC地址?其实修改MAC地址的方法很简单&#xff0c;可以通过硬件…

K8s出现问题时,如何排查解决!

K8s问题的排查 1. POD启动异常、部分节点无法启动pod2. 审视集群状态3. 追踪事件日志4. 聚焦Pod状态5. 检查网络连通性6. 审视存储配置7. 研究容器日志8. K8S集群网络通信9. 问题&#xff1a;Service 是否通过 DNS 工作&#xff1f;10. 总结1、POD启动异常、部分节点无法启动p…

普通Java项目打包可执行Jar

普通Java项目打包 IDEA配置 在项目配置中选择 Artifacts -> JAR -> From modules with dependencies 选择项目模块&#xff0c;程序主类、依赖引入方式、清单文件位置 确认Jar名称和Jar输出目录 通过 Build -> Build Artifact -> Build 打包Jar文件 Java打包可执…

JavaWeb笔记之SVN

一、版本控制 软件开发过程中 变更的管理&#xff1b; 每天的新内容;需要记录一下&#xff1b; 版本分支;整合到一起&#xff1b; 主要的功能对于文件变更的追踪&#xff1b; 多人协同开发的情况下,更好的管理我们的软件。 大型的项目;一个团队来进行开发; 1: 代码的整合 2: 代…

2023-强网杯-【强网先锋-ez_fmt】

文章目录 ez_fmt libc-2.31.so检查main思路exp 参考链接 ez_fmt libc-2.31.so 检查 没有地址随机化 main 简单粗暴的printf格式化字符串漏洞 思路 泄露地址&#xff0c;覆盖返回地址形成ROP链 printf执行时栈上存在__libc_start_main243的指令的地址&#xff0c;可以泄露…

C++哈希表的实现

C哈希表的实现 一.unordered系列容器的介绍二.哈希介绍1.哈希概念2.哈希函数的常见设计3.哈希冲突4.哈希函数的设计原则 三.解决哈希冲突1.闭散列(开放定址法)1.线性探测1.动图演示2.注意事项3.代码的注意事项4.代码实现 2.开散列(哈希桶,拉链法)1.概念2.动图演示3.增容问题1.拉…

MyBatis 架构分析

文章目录 三层架构一、基础支撑层1.1 类型转换模块1.2 日志模块1.3 反射工具模块1.4 Binding 模块1.5 数据源模块1.6 缓存模块1.6 解析器模块1.7 事务管理模块 二、核心处理层2.1 配置解析2.2 SQL 解析与 scripting 模块。2.3 MyBatis 中的 scripting 模块就是负责动态生成 SQL…

SpringCloud Alibaba(itheima)

SpringCloud Alibaba 第一章 微服务介绍1.1系统架构演变1.1.1单体应用架构1.1.2垂直应用架构1.1.3分布式架构1.1.4 SOA架构1.1.5微服务架构 1.2微服务架构介绍1.2.1微服务架构的常见问题1.2.2微服务架构的常见概念1.2.3微服务架构的常见解决方案 1.3 SpringCloud Alibaba介绍1.…

用23种设计模式打造一个cocos creator的游戏框架----(二十二)原型模式

1、模式标准 模式名称&#xff1a;原型模式 模式分类&#xff1a;创建型 模式意图&#xff1a;用原型实例指定创建对象的种类&#xff0c;并且通过复制这些原型创建新的对象 结构图&#xff1a; 适用于&#xff1a; 1、当一个系统应该独立于它的产品创建、构成和表示时 2、…

BUUCTF-Crypto合集-WP

获取CTF工具可关注CSJH网络安全团队&#xff0c;回复CTF工具 一眼就解密 下面的字符串解密后便能获得flag&#xff1a;ZmxhZ3tUSEVfRkxBR19PRl9USElTX1NUUklOR30 注意&#xff1a;得到的 flag 请包上 flag{} 提交 大小写字母加数字&#xff0c;而且等于号结尾&#xff0c;bas…

实在智能斩获钛媒体2023全球创新评选科技类「 大模型创新应用奖」

近日&#xff0c;历时三天的钛媒体2023 T-EDGE全球创新大会以“新视野新链接”为主题在北京隆重举办。作为科创领域全新高度的年度盛事&#xff0c;大会吸引了AI各产业链近百位海内外创投人、尖端企业家、商业领袖和国际嘉宾齐聚一堂&#xff0c;围绕新一轮AI革命、智慧数字化、…

Java中使用JTS实现WKB数据写入、转换字符串、读取

场景 Java中使用JTS实现WKT字符串读取转换线、查找LineString的list中距离最近的线、LineString做缓冲区扩展并计算点在缓冲区内的方位角&#xff1a; Java中使用JTS实现WKT字符串读取转换线、查找LineString的list中距离最近的线、LineString做缓冲区扩展并计算点在缓冲区内…

从Maven初级到高级

一.Maven简介 Maven 是 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建和依赖管理支持的工具。 一个 Maven 工程有约定的目录结构&#xff0c;约定的目录结构对于 Maven 实现自动化构建而言是必不可少的一环&#xff0c;就拿自动编译来说&#xff0c;Maven 必须 能…

python调用DALL·E绘画

实现用gpt的api和他对话后&#xff0c;我们试着调用DALLE的api进行绘画 参考文档 OpenAI API 运行代码 from openai import OpenAIclient OpenAI()user_prompt input("请输入您想生成的图片描述: ")response client.images.generate(model"dall-e-3"…

分享70个Java源码总有一个是你想要的

分享70个Java源码总有一个是你想要的 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 源码下载链接&#xff1a;https://pan.baidu.com/s/1uyWfeUuO_4jRbAEw825qRw?pwd6666 提取码&#xff1a;6666 项目名称 CRUD is ReallyU…

电商数据之巅:挖掘无限价值的蓝海

在数字时代的大潮中&#xff0c;数据已成为新的黄金和石油&#xff0c;尤其在电商领域。电商平台每天都在产生海量的数据&#xff0c;这些数据不仅是对消费者行为的记录&#xff0c;更是隐藏着无限的商机和价值。本文将带你走进电商数据的神奇世界&#xff0c;探寻其无尽可能的…