C++的深度学习(4)

目录

 类与对象

类的访问限定符及封装

C++中struct和class的区别是什么?

类的作用域

类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?

结构体内存对齐规则

关于结构体对齐及内存对齐的的问题

this指针

this指针的特性

this指针存在哪里?

this指针可以为空吗?

类的6个默认成员函数

构造函数

构造函数特性

构造函数体赋值 

初始化列表

explicit关键字

 static成员

 static成员特性

关于static的问题

析构函数

析构函数特性

拷贝构造函数

拷贝构造函数特征

拷贝构造函数被调用的三种情况

赋值运算符重载

运算符重载

赋值运算符重载

浅拷贝

日期类的实现

const成员

const修饰类的成员函数

const修饰类的成员函数的问题

友元

友元函数

友元类 

内部类


 类与对象

类的定义
C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。
class className
{
 // 类体:由成员函数和成员变量组成
};
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号。
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
类的两种定义方式:
1. 声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2. 声明放在.h文件中,类的定义放在.cpp文件中
C++的深度学习(4)_第1张图片

类的访问限定符及封装

 访问限定符:

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给

外部的用户使用。
【访问限定符说明】
1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

C++中struct和class的区别是什么?

C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。
和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是的成员默认访问方式是private。
封装
面向对象的三大特性:封装、继承、多态。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理:我们如何管理兵马俑呢?比如如果什么都不管,兵马俑就被随意破坏了。那么我们首先建了一座房子把兵马俑给封装起来。但是我们目的全封装起来,不让别人看。所以我们开放了售票通道,可以买票突破封装在合理的监管机制下进去参观。类也是一样,我们使用类数据和方法都封装到一下。
不想给别人看到的,我们使用protected/private把成员封装起来。开放一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符 指明成员属于哪个类域。
class person {
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender;
	char _age;
};
void person::PrintPersonInfo() {
	cout << _name << " " << _gender << " " << _age << endl;
}

类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?

一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。

结构体内存对齐规则

1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值 
VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是 所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

关于结构体对齐及内存对齐的的问题

1. 结构体怎么对齐? 为什么要进行内存对齐
(结构体内存对齐规则)
内存对齐:

平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,对于访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

2. 如何让结构体按照指定的对齐参数进行对齐
输入#pragma pack(push) 和#pragma pack(pop) 以及#pragma pack()
调用pack时不指定参数,n将被设成默认值
3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景
小端模式:数据的高字节,存放在高地址中。 计算机读取数据的方向,是从高地址开始读取的; 大端模式:数据的高字节,存放在低地址中。
通用处理器中,ARM通常是大端模式,x86体系机构是小端模式。
方法一:通过读取同一地址的整型数据和字符数据来区别大端和小端    
int main() {
	unsigned int data, * point;
	point = &data;//获取整型数据的地址,保存在point中
	data = 0;
	*(type *)point = 0x22;
	if (data == 0x22)
	{
		cout << "此处理器是小端" << endl;
	}
	else  if (data == 0x22000000)
	{
		cout << "此处理器是大端" << endl;
	}
	else
	{
		cout << "暂时无法判断机器类型" << endl;
	}
}
方法二:通过union的共享内存特性,来判断机器类型 
int main() {
	union {
		char str;
		int data;
	};
	data = 0x11223344;
	if (str == 0x11) {
		cout << "此机器是大端" << endl;
	}
	else if (str == 0x44) {
		cout << "此机器是小端" << endl;
	}
	else {
		cout << " 无法判断" << endl;
	}
}

方法三:使用强制转换造成的数据损失来判断
#include
using namespace std;
int main() {
	int i = 1; // i 在的二进制就是 00000001 在内存中占 4个字节 
	char* m = (char*)&i; //将 i 地址传给 p 并强制转换成char 类型,为的是造成 *p和i 所占字节数不 
                           同,因为可以判断 *p的数据损失情况
	if (*m)
		cout << "此机器为小端" << endl; //若是小端则留下的是00000001的高位(即 00)
	else
		cout << "此机器大端" << endl; //若是大端,则*p留下的就是00000001的低位(即01)
	return 0;
}

大端模式的优点
    符号位在所表示数据的内存第一个字节中,能快速判断数据的正负和大小

小端模式的优点
    (1)内存的低地址存放数据的低字节,所以数据强制类型转换时不用调整字节的顺序
    (2)CPU做数值运算时从内存中按顺序依次由低到高取数据进行运算,直到最后刷新最高位的符号位,这样的运算方式会更高效
     大小端可以用于网络主机之间传输数据

this指针

C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

this指针的特性

1. this指针的类型:类类型* const
2. 只能在“成员函数”的内部使用
3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
void Test() {
	int _xxx = 0;
	cout << _xxx << endl;
}
void Test(Data* this) {
	int _xxx = 0;
	cout < _xxx << endl;
}

this指针存在哪里?

不考虑有时的编译器优化的话,IA-32 下,微软 C++ 编译器会放在 ECX 寄存器里,GCC 会把 this 指针作为第一个推入栈中的参数。

this指针可以为空吗?

当我们调用函数时,如果函数内部不需要使用到this指针,也就是不需要通过this指针指向当前对象并对其进行操作时,this指针可以为空(eg:在函数中什么都不放,或者随便打印一个字符串);如果调用的函数需要this指针指向当前对象,并进行操作时,this指针不能为空,否则就会发生错误(空指针引用)

类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类

任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数
class Date {};
C++的深度学习(4)_第2张图片

构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
class Data {
public:
	void SData(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	void Display() {
		cout << _year << "-" << _month << "-" << _day<< endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void TestDate() {
	Data d1, d2;
	d1.SData(2222, 2, 2);
	d1.Display();

	d2.SData(1111, 1, 1);
	d2.Display();
}

构造函数特性

构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
class Data {
public:
	Data() {};//无参构造
	Data(int year, int month, int day) {  //带参构造
		_year = year;
		_month = month;
		_day = day;
	}
	void Display() {
		cout << _year << "-" << _month << "-" << _day<< endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void TestDate() {
	Data d1; // 调用无参构造函数
	d1.Display();
	Data d2(2015, 1, 1); // 调用带参的构造函数
	d2.Display();
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	Data d3();

}
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
// 如果用户显式定义了构造函数,编译器将不再生成
// 没有定义构造函数,对象也可以创建成功,因此调用的是编译器生成的默认构造函数
关于编译器生成的默认成员函数,在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char...,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数

构造函数体赋值 

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

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造 函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内 可以多次赋值。

初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括 号中的初始值或表达式。

class T {
public:
	T(int year, int month)
		:_year(year)
		, _month(month)
	{}

private:
	int _year;
	int _month;
};

【注意】

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

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

引用成员变量    const成员变量   自定义类型成员(该类没有默认构造函数)

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

C++的深度学习(4)_第3张图片

C++的深度学习(4)_第4张图片

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

explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。

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

  C++的深度学习(4)_第5张图片

 static成员

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的 成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化

 static成员特性

1. 静态成员为所有类对象所共享,不属于某个具体的实例

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

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

4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

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

关于static的问题

实现一个类,计算中程序中创建出了多少个类对象

C++的深度学习(4)_第6张图片

静态成员函数可以调用非静态成员函数吗?

C++的深度学习(4)_第7张图片

非静态成员函数可以调用类的静态成员函数吗?

C++的深度学习(4)_第8张图片

所谓的静态成员和非静态成员也是一样的,他们都是成员变量或常量
所以不存在权限问题,都可以互相调用
而静态只是相当于设置为全局变量,但非静态就相当于局部变量而已

那么如何让静态成员函数访问非静态成员?

1 在静态函数的形参表里加上实例的地址

C++的深度学习(4)_第9张图片

 2 在静态函数中使用全局变量

C++的深度学习(4)_第10张图片

this指针赋值给静态成员,在静态成员函数内部访问this指向的静态成员 

C++的深度学习(4)_第11张图片

 4 使用static_cast<>() 进行内部转换

C++的深度学习(4)_第12张图片

 PS:C++11支持非静态成员变量在声明时进行初始化赋值

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

析构函数

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

析构函数特性

析构函数是特殊的成员函数。
其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数

拷贝构造函数

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

拷贝构造函数特征

拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
3. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
C++的深度学习(4)_第13张图片

拷贝构造函数被调用的三种情况

1) 当用一个对象去初始化同类的另一个对象时,会引发复制构造函数被调用。

2) 如果函数 F 的参数是类 A 的对象,那么当 F 被调用时,类 A 的复制构造函数将被调用。换句话说,作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。

3) 如果函数的返冋值是类 A 的对象,则函数返冋时,类 A 的复制构造函数被调用。换言之,作为函数返回值的对象是用复制构造函数初始化 的,而调用复制构造函数时的实参,就是 return 语句所返回的对象。

赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类
型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型或者枚举类型的操作数
用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的 操作符有一个默认的形参this,限定为第一个形参
.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载
#include
using namespace std;
class Date {
public:
	Date(int year = 1111, int month = 1, int day = 1) {
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator==(const Date& d2) {
		return _year == d2._year
			&& _month == d2._month
			&& _day == d2._day;

	}// bool operator==(Date* this, const Date& d2)
 // 这里需要注意的是,左操作数是this指向的调用函数的对象
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;
//
//}//我们需要在类外使用重载因为参数太多,在类内会多出一个this指针
void Test() {
	Date d1(2222, 2, 2);

	Date d2(1,1,1);
	cout << (d1 == d2) << endl;
}
int main() {
	Test();
	return 0;
}

赋值运算符重载

#include
using namespace std;
class Date {
public:
	Date(int year = 1111, 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& operator=(const Date& d){
		if (this != &d){
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

private:
	int _year;
	int _month;
	in

赋值运算符主要有四点:

1. 参数类型

2. 返回值

3. 检测是否自己给自己赋值

4. 返回*this

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

浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

一般情况下,只需使用系统提供的浅拷贝构造函数即可,但是,如果对象的数据成员包括指向堆空间的指针,就不能使用这种拷贝方式,因为两个对象都拥有同一个资源,对象析构时,该资源将经历两次资源返还,此时必须自定义深拷贝构造函数,为创建的对象分配堆空间,否则会出现动态分配的指针变量悬空的情况

日期类的实现

class Date
{
public:
 // 获取某年某月的天数
 int GetMonthDay(int year, int month);
 
 // 全缺省的构造函数
 Date(int year = 1900, int month = 1, int day = 1);
 // 拷贝构造函数
 // d2(d1)
 Date(const Date& d);
 
 // 赋值运算符重载
 // d2 = d3 -> d2.operator=(&d2, d3)
 Date& operator=(const Date& d);
 // 析构函数
 ~Date();
 // 日期+=天数
 Date& operator+=(int day);
 // 日期+天数
 Date operator+(int day);
 // 日期-天数
 Date operator-(int day);
// 日期-=天数
 Date& operator-=(int day);
 // 前置++
 Date& operator++();
 // 后置++
 Date operator++(int);
 // 后置--
 Date operator--(int);
 // 前置--
 Date& operator--();
 
 // >运算符重载
 bool operator>(const Date& d);
 // ==运算符重载
 bool operator==(const Date& d);
 // >=运算符重载
 inline bool operator >= (const Date& d);
 
 // <运算符重载
 bool operator < (const Date& d);
 // <=运算符重载
 bool operator <= (const Date& d);
 // !=运算符重载
 bool operator != (const Date& d);
 // 日期-日期 返回天数
 int operator-(const Date& d);
private:
 int _year;
 int _month;
 int _day;
};
class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month)
	{
		static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		int day = days[month];
		if (month == 2
			&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{
			day += 1;
		}
		return day;
	}
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1) {
		_year = year;
		_month = month;
		_day = day;
	}
	// 拷贝构造函数
	// d2(d1)
	Date(const Date& d) {
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	bool IsLeapYear(int year)const {
		if ((0 == year % 4 && 0 != year % 100) ||
			(0 == year % 400)) {
			return true;
		}
		return false;
	}
	// 赋值运算符重载
	// d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d) {
		if (this != &d) {
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
	// 析构函数
	~Date() {};
	// 日期+=天数
	Date& operator+=(int day) {
		*this += day;
		return *this;
	}
	// 日期+天数
	Date operator+(int days) {
		if (days < 0) {
			return *this + (0 - days);
		}
		Date temp(*this);
		temp._day += days;
		int daysOfMonth = 0;
		while (temp._day > (daysOfMonth = GetDayOfMonth(temp._year,temp._month))) {
			temp._day -= daysOfMonth;
			temp._month += 1;
			if (temp._month = 13) {
				temp._year += 1;
				temp._month = 1;
			}
		}
		return temp;
	}

	// 日期-天数
	Date operator-(int day) {
		if (day < 0) {
			return *this + (0 - day);
		}
		Date temp(*this);
		temp._day -= day;
		while (temp._day < 0) {
			temp._month -= 1;
			if (temp._month == 0) {
				temp._year -= 1;
				temp._month = 12;
			}
			temp._day += GetDayOfMonth(temp._year, temp._month);
		}
		return temp;
	}
	// 日期-=天数
	Date& operator-=(int day) {
		*this -= day;
		return *this;
	}

	// 前置++
	// 前置++ 和 后置++
	// 他们都是单目运算符---只要一个操作数
	// d1++   ++d1
	// 前置++:前+1  然后返回加1之后的结果
	Date& operator++() {
		_day += 1;
		return *this;
	}
	// 后置++
	// 后置++: 先使用 然后再+1
	// int a = 10;
	// int b = a++;   b=10   a=11
	// 后置++中:必须要给*this+1  但是要返回*this加1之前的结果
	Date operator++(int) {
		Date temp(*this);
		_day += 1;
		return temp;
	}
	// 后置--
	Date operator--(int) {
		Date temp(*this);
		_day -= 1;
		return temp;
	}
	// 前置--
	Date& operator--() {
		_day -= 1;
		return *this;
	}

	// >运算符重载
	bool operator>(const Date& d)const;
	// ==运算符重载
	bool operator==(const Date& d) {
		return _year == d._year &&
			_month == d._month &&
			_day == d._day;
	}
	// >=运算符重载
	inline bool operator >= (const Date& d)const {
		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._month) {
			return true;
		}
		else {
			return false;
		}
	}

	// <运算符重载
	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._month) {
			return true;
		}
		else {
			return false;
		}
	}
	// <=运算符重载
	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._month) {
			return true;
		}
		else {
			return false;
		}
	}
	// !=运算符重载
	bool operator != (const Date& d) {
		return !(*this == d);
	}
	// 日期-日期 返回天数
	size_t operator-(const Date& d) {
		Date minDate(*this);
		Date maxDate(d);
		if (maxDate < minDate) {
			minDate = d;
			maxDate = *this;
		}
		size_t gap = 0;
		while (maxDate != minDate) {
			++minDate;
			gap++;
		}
		return gap;
	}
private:
	int GetDayOfMonth(int year, int month)const {
		int days[] = { 0, 31,28,31,30,31,30,31,31,30,31,30,31 };
		if (IsLeapYear(year)) {
			days[2] += 1;
		}
		return days[month];
	}
private:
	int _year;
	int _month;
	int _day;
};

const成员

const修饰类的成员函数

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

C++的深度学习(4)_第14张图片

const修饰类的成员函数的问题

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

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

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

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

C++的深度学习(4)_第15张图片

总结:

const对象不能调用非const成员函数
非const对象可以调用const成员函数
const成员函数不能调用非const成员函数
非const成员函数可以调用非const成员函数

 若const对象想调用非const成员函数,则需要进行强制类型转换const_cast (Obj)

C++的深度学习(4)_第16张图片

若const成员函数想调用非const成员函数,则需要对this指针进行强制类型转换const_cast (*this) 

C++的深度学习(4)_第17张图片

友元

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

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

友元函数

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

C++的深度学习(4)_第18张图片

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

友元函数不能用const修饰

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

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

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

友元类 

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

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

比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time 类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

友元关系不能传递 如果B是A的友元,C是B的友元,则不能说明C时A的友元。

内部类

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

注意此时这个内部类是一个独立的 类,它不属于外部类,更不能通过外部类的对象去调用内部类。

外部类对内部类没有任何优越的访问权限。

注意:

内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中 的所有成员。但是外部类不是内部类的友元。

特性:

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

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

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

class A
{
private:
 static int k;
 int h;
public:
 class B
 {
 public:
 void foo(const A& a)
 {
 cout << k << endl;
 cout << a.h << endl;
 }
 };
};

你可能感兴趣的:(C++,c++)