C++必修:类与对象(二)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++学习
贝蒂的主页:Betty’s blog

1. 构造函数

1.1. 定义

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

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。

下面是一个日期类的构造函数

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(1,1,1);//自动调用d1.Print();return 0;
}

img

  • 构造函数的功能就相当于我们之前书写的初始化函数,但由于其自动调用的特性,大大提升了代码的容错率。

1.2. 注意

  1. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
class Date
{
public:/*Date(int year, int month, int day){_year = year;_month = month;_day = day;}*///编译器会自动生成一个无参的默认构造函数void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
  1. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
class Date
{
public:Date()//无参{_year = 1900;_month = 1;_day = 1;}Date(int year = 1900, int month = 1, int day = 1)//全缺省{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d;//引起歧义return 0;
}

img

当存在多个默认构造函数时,一旦我们对对象进行实例化,编译器不知道调用哪个构造函数,就会引起歧义。

  1. 编译器生成的默认构造函数只会对自定义类型(类)进行初始化,内置类型(int,double…)不会进行初始化,即调用自定义类型的构造函数。
class Betty
{
public:Betty(){cout << "Betty" << endl;}
private:int _a;
};
class Date
{
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:Betty b;int _year;int _month;int _day;
};
int main()
{Date d;d.Print();return 0;
}

img

从上述实例观察,编译器自动生成的默认构造函数的确只对自定义类型进行初始化。

**特别注意:**C++11 中针对内置类型成员不初始化的缺陷,又进行了优化,即:内置类型成员变量在类中声明时可以给默认值

class Date
{
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year = 1;//缺省值int _month = 1;//缺省值int _day = 1;//缺省值
};
int main()
{Date d;d.Print();return 0;
}

img

2. 初始化列表

2.1. 定义

初始化列表作用与构造函数类似,它是在构造函数中以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。

下面我们还是以一个日期类来示范:

class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){//...}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d(2024,1,3);d.Print();return 0;
}

img

2.2. 注意

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:引用成员变量,const成员变量,自定义类型成员(且该类没有默认构造函数时)。因为这些变量都需要在定义时初始化。
class A
{
public:A(int a):_a(a){}
private:int _a;
};
class B
{
public:B(int a, int ref):_b(a), _ref(ref), _n(3){}
private:A _b; // 没有默认构造函数int& _ref; // 引用const int _n; // const常量
};
  1. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
class A
{
public:A(int a = 1)//默认构造:_a(a){cout << "A(int a = 1)" << endl;}
private:int _a;
};
class B
{
public:B(int a):_m(a){}
private:int _m;A _b; 
};
int main()
{B b(2);return 0;
}

img

  1. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << endl;cout << _a2 << endl;}
private:int _a2;int _a1;
};
int main()
{A aa(1);aa.Print();
}//输出??

如果是以初始化列表的顺序,那应该输出1和1。如果以声明顺序,那应该是1与随机值。

img

3. 析构函数

3.1. 定义

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。其特点如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

下面是一个日期类的析构函数:

class Date
{
public://构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}//析构函数~Date(){_year = _month = _day = 0;}
private:int _year;int _month;int _day;
};

析构函数就相当于C语言中的销毁函数,但由于其自动调用的特性,大大提升了代码的容错率。

3.2. 注意

  1. 如果类中没有显式定义析构函数,则C++编译器会自动生成一个析构函数,一旦用户显式定义编译器将不再生成。
class Date
{
public://构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}//析构函数/*~Date(){_year = _month = _day = 0;}*///编译器会自动生成一个析构函数
private:int _year;int _month;int _day;
};
  1. 编译器生成的析构函数对内置类型(int,double…)不会进行处理,对于自定义类型调用其析构函数。
class Betty
{
public:~Betty(){cout << "~Betty" << endl;}
private:int _a;
};
class Date
{
public://构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}//默认生成
private:Betty b;int _year;int _month;int _day;
};

img

  1. 因为指针类型也属于内置类型,所以默认成员在动态内存开辟内存后,必须显式写成析构函数。不能靠编译器默认生成。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 2){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array!=nullptr){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};

