[C/C++]详解C++的类和对象

详解C++的类和对象

  • 1.面向对象
  • 2.类的引入
  • 3.类的定义
  • 4.类的访问限定符及封装
    • 4.1 访问限定符
    • 4.2 封装
  • 5.类的作用域
  • 6.类的实例化
  • 7.类对象模型
    • 7.1 计算类对象的大小
    • 7.2 结构体内存对齐规则
  • 8.类成员函数的this指针
    • 8.1 this指针是什么
    • 8.2 this指针的特性
  • 9.类的6个默认成员函数
    • 9.1 构造函数
    • 9.2 析构函数
    • 9.3 拷贝构造函数
    • 9.4 赋值操作符重载
    • 9.5 取地址及const取地址操作符重载
  • 10.const成员
    • 10.1const修饰类的成员函数
  • 11.构造函数的进阶
    • 11.1构造函数体赋值
    • 11.2 初始化列表
    • 11.3 explicit关键字
  • 12.C++11 的成员初始化新操作。
  • 13.友元
    • 13.1 友元函数
    • 13.2 友元类
  • 14.static成员
    • 14.1 概念
    • 14.2 特性
  • 15.内部类
    • 15.1概念
    • 15.2特性
  • 16.再次理解封装
  • END
  • 写第一个类(Date类)

类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起。对象是具有类类型的变量。类和对象是面向对象编程技术中的最基本的概念。

1.面向对象

首先来理解什么是面向对象编程。
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

2.类的引入

在C++中的结构体内不仅可以定义变量,也可以定义函数。在C++中常用class来代替struct

struct Student
{
	void Set(const char* name, const char* gender, int age)
	{
	}
	void Print()
	{
	}
	char _name[20];
	char _gender[3];
	int _age;
};

3.类的定义

class className
{
// 类体:由成员函数和成员变量组成
}; 

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,类定义结束时一定要加后面分号。
类中的元素称为类的成员

  1. 数据称为类的属性或者成员变量;
  2. 函数称为类的方法或者成员函数
    类的两种定义方式
  3. 声明和定义全部放在类体中,成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

class className
{
    public:
    	//公共的行为或属性
 
    private:
    //私有的行为或属性
};
  1. 声明放在.h文件中,类的定义放在.cpp文件中
    在.h中
class className
{
    public:
    	void show();
    	//公共的行为或属性
 
    private:
    //私有的行为或属性
};

在.cpp中

void className::show()
{
	cout<<_name<<endl;
}

一般情况会采用第二种方式。

4.类的访问限定符及封装

4.1 访问限定符

访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
C++的访问限定符三种:public(公有) , protected(保护), private(私有)

  1. public修饰的成员在类外可以直接被访问;
  2. protected和private修饰的成员在类外不能直接被访问;
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止;
  4. class的默认访问权限为private,struct为public。

4.2 封装

面向对象的有三大特性:封装、继承、多态。在类和对象阶段,只研究类的封装特性。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。封装本质上是一种管理。

5.类的作用域

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

class className
{
    public:
    	void show();
    	//公共的行为或属性
 
    private:
    //私有的行为或属性
};

void className::show()
{
	cout<<_name<<endl;
}

6.类的实例化

类类型创建对象的过程,称为类的实例化。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图。与C语言中的结构体相同。
类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间存储类成员变量

7.类对象模型

7.1 计算类对象的大小

一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。
我们通过对下面的不同对象分别获取大小进行分析:

// 类中既有成员变量,又有成员函数
class A1 {
	public:
	void f1(){}
	private:
	int _a;
};
// 类中仅有成员函数
class A2 {
	public:
	void f2() {}
};
// 类中什么都没有---空类
class A3
{};

sizeof(A1) = 4; sizeof(A2) = 1; sizeof(A3) = 1;

7.2 结构体内存对齐规则

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

8.类成员函数的this指针

this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。

8.1 this指针是什么

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

8.2 this指针的特性

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

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

若类中任何成员都没有就称之为空类。但它不会真的什么都没有,编译器会自动生成6个默认的成员函数:
构造函数:完成初始化工作;
析构函数:完成清理函数;
拷贝构造:使用同一个类的对象初始化创建对象;
赋值重载:主要是把一个对象赋值给另一个对象;
取地址重载:有两个函数,主要是对普通对象和const对象取地址;

