C++好难(3):类和对象(中篇)

这篇具有很好参考价值的文章主要介绍了C++好难(3):类和对象(中篇)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【本章目标】

  • 类的6个默认成员函数
  • 构造函数
  • 析构函数
  • 拷贝构造函数
  • 赋值运算符重载
  • const成员函数
  • 取地址及const取地址操作符重载

目录

【本章目标】

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

2.构造函数

2.1概念

2.2构造函数的特性

特性一

特性二

特性三

特性四

特性五

特性六

特性七

2.3总结

3.析构函数

3.1概念:

3.2特性

特性一

特性二

特性三

特性四

特性五

4.拷贝构造函数

4.1概念

4.2拷贝构造的特性

特性一

特性二

特性三

特性四:

4.3 总结

5.赋值运算符重载

5.1运算符重载

5.2赋值运算符重载

5.3赋值运算符重载的一个特性

6. const 成员

6.1const成员函数

6.2思考

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

练习


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

C++好难(3):类和对象(中篇) 空类里面不是什么都没有,而是会自动生成上面6个默认成员函数

主要:

这6个默认的成员函数是“缺省”的,你不写这6个函数,编译器就会自动生成,但是你如果写了某一个,那编译器就不会生成了

2.构造函数

2.1概念

对于以下Date类

class Date
{
public:
	void Init(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.Init(2023, 3, 1);
	d1.Print();

	Date d2;
	d2.Init(2023, 5, 1);
	d2.Print();
	return 0;
}

这个Date类可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

这时候就需要用到我们的构造函数

2.2构造函数的特性

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

特性一

(1)构造函数的函数名与类名相同

C++好难(3):类和对象(中篇)

特性二

(2)构造函数无返回值,这里指的是返回值不用写,而不是void

特性三

(3)对象实例化时编译器自动调用对应的构造函数

当你用类创建一个对象时,编译器会自动调用该类的构造函数对新创建的变量进行初始化。

特性四

(4)构造函数可以重载

这就意味着我们可以写很多不同初始化的构造函数,当我们需要那种初始化,我们传递对应的参数即可

class Date
{
public:
	//无参的构造函数
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}

	//带参的构造函数
	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, 1, 1);//调用有参的构造函数
	d2.Print();

	return 0;
}

注意:在通过无参构造函数创建对象时对象后面不用加括号,否则就是函数声明

特性五

(5)默认构造函数

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

class Date
{
public:
	//全缺省的构造函数
	Date(int year = 2023, int month = 5, 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 d1;
	d1.Print();

	Date d2(2023, 1, 1);
	d2.Print();
	return 0;
}

d1没有传参,会直接调用构造函数的缺省值,d2会进行赋值操作

特性六

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

class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1; // 此处调用的是编译器生成的默认构造函数
	//可以看到我们没有定义构造函数,对象也可以创建成功,
	d1.Print();

	return 0;
}

打印结果:

C++好难(3):类和对象(中篇)

我们可以看到d1调用了编译器创建的默认构造函数,单初始化结构却是 -858993460,
而不是初始化为0

这时候就有人想说:这编译器生成的默认构造函数有个√8用?

这是因为编译器有一套自动生成的构造函数机制:

  • 编译器自动生成的构造函数对内置类型不做处理
  • 对自定义类型,编译器会再去调用他们自己的构造函数

内置类型就是编译器创建的基本类型,如int,char等等

自定义类型就是class、struct定义的类对象

如下代码:

class Week
{
public:
	Week()
	{
		cout << " Week()" << endl;
		_week = 1;
	}
private:
	int _week;
};

class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
    // 内置类型
	int _year; // 年
	int _month; // 月
	int _day; // 日

	// 自定义类型
	Week w1;
};

int main()
{
	Date d1;
	d1.Print();

	return 0;
}

调用结果:

C++好难(3):类和对象(中篇)

可以看到:

默认生成的构造函数堆内置类型成员变量不做处理,
对于自定义类型成员变量才会处理(去调用他们自己的默认构造函数)

特性七

(7)如果一个类中的成员全是自定义类型,我们就可以用默认生成的构造函数;
如果有内置类型的成员,或者需要显示传参初始化,那么都要自己实现构造函数。

2.3总结

默认构造函数有三种:

  1. 我们不写,让编译器自动生成的
  2. 我们自己写的  无参  的构造函数
  3. 我们自己写的  全缺省  的构造函数

虽然我们在不写的情况下,编译器会自动生成构造函数,但是编译器自动生成的构造函数可能达不到我们想要的效果,

所以大多数情况下都需要我们自己写构造函数,并且最好是写全缺省的构造函数

3.析构函数

3.1概念:

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

3.2特性

析构函数和构造函数一样,也是一个特殊的成员函数

我们都知道,当一个类对象销毁时,其中的局部表里也会是随着该对象的销毁而销毁

我们通常会写一个destroy函数来进行销毁操作

而析构函数就相当于destroy函数的作用

class Stack
{
public:
	// 构造函数
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			cout << "malloc fail" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}

	// 析构函数
	~Stack()
	{
		free(_a);
		_a = nullptr;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st;
	return 0;
}

在数据结构中,我们实现栈时都会写一个Destroy函数再程序结束前销毁动态开辟的内存,
而如果我们在使用完后没有及时销毁,那么就可能导致内存泄露的问题

而析构函数的出现就是为了防止我们忘记销毁,对象实例化后,同构造函数一样,它不需要我们主动调用,它是在对象生命周期结束后自动调用,需要注意的是,析构函数没有参数所以不能重载。

特性一

(1)析构函数的名是在类名前面加上字符    ~  

class Date
{
public:
	// 构造函数
	Date()
	{

	}

	// 析构函数
	~Date()
	{

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

特性二

(2)析构函数无参数不能重载,无返回值

特性三

(3)对象声明周期结束时,编译器会自动第哦啊用析构函数

如下代码:

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()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 5, 1);
	d1.Print();

	return 0;
}

编译结果:

C++好难(3):类和对象(中篇)

可以看到我们没有调用~Date()这个函数,但是编译器是会默认调用这个析构函数的

特性四

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

  • 编译器自动生成的析构函数对内置类型不做处理
  • 对于自定义类型,编译器会再去调用他们自己的析构函数

和构造函数一样

特性五

(5)先构造的后析构,后构造的先析构

析构函数的析构顺序和栈一样,先进后出

C++好难(3):类和对象(中篇)

4.拷贝构造函数

4.1概念

在我们编写代码的过程中,免不了要对以一个类对象进行拷贝,生成一个新的和原来一样的对象

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

来看下面的代码:

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;
	}

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

int main()
{
	Date d1(2023, 5, 1);
	Date d2(d1); // 用已存在的对象d1创建对象d2

	return 0;
}

我们这里对象 d2 就是对 对象 d1 的拷贝构造,通过调试我们就可以看到:

C++好难(3):类和对象(中篇)

4.2拷贝构造的特性

特性一

(1)拷贝构造函数是构造函数的一个重载形式

因为拷贝构造函数的函数名也与类名相同

特性二

(2)拷贝构造函数的参数只有一个且必须用引用传参

传值传参会发生错误,无限递归的错误

传值传参为什么会发生无限递归呢?

C++好难(3):类和对象(中篇)

 当进行传值传参的时候,我们要调用  拷贝构造函数  就需要先  传参,这时的传参是传值传参,在传参的过程中又需要进行对象的拷贝构造,如此循环发生无限递归。

这里用引用来解决了问题加const的原因是因为我们怕有些人写错顺序,导致原来的d被改变

//拷贝构造
Date(const Date& d) 
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

C++好难(3):类和对象(中篇)

特性三

(3)若未进行显示定义,系统生成默认拷贝构造函数

系统生成的默认拷贝构造函数对象,按内存存储字节完成拷贝,
这种拷贝我们叫做浅拷贝(或值拷贝)

如下代码所示:

class Date
{
public:
	// 构造函数
	Date(int year = 0, 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 d1(2023, 5, 1);
	Date d2(d1); // 用已存在的对象d1创建对象d2

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

运行结果:

C++好难(3):类和对象(中篇)

在上述代码中,我们没有编写拷贝构造函数,但编译器通过自动生成的拷贝构造函数完成了d2对d1的拷贝

这就要谈谈编译器自动生成拷贝构造函数的机制:

  • 编译器自动生成的拷贝构造函数对内置类型会完成浅拷贝(值拷贝)
  •  对于自定义类型,编译器会再去调用他们自己的默认拷贝构造函数

特性四:

(4)编译器自动生成的拷贝构造函数不能实现 深拷贝 

什么是  浅拷贝  什么是  深拷贝

浅拷贝:对值进行拷贝,不会另外开空间

深拷贝:会另开辟一块空间,再将需要拷贝的值放入该空间中

显然,对于日期这样的类是没有必要进行深拷贝的,但对于一些复杂的数据结构,我们就不能再进行浅拷贝了,否则会出现指向同一区域的错误

如下代码:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_data = (int*)malloc(sizeof(int) * capacity);
		if (_data == nullptr)
		{
			cout << "malloc fail" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		free(_data);
		_data = nullptr;
	}
private:
	int* _data;
	int _top;
	int _capacity;
};

int main()
{
	Stack st1;
	Stack st2(st1);
	return 0;
}

我们用的是编译器自动生成的拷贝构造函数,进行调试我们可以看到:st2已经完成了对st1的拷贝

C++好难(3):类和对象(中篇)

但是程序在运行的时候还是崩溃了

C++好难(3):类和对象(中篇)

原因就是进行了浅拷贝的问题!

我们仔细看上述调试结构,会发现st1和st2指向的地址是同一块地址,这就说明再进行拷贝的时候,st2并没有开辟新的空间

C++好难(3):类和对象(中篇)

这时我们不管对他们两个哪一个做出该改变,都会影响到另一个

如果在我们自己定义的析构函数是正确的情况下,当程序运行结束,st2 栈将被先析构,然后再去析构st1

而此时,st2析构的时候 st2 和 st1 指向同一块儿空间已经被释放了,那么当 st1 栈再去调用析构函数的时候,会再次对那一块空间进行释放,造成一块空间被多次释放的问题,导致程序崩溃!

显然这种结构是我们不想看到的,所以再这种复杂数据结构的拷贝过程中,编译器自动生成的拷贝构造函数就不能满足我们的要求了。

4.3 总结

我们不写,编译器会默认生成一个拷贝构造:

(1)内置类型的成员会完成值拷贝,也就是浅拷贝

像 Date 这样的类,需要的就是浅拷贝,那么编译器自动生成的拷贝构造函数就够用了,我们不需要自己写。

(2)自定义类型的成员,去调用这个成员的拷贝构造

像 Stack 这样的类,它是自己直接管理资源,那么需要自己实现深拷贝,浅拷贝的话会导致析构两次、程序崩溃的问题。

5.赋值运算符重载

运算符重载和函数重载是不一样的

  • 函数重载:支持函数名相同、参数不同的函数,可以同时使用
  • 运算符重载:自定义类型对象可以使用运算符

5.1运算符重载

创建一个日期Date类,现在要判断对象d1和对象d2是否相等要如何来做呢?

class Date
{
public:
	//构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 5, 1);
	Date d2(2023, 3, 1);

	return 0;
}

如果我们直接这样写:

C++好难(3):类和对象(中篇)

肯定是错误的,可以看到编译器报的错误是没有与Date类型匹配的运算符

这时因为 运算符默认都是个内置类型使用的。自定义类型的变量用这些运算符,得自己编写运算符重载函数来实现

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名:关键字operator后面接需要重载的运算符符号。

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

注意:

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

下面以  d1==d2为例,通过代码的形式来学习运算符重载

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1) // 构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//==的运算符重载函数
	bool operator==(const Date& d)  // 这里等价于 bool operator==(Date* this, const Date& d2)
	{
		// 这里需要注意的是,左操作数是this指向的调用函数的对象

		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 5, 1);
	Date d2(2021, 3, 1);

	//显示调用:
	d1.operator==(d2);
	//我们一般就直接这样写
	d1 == d2;//同上,编译器会自动识别转换为 d1.operator==(d2) --> d1.operator(&d1, d2);

	cout << (d1 == d2) << endl; 
	// 注意运算符重载打印时要加括号(),因为运算符优先级的关系

	return 0;
}

当然,我们也可以将运算符重载放到类外,但这样就无妨访问到收private私有类型保护的成员变量,我们可以用将成员变量设置为public的公共类型,这样当然是不好的,等到后面学习了友元函数就可以解决

我们先将成员函数设为public来看一看,放到类外的运算符重载,代码应该怎么写

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1) // 构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
//private:    //将成员函数变为公共类
	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(2023, 5, 1);
	Date d2(2021, 3, 1);

	//显示调用
	operator==(d1, d2);

	//一般写法:
	d1 == d2; //同上,如果没有重载会报错,如果重载了它会转换为 operator==(d1, d2);

	cout << (d1 == d2) << endl;

	return 0;
}

可以看到在类外时,因为没有 this 指针,所以再显示调用函数时,其形参我们需要设置两个

放到类外的写法是不推荐的,因为他破坏了封装,这里只是为了让大家看看类内写法和类外写法的区别,以及理解隐藏的this参数

在编写好运算符重载函数后,无论它是在类内还是类外,我们写的时候都是直接写  d1 == d2 

这里我们可以在写一下其他的比较,比如 < , > , <=,  >= , !=

代码如下:

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1) // 构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//==的运算符重载函数
	bool operator==(const Date& d)  
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

	// d1 < d2
	bool operator<(const Date& d)
	{
		if (_year < d._year)
		{
			return true;
		}
		else if (_year == d._year && _month < d._month)
		{
			return true;
		}
		else if (_year == d._year && _month == d._month && _day < d._day)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	// d1 <= d2
	bool operator<=(const Date& d)
	{
		return *this < d || *this == d;
	}

	// d1 > d2
	bool operator>(const Date& d)
	{
		return !(*this <= d);
	}

	// d1 >= d2
	bool operator>=(const Date& d)
	{
		return !(*this < d);
	}

	// d1 != d2
	bool operator!=(const Date& d)
	{
		return !(*this == d);
	}

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

5.2赋值运算符重载

赋值运算符:

赋值运算符的重载还是以日期Date类未例:

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& 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;
};

至于我们为什么要这样写赋值运算符重载,有以下原因:

原因1:

为什么参数类型要设置为引用,并用const进行修饰?

C++好难(3):类和对象(中篇)

由于是自定义类型传参,我们如果使用 传值 传参,会额外调用一次拷贝构造函数,所以函数的第二个参数最好使用 引用传参(第一个参数是默认的 this 指针,我们不用管)。

其次,第二个参数,即赋值运算符的右操作数,我们在函数体内不会对其进行修改,所以最好加上 const 进行修饰。

原因2:

为什么函数要写返回值?并且函数的返回值要使用 引用返回

C++好难(3):类和对象(中篇)

写返回值是因为,在赋值操作的时候我们经常会遇到连续赋值的场景,如d1 = d2 = d3 = d4

写返回值就能实现连续赋值的情况,更加模拟赋值运算符

用引用返回是为了避免不必要的拷贝,适用于出来作用域返回值没有被销毁的场景

具体可以看之前我写的c++入门那篇文章

原因3

为什么要用 if ,他是用来干嘛的?

C++好难(3):类和对象(中篇)

这个 if 条件判断是用来检查,赋值是否是给自己赋值的,
因为我们在赋值操作时有时会写成:d1 = d1  的情况

为了防止不必要的操作

原因4

为什么返回的是 *this?

对于  赋值运算 d1 = d2  的情况,是将d2的值赋值给d1

对其显示处理就是:d1.operator = (d2)

又因为赋值运算的计算顺序是从右往左走的,在连续赋值的时候就会有所体现

所以我们应该返回赋值运算的左操作数,而在类中左操作数就是this指针

this是d1的地址,返回*this,就是返回d1

5.3赋值运算符重载的一个特性

一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节进行的值拷贝。

class Date
{
public:
	Date(int year = 2023, 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 d1;
	Date d2(2023, 5, 1);

	// 这里d1调用的编译器生成 operator= 完成拷贝,d2和d1的值也是一样的。
	d1 = d2;

	d1.Print();
	d2.Print();

	return 0;
}

结果:

C++好难(3):类和对象(中篇)

可以看到赋值运算符重载编译器也可以自动生成,并且也是支持连续赋值的。

6. const 成员

6.1const成员函数

我们把 const 修饰的  “成员函数”  称为 const 成员函数

const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

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:
	int _year;
	int _month;
	int _day;
};

int main()
{
	const Date d1(2022, 9, 1);//用const修饰对象d1,使其成员在使用时不会改变
	d1.Print();

	return 0;
}

以上代码中,我们用const来修饰类对象d1,使得d1里面的成员变量不会再之后的使用中被修改

但是再编译时会报错:

C++好难(3):类和对象(中篇)

很明显我们的Print函数没有去该改变成员变量,那为什么还会报错呢?

这是因为,在 d1 对象去调用 Print 函数的时候,实参会把 d1 的地址传过去,但是 d1 是被 const 修饰的,也就是传过去的是 const Date* ;

而在 Print1 函数这边,形参部分会有一个隐含的 this 指针,也是 Date*  this
也就是把 const Date* 传给了 Date*  this,在这里属于权限的放大,所以编译会不通过。

 C++好难(3):类和对象(中篇)

那么我们应该怎么解决呢?

这里我们就可以用const来修饰成员函数:const 放在成员函数之后,实际就是修饰 this 指针:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

    // const成员函数
	void Print1() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	const Date d1(2022, 9, 1);//用const修饰对象d1,使其成员在使用时不会改变
	d1.Print1();

	return 0;
}

那么在参数传递部分,实参还是和上面一样,形参部分因为 const 修饰的成员函数,所以就变成了 const Date*  this,那么此时就是权限相等了。

C++好难(3):类和对象(中篇)

6.2思考

1. const对象可以调用非const成员函数吗?

 

不可以,就像上面的Print函数一样,会报错

非 const 成员函数,即成员函数的 this 指针没有被 const 所修饰,我们传入一个被 const 修饰的对象,使用没有被 const 修饰的 this 指针进行接收,属于权限的放大,函数调用失败。

2. 非const对象可以调用const成员函数吗?

 

可以,因为是权限缩小

我们传入 成员函数的是一个 大的,而成员函数在接受时,由于const的存在,将其 缩小

属于权限缩小,可以被执行

3. const成员函数内可以调用其它的非const成员函数吗?

 

不可以,在一个被 const 所修饰的成员函数中调用其他没有被 const 所修饰的成员函数,

也就是将一个被 const 修饰的 this 指针的值赋值给一个没有被 const 修饰的 this 指针,属于权限的放大,函数调用失败。

4. 非const成员函数内可以调用其它的const成员函数吗?

 

可以,在一个没有被 const 所修饰的成员函数中调用其他被 const 所修饰的成员函数,

也就是将一个没有被 const 修饰的 this 指针的值赋值给一个被 const 修饰的 this 指针,属于权限的缩小,函数调用成功

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

取地址操作符重载 const 取地址操作符重载,这两个默认成员函数一般不用自己重新定义,使用编译器自动生成的就行。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    
    //显示写出:取地址操作符重载

	//普通对象 取地址操作符重载
	Date* operator&()
	{
		return this;
	}

	//const对象 取地址操作符重载
	const Date* operator&() const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 5, 7);
	const Date d2(2023, 5, 7);

	cout << &d1 << endl;
	cout << &d2 << endl;
	return 0;
}

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,我们作为了解、知道有这两个东西存在即可

只有特殊情况,才需要重载,比如想让别人获取到指定的内容,就可以自己实现。

练习:

根据前面所学知识的一个练习:日期类的实现

要求:

1.能获取某年某月的天数

2.写出构造函数、拷贝构造函数、析构函数

3.判断两个日期的关系: > ; < ; <= ; >= ; != ; ==

4.日期+天数、日期+=天数

5.日期-天数、日期-=天数

6.前置++、后置++,前置--,后置--

7.日期-日期,返回天数

代码如下:文章来源地址https://www.toymoban.com/news/detail-438034.html

#pragma once
#include<iostream>
#include<string.h>
using namespace std;


//声明:

class Date
{
public:
	void Print();

	//构造函数
	Date(int year = 2023, int month = 1, int day = 1);

	//拷贝构造函数
	Date(const Date& d);

	//析构函数
	~Date();

	bool operator<(const Date& d);
	bool operator<=(const Date& d);
	bool operator>(const Date& d);
	bool operator>=(const Date& d);
	bool operator==(const Date& d);
	bool operator!=(const Date& d);

	Date& operator+= (int day);
	Date operator+ (int day);
	Date& operator-= (int day);
	Date operator- (int day);

	// ++d
	Date& operator++();

	// d++
	Date operator++(int);

	// --d
	Date& operator--();

	// d--
	Date operator--(int);

	//日期-日期
	int operator-(const Date& d);

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







//定义:

void Date::Print()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

int GetMonthDay(int year, int month)
{
	int monthArray[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;
	}
	else
	{
		return monthArray[month];
	}
}

Date::Date(int year, int month, int day)
{
	if (month > 0 && month < 13 && (day > 0 && day <= GetMonthDay(year, month)))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "日期非法" << endl;
	}
}

Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

Date::~Date()
{
	_year = 2023;
	_month = 1;
	_day = 1;
}


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

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

bool Date::operator<=(const Date& d)
{
	return *this < d || *this == d;
}

bool Date::operator>(const Date& d)
{
	return !(*this <= d);
}

bool Date::operator>=(const Date& d)
{
	return !(*this < d);
}

bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}


Date& Date::operator+= (int day)
{
	if (day < 0)
	{
		*this -= -day;
		return *this;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}

Date Date::operator+ (int day)
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}

Date& Date::operator-= (int day)
{
	if (day < 0)
	{
		*this += -day;
		return *this;
	}
	_day -= day;
	while (_day <= 0)
	{
		if (--_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

Date Date::operator- (int day)
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}


// ++d  显示去调:d1.operator++();
Date& Date::operator++()
{
	*this += 1;//调用上面写的运算符重载
	return *this;
}

// d++  后置++的类型是编译器规定的这样写
//显示去调:d1.operator++(0);
Date Date::operator++(int)
{
	Date tmp(*this);

	*this += 1;

	return tmp;
}

// --d
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

// d--
Date Date::operator--(int)
{
	Date tmp(*this);

	*this -= 1;

	return tmp;
}

int Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n * flag;
}




//测试
void text4()
{
	Date d1(2023, 5, 6);
	Date d2(2001, 3, 6);
	int life = d1 - d2;
	cout << life << endl;
}

int main()
{
	text4();



	return 0;
}

到了这里,关于C++好难(3):类和对象(中篇)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++精华铺】5.C++类和对象(中)类的六个默认成员函数

    目录 1. 六个默认成员函数 2. 构造函数 2.1 概念 2.2 默认构造 2.2.1 系统生成的默认构造 2.2.2 自定义默认构造函数  2.3 构造函数的重载 3. 析构函数 3.1 概念  3.2 系统生成的析构函数  3.3 自定义析构函数 4. 拷贝构造 4.1 概念  4.2 默认生成的拷贝构造(浅拷贝)  4.3 自定义拷贝构

    2024年02月13日
    浏览(77)
  • 【C++】类和对象③(类的默认成员函数:拷贝构造函数 | 赋值运算符重载)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 拷贝构造函数 概念 拷贝构造函数的特性及用法 赋值运算符重载 运算符重载 赋值运算符重载 结语 本篇主要内容:类的6个默认成员函数中的 拷贝构造函数 和 赋值运算符重载 在上篇文章中我们讲到了类的默认成员函数的

    2024年04月17日
    浏览(48)
  • C++好难(4):类和对象(下)

    okk我们终于来到了C++类和对象的最后一节,大多都是对之前学习的内容做的补充 所以加油继续冲啦!        ∧_∧::    (´・ω・`)::   /⌒ ⌒)::  /へ__  / /:: (_\\  ミ)/::  | `-イ::  /  y  ):: //  /:: / /:: ( く::: |\ ヽ::: 再谈构造函数 Static成员 友元 内部类 匿名对

    2024年02月03日
    浏览(37)
  • 【C++练级之路】【Lv.3】类和对象(中)(没掌握类的6个默认成员函数,那你根本就没学过C++!)

    欢迎各位小伙伴关注我的专栏,和我一起系统学习C++,共同探讨和进步哦! 学习专栏 : 《进击的C++》 在C++的学习中,类和对象章节的学习尤为重要,犹如坚固的地基,基础不牢,地动山摇;而默认成员函数的学习,在类和对象的学习里最为重要。所以要 学好C++,学好默认

    2024年02月04日
    浏览(49)
  • 【C++】类和对象(中篇)

    在往期 类和对象(上篇) 中,我们初步接触了C++中的类和对象,接下来我会和大家分享有关这个知识点更多的内容~ 如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成默认成员函数。 默认成员函数:用户

    2024年02月15日
    浏览(57)
  • C++从入门到精通——类和对象(中篇)

    如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。 对于以下的日期类: 运行结果: 对于Date类,可以通过SetDate公有的方法给对象设置内容,但是如果每次创建对象都调用该方

    2024年04月12日
    浏览(37)
  • 【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 | 再谈构造函数:初始化列表,隐式类型转换,缺省值)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 取地址及const取地址操作符重载 再谈构造函数 初始化列表 隐式类型转换 explicit 成员变量缺省值 结语 本篇主要内容:类的六个默认成员函数中的 取地址 及 const取地址重载 , 构造函数 初始化列表 , 隐式类型转换

    2024年04月26日
    浏览(51)
  • 【C++类和对象】日期类的实现

    hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹 💥 个人主页 :大耳朵土土垚的博客 💥 所属专栏 :C++入门至进阶 这里将会不定期更新有关C++的内容,希望大家多多点赞关注收藏💖💖 通过下面的学习我们将构建简单日期计算器的各种

    2024年04月23日
    浏览(51)
  • C++ 类和对象篇(一) 类的引入

    目录 一、类的概念 二、类的引入 三、类的定义  1.在C++中定义一个类 2.struct 和 class 的区别 3.成员函数的声明与定义 四、封装及类的访问限定符 1.封装 2.类的访问限定符        五、类的作用域和生命周期 六、类的实例化 0. 概念 1. 隐式创建 2. 显式创建 3. 显式new创建 七、

    2024年02月14日
    浏览(35)
  • 【C++】类和对象详解(类的使用,this指针)

    提示:这里可以添加本文要记录的大概内容: 在计算机编程领域,程序设计的方法论不断演化,从最初的面向过程到如今更为强大而灵活的面向对象。本文将深入探讨C++中关于类和对象的概念,为读者提供对面向对象编程的深刻理解。 提示:以下是本篇文章正文内容,下面

    2024年02月02日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包