多重作用的类和对象

目录

一、类的认识

二、类的访问限定符及封装

三、类的作用域及实例化

四、类的对象大小的计算

五、类成员函数的this指针

六、构造函数

七、析构函数

八、拷贝构造函数

九、运算符重载

十、日期类的实现

得到每一月的天数

创建构造函数

比较日期大小

日期+-天数

前置++、--,后置++、--

日期-日期

求多少天的某一天是星期几

重载输入输出运算符

十一、const成员

十二、explicit关键字

十三、static成员

十四、C++11 的成员初始化新玩法

十五、友元

十六、内部类


一、类的认识

面向过程:

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题

面向对象:

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成

比如设计简单外卖系统

面向过程:关注实现下单、接单、送餐等这些过程。体现到代码层面 -- 方法/函数

面向对象:关注实现类对象及类对象间的关系,用户,商家,骑手以及他们之间的关系。体现到代

码层面 -- 类的设计及类之间关系

类的引入

在C中结构体内只能定义变量,而在C++中结构体内能定义变量及函数

这是因为C++为了兼容C,所以struct保留了下来

类的定义

C++中由此种结构体又推出了一个新的概念,叫做类,关键字为class

class 类名

{类体};

注意:和结构体一样,不要忘了最后面的分号(;)

类体中可存放成员变量及成员函数

注意:如果在类中定义函数,那么编译器可能会将其作内联函数处理,一般情况下都是放成员函数

的声明,在类外定义成员函数

二、类的访问限定符及封装

访问限定符:public(公有)、private(私有)、protected(保护)

public修饰的成员在类外可以直接被访问

protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)

访问权限作用域从该访问限定符出现的位置开始,直到下一个访问限定符出现时为止

如果后面没有访问限定符,作用域到}类结束

class的默认访问权限为private,struct为public(因为struct要兼容C)

封装

概念:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和

对象进行交互

正是因为封装,所以一般是将成员变量设为私有,将成员函数设为公有

三、类的作用域及实例化

类的作用域

类定义了一个新的作用域,在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域

类的实例化

概念 :用类类型创建对象的过程,称为类的实例化

类只是一个模型,限定了有哪些成员,但并没有分配实际的空间来存储它

类与C语言结构体类似,只是一个是创建结构体变量,另一个是创建对象,实例化出的对象占用实

际的物理空间,存储类成员变量,如下图,d1就是一个对象,下面是访问成员函数

类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图

四、类的对象大小的计算

对于类而言,只保存成员变量,成员函数存放在公共的代码段,所以计算类或对象大小,只看成员

变量

结论:计算类或者类对象大小,只看成员变量,考虑内存对齐,C++内存对齐规则与C结构体一致

 

空类与只有成员函数的类所占空间一致,会给1byte,这1byte不存储有效数据,只是为了占位,

来标识这个类

五、类成员函数的this指针

由上图可知,d1和d2都是调用的同一个Print函数

函数体中没有关于不同对象的区分,那当d1调用Print函数时,该函数是如何知道应该设置d1对

象,而不是设置d2对象呢? 

C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象,在函

数体中所有成员变量的操作,都是通过该指针去访问,这就是隐藏的this指针

 

实际上,调用成员函数应该为上图,只是this是隐藏的

this指针的特性

this指针的类型:类类型* const

只能在“成员函数”的内部使用,但是一般情况下,不会像下面这样显示写

this指针本质上是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this

形参,不能显示声明形参this

this指针是成员函数第一个隐含的指针形参,不能显示传实参给this

注意:this指针一般情况下是存储在栈上的,有些编译器会放到寄存器中,如vs2013,放到ecx中

正常运行

运行崩溃

 

分析:

p虽然是空指针,会传递给this指针,但是p调用成员函数不会编译报错,因为空指针不是语法错

误,编译器检查不出来,同时第一个没有使用成员变量,不存在空指针解引用,所以正常运行,但

第二个有访问成员变量,所以会发生空指针解引用,造成程序运行崩溃

六、构造函数

概念:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,

在对象的生命周期内只调用一次,主要作用是初始化对象,不是开辟空间

特性

函数名与类名相同

无返回值

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

构造函数可以重载

Date d1; // 调用无参构造函数,后面不能跟括号,否则就是函数声明

Date d2 (2015, 1, 1); // 调用带参的构造函数  

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

式定义,编译器将不再生成

对于内置类型(基本类型:char、int、double等等)的成员变量不做初始化处理

对于自定义类型的成员变量会去调用它的默认构造函数初始化,如果没有默认构造函数就会报错

 

默认构造函数:全缺省,无参 ,我们不写编译器默认生成的

默认构造函数只能有一个

虽然从语法上无参构造函数和全缺省构造函数是函数重载,但在调用时存在二义性问题

为了便于区分,如上图,如形参为year,成员变量为_year,在最前面加了个_

注意:全局对象比局部对象先构造

再谈构造函数

前面的构造函数,都是在函数体内初始化的,构造函数体中的语句只能将其称作赋初值,而不能

称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值

初始化列表初始化成员变量

初始化列表—成员变量定义的地方

类名(参数) 冒号(:)  成员变量(形参或者常量) 逗号(,)  成员变量(形参或者常量) 

注意:

每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

类中包含以下成员,必须放在初始化列表中进行初始化

引用成员变量

const成员变量

  

自定义类型成员(该类没有默认构造函数)

原因:上述三种变量都必须在定义的时候初始化,否则会编译报错,其它类型成员变量在哪儿初始

化都可以如int year、int month等等

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会

先使用初始化列表初始化

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无

关,如下图

七、析构函数

概念:对象在销毁时会自动调用析构函数,完成类的一些资源清理工作

特性

析构函数名是在类名前加上字符 ~

无参数无返回值

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

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

与编译器默认生成构造函数类似,对于内置类型成员变量不做处理,对于自定义类型成员变量会去

调用它的析构函数 

对于在堆上申请的空间,得在析构函数中添加free释放掉 ,以及将指针置为nullptr,如下图

 

先调用构造函数的会后析构,后调用构造函数的会先析构

局部对象先析构,全局对象后析构,如果静态对象在局部,比全局先析构,在局部对象之后析构

八、拷贝构造函数(特殊成员函数)

概念:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),用已存在的类类

型对象创建新对象时由编译器自动调用

特征

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

拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用

因为调用拷贝构造,需要先传参数,传值传参又是一个拷贝构造,调用拷贝构造,需要先传参数,

传值传参又是一个拷贝构造,会无穷递归调用

若未显示定义,系统生成默认的拷贝构造函数

内置类型成员,会完成按字节序的拷贝(浅拷贝)

自定义类型成员,会调用他的拷贝构造

九、运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数

返回类型+operator+要重载的运算符(>、<、+)+参数列表

注意

不能通过连接其他符号来创建新的操作符:比如operator@ 

重载操作符必须有一个类类型或者枚举类型的操作数

用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义

作为类成员的重载函数时,看起来比实际会少一个操作数,即this指针

定义在类中

定义在全局,注意要保证类的成员可访问,否则会编译报错(私有成员就不可访问)

 因为类的成员函数,会有一个隐藏的this指针,所以会比全局少一个参数

.* 、:: 、sizeof 、?: 、. 以上5个运算符不能重载(注意是.*,不是*)

拷贝构造与赋值重载的区别

拷贝构造:一个已经存在的对象拷贝初始化一个马上创建实例化的对象

赋值重载:两个已经存在的对象之间进行赋值拷贝

对于赋值重载,最好是采用引用返回,传值返回会发生拷贝构造

 

编译器默认生成赋值重载,与拷贝构造做的事情类似

内置类型成员,会完成字节序值拷贝--浅拷贝

自定义类型成员变量,会调用它的operator=

十、日期类的实现

得到每一月的天数

首先创建一个静态数组,然后把每个月的天数,作为每个元素的大小即可,最好创建一个13个元素

的数组,这样第一个元素赋值一个0,其它元素按每月多少天赋值,即arr[1]=31,arr[2]=28

因为2月的天数有变,所以得考虑平年和闰年,是闰年天数就+1

创建构造函数

这里采用缺省值的方法来初始化成员变量

注意:声明和定义只能其中一个有缺省值,同时存在会报错 

同时还得判断日期是否合法,不合法就说明一下,并打印日期

比较日期大小

总共有6个运算符,即>、<、>=、<=、!=、==,只需写>和==,其余的复用即可

对>运算符重载

首先判断年的大小,d1大就true

如果是年相等,就判断月

如果年和月都相等,就判断天

除了上述三种情况,都算d2大

对==运算符重载

年月日都相等时,才相等,所以用&&连接

比如对>=运算符重载,可写成d1 > d2 ||  d1 == d2

   

日期+=天数

首先+=天数

然后判断_day是否大于那月的天数

大于那月天数,就减去哪月天数,月+1

如果月+1后是13,那年就+1,月就赋值为1

最后返回*this,采用传引用返回,能减少拷贝构造

日期-=天数

首先得-=天数

然后判断_day是否<=0 

进入循环,首先月-1,然后判断月是否为0,为0,年-1,月赋值为12,_day再+那月天数

最好返回*this,采用引用返回,减少传参 

日期+天数

首先得创建一个临时的日期变量,因为不能改变原对象,采用拷贝构造

然后对ret复用前面的+=天数即可

日期-天数 

与日期+天数同理

注意:日期+/-天数,不能采用引用返回,因为返回的是局部对象 

前置++

传引用返回,减少拷贝构造

后置++

得创建一个临时变量,因为后置++是先使用后++

后置++比前置++多了个int类型的参数,是为了区分前置++和后置++,且只能是int,这是C++规定

 

前置--和后置--同理

日期-日期

先创建两个日期的临时变量,初始默认*this是较大的,d2是较小的

所以再创建一个int类型的变量赋值为1,默认*this>d2

 

然后再比较*this和d2谁大谁小,如果与初始默认不同,就颠倒过来,flag就为-1

然后用while循环,判断条件是max不等于min,相等就跳出循环,循环体内是天数+1,min+1

最后返回天数与flag的积,表示相减为正或为负

 

求多少天的某一天是星期几

需要找一个参照日期,这里找的是1900年1月1日,刚好是星期一,方便处理

然后创建一个指针数组,有7个元素,代表从星期一到星期日 

再复用前面的日期-日期,然后用其返回值%7即可

重载输入输出运算符

重载输出运算符

注意:重载输出运算符的函数不能成为类的成员函数,因为成员函数的第一个参数是this指针,也

就是<<的第一个操作数,如果是成员函数的话,使用时就得写为d1<<cout,这显然不符合我们的

预期,所以得把此函数定义为全局的函数

这里没有采用void返回类型,是因为如果连续输出,void返回达不到目的,比如cout<<d1<<d2

对于输入运算符重载同理,只是对象不能加const

十一、const成员

如上图,被const修饰的对象,无法调用成员函数Print(),因为这里涉及隐含的this指针,形参为

A* const  this,实参被const修饰,为权限放大,所以编译报错,又因为this是隐藏的,所以无法

在参数中加const,所以就有了const修饰成员函数

对于取地址(&),不需要重载,因为编译器会生成默认取地址的重载,只有特殊情况,才需要重

载,比如想让别人获取到指定的内容

总结:成员函数加const是比较好的,建议能加const都加上,这样普通对象和const对象都可以调

用,但是如果要修改成员变量的成员函数是不能加的

十二、explicit关键字

用explicit修饰构造函数,将会禁止单参构造函数的隐式转换,如下图

十三、static成员

概念:声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变

量;用static修饰的成员函数,称之为静态成员函数

特性

静态成员为所有类对象所共享,不属于某个具体的实例,比如静态成员变量count,初始化为0,

在对象d1中,加了一次为1,在对象d2中,依然为1,不会为0

静态成员变量必须在类外定义初始化,定义时不添加static关键字

类静态成员即可用类名::静态成员或者对象.静态成员来访问

静态成员函数没有隐藏的this指针,不能访问任何非静态成员,只能访问静态成员变量和静态成员

函数,但非静态成员函数可以调用静态成员函数

静态成员和类的普通成员一样,也有public、protected、private3种访问级别,可以具有返回

十四、C++11 的成员初始化新玩法

C++11支持非静态成员变量在声明时进行初始化赋值,目的是为了解决之前对自定义成员变量调用

它的默认构造函数初始化,对内置类型不做处理,而打的一个补丁

注意:这里是给声明的成员变量给缺省值,不是初始化!

十五、友元

友元分为友元函数和友元类

友元缺点:增加耦合度,破坏了封装,不宜多用

友元函数

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类

的内部声明,声明时需要加friend关键字

友元函数可访问类的私有和保护成员,但不是类的成员函数

友元函数不能用const修饰

友元函数可以在类定义的任何地方声明,不受类访问限定符限制

一个函数可以是多个类的友元函数

友元函数的调用与普通函数的调用和原理相同 

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的所有成员

友元关系是单向的,不具有交换性

比如A类和B类,在A类中声明B类为其友元类,那么可以在B类中直接访问A类的私有成员变量,

但想在A类中访问B类的私有成员变量则不行

  

友元关系不能传递

如果B是A的友元,C是B的友元,但不能说明C是A的友元

十六、内部类

概念:如果一个类定义在另一个类的内部,这个类就叫做内部类

内部类B和在全局定义是基本一样的,只是它受A类的类域限制

内部类B天生就是外部类A的友元,也就是B中可以访问A的私有,A不能访问B的私有

特性

内部类可以定义在外部类的public、protected、private都是可以的

内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名

sizeof(外部类)=外部类,和内部类没有任何关系

//头文件

#pragma once
#include<iostream>
using namespace std;
class Date
{
	friend ostream& operator<<(ostream& out,const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	//构造函数
	Date(int year = 1970, int month = 1, int day = 4);
	//求每个月的天数
	int GetMonthDay(int _year, int _day)const;
	//打印日期
	void Print() const;
	//比较日期大小
	bool operator>(const Date& d2) const;
	bool operator<(const Date& d2) const;
	bool operator==(const Date& d2) const;
	bool operator>=(const Date& d2) const;
	bool operator<=(const Date& d2) const;
	bool operator!=(const Date& d2) const;
	//求多少天后的日期
	Date& operator+=(int day);
	Date operator+(int day) const;
	//求多少天前的日期
	Date& operator-=(int day);
	Date operator-(int day) const;
	//前置++、后置++
	Date& operator++();
	Date operator++(int);
	//前置--、后置--
	Date& operator--();
	Date operator--(int);
	//求两个日期相差多少天
	int operator-(const Date& d2);
	//日期赋值
	Date& operator=(const Date& d2);
	//求星期几
	void WeekDay();
private:
	int _year;
	int _month;
	int _day;
};

//定义文件

#include"date.h"

int Date::GetMonthDay(int _year, int _month)const
{
	static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = arr[_month];
	if (_month == 2 && ((_year % 4 == 0 && _year % 100 != 0) || (_year % 400 == 0)))
	{
		day += 1;
	}
	return day;
}

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

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

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

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

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

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

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

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

Date Date::operator+(int day) const
{
	Date ret(*this);
	ret += day;

	return ret;
}

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

Date Date::operator-(int day) const
{
	Date ret(*this);
	ret -= day;

	return ret;
}

Date& Date::operator++()
{
	*this += 1;
	return *this;
}

Date Date::operator++(int)
{
	Date ret(*this);
	*this += 1;

	return ret;
}

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

Date Date::operator--(int)
{
	Date ret(*this);
	*this -= 1;

	return ret;
}

int Date::operator-(const Date& d2)
{
	Date max = *this;
	Date min = d2;
	int flag = 1;//默认d1>d2
	if (*this < d2)
	{
		max = d2;
		min = *this;
		flag = -1;
	}
	int count = 0;
	while (min != max)
	{
		++min;
		++count;
	}
	return flag * count;
}

Date& Date::operator=(const Date& d2)
{
	_year = d2._year;
	_month = d2._month;
	_day = d2._day;

	return *this;
}

void Date::WeekDay()
{
	Date d(1900, 1, 1);
	const char* arr[7] = { "星期一","星期二","星期三","星期四","星期五","星期六","星期日" };
	int count = (*this) - d;
	cout << arr[count % 7] << endl;
}

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}

istream& operator>>(istream& in, Date& d)
{
	in >> d._year>> d._month>> d._day;
	return in;
}

//测试文件

#include"date.h"

void test1()
{
	Date d1(2021, 2, 9);
	Date d2(2021, 2, 9);
	cout << (d1 > d2) << endl;
	cout << (d1 < d2) << endl;
	cout << (d1 <= d2) << endl;
	cout << (d1 >= d2) << endl;
	cout << (d1 == d2) << endl;
	cout << (d1 != d2) << endl;
}

void test2()
{
	Date d1(2022, 1, 30);
	d1 += 1500;
	d1.Print();

	Date d2(2020, 1, 15);
	d2 -= 1500;
	d2.Print();
}

void test3()
{
	//Date d1(2022, 3, 15);
	//Date d2(2016, 1, 19);
	//int ret = d1 - d2;
	//cout << ret;

	//cout << d1;
	//cout << d2;
	//Date d3;
	//cin >> d1;
	//cout << d1;
	//cout << d3;

	/*Date d1;
	cin >> d1;*/
	/*Date d1;
	cin >> d1;
	cout << d1;*/

	Date d1(2022, 2, 25);
	d1.WeekDay();
}
int main()
{
	//test1();
	//test2();
	test3();
	return 0;
}

  • 16
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值