比如说上述代码,默认生成的析构函数并不会释放其内存,就可能造成内存泄漏。

4. 拷贝构造函数

4.1. 定义

**拷贝构造函数:**只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。其特点如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d) {_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024,2,2);Date d2(d1);//拷贝构造Date d3 = d1;//拷贝构造d1.Print();d2.Print();d3.Print();return 0;
}

img

4.2. 注意

  1. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
	Date(const Date d) //error{_year = d._year;_month = d._month;_day = d._day;}

img

  1. 若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date(const Date& d)//{//	_year = d._year;//	_month = d._month;//	_day = d._day;//}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 2, 2);Date d2(d1);//拷贝构造Date d3 = d1;//拷贝构造d1.Print();d2.Print();d3.Print();return 0;
}

img

  1. 因为编译器默认生成的拷贝构造函数是值拷贝,在某些场景下就会出错。比如说以下场景:
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);//默认拷贝构造return 0;
}

img

为什么会出现这种情况呢?让我们看看下面这幅图:

img

因为默认生成的拷贝构造只是进行只拷贝,对于sizecapacity拷贝并不会出现问题,但是当s1的_array拷贝给s2的_array时,就会让s1与s2的同时指向同一片空间。而我们知道当对象的作用域结束时,会自动调用析构函数,同时对同一片空间析构两次,就会保错。所以当类中需要资源申请时,都需要手动写拷贝构造。

  1. 拷贝构造的应用场景有很多,能用引用尽量用引用,减少拷贝,提高程序效率。

5. 运算符重载

5.1. 定义

C++为了增强代码的可读性引入了运算符重载,运算符重载是具由运算符operator定义有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。该函数能让我们自定义类型像内置类型一样使用-+*/等运算符。

下面实现了简单判断日期是否相当的运算符重载:

class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){//...}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}int _year;int _month;int _day;
};
bool operator == (const Date&d1,const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
int main()
{Date d1(2024,1,1);Date d2(2024, 1, 1);if (d1 == d2)//也可以显示调用operator==(d1,d2);{cout << "日期相等" << endl;}else{cout << "日期不相等" << endl;}return 0;
}

当然我们也可以将运算符重载声明在类中。

bool operator==(const Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}

5.2. 注意

  1. 不能通过连接其他符号来创建新的操作符:比如operator@重载操作符必须有一个类类型参数
  2. 用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不 能改变其含义
  3. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  4. .* sizeof ? : :: . 注意以上5个运算符不能重载。

6. 赋值运算符重载

6.1. 定义

赋值运算符重载是将运算符 =进行运算符重载。但是它相较于其他运算符重载有着自己独特的特点。

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date& operator=(const Date& d)//赋值运算符重载{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};
  • 参数类型:const T& ,传递引用可以提高传参效率。
  • 返回值类型:T& ,返回引用可以提高返回的效率,支持连续赋值。
  • 检测是否自己给自己赋值。
  • 返回* this :要复合连续赋值的值。

6.2. 注意

  1. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){cout << "Time& operator=(const Time& t)" << endl;if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 2024;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;Date d2;d1 = d2;return 0;
}

img

  1. 因为编译器默认生成默认赋值运算符重载的是值拷贝,在某些场景下就会出错。具体实例参考拷贝构造函数。
  2. 赋值运算符只能重载成类的成员函数不能重载成全局函数。
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
Date& operator=(Date& left, const Date& right)//error
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}

因为赋值运算符如果不显式实现,编译器会生成一个默认的赋值运算符重载。此时用户再在类外自己实现一个全局的赋值运算符重载,就会和编译器在类中生成的默认赋值运算符重载冲突。

7. const修饰函数

首先我们得知道一个规则就是,**const修饰的常变量不能赋值给普通变量,因为这样造成const权限的放大,但是普通变量可以赋值给const修饰的常变量。**所以让我们来看看这段代码:

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};int main()
{const Date d2(2022, 1, 13);d2.Print();//errorreturn 0;
}