class A
{
public:
    A();//构造函数
    A(const A& a);//拷贝构造函数
    ~A();//析构函数
    A& operator=(const A& a);//赋值运算符重载
    A* operator &();//取地址运算符重载
    const A* operator &() const;//const修饰的取地址运算符重载
};

下面我将对这六个默认的成员函数进行介绍。

9.1 构造函数

构造函数是一个特殊的成员函数在对象的生命周期内只调用一次。 构造函数名与类名相同。构造函数没有返回值。在对象进行实例化的时候编译器自动调用对应的构造函数。构造函数可以重载。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。
以Date类为例

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)			//构造函数
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "1" << endl;
	}
private:
	int _year;		//对象的命名风格
	int _month;
	int _day;
};
class Date
{
public:
	// 1.无参构造函数
	Date ()
	{}
	// 2.带参构造函数
	Date(int year , int month, int day)			//构造函数
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "1" << endl;
	}
private:
	int _year;		//对象的命名风格
	int _month;
	int _day;
};
void Test()
{
	Date d1; // 调用无参构造函数
	Date d2 (2021, 5, 30); // 调用带参的构造函数
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	Date d3();
}
  1. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
  2. 无参的和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
  3. 构造函数会对自定类型成员调用的它的默认成员函数

9.2 析构函数

析构函数:与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。
析构函数名也应与类名相同,只是在函数名前面加一个位取反符~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。

typedef int DataType;
class SeqList
{
public :
	SeqList (int capacity = 10)
	{
		_pData = (DataType*)malloc(capacity * sizeof(DataType));
		assert(_pData);
		_size = 0;
		_capacity = capacity;
	}
	~SeqList()				//析构函数
	{
		if (_pData)
		{
			free(_pData ); // 释放堆上的空间
			_pData = NULL; // 将指针置为空
			_capacity = 0;
			_size = 0;
		}
	}
private :
	int* _pData ;
	size_t _size;
	size_t _capacity;
};

会自定类型成员调用它的析构函数。

9.3 拷贝构造函数

1.概念
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
2.特性
拷贝构造函数也是特殊的成员函数。
其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用,会被认为重新定义了一个对象,并且继续使用拷贝构造,这样无限递归。
class Date
{
public:

	Date(int year = 1900, int month = 1, int day = 1)			//构造函数
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "1" << endl;
	}

	Date(Date& d)						//拷贝构造函数,不可以Date(Date d)会造成无限递归
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "2" << endl;
	}
	~Date()														//析构函数
	{
		
	}

private:
	int _year;		//对象的命名风格
	int _month;
	int _day;
};
  1. 系统会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
  2. 当对象中存在如栈这种结构,我们就需要自己写拷贝构造函数,进行深拷贝。

9.4 赋值操作符重载

1.运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
需要注意的:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型或者枚举类型的操作数
  3. 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
  4. 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参
  5. .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。

2.赋值运算符重载
赋值运算符主要有四点:

  1. 参数类型
  2. 返回值
  3. 检测是否自己给自己赋值
  4. 返回*this
  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。
    3.一点点示例
class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1);				//1.构造函数,全缺省。
	void Print();		//打印

	//析构,拷贝构造,赋值重载,可以不写,默认生成
	//stack才写

	//运算符重载,日期加减
	Date& operator+=(int day);			//+=之后,day这个实体还是存在的,所以可以返回引用
	Date operator+(int day);

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

	//()为空是前置++,增加(int)是为了构成重载,后置++;
	Date& operator++();								//++d 前置可以引用,因为是先加自己身上再返回
	Date operator++(int);							//d++ 用int占位,不需要给实参

	Date& operator--();
	Date operator--(int);

	// 运算符重载
	bool operator>(const Date& d)const;
	bool operator<(const Date& d)const;
	bool operator==(const Date& d)const;
	bool operator<=(const Date& d)const;
	bool operator>=(const Date& d)const;
	bool operator!=(const Date& d)const;

	// 日期-日期 返回天数
	int operator-(const Date& d);

private:				//私有
	int _year;
	int _month;
	int _day;
};

inline int GetDay(int year, int month)									//返回当月最大日期,频繁调用所以内联	
{
	static int days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };		//12个月的日期,只创建一次,放在静态区
	int day = days[month - 1];
	if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) )		//判断闰年
	{
		day = 29; 
	}
	return day;
}

Date::Date(int year, int month, int day)				//指定类域,缺省函数只能在一个地方出现
{
	if (year >= 0
		&& month <= 12 && month >= 1
		&& day > 0 && day <= GetDay(year, month))		//判断日期的合法性
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << " no " << endl;
		assert(false);
	}

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

Date& Date::operator+=(int day)						//
{
	if (day < 0)									//day<0
	{
		_day -= -day;
	}
	else
	{
		_day += day;

		while (_day > GetDay(_year, _month))			//判断day是否合法,不合法就月份+1,月份超出就置1,年++;
		{
			cout << GetDay(_year, _month) << _month<<endl;
			_day -= GetDay(_year, _month);
			_month++;
			if (_month > 12)
			{
				_month = 1;
				_year++;
			}
		}
	}

	return *this;
}

Date Date::operator+(int day)			//需要创建一个新的对象,返回这个对象
{
	Date ret(*this);					//使用默认拷贝构造函数(浅拷贝)

	ret += day;					//直接进行复用,直接操作对象,不要操作成员。

	return ret;

}

Date& Date::operator-=(int day)
{
	if (day < 0)								//减负数
	{
		_day += -day;
	}
	else										
	{
		_day -= day;

		while (_day <= 0)
		{
			_month--;							//先-月,因为要往上一个月算,
			if (_month < 0)						//逻辑参考+=
			{
				_month = 12;
				_year--;
			}
			_day += GetDay(_year, _month);
		}
	}

	return *this;
}

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

	ret -= day;

	return ret;
}



//Date:: 声明域
bool Date::operator>(const Date& d)const
{
	if (_year < d._year)
	{
		return false;
	}
	else if (_year > d._year)
	{
		return true;
	}
	else
	{
		if (_month < d._month)
		{
			return false;
		}
		else if (_month > d._month)
		{
			return true;
		}
		else
		{
			if (_day > d._day)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	}

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

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

	return *this;
}
Date Date::operator++(int)
{
	Date ret(*this);
	*this += 1;					//这里不能对ret操作,因为要做返回值,this实际上要加,但为了返回一个加之前,所以返回一个ret

	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& d)			//设置两端,然后遍历整个区间,++i就可以。
{
	int flag = 1;					//判断日期正负,默认为正
	Date max = *this;				//默认d小,如果不采取这种方式,下面while会存在编译错误
	Date min = d;

	if (*this < d)
	{
		max = d;				//拷贝构造的另一种表达法,不能重复构建对象,重复定义根据就近原则会以上面的为最终结果
		min = *this;
		flag = -1;
	}


	int n = 0;

	while (max > min)
	{
		++n;
		++(min);
	}

	return n * flag;			//大小*正负
}

9.5 取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。并且非常少使用
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。

class Date
{
public :
	Date* operator&()
	{
		return this ;
	}
	const Date* operator&()const
	{
		return this ;
	}
private :
	int _year ; // 年
	int _month ; // 月
	int _day ; // 日
};

10.const成员

10.1const修饰类的成员函数

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

class Date
{
public :
	void Display ()
	{
		cout<<"Display ()" <<endl;
		cout<<"year:" <<_year<< endl;
		cout<<"month:" <<_month<< endl;
		cout<<"day:" <<_day<< endl<<endl ;
	}
	void Display () const
	{
		cout<<"Display () const" <<endl;
		cout<<"year:" <<_year<< endl;
		cout<<"month:" <<_month<< endl;
		cout<<"day:" <<_day<< endl<<endl;
	}
private :
	int _year ; // 年
	int _month ; // 月
	int _day ; // 日
};

下面来一段绕口令:

  1. const对象不可以调用非const成员函数
  2. 非const对象可以调用const成员函数
  3. const成员函数内不可以调用其它的非const成员函数
  4. 非const成员函数内可以调用其它的const成员函数
    总结:权限只能缩小不能放大

11.构造函数的进阶

11.1构造函数体赋值

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

11.2 初始化列表

构造函数的一项重要功能是对成员变量进行初始化,为了达到这个目的,可以在构造函数的函数体中对成员变量一一赋值,还可以采用初始化列表。
注意:

  1. 使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便,尤其是成员变量较多时,这种写法非常简单明了。
  2. 初始化列表可以用于全部成员变量,也可以只用于部分成员变量。
  3. 成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。
class Date
{
public:
	Date(int year, int month, int day)		//以冒号开始,接着是逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。 	
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

特别的:
5. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
6. 类中包含这些成员,必须放在初始化列表位置进行初始化:引用成员变量,const成员变量,自定义类型成员(该类没有默认构造函数)
7. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
8. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关

11.3 explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
explicit构造函数是用来防止隐式转换的。用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。

class Date
{
public:
	Date(int year)
		:_year(year)
	{}
	explicit Date(int year)
		:_year(year)
	{}
private:
	int _year;
	int _month:
	int _day;
}

12.C++11 的成员初始化新操作。

C++11支持非静态成员变量在声明时进行初始化赋值,但这里不是初始化,这里是给声明的成员变量缺省值

class B
{
public:
	B(int b = 0)
	:_b(b)
	{}
	int _b;
	};
	
class A
{
public:
	void Print()
	{
		cout << a << endl;
		cout << b._b<< endl;
		cout << p << endl;
	}
private:
	// 非静态成员变量,可以在成员声明时给缺省值。
	int a = 10;
	B b = 20;
	int* p = (int*)malloc(4);
	static int n;
};

int A::n = 10;

int main()
{
	A a;
	a.Print();
	return 0;
}

13.友元

友元分为:友元函数和友元类
在 C++ 中,一个类中可以有 public、protected、private 三种属性的成员,通过对象可以访问 public 成员,只有本类中的函数可以访问本类的 private 成员。现在,我们来介绍一种例外情况——友元(friend)。借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。
friend 的意思是朋友,或者说是好友,与好友的关系显然要比一般人亲密一些。我们会对好朋友敞开心扉,倾诉自己的秘密,而对一般人会谨言慎行,潜意识里就自我保护。在 C++ 中,这种友好关系可以用 friend 关键字指明,中文多译为“友元”,借助友元可以访问与其有好友关系的类中的私有成员。

13.1 友元函数

为了方便使用cout,cin 这种输入输出,尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

class Date
{
	friend ostream& operator<< (ostream& out,const Date& d);					//友元函数,可以访问私有的
	friend istream& operator>> (istream& in, Date& d);
public:

	Date(int year = 1900, int month = 1, int day = 1)			//构造函数
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "1" << endl;
	}
private:
	int _year;		//对象的命名风格
	int _month;
	int _day;
};

ostream& operator<< (ostream & out,const Date&d)				//设为全局,避免抢位置			
{
	out << d._year << "-" << d._month << "-" << d._day;

	return out;
}

istream& operator>> (istream& in, Date& d)						//<<>>是输入输出运算符,所以重载后就用了
{
	in>> d._year >> d._month >> d._day;
	
	return in;
}

另外的:

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用和原理相同

13.2 友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
友元关系不能传递

14.static成员

14.1 概念

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

14.2 特性

  1. 静态成员为所有类对象所共享,不属于某个具体的实例
  2. 静态成员变量必须在类外定义,定义时不添加static关键字
  3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值

15.内部类

15.1概念

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

15.2特性

  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;//OK
			cout << a.h << endl;//OK
		}
	};
};
int A::k = 1;

16.再次理解封装

C++是基于面向对象的程序,面向对象有三大特性即:封装、继承、多态。
C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道,知道了有些情况下也没用,反而增加了使用或者维护的难度,让整个事情复杂化。

END

类和对象的基本概念的介绍就到此为止,希望大家多多指正,一键三连。

写第一个类(Date类)

介绍完了类和对象的知识,就可以写自己的类了,下面以Date类为例做个示范:包括了构造函数,运算符的重载等。
.h文件

#pragma once
#include
#include

using std::cout;
using std::endl;
using std::cin;

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1);				//1.构造函数,全缺省。
	void Print();		//打印

	//析构,拷贝构造,赋值重载,可以不写,默认生成
	//stack才写

	//运算符重载,日期加减
	Date& operator+=(int day);			//+=之后,day这个实体还是存在的,所以可以返回引用
	Date operator+(int day);

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

	//()为空是前置++,增加(int)是为了构成重载,后置++;
	Date& operator++();								//++d 前置可以引用,因为是先加自己身上再返回
	Date operator++(int);							//d++ 用int占位,不需要给实参

	Date& operator--();
	Date operator--(int);

	// 运算符重载
	bool operator>(const Date& d)const;
	bool operator<(const Date& d)const;
	bool operator==(const Date& d)const;
	bool operator<=(const Date& d)const;
	bool operator>=(const Date& d)const;
	bool operator!=(const Date& d)const;

	// 日期-日期 返回天数
	int operator-(const Date& d);

private:				//私有
	int _year;
	int _month;
	int _day;
};

.cpp

#include"Date.h"

inline int GetDay(int year, int month)									//返回当月最大日期,频繁调用所以内联	
{
	static int days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };		//12个月的日期,只创建一次,放在静态区
	int day = days[month - 1];
	if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) )		//判断闰年
	{
		day = 29; 
	}
	return day;
}

Date::Date(int year, int month, int day)				//指定类域,缺省函数只能在一个地方出现
{
	if (year >= 0
		&& month <= 12 && month >= 1
		&& day > 0 && day <= GetDay(year, month))		//判断日期的合法性
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << " no " << endl;
		assert(false);
	}

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

Date& Date::operator+=(int day)						//
{
	if (day < 0)									//day<0
	{
		_day -= -day;
	}
	else
	{
		_day += day;

		while (_day > GetDay(_year, _month))			//判断day是否合法,不合法就月份+1,月份超出就置1,年++;
		{
			cout << GetDay(_year, _month) << _month<<endl;
			_day -= GetDay(_year, _month);
			_month++;
			if (_month > 12)
			{
				_month = 1;
				_year++;
			}
		}
	}

	return *this;
}

Date Date::operator+(int day)			//需要创建一个新的对象,返回这个对象
{
	Date ret(*this);					//使用默认拷贝构造函数(浅拷贝)

	ret += day;					//直接进行复用,直接操作对象,不要操作成员。

	return ret;

}

Date& Date::operator-=(int day)
{
	if (day < 0)								//减负数
	{
		_day += -day;
	}
	else										
	{
		_day -= day;

		while (_day <= 0)
		{
			_month--;							//先-月,因为要往上一个月算,
			if (_month < 0)						//逻辑参考+=
			{
				_month = 12;
				_year--;
			}
			_day += GetDay(_year, _month);
		}
	}

	return *this;
}

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

	ret -= day;

	return ret;
}



//Date:: 声明域
bool Date::operator>(const Date& d)const
{
	if (_year < d._year)
	{
		return false;
	}
	else if (_year > d._year)
	{
		return true;
	}
	else
	{
		if (_month < d._month)
		{
			return false;
		}
		else if (_month > d._month)
		{
			return true;
		}
		else
		{
			if (_day > d._day)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	}

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

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

	return *this;
}
Date Date::operator++(int)
{
	Date ret(*this);
	*this += 1;					//这里不能对ret操作,因为要做返回值,this实际上要加,但为了返回一个加之前,所以返回一个ret

	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& d)			//设置两端,然后遍历整个区间,++i就可以。
{
	int flag = 1;					//判断日期正负,默认为正
	Date max = *this;				//默认d小,如果不采取这种方式,下面while会存在编译错误
	Date min = d;

	if (*this < d)
	{
		max = d;				//拷贝构造的另一种表达法,不能重复构建对象,重复定义根据就近原则会以上面的为最终结果
		min = *this;
		flag = -1;
	}


	int n = 0;

	while (max > min)
	{
		++n;
		++(min);
	}

	return n * flag;			//大小*正负
}

你可能感兴趣的:(C++,对象,类,c++,类,封装)