【C++】类和对象(二)

类和对象(二)

类中有六个默认成员函数:

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

class Date {};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PXhmGDgv-1678794062412)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230311153324321.png)]

默认成员函数, 我们如果不写,编译器就会自动生成一个

但是如果我们实现了任意一个成员函数,编译器就不会会生成了

构造函数:

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

需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务并不是开空间创建对象,而是初始化对象。
其特征如下:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
    用户显式定义编译器将不再生成。
using namespace std;
class Stack
{
public:
	//构造函数
	Stack()
	{
		_a= nullptr;
		_size = _capacity = 0;
	}
    private:
	// 成员变量
	int* _a;
	int _size;
	int _capacity;
}
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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再
生成
   // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
Date d1;
return 0;
}

有参构造函数和无参构造函数:

注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

Stack st;//无参的时候需要这么写,不可以Stack st()这个是报错
Stack st(4);//有参和无参的写法区别

为什么这里无参构造函数,调用时加括号:Stack st()会报错?

编译器不清楚你到底要定义出一个对象还是定义个函数出来

这里我们写两个构造函数太过于麻烦了,可以利用全缺省来优化

Date(int year=2023, int month=3, int day=11)
	{
		_year = year;
		_month = month;
		_day = day;
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zh8FZbDM-1678794062413)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230311155255296.png)]

下面这两个函数,是否可以同时存在?

Date()
	{
		_year = 2023;
		_month = 3;
		_day = 11;
	}
	Date(int year=2023, int month=3, int day=11)
	{
		_year = year;
		_month = month;
		_day = day;
	}

从语法上,是可以同时存在,因为这两个完全符合重载函数的定义

实际上,这里是不可以同时存在的,因为编译器说他重载函数的调用不明确

> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ETAOM7dJ-1678794062413)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230311155810692.png)]

默认构造函数的问题:

  • 默认生成构造数,内置类型成员不做处理,

  • 自定义类型的成员,会去调用它的默认构造(这里指的默认构造是指不用传参数的构造函数)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O47xkkM7-1678794062413)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230311161445220.png)]

因为默认生成构造函数不会对内置类型进行处理,因此才有下面这个情况

(img-5IOzchOo-1678794062414)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230311162332054.png)]

生成默认的构造函但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默
认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的
默认构造函数并没有什么用??

> 解答:C++把类型分成内置类型(基本类型)和自定义类型。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5IOzchOo-1678794062414)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230311162332054.png)]

内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

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

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在
类中声明时可以给默认值。

在声明的时候给缺省值:

private:
	//内置类型
	//在声明位置给缺省值
	int _year=1;
	int _month=1;//这里是声明不是初始化
	int _day=1;

当成员变量同时有自定义类型和内置类型时:

默认构造函数,只给自定义类型赋值,内置类型没有初始化

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。

因为调用是,会存在歧义。

一般建议,每个类都提供一个默认的构造函数

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。

因为调用是,会存在歧义。

一般建议,每个类都提供一个默认的构造函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl0PbDZt-1678794062414)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230311172038425.png)]

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;
}
private:
int _year;
int _month;
int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{
Date d1;
}

析构函数:

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

析构函数是特殊的成员函数,其特征如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
    函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class Stack
{
public:
	//构造函数
	Stack()
	{
		_a = nullptr;
		_size = _capacity = 0;
	}
	Stack(int n)//构造函数可以进行函数重载
	{
		_a = (int*)malloc(sizeof(int) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_size = 0;
	}
	~Stack()//析构函数不可以重载
	{//析构函数
		free(_a);
		_a = NULL;
	}
	//成员函数
   /*void Init(int n)
   {
	   _a = (int*)malloc(sizeof(int) * n);
	   if (nullptr == _a)
	   {
		   perror("malloc申请空间失败");
		   return;
	   }

	   _capacity = n;
	   _size = 0;
   }*/

	void Push(int x)
	{
		//...
		_a[_size++] = x;
	}

	...

private:
	// 成员变量
	int* _a;
	int _size;
	int _capacity;
};

int main()
{
	Stack st;//无参的时候需要这么写,不可以Stack st()这个是报错
	Stack st(4);//有参和无参的写法区别

	st.Push(1);
	//析构函数不需要调用,它会自动调用
	return 0;
}

析构函数不可以重载,并且它会自动调用,不需要你去调用它

class Date
{
public:
	Date()
	{
		_year = 2023;
		_month = 3;
		_day = 11;
	}
	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;
	d1.print();
	Date d2(2023,3,12);
	d2.print();
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Srj9O30s-1678794062414)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230311154946314.png)]

关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器
生成的默认析构函数,对自定类型成员调用它的析构函数。

class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
// 程序运行结束后输出:~Time()
// 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,_day三个是
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数
// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Dat类的析构函
// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

拷贝/复制函数:

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

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
    因为会引发无穷递归调用。
Date(Date d)
	//这里语法编译编不过去的
	//如果能编译过去,这里应该是会无穷递归
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

这里语法编译编不过去的
如果能编译过去,这里应该是会无穷递归

为什么会无穷递归呢?

因为传值传参就是一个拷贝构造,拷贝构造也相当于传值传参,因此无穷递归

> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bTg86x2H-1678794062414)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230312091947389.png)]

我们先来看一个示例:这两个调用有什么区别:

void func1(Date d)
	{

	}
	void func2(Date& d)
	{

	}
int main()
{
	Date d1;//2023.3.11
	//这两个调用什么区别吗?
	func1(d1);
	func2(d1);
	return 0;
}
  1. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
    字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

内置类型,编译器可以直接拷贝,浅拷贝
自定义类型的拷贝,需要调用构造函数

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的。

都去调用拷贝构造函数就好了。

	Date d2 = d1;//这个也是拷贝构造

一般拷贝构造的参数会加const,防止有人把赋值语句的位置写反了。

Date(const Date& d)//这里强制要用引用&
	{//拷贝构造函数
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
  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-CN3yvSJh-1678794062415)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230314193941029.png)]

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

拷贝构造的使用场景:

class Date
{
public:
	Date(int year = 2023, int month = 3, int day = 11)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)//这里强制要用引用&
	{//拷贝构造函数
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	int getmonthday(int year, int month)
	{
		int monthArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2 && ((year % 4 == 0 && year % 100 != 0 )|| (year % 400 == 0)))
			return 29;
		return monthArr[month];
	}
	Date GetXDay(int x)
	{//计算x天后的日期
		_day += x;
		while (_day > getmonthday(_year, _month))
		{
			_day -= getmonthday(_year, _month);
			++_month;
			if (_month == 13)
			{
				_year++;
				_month = 1;
			}
		}
		return *this;
	}
	void print()
	{
		cout << _year << '.' << _month << '.' << _day << endl;
	}
private:
	//内置类型
	//在声明位置给缺省值
	int _year = 1;
	int _month = 1;//这里是声明不是初始化
	int _day = 1;
};
int main()
{
	//写一个时间计算器
	//算算150天以后的日期
	Date d1(2023, 3, 12);
	Date d2 = d1.GetXDay(150);//2023.8.9
	d1.print();//d1和d2都被改了
	d2.print();
	return 0;
}
Date GetXDay(int x)
	{//计算x天后的日期
		Date tmp = *this;//这两行都是拷贝构造出来一个临时变量出来
		//Date tmp(*this);
		tmp._day += x;
		while (tmp._day > getmonthday(tmp._year, tmp._month))
		{
			tmp._day -= getmonthday(tmp._year, tmp._month);
			++tmp._month;
			if (tmp._month == 13)
			{
				tmp._year++;
				tmp._month = 1;
			}
		}
		return tmp;
	}

这里我们发现d1和d2都被改了,我们可以用拷贝构造函数来优化。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wP9DbFJR-1678794062415)(C:\Users\小卢\AppData\Roaming\Typora\typora-user-images\image-20230312100825190.png)]

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CPU平均负载高问题定位分析

一、Linux操作系统CPU平均负载 1.1什么是CPU平均负载 1.2 怎么查看平均负载数值 二、Linux操作系统CPU使用率和平均负载区别 CPU使用率和平均负载区别 三、阿里云Linux操作系统CPU压测环境准备 3.1 核心命令应用场景 3.2 模拟生产环境出现的多种问题环境准备 分析工具安…

嵌入式 串口通信

目录 1、通信的基本概念 1.1 串行通信 1.2 并行通信 2、串行通信的特点 2.1 单工 2.2 半双工 2.3 全双工 3、串口在STM32的引脚 4、STM32的串口的接线 4.1 STM32的串口1和电脑通信的接线方式 4.2 单片机和具备串口的设备连接图 5、串口通信协议 6、串口通信…
最新文章