这段代码会出错,因为d2进行函数传参是将const Date*传过去,而函数接受参数的类型为Date*,这样就会造成权限的放大。为了解决这个问题,就需要使用const修饰原函数

	void Print() const{cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}

并且与原函数构成重载,可以同时存在。

8. 取地址及const取地址操作符重载

我们知道对自定义类型使用运算符需要对其进行重载,那么&自然也不例外。

class Date
{
public:Date* operator&(){return this;}const Date* operator&()const{return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如不想让别人获取到原有地址!

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

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

相关文章

(成品论文22页)24深圳杯数学建模A题1-4问完整代码+参考论文重磅更新!!!!

论文如下&#xff1a; 基于三球定位的多个火箭残骸的准确定位 针对问题一&#xff1a;为了进行单个残骸的精确定位&#xff0c;确定单个火箭残骸发生音爆 时的精确位置和时间&#xff0c;本文基于三球定位模型&#xff0c;考虑到解的存在性和唯一性&#xff0c; 选取了四个监测…

神经网络反向传播算法

今天我们来看一下神经网络中的反向传播算法&#xff0c;之前介绍了梯度下降与正向传播~ 神经网络的反向传播 专栏&#xff1a;&#x1f48e;实战PyTorch&#x1f48e; 反向传播算法&#xff08;Back Propagation&#xff0c;简称BP&#xff09;是一种用于训练神经网络的算…

JVM支持的可配置参数查看和分类

JVM参数大致可以分为三类: 标注指令:-开头。 这些是所有的HotSpot都支持的参数。可以用java-help 打印出来。 非标准指令: -X开头。 这些指令通常是跟特定的HotSpot版本对应的。可以用java -X打印出来。 不稳定参数: -XX 开头。 这一类参数是跟特定HotSpot版本对应的&#x…

[Java、Android面试]_24_Compose为什么绘制要比XML快?(高频问答)

欢迎查看合集&#xff1a; Java、Android面试高频系列文章合集 本人今年参加了很多面试&#xff0c;也有幸拿到了一些大厂的offer&#xff0c;整理了众多面试资料&#xff0c;后续还会分享众多面试资料。 整理成了面试系列&#xff0c;由于时间有限&#xff0c;每天整理一点&am…

常见公式的几何解释

本文旨在深入探讨常见数学公式的几何意义&#xff0c;通过直观的图形和解释&#xff0c;帮助读者更好地理解并掌握这些公式的本质。文章首先概述了公式与几何图形之间的紧密联系&#xff0c;然后选取了几个典型的数学公式&#xff0c;进行详细解析。每个公式都将配以相应的几何…

Linux操作系统·进程管理

一、什么是进程 1.作业和进程的概念 Linux是一个多用户多任务的操作系统。多用户是指多个用户可以在同一时间使用计算机系统&#xff1b;多任务是指Linux可以同时执行几个任务&#xff0c;它可以在还未执行完一个任务时又执行另一项任务。为了完成这些任务&#xff0c;系统上…

数据库基础--MySQL简介以及基础MySQL操作

数据库概述 数据库&#xff08;DATABASE&#xff0c;简称DB&#xff09; 定义:是按照数据结构来组织、存储和管理数据的仓库.保存有组织的数据的容器(通常是一个文件或一组文件) 数据库管理系统(Database Management System,简称DBMS) 专门用于管理数据库的计算机系统软件;…

【补充】图神经网络前传——图论

本文作为对图神经网络的补充。主要内容是看书。 仅包含Introduction to Graph Theory前五章以及其他相关书籍的相关内容&#xff08;如果后续在实践中发现前五章不够&#xff0c;会补上剩余内容&#xff09; 引入 什么是图&#xff1f; 如上图所示的路线图和电路图都可以使用…

【Spring Cloud】服务容错中间件Sentinel入门

文章目录 什么是 SentinelSentinel 具有以下特征&#xff1a;Sentinel分为两个部分: 安装 Sentinel 控制台下载jar包&#xff0c;解压到文件夹启动控制台访问了解控制台的使用原理 微服务集成 Sentinel添加依赖增加配置测试用例编写启动程序 实现接口限流总结 欢迎来到阿Q社区 …

【介绍下Unity编辑器扩展】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

【docker】Spring Boot3.x 打包 Docker容器

Docker化Spring Boot应用 创建文件夹 demo mkdir democd demo创建Dockerfile # 两个 openjdk 二选一 #FROM openjdk:17-jre-alpineFROM eclipse-temurin:17MAINTAINER chengxuyuanshitang <chengxuyuanshitangXX.com>RUN mkdir -p /workspace/java/demoCOPY demo.ja…

Android 11 裁剪系统显示区域(适配异形屏)

概述 在显示技术中&#xff0c;"OverScan"&#xff08;超扫描&#xff09;是一种调整显示图像边界的技术。通常情况下&#xff0c;OverScan 会在显示屏的边缘周围裁剪一小部分图像。这种裁剪是为了确保显示内容在屏幕上的完整可见性&#xff0c;尤其是在老式电视或投…

C++入门基础(二)

目录 缺省参数缺省参数概念缺省参数分类全缺省参数半缺省参数声明与定义分离 缺省参数的应用 函数重载函数重载概念例子1 参数类型不同例子2 参数的个数不同例子3 参数的顺序不同 C支持函数重载的原理--名字修饰(name Mangling) 感谢各位大佬对我的支持,如果我的文章对你有用,欢…

Visual Studio导入libtorch(Cuda版)

Visual Studio导入libtorch&#xff08;Cuda版&#xff09; 一、安装 官网&#xff1a;https://pytorch.org/get-started/locally/ 相应地选择并下载 二、环境变量配置 解压zip&#xff0c;得到libtorch文件夹&#xff0c;将libtorch\lib和libtorch\bin对应路径添加到系统环…

使 Elasticsearch 和 Lucene 成为最佳向量数据库:速度提高 8 倍,效率提高 32 倍

作者&#xff1a;来自 Elastic Mayya Sharipova, Benjamin Trent, Jim Ferenczi Elasticsearch 和 Lucene 成绩单&#xff1a;值得注意的速度和效率投资 我们 Elastic 的使命是将 Apache Lucene 打造成最佳的向量数据库&#xff0c;并继续提升 Elasticsearch 作为搜索和 RAG&a…

【JVM】简述类加载器及双亲委派机制

双亲委派模型&#xff0c;是加载class文件的一种机制。在介绍双亲委派模型之前&#xff0c;我需要先介绍几种类加载器&#xff08;Class Loader&#xff09;。 1&#xff0c;类加载器 Bootstrap&#xff0c;加载lib/rt.jar&#xff0c;charset.jar等中的核心类&#xff0c;由…

JWT是什么?如何使用?

JWT是什么&#xff1f;如何使用&#xff1f; 前言什么是JWT&#xff1f;概念工作方式JWT的组成HeaderPayloadSignatrue 实战引入依赖自定义注解定义实体类定义一个JWT工具类业务校验并生成token定义拦截器配置拦截器定义接口方法并添加注解开始验证 使用场景注意事项 JWT与传统…

ASR语音转录Prompt优化

ASR语音转录Prompt优化 一、前言 在ASR转录的时候&#xff0c;我们能很明显的感受到有时候语音识别不是很准确&#xff0c;这过程中常见的文本错误主要可以归纳为以下几类&#xff1a; 同音错误&#xff08;Homophone Errors&#xff09; 同音错误发生在不同词语发音相似或相…

用Excel做一个功能完备的仓库管理系统

1 基本设计思路 用到的Excel技术&#xff1a;sumif, vlookup, 表格(table)。基本思路&#xff1a;在有基础的商品、仓库等信息的情况下&#xff0c;对商品的每一个操作都有对应的单据&#xff0c;然后再汇总统计。标识&#xff1a;为了在不同的维度统计数量&#xff0c;各单据…

谷粒商城实战(020 RabbitMQ-消息确认)

Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第258p-第p261的内容 消息确认 生产者 publishers 消费者 consumers 设置配置类 调用api 控制台 抵达brocker 代理 新版本ReturnCallbac…