【C++】类与对象的学习(中)

news/2024/8/31 9:52:13 标签: c++

目录

一、默认成员函数:

二、构造函数:

1、定义:

2、理解:

三、析构函数:

1、定义:

2、理解:

四、拷贝构造:

1、定义:

2、理解:

五、运算符的重载:

1、一般运算符的重载:

2、赋值运算符的重载:

1、格式:

2、注意:


一、默认成员函数:

一个类中,里面什么都不写的话就叫做空类,但在其中并不是什么都没有的,编译器会自动生成6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

二、构造函数:

1、定义:

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

这是每次创建一个实例时,这个函数都会调用一次。

2、理解:

1、函数名与类名相同。

2、无返回值(也不用写void)。

3、对象实例化的时候,编译器自动调用对应的构造函数。

接下来用一串代码理解上述3条特征。

class Date
{
public:
	Date()
	{
		_year = 2;
		_month = 2;
		_day = 2;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Print();
	return 0;
}

上述代码中,Date()这个就是一个构造函数,和类名相同,也不用写任何返回值,可以看到运行后的结果是已经调用构造函数Date后的值

4、函数构造可以进行重载。

注意:无参构造函数的调用后面不加(),这样和函数的声明会产生歧义。

class Date
{
public:
	Date()
	{
		cout << "无参数构造函数的调用" << endl;
		_year = 2;
		_month = 2;
		_day = 2;
	}
	Date(int year, int month, int day)
	{
		cout << "有三个参数构造函数的调用" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;//调用无参构造函数
	d1.Print();
	Date d2(2024,7,4);//调用带参数的构造函数
	d2.Print();
	return 0;
}

5、如果类中没有显式定义构造函数,那么C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

6、首先要知道:

C++把类型分为了两种:

a、内置类型(基本类型):int/char/double...

b、自定义类型:struct/class/union...

如果我们不写构造函数,那么编译器就会默认生成构造函数,此时这个默认构造函数会对内置类型不做任何处理(虽然有些编译器会进行处理,但那是个性化的,不是所有的编译器都会处理),自定义类型就会去调用它的默认构造。

class Date
{
public:
	Date()
	{
		cout << "无参数构造函数的调用by Date" << endl;
		_year = 2;
		_month = 2;
		_day = 2;
	}
	Date(int year, int month, int day)
	{
		cout << "有三个参数构造函数的调用by Date" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};


class Stu
{
private:
	char name[20];
	int age;
	char id[15];

	Date d1;
};

int main()
{
	Stu s1;
	return 0;
}

如上所示:

实例化Stu这个类,但是发现没有写构造函数,就会默认生成个构造函数,那些内置类型不做处理,自定义类型d1就会调用它的默认构造(Date())。

如下图都是内置类型,就不会做任何处理。

7、无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个

理解:不传参就可以调用的是默认构造函数
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。

所以:

1、一般情况下,有内置类型成员,就需要自己写构造函数,不能用编译器自己生成。

2、如果全部都是自定义类型的成员,那么就可以考虑使用编译器自己生成的构造函数。

ps:

在C++11中,在成员变量声明的时候可以给缺省值。

如上所示:

在Date这个类中,可以给默认缺省值,给编译器生成默认构造函数用。

注意:

C++11以后才支持的,这里是进行声明(因为没有创建空间),不是初始化!!!

对于构造函数:
一般情况下都需要自己写,

但是,下面两种情况可以不用自己写:

a、内置成员都有缺省值,且初始化符合我们的要求。

b、全是自定义类型的构造,且这些类型都定义默认构造。

三、析构函数:

1、定义:

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

2、理解:

1、析构函数名就是类名前面加上一个“~”;

2、和构造函数一样没有参数返回类型,并且没有参数,所以不能够重载

3、 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数,在程序结束的时候自动调用;

4、自动生成的默认析构函数,对内置类型不做处理,自定义类型会调用其析构函数。

那么什么时候可以显示定义析构函数,什么时候可以不用显示定义析构函数呢:

1、一般,当存在动态申请资源的时候就需要显示定义;

2、当没有动态申请资源的时候就不用显示定义;

3、当需要释放资源的成员变量都是自定义类型的时候,不用写析构,这个自定义类型就会调用它的析构函数。

#include<iostream>
using std::cout;
using std::endl;

class Stack
{
public:
	Stack()
	{
		cout << "Stack()已经调用" << endl;
		_a = (int*)malloc(sizeof(int) * 4);
		if (_a == nullptr)
		{
			perror("stack malloc fail");
			return;
		}
		_capacity = 4;
		_top = 0;
	}

	Stack(int capacity)
	{
		cout << "Stack(int capacity)已经调用" << endl;
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("stack malloc fail");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}

	~Stack()
	{
		cout << "~Stack()已经调用" << endl;
		free(_a);
		_a = nullptr;
		_capacity = _top = 0;
	}
private:
	int* _a =  nullptr;
	int _top = 0;
	int _capacity;
};

int main()
{
	Stack s1;
	Stack s2(10);
	return 0;
}

如上~Stack()就是一个析构函数,当对象销毁时就会自动调用。

四、拷贝构造:

首先要了解:C++规定,传参赋值中:创建一个已有对象一模一样的新对象,内置类型直接拷贝,传值的自定义类型必须调用拷贝构造完成拷贝(深浅拷贝有关系)

1、定义:

首先要了解到,拷贝引入是在创建对象的时候,创建一个已有对象一模一样的新对象。

它只有一个形参,该形参是我要复制的对象(一般可以用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

2、理解:

1、拷贝构造是构造函数的一种重载形式;

2、拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

3、当没有显示定义拷贝构造函数的时候,默认拷贝构造会对内置类型成员完成值拷贝

                                                                           对自定义类型成员会调用它的拷贝构造

此时在日期类中,可以不用写默认拷贝拷贝构造,正常拷贝,但是有些情况,比如在栈这个类中,如果使用编译器给的默认拷贝构造,会存在问题,会拷贝出同一个指向同一空间的指针,这时如果析构的话,会对同一块空间释放两次,这时程序就会崩溃了。

针对上述为题,我们就需要自己实现拷贝构造,实现深拷贝来解决上述问题。

就要完成开辟一块空间,然后在这块空间里面,拷贝那些值。

class Date
{
public:
	Date(int year = 2000, 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, 7, 13);
	Date d2(d1);
	return 0;
}

上述就是浅拷贝的例子。

接下来,对这个栈实现深拷贝:

	Stack(Stack& st)
	{
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (_a == nullptr)
		{
			perror("stack malloc fail");
			return;
		}
		memcpy(_a, st._a, sizeof(int) * st._capacity);
		_top = st._top;
		_capacity = st._capacity;
	}

五、运算符的重载:

1、一般运算符的重载:

C++相对于C语言,增加了运算符重载。使用关键字operator后面加上要重载的运算符。

函数名:operator<

函数原型:返回值类型 operator操作符(参数列表)

注意:

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

bool operator<(const Date& x)
{
	if (_year > x._year)
	{
		return false;
	}
	else if (_year == x._year && _year > x._year)
	{
		return false;
	}
	else if (_year == x._year && _year > x._year && _day > x._day)
	{
		return false;
	}
	return true;
}

以上就是重载<运算符,_year实际上是this->_year,但是如果写在全局域中,那么就会无法访问私有成员,解决办法可以用友元,或者把operator<写成成员函数。

2、赋值运算符的重载:

1、格式:

1、参数类型:const 类名&,传递引用可以提高传参效率
2、返回值类型:类名&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
3、返回*this :要复合连续赋值的含义

2、注意:

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

在这默认生成赋值重载和拷贝构造行为一样,

对内置类型成员进行值拷贝/浅拷贝,

对自定义类型成员会调用它的赋值重载。

Date& operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return (*this);
}

以上就是在日期类的重载赋值符,这个与拷贝构造不一样,拷贝构造是通过一个类创造一个一样的类,而这个是将一个已经存在的类赋值给另一个已经存在的类。


http://www.niftyadmin.cn/n/5561743.html

相关文章

Android获取当前屏幕显示的是哪个activity

在 Android 中&#xff0c;要获取当前屏幕显示的 Activity&#xff0c;可以使用以下几种方法&#xff1a; 方法一&#xff1a;使用 ActivityManager 获取当前运行的任务信息 这是一个常见的方法&#xff0c;尽管从 Android 5.0 (API 21) 开始&#xff0c;有些方法变得不太可靠…

RocketMQ实现分布式事务

RocketMQ的分布式事务消息功能&#xff0c;在普通消息基础上&#xff0c;支持二阶段的提交。将二阶段提交和本地事务绑定&#xff0c;实现全局提交结果的一致性。 1、生产者将消息发送至RocketMQ服务端。 2、RocketMQ服务端将消息持久化成功之后&#xff0c;向生产者返回Ack确…

Web开发-LinuxGit基础4-联网-克隆与Push

已知我在Gitee上&#xff0c;注册了用户名"二傻子"&#xff0c;和邮箱地址"3591178539qq.com"&#xff0c;并且已经完成了SSH公钥配置。现在&#xff1a; 1. 给出一个示例&#xff0c;下载别人的库&#xff0c;保存到文件夹GiteeWinShiNing当中&#xff1b…

技能学习——利用电脑连接华为手机

1. 下载scrcpy链接 https://github.com/Genymobile/scrcpy/blob/master/doc/connection.md 2. scrcpy使用教程链接 https://blog.csdn.net/was172/article/details/99705855 3. 华为手机无法显示设备解决方法参考连接 https://blog.csdn.net/qq_37651894/article/details/12796…

STM32-寄存器点灯案例详解

本文以PA1引脚点亮LED灯为案例&#xff0c;解析了STM32寄存器操作的配置过程&#xff0c;以及从手册查询方法和寄存器配置步骤。 一、概念 1.十六进制和二进制之间相互转换关系 首先&#xff0c;需要了解十六进制和二进制之间的基本转换方法。十六进制是一种基数为16的数制&…

Jupyter Notebook基础:用IPython实现动态编程

Jupyter Notebook基础&#xff1a;用IPython实现动态编程 1. 引言 Jupyter Notebook是一个基于Web的交互式计算环境&#xff0c;允许用户创建和共享包含实时代码、方程式、可视化和文本叙述的文档。它广泛应用于数据清洗与转换、数值模拟、统计建模、机器学习以及其他数据科学…

tree组件实现折叠与展开功能(方式1 - expandedTree计算属性)

本示例节选自vue3最新开源组件实战教程大纲&#xff08;持续更新中&#xff09;的tree组件开发部分。考察响应式对象列表封装和computed计算属性的使用&#xff0c;以及数组reduce方法实现结构化树拍平处理的核心逻辑。 实现思路 第一种方式&#xff1a;每次折叠或展开后触发…

[GXYCTF2019]Ping Ping Ping1

打开靶机 结合题目名称&#xff0c;考虑是命令注入&#xff0c;试试ls 结果应该就在flag.php。尝试构造命令注入载荷。 cat flag.php 可以看到过滤了空格,用 $IFS$1替换空格 还过滤了flag&#xff0c;我们用字符拼接的方式看能否绕过,ag;cat$IFS$1fla$a.php。注意这里用分号间隔…