C++——类和对象(2)

文章目录

  • 一、前言
  • 二、类和对象------中
    • [2.1 默认成员函数](#2.1 默认成员函数)
    • [2.2 构造函数](#2.2 构造函数)
      • [2.2.1 顺序表的初始化](#2.2.1 顺序表的初始化)
      • [2.2.2 Date类默认构造](#2.2.2 Date类默认构造)
      • [2.2.3 Date类初始化构造](#2.2.3 Date类初始化构造)
        • [2.2.3.1 无参构造](#2.2.3.1 无参构造)
        • [2.2.3.2 全缺省构造](#2.2.3.2 全缺省构造)
      • [2.2.4 构造函数的特点](#2.2.4 构造函数的特点)
    • [2.3 析构函数](#2.3 析构函数)
      • [2.3.1 顺序表的销毁](#2.3.1 顺序表的销毁)
      • [2.3.2 Satck类和MyQueue类的析构](#2.3.2 Satck类和MyQueue类的析构)
      • [2.3.3 析构函数的特点](#2.3.3 析构函数的特点)
    • [2.4 拷贝构造函数](#2.4 拷贝构造函数)
      • [2.4.1 Date日期类的拷贝构造](#2.4.1 Date日期类的拷贝构造)
      • [2.4.2 Stack类和MyQueue类的拷贝构造](#2.4.2 Stack类和MyQueue类的拷贝构造)
      • [2.4.3 拷贝构造函数的特点](#2.4.3 拷贝构造函数的特点)
    • [2.5 运算符重载](#2.5 运算符重载)
      • [2.5.1 重载规则](#2.5.1 重载规则)
      • [2.5.2 赋值运算符重载](#2.5.2 赋值运算符重载)
        • [2.5.2.1 operator==](#2.5.2.1 operator==)
      • [2.5.3 取地址运算符重载](#2.5.3 取地址运算符重载)
        • [2.5.3.1 const成员函数](#2.5.3.1 const成员函数)
      • [2.5.3.2 取地址运算符重载的两种方式](#2.5.3.2 取地址运算符重载的两种方式)
  • 三、总结

一、前言

学前三问------今天我还在学习吗?我是不是真的在努力学习?学习完之后有什么新的收获?古话说得好,每日三省吾身,我们也得当仁不让啊。今天咱们接着学习类和对象,再深入探讨类和对象,领悟C++的语法之美。

二、类和对象------中

2.1 默认成员函数

在C++中,我们在编写一个类的时候,编译器会默认生成6个成员函数,顾名思义------默认成员函数就是不显示实现出来的,而是编译器自动生成的。那么这6个成员函数分别是哪些呢?先让我们来看思维导图,如下:

但是,虽然编译器默认实现了6个成员函数,这也并不意味着皆大欢喜,仍然存在两个注意点:
1、默认输生成的成员函数的行为是什么,是否满足我们的需求?
2、如果不满足我们的需求,我们又该如何自己实现?

2.2 构造函数

在C语言数据结构------如顺序表学习的时候,我们都会自己写一个Init初始化函数,而在C++中,我们则需要使用构造函数,它的功能和Init一样,但是构造函数具有自动调用的特点,这一点比起手动调用初始化函数就有了完美的优势。

2.2.1 顺序表的初始化

2.2.2 Date类默认构造

cpp 复制代码
//默认构造
#include <iostream>
using namespace std;
class Date
{
public:
	/*Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}*/
	Date()
	{
		_year;
		_month;
		_day;
	}

	void Print()
	{
		cout << _year << endl;
		cout << _month << endl;
		cout << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Print();
	/*Date d2(2005, 05, 29);
	d2.Print();*/
	return 0;
}

2.2.3 Date类初始化构造

2.2.3.1 无参构造
cpp 复制代码
//初始化
#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//Date()
	//{
	//	_year;
	//	_month;
	//	_day;
	//}

	void Print()
	{
		cout << _year << endl;
		cout << _month << endl;
		cout << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	/*Date d1;
	d1.Print();*/
	Date d2(2005, 5, 29);
	d2.Print();
	return 0;
}
2.2.3.2 全缺省构造
cpp 复制代码
#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1, int month = 2, int day = 3)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	/*Date()
	{
		_year;
		_month;
		_day;
	}*/

	void Print()
	{
		cout << _year << endl;
		cout << _month << endl;
		cout << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Print();
	/*Date d2();
	d2.Print();*/
	return 0;
}

2.2.4 构造函数的特点

1、函数名与类名相同;
2、无返回值,也不需要写void;
3、对象实例化编译器会自动调用对应的构造函数;
4、若没有显示实现构造函数,则编译器会调用默认构造,反之则不会调用默认构造;
5、构造函数可以重载;
6、在C++语法上:无参构造,全缺省构造,以及我们不写构造时编译器默认生成的构造统称为默认构造。这三种构造函数只能存在一个,若同时存在可能会报错。

2.3 析构函数

在顺序表表中除了Init这个初始化函数必不可少之外,还有一个函数也必不可少------Destroy销毁。而在C++中则使用析构函数来实现销毁作用。

2.3.1 顺序表的销毁

2.3.2 Satck类和MyQueue类的析构

cpp 复制代码
#include <iostream>
using namespace std;
class Stack
{
public:
	Stack(int n = 4)
	{
		arr = (int*)malloc(sizeof(int) * 4);
		if (arr == nullptr)
		{
			perror("malloc fail!");
			return;
		}
		capacity = n;
		top = 0;
	}
	~Stack()
	{
		cout << "~Stack" << endl;
		if (arr)
			free(arr);
		capacity = 0;
		top = 0;
	}
private:

	int* arr;
	int capacity;
	int top;
};
class MyQueue//MyQueue函数编译器会实现默认构造,析构也会自动调用Stack的析构函数
{
public:
private:
	Stack pushst;
	Stack popst;
};
int main()
{
	Stack st;//实现一次析构
	MyQueue mq;//实现两次析构
	return 0;
}

2.3.3 析构函数的特点

1、析构函数名就是在类名前加~

2、无参数无返回值,也不用加void;

3、一个类只能定义一个析构函数 ,若没有显示定义析构函数,编译器会调用默认的析构函数;

4、当对象的生命周期结束 的时候,编译器会自动调用析构函数

5、编译器自动生成的析构函数对内置类型不做处理,自定义类型会调用自己的析构函数

6、即使我们显示实现了析构函数,但是对于自定义类型成员还是会调用他自己的析构函数

7、如果类中并没有申请资源,则可以不调用析构函数,如Date日期类,可以进行默认析构 (即在栈区上开辟一块空间,随着函数栈帧的结束自动销毁 ),但是Satck类和MyQueue类是在堆区上申请资源,需要自己实现析构函数,否则会出现资源泄露;

8、对于一个局部域的多个对象,后定义的先析构,但是注意:static修饰的对象会在局部域的其他成员析构之后最后再进行析构。

2.4 拷贝构造函数

2.4.1 Date日期类的拷贝构造

cpp 复制代码
#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1, 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;
	}
	//Date( Date d)
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}
	Date(Date* d)
	{
		_year = d->_year;
		_month = d->_month;
		_day = d->_day;
	}
	void Print()
	{
		cout << _year << endl;
		cout << _month << endl;
		cout << _day << endl;
	}
	void Func1(Date d)
	{
		cout << &d << endl;
		d.Print();
	}
	Date& Func2(Date d)//传引用返回
	{
		Date tmp(2025, 5, 20);
		tmp.Print();
		return tmp;
	}

	Date Func3(Date& d)//传值返回
	{
		Date tmp(2025, 5, 20);
		tmp.Print();
		return tmp;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2005, 5, 29);
	Date d;
	//d.Func1(d1);
	//cout << &d1 << endl;


	//Date d2(&d1);//这里传的是地址,只是普通的构造,不是拷贝构造
	//d1.Print();
	//d2.Print();

	//Date d3(d1);//这里是拷贝构造
	//d3.Print();

	//Func2为传引用返回,随着函数的结束局部域对象被销毁,会出现野指针
	Date ret1 = d.Func2(d1);
	ret1.Print();

	cout << endl;

	//Func3为传值返回,传值返回会产生临时对象,把返回值存在临时对象中,由这个临时对象返回给接收值
	Date ret2 = d.Func3(d1);
	ret2.Print();

	return 0;
}

2.4.2 Stack类和MyQueue类的拷贝构造

cpp 复制代码
#include <iostream>
#include <assert.h>
using namespace std;
typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)//普通构造
	{
		arr = (STDataType*)malloc(sizeof(STDataType) * 4);
		if (arr == nullptr)
		{
			perror("malloc fail!");
			return;
		}
		capacity = n;
		top = 0;
	}
	Stack(const Stack& st)//拷贝构造
	{
		arr = (STDataType*)malloc(sizeof(STDataType) * st.capacity);
		if (arr == nullptr)
		{
			perror("malloc fail!");
			return;
		}
		top = st.top;
		capacity = st.capacity;
	}
	void Push(int x)
	{
		if (capacity == top)
		{
			int newcapacity = 2 * capacity;
			int* tmp = (STDataType*)realloc(arr, sizeof(STDataType) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail!");
				return;
			}
			capacity = newcapacity;
			arr = tmp;
		}
		arr[top++] = x;
	}
	int Front()
	{
		assert(top > 0);
		return arr[top - 1];
	}
	~Stack()
	{
		cout << "~Stack" << endl;
		if (arr)
			free(arr);
		arr = nullptr;
		capacity = 0;
		top = 0;
	}
private:
	STDataType* arr;
	int capacity;
	int top;
};
class MyQueue//MyQueue默认调用Satck的构造,析构域拷贝构造
{
public:
private:
	Stack pushst;
	Stack popst;
};
int main()
{
	Stack st1;//析构1次
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);
	st1.Push(4);
	cout << st1.Front() << endl;

	Stack st2 = st1;//如果没有拷贝构造,析构st1和st2的时候会析构同一块空间,发生报错

	MyQueue mq1;//析构2次
	MyQueue mq2;//析构2次

	return 0;
}

2.4.3 拷贝构造函数的特点

1、拷贝构造函数是构造函数是一个重载。

2、拷贝构造的第一个参数必须是类类型对象的引用,使用传值方式编译器会直接报错,因为这在语法逻辑上会引起无穷递归调用。

3、C++规定自定义类型对象进行拷贝行为时都会进行拷贝构造,所以传值传参和传值返回都会调用拷贝构造。

4、若未显示定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型会进行浅拷贝------一个字节一个字节的拷贝,对于自定义类型会调用它自己的拷贝构造。

5、传值返回和传值传参会产生一个临时对象调用拷贝构造,而传引用返回返回的是返回对象的别名,并没有产生拷贝。但是如果返回的是当前函数局部域的局部对象,当函数结束的时候,函数栈帧随之销毁,那么此时传引用返回的是一个野指针。传引用返回虽然可以减少拷贝,但是一定要确保返回对象在函数栈帧销毁之后还存在,以避免野指针的情况。

2.5 运算符重载

2.5.1 重载规则

1、当运算符被用于类类型的对象时,C++语法允许我们通过运算符重载的形式指定新的含义,并且语法规定类类型对象使用运算符时,必须使用运算符重载,否则会编译报错。

2、运算符重载是一个函数,它的名字由operator和后面定义的运算符共同组成。

3、运算符重载函数也具有返回类型、参数列表和函数体。4、运算符重载函数的参数个数和运算符作用的对象数量一样多。

5、如果一个重载运算符函数的成员函数,那么它的第一个运算对象默认传给this指针,因此运算符重载作为成员函数时,参数比成员对象少一个。

6、运算符重载之后,它的优先级和结合性与对应的内置类型运算符保持一致。

7、不能创建语法中没有的运算符,并且这五个运算符不支持重载。(.* :: sizeof ?: . )。

8、重载的运算符需要具有意义,不能无意义的重载运算符。

2.5.2 赋值运算符重载

2.5.2.1 operator==

我们在写一个类时,可能会在这个类中定义两个或更多的对象,但是如果我们直接使用运算符对这些对象进行运算的话,C++的语法是不支持的,这个时候我们就必须使用operator赋值运算符来进行运算符重载。例子如下:


2.5.3 取地址运算符重载

2.5.3.1 const成员函数

1.const修饰的成员函数称为const成员函数,const放在成员函数列表的后面。

2.const实际修饰的是成员函数隐含的this指针

如果我们对Print成员函数加上const修饰,那又会怎么样呢?

2.5.3.2 取地址运算符重载的两种方式

取地址运算符重载可以分为普通取地址运算符重载和const取地址运算符重载。但是对于这两个类型的重载,编译器自己生成的就够用了,一般不用自己显示实现取地址运算符的重载,除非在比较极端的场景:如我们不想让比如知道正确的地址,我们需要返回一个错误的地址。

三、总结

以上就是类和对象------中的全部内容了,相比于类和对象------上,这一节内容的难度可谓是直线上升了,有很多比较难的知识点,大家一定得下来好好消化,加油!

成功比的不是天赋,而是坚持

相关推荐
鼠鼠我捏,要死了捏2 分钟前
深入解析JVM垃圾回收调优:性能优化实践指南
java·jvm·gc
玩代码5 分钟前
Unity里的加力
开发语言·unity
都叫我大帅哥31 分钟前
TOGAF数据架构阶段完全指南:从理论到Java实战
java
天安彩1 小时前
mac下 vscode 运行 c++无法弹出窗口
c++·vscode·macos·clang
程序员编程指南1 小时前
Qt 网络编程进阶:WebSocket 通信
c语言·网络·c++·qt·websocket
玩代码1 小时前
Spring Boot2 静态资源、Rest映射、请求映射源码分析
java·spring boot·源码分析·spring boot2
小白的代码日记1 小时前
Java经典笔试题
java·开发语言
sakoba1 小时前
nginx学习
java·运维·学习·nginx·基础
山烛2 小时前
Python 数据可视化之 Matplotlib 库
开发语言·python·matplotlib·数据可视化
经典19922 小时前
Spring Boot 遇上 MyBatis-Plus:高效开发的奇妙之旅
java·spring boot·mybatis