C++类与对象进阶篇

目录

前言

1.类的6个默认成员函数 

1.1默认函数的引入 

2.构造函数

3.析构函数

4.拷贝构造

5.赋值运算符重载

 6.const成员

7.取地址操作符重载

前言

前一篇内容我们主要讲了类与对象的引入和介绍,我们初步认识了类和对象,接下来 我们要进入类和对象和核心6大默认函数,而我们主要讲解的前四个默认函数,至于最后两个呢,在我们日常开发工作中并不常用。

1.类的6个默认成员函数 

1.1默认函数的引入 

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情 况下,都会自动生成下面6 个默认成员函数。定义一个空类:
class Data
{};

2.构造函数

 大家不要被这个名字给误导了,构造函数的作用不是构造,而是对类对象进行初始化.它是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。

构造函数特性:

  • 无返回值(注意注意…,不是说返回值为void,而是指不写)
  • 函数名与类名相同
  • 编译器自动调用
  • 支持重载

 我们试着写一写日期类的构造函数。

class Data
{
public:
	Data()
	{//无参数构造函数
		_year = 2023;
		_month = 8;
		_day = 25;
	}
	Data(int year, int month=11, int day=10)
	{//半缺省构造函数也可以直接写一个不初始化的构造函数
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Data d1(2023, 5, 24);
	Data d2;
	return 0;
}

 我们这里分别调用无参的构造函数和半缺省的重载构造函数

 注意点1:

如果我们在定义类时,没有显示的定义构造函数(手动构造的意思),那么C++编译器便会自动生成一个无参构造函数,相反,只要我们手动写了任何形式的构造函数,编译器都不会生成默认函数

class Data
{
public:
	//Data()
	//{//无参数构造函数
	//	_year = 2023;
	//	_month = 8;
	//	_day = 25;
	//}
private:
	int _year;
	int _month;
	int _day;
};

说好的自动生成构造函数呢,好像并且进行初始化呢?结果好像并不是想象中的那样,其实并不是编译器没有处理,只是这是C++的一个小缺陷,它初始化成员属性时用的值刚好也是基础类型变量没初始化时编译器给其赋的值,所以我们才看到如上效果.但是其构造函数会对自定义类型进行一定处理,什么处理呢?那就是通过自身的构造函数去调用自定义类型的构造函数.看下面的代码,在定义自定义类person。

class person
{
public:
	person()
	{
		aa = 10;
	}
private:
	int aa;
};

在data的成员变量里面加入自定义类型 ,发现自定义类型通过Data的系统默认构造函数去调用了自定义类型的构造函数。

 注意点2:

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、编译器自动生成的构造函数,都可以认为是默认成员函数,这一点一定要记清楚哦。

 

 原因在于这两者在函数重载方面本质上是一样的,编译器不知道调用哪一个函数产生了歧义。

3.析构函数

 析构函数的作用清理资源(释放动态内存),其写法和构造函数及其相似。

析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作

析构函数的特点:

  • 函数名是~类名,并且~在前面.

  • 无参数,无返回值,无返回值

  • 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数

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

#include<iostream>
using namespace std;

class String
{
public:
	String(const char* str = "jack")
	{
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}
	~String()
	{
		cout << "~String()" << endl;
		free(_str);
	}
private:
	char* _str;
};
class Person
{
private:
	String _name;
	int _age;
};
int main()
{
	Person p;
	return 0;
}

 

 明显可以看出来自动调用了析构函数!

注意点:

析构函数和构造函数类似,对于基础类型(int,char等)相当于没做处理,而对于自定义类型来说,该类对象的析构函数会调用其自定义类型的析构函数.

4.拷贝构造

构造函数 只有单个形参 ,该形参是对本 类类型对象的引用 ( 一般常用 const 修饰 ) ,在用 已存在的类类型对象 创建新对象时由编译器自动调用。
拷贝构造函数也是特殊的成员函数,其 特征 如下:
1. 拷贝构造函数 是构造函数的一个重载形式
2. 拷贝构造函数的 参数只有一个 必须使用引用传参 ,使用 传值方式会引发无穷递归调用

拷贝构造有两种调用方式,这里博主先介绍一下。

 括号法:

Data d1(d2);

赋值法:

Data d1=d2;

 关于特征的第二点:

 这里如果直接是值拷贝的话,会一直陷入Data d2(d1)的死循环,所以这里只能用传参拷贝也就是加入引用,

Data(const Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

	}

 我们知道拷贝构造是默认成员函数,我们不写编译器也是可以自动为我们生成的,像下图这样也是拷贝成功了的。

若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷 贝,这种拷贝我们叫做浅拷贝,或者值拷贝。

那么 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了 ,我们还需要自己实现吗?当然像 日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

#include<iostream>
using namespace std;

class String
{
public:
	String(const char*str="jack")
	{
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}
	~String()
	{
		cout << "~string" << endl;
		free(_str);
	}
private:
	char* _str;
};
int main()
{
	String s1("hello");
	String s2(s1);
	return 0;
}

 运行下面程序的时候,我们发现了什么,程序崩溃了这是为什么呢?

 突然发现屏幕上面打印出来两个析构函数这是为什么呢?仔细一想才发现,我们运行,就会发现程序会崩溃,至于崩溃的原因就是第一次定义对象时,调用的是构造函数,在堆区开辟了一块空间,但是第二次定义对象时候,调用的是拷贝构造,也就是说,这一次并没有再开辟空间,而是直接把s1的内容复制一份给s2,那么s1和s2的成员都是指向了一块空间,但是会调用两次析构函数,同一块内存空间不可调用两次,所以会崩溃。这里我们只需要记住,如果只有基础类型,而不涉及动态空间,就可以不用写显示拷贝,反之需要.深拷贝我们要自己编写,后续会介绍。

5.赋值运算符重载

 C++为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数,也有其返回值类 型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。我们知道编译器可以很简单的处理1+1=2,但是对于特定的日期类,年加年,月加月,日加日,连续处理数据时候,C++的方便出就体现出来了,他可以在内部将一系列加号封装起来,统一用一个+好来实现数据的重载。

函数名字为:关键字 operator 后面接需要重载的运算符符号
函数原型 返回值类型  operator 操作符 ( 参数列表 )
注意:
  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型或者枚举类型的操作数
  3. 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员的重载函数时,其形参看起来比操作数数目少的成员函数的 操作符有一个默认的形参this,限定为第一个形参
  5. .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

现在开始我们来设计一个=的重载

class Date
{
public:
	Date(int year=2002,int month=10,int day=10)
	{
		_year = year;
		_month = month;
		_day = day;
	}
void operator = (Date data)
{
	_year = data._year;
	_month = data._month;
	_day = data._day;
}

private:
	int _year;
	int _month;
	int _day;
};

这样就设计好啦~输入测试用例,发现完美嗷~ 

Date d1(2023, 8, 25);
	Date d2;
	d2 = d1;

还记得=还有一个特性吗?那就是连续赋值,比如a =b =c=d;如果我们也这样对类进行操作,可以吗?很明显,发生错误.当d2=d1时,编译器会翻译成d2.operator=(&d2,d1),而这个函数并没有返回值,那么d3也就接收不到参数了,所以会报错了,突然想到了我们还有this指针,我们并没有使用它,它一直被隐藏咯,这是不是刚刚好呀,我们返回*this指针,就可以完美解决这个问题啦。

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

 

 6.const成员

我们试着编写这样一个代码

class Date
{
public:
	void print()
	{
		cout << "year是" << _year << endl;
		cout << "month是" << _month << endl;
		cout << "day是" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	const Date d2;

	d1.print();
	d2.print();
	return 0;
}

突然返现d2怎么也调用不了print函数,这是怎么回事呢?其实这就是const的问题.我们知道d2的类型是const Date,那么d2在调用print函数时,系统便会传给print一个this指针,而this指针的类型是Date*,但是d2的地址类型是const Date*,将一个const修饰的指针传给普通指针接收,将会扩大权限这种情况和常量引用相似,所以为了解决这种办法,我们需要加上const修饰this指针,而加的地方就是在函数后面。解决方法如下:

7.取地址操作符重载

	Date* operator&()
	{
		return this;
	}

	const Date* operator&()const
	{
		return this;
	}
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比 如想让别人获取到指定的内容!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值