[C++系列]一篇文章搞懂类和对象,6大成员函数将再也不是难题


前言

学习C++,首先要了解的便是它和C语言的区别所在,很多的新手可能和我一般,学校是先开设了C语言,之后才开设的C++课程,因此学习C++之前,我们要明白:

  • C语言是面向过程的,更多的关注的是过程,分析出求解问题的具体步骤,通过函数调用来逐步解决这个问题。
  • C++是基于面向对象的,首先关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互来完成。
    以我自己的理解来看,就是将一个问题所涉及到的所有的东西,元素,函数等全部打包放在一个地方,它内部就能够解决掉它自身的很多问题,如果使用的时候只需要调用它就完全可以了。

类与对象(上)

01 .类的引入及定义

两种定义类1.struct 类名 { }2. class 类名 {},这里我们重点介绍的是后面一种。

class Student
{
public: //公有的成员在类外可见
	//成员函数
	void display()
	{}

	void setId(int id)
	{
		_id = id;
	}

	void setNumber(int number)
	{
		_number = number;
	}
private:  //私有的成员在类外不可见
	//成员变量, 属性
	int _number;
	int _id;
protected:  // 保护的成员在类外不可见
	char name[10];
	char gender[10];
	char major[10];
};

【面试题】C++中的struct和class的区别是什么呢?
C++需要兼容C语言,所以C++中的struct可以当成结构体去进行使用,也可以用来定义类;它和class定义类是一样得,区别在于struct的成员默认访问方式是public,class的成员默认访问方式是private。

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

上面我们所看到的public和protected,private都是类的访问限定符对于访问限定符的说明:

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

封装:用类将成员和函数将合在一起,使得对象更加的完善,通过访问权限选择性的将接口提供给外部用户来进行使用,实质上是一种管理。

03 .类的作用域和类的实例化

如果在类体外定义成员的话,需要使用::作用域解析符指明成员属于那个类域

class B
{
private:
	int _a;

	void fun() {
		cout << "class B fun()" << endl;
	}
	void fun2();
};

void B::fun2() {//类域之中的fun2的定义
	cout << "B::fun2()" << endl;
}

类的实例化:

  • 创建一个类类型变量的过程称之为类的实例化
  • 相当于类只是一张设计图,通过类创建出的变量才是将其变换成一个真正的实物
  • 未实例化前的类不占据空间

04 .类对象模型

在这里插入图片描述
类的大小也遵循内存对齐的规则:并且在类中嵌套类的时候,如果此类没有创建变量,不计算其内存大小

class G
{
	char _c;   //1
	double _d; // 16
	int _a;  // 20
	char _c2;  //21
	//24
	//嵌套类本身遵循内存对齐的原则,计算大小: H: 24
	class H
	{
		double _d;  //8
		char _c; //9
		int _a; //16
		char _c1; //17
		//24
	};
	H _h;//未创建变量时,不计算内存大小
};

05 .this指针

this指针:

  1. this指针类型为 类类型
  2. this指针只存在于成员函数之中,始终指向当前所调用的这个函数,是不能够改变的,是一个形参,不能够算作成员对象
  3. this始终作为成员函数第一个形参,编译器会自动传递,不需要显式定义此函数
  4. this不是类的成员,只是一个函数形参,一般存在于栈中,不会做优化

this解引用:

  • this未进行解引用,可能是因为内部没有使用this指针所指地址的变量,因此当作一个参数进行传参,不会发生未定义行为
  • 如果this所指向的变量需要被改变或使用,这个时候this是一个指针被解引用,若此时this为空会导致异常

类与对象(中)

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

如果一个类中什么成员都没有,简称为空类。空类中并不是什么都没有,任何一个类在外面不写的情况下,都会自动生成下面的6个默认成员函数。

0.1 构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名为构造,但其主要任务并不是开空间创建对象,而是初始化对象

  1. 函数名与类名相同,无返回值,对象实例化时编译器会自动调用所对应的构造函数,构造函数可以重载
  2. 如果类中存在自定义成员,则构造函数会自动调用自定义成员的默认构造完成初始化,如果自定义成员没有默认构造则会产生编译错误
  3. 默认构造只能够存在一个,在声明一个函数的时候,是不会调用无参构造的
编译器默认生成的构造函数
Date()
{

}
显示定义的无参构造
Date()
{
		_year = y;
		_month = m;
		_day = d;
}
全缺省的构造函数
Date(int y = 2020, int m = 5, int d = 20)
{
		_year = y;
		_month = m;
		_day = d;
}
重载构造函数
Date(float f)
{

}

explicit关键字
构造函数对於单个参数的构造函数还具有类型转换的作用(调用构造创建一个匿名对象,通过匿名对象来给所需要创建的对象进行赋值或拷贝构造),而为了避免这种隐式转换,可以使用explicit关键字来修饰构造函数,将会禁止单参构造函数的隐式转换。

构造函数初始化列表

class Time {
public:
	Time(int a = 1)
		:_a(a)
	{
		cout << "Time(int)" << endl;
	}
private://这里是成员变量声明的地方,而引用和const类型变量,这两者定义时必须初始化
	int _a;
};
  1. 类中必须放在初始化列表中的有引用成员变量,const成员变量,自定义类型成员(没有默认构造函数),其他成员可以不进行显示初始化
  2. 每个成员变量在初始化列表中只能够出现一次,因为初始化列表是对象的成员变量定义的地方
  3. 成员变量在初始化列表中初始化的顺序,必须和声明顺序一致,与其在初始化列表中的顺序无关(最好保持初始化列表和声明顺序一致)

0.2 析构函数

因为类的一些资源并不在类中,因此在对象生命周期结束的时候需要对资源进行清理和释放(时清理资源不是销毁对象),则会自动调用析构函数,完成资源清理的工作。

  1. 析构函数名 在类名前加上取反符号 ~,没有参数也没有返回值
  2. 一个类有且只有一个析构函数,若未进行显式定义,系统会自动生成默认的析构函数
  3. 在对象生命周期结束时,编译系统会自动调用析构函数
  4. 如果没有资源需要清理,可以不用显式写析构函数,直接使用编译器默认生成的析构函数即可
class A {
public:
	~A()
	{
		cout << "~A()" << endl;
	}
	int _a;
};
  • 全局对象先于局部对象进行构造
  • 静态对象先于普通对象进行构造

0.3 拷贝构造函数

拷贝构造是构造函数的一个重载形式,它是用一个已经存在的对象去创建一个新的对象,创建的新对象和当前所存在的对象内容完全相同;

class Date {
public:
	//构造函数
	Date(int y = 1, int m = 1, int d = 1) {
		_year = y;
		_month = m;
		_day = d;
	}
	//拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

void Test() {
	Date d;
	Date d2(2020, 4, 1);
	Date& rd = d;
	Date copy1(d);
	Date copy2(Date(2020, 5, 20));//优化,直接调用构造函数创建copy2
		                          //不优化:调用构造创建匿名对象,+ 拷贝构造
}

若没有显式定义拷贝构造函数的话,系统会默认生成拷贝构造函数,但智慧按照内存存储的字节序完成浅拷贝,拷贝对象模型中的内容,不会拷贝资源,因此如果需要拷贝资源一定要显式定义拷贝构造函数。

拷贝构造函数的参数只有一个(一般用const修饰),必须使用引用传参,如果使用传值方式的话会引发无穷次的递归调用。
在这里插入图片描述

0.4运算符重载

运算符重载是具有特殊函数名的函数,也具备其返回值的类型,函数名,参数列表,其返回值类型和参数列表与普通的函数类似,其作用旨在增强代码的可读性,定义和使用与普通函数一致。

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

	
	bool operator==(const Date& d) {//底层接口 bool operator==(Date* const this, const Date& d)
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

//输出运算符重载函数
ostream& operator<<(ostream& _cout, const Date& date) {
	_cout << date._year << " " << date._month << " " << date._day << endl;
	return _cout;
}
  • 重载操作符必须有一个类类型或枚举类型的操作数,不能通过连接其他符号来创建新的操作符
  • 作为类成员的重载函数时,成员函数的第一个操作符默认为形参this
  • .* ,::,sizeof,?:, .以上5个匀速那副不能重载,笔试选择题中较为热门

赋值运算符重载:

class Date
{
public:
	Date(int y = 1, int m = 1, int d = 1)
	{
		_year = y;
		_month = m;
		_day = d;
	}

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "Date(const Date& d)" << endl;
	}

//优化 ,避免自己给自己赋值
	Date& operator=(const Date& d2)
	{
		//判断是否给自己赋值
		if (this != &d2)
		{
			_year = d2._year;
			_month = d2._month;
			_day = d2._day;
		}

		cout << "operator=(const Date& d2)" << endl;
		//返回当前调用此函数的对象本身
		return *this;
	}

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


void Test() {
	Date d(2020, 5, 22);
	Date d2(2019, 1, 1);

	//如果对象都存在,调用赋值运算符重载函数,如果左边对象不存在,则调用拷贝构造
	d2 = d;
	d2.operator==(d);// 同上等价
	d2 = d2;
	Date d3(2018, 10, 1);

	//连续赋值:从右向左赋值
	d = d2 = decltype;
	d.operator=(d2.operator=(d3));//同上面等价
	Date d4 = d3;//因为d4不存在,则调用拷贝构造,用d3创建d4对象
}
  • 赋值运算符重载函数d=d2,修改已经存在的对象内容,不是去创建新的对象
  • 如果当前类中有资源存在,必须显式定义赋值运算符重载函数完成深拷贝,否则会采用编译器默认生成的字节拷贝,只能够浅拷贝
  • 如所需赋值的对象存在,则直接调用赋值运算符重载函数,如所需赋值对象不存在,则直接调用拷贝构造

0.5 const成员函数

将const修饰的类成员函数称之为const函数,它实际修饰的是该成员函数的隐式this指针,表明在该成员函数中不能够对类的任何成员进行修改。
在这里插入图片描述
const函数和非const函数

void printD()  // 等价于 printD(Date* const this)
{
	cout << _year << " " << _month << " " << _day << endl;
	//可以修改内容 
	this->_year = 100;
	//可以调用const成员函数
	fun();
}

void printD() const  //等价于 printD(const Date* const this)
{
	cout << _year << " " << _month << " " << _day << endl;
	//不能修改内容 this->_year = 100;这是错误的
	//不能调用非const成员函数,读写的权限不能被放大 fun()
	//不能进行自加操作 ++*this;
}

void fun()const
{

}

在这里插入图片描述

0.6取地址

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

这两个运算符一般不需要重载使用编译器默认生成的即可,如果想要让别人获取到指定的内容的话,才需要进行重载。

02.六大成员函数总结对比

在这里插入图片描述

类与对象(下)

01.静态成员

在这里插入图片描述

//int cnt = 0;  //定义全局变量的话,安全性较低,容易篡改

class Date
{
public:
	Date(int year = 2020, int month = 12, int day = 20)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		++cnt;
		cout << "Date(int ,int ,int)" << endl;
	}

	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		++cnt;
		cout << "Date(const Date&)" << endl;
	}


	//静态成员函数:函数内部没有this指针
	static int getCount()
	{

	}


	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		getCount();
		cout << cnt << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;

public:
	static int cnt;
};

//静态成员必须在类外初始化
int Date::cnt = 0;

Date fun(Date d) //拷贝构造
{
	cout << &d.cnt << endl;
	return d;
}

void Test()
{
	Date d;//构造
	Date d2 = fun(d);//拷贝函数 fun:进行优化,只有两次拷贝构造,传参创建d2

	//静态成员变量/静态成员函数访问方式:
	// 1. 对象访问
	cout << d.getCount() << endl;
	cout << d2.getCount() << endl;
	cout << &d.cnt << endl;
	cout << &d2.cnt << endl;
	// 2. 类名 + 作用域限定符
	cout << &Date::cnt << endl;
	cout << Date::cnt << endl;
	cout << Date::getCount() << endl;
	//普通成员只能通过对象访问,不能通过类名访问
	d.Display();
	//Date::Display(); //不支持
}

对于C++11的初始化方式,相对于给一个缺省值

private:
	int _year = 1;
	int _month = 1;
	int _day = 1;

它将是初始化时候的最后一个候选,先优先前面的进行选择
如果有缺省的构造函数,那么就先优先使用缺省构造函数的数据

02.友元函数

在这里插入图片描述

class Date
{
public:
	friend ostream& operator<<(ostream& outputS, Date& d);
	friend istream& operator >> (istream& inputS, Date& d);
	friend class B;
}

class B
{
public:
	//disPlay, fun, fun1都为Date类的的友元函数
	void disPlay(const Date& d)
	{
		cout << d._year << d._month << d._day << endl;
	}

	void fun(const Date& d)
	{
		cout << d._year << d._month << d._day << endl;
	}

	void fun1(const Date& d)
	{
		cout << d._year << d._month << d._day << endl;
	}
};

输出流和输入流
ostream& operator<<(ostream& outputS, Date& d)
{
	outputS << d._year << "-" << d._month << "-" << d._day << endl;
	return outputS;
}

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

03.内部类

在这里插入图片描述

enum Color
{
	BLACK,
	WHITE
};

class C
{
public:
	class D
	{
	public:
		void fun(const C& c)
		{
			//可以通过外部类对象访问外部类的私有成员
			cout << c._color << endl;
			cout << c._c << endl;
			cout << c._sc << endl;
			cout << C::_sc << endl;
			//可以直接访问外部类的static成员
			cout << _sc << endl;

		}
	private:
		int _d;
	};
private:
	int _c;
	static int _sc;
	Color _color;
	//内部类可以在类的任何地方定义
	class E
	{
	private:
		int _e;
	};
};

实践 日期类的实现

#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)//构造函数
	{
		//在进行一个日期赋值前需要判断日期是否合法
		if (year > 0 && month > 0 && month < 13
			&& day > 0 && day <= getMonthDay(year, month))
		{
			_year = year;
			_month = month;
			_day = day;
		}
		else
		{
			cout << "日期不合法: " << year << "-" << month << "-" << day << endl;
			cout << "重置为默认值: 2000-1-1" << endl;
			_year = 2000;
			_month = 1;
			_day = 1;
		}
		
	}

	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];
		//如果是2月且为闰年,+1
		if (month == 2
			&& (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))
			++day;
		return day;
	}
	// a += b
	Date& operator+=(int day) //加等操作
	{
		// a += -b  --> a -= b
		if (day < 0)
			return *this -= -day;

		//2020.5.1  + 20  --> 2020.5.21
		//2020.5.21 + 20 --> 2020.5.41 --> 进位 --> -31 -->月份进位 --> 2020.6.10
		//2020.12.6 + 90 --> 2020.12.96 --> 进位 --> -31 -->月份进位 --> 2020.13.65 -->年进位
		// --> 2021.1.65--> --> 进位 --> -31 -->月份进位 --> 2021.2.34 --> --> 进位 --> -28 -->月份进位 --> 2021.3.6
		_day += day;
		//判断日期是否溢出,溢出需要进位
		while (_day > getMonthDay(_year, _month))
		{
			//减去当月的天数,月份进位
			_day -= getMonthDay(_year, _month);
			++_month;
			//判断月份是否溢出
			if (_month == 13)
			{
				//进位到下一年的1月
				_month = 1;
				_year++;
			}
		}
		return *this;
	}

	//前置++: ++d: 首先++,返回++之后的值
	Date& operator++()
	{
		return *this += 1;
		//return (*this).operator+=(1);
		//return *this;
	}

	//后置++: d++: 本身++, 返回++之前的值
	//前置++, 后置++都为单目运算符
	//如果为成员函数,则本质上不需要显式传参,编译器会自动传入this指针
	//int: 形参不是一个真正的参数,只是一个标记参数,编译器看到这样的定义,通过语法树搜索,可以解释为后置++
	Date operator++(int)
	{
		//保存++之前的值
		Date ret(*this);
		//++
		*this += 1;
		//返回++之前的值
		return ret;		
	}

	Date& operator-=(int day)//减等操作
	{
		if (day < 0)
			return *this += -day;
		_day -= day;
		//判断_day是否为负值或者0, 退位
		//2020.5.24 - 30 --> 2020.5.-6 --> 月份退位 --> +30 --> 2020.4.24
		while(_day <= 0)
		{
			//月份退位
			--_month;
			//月份是否为负值或者0
			if (_month == 0)
			{
				_month = 12;
				//年份退位
				--_year;
			}
			_day += getMonthDay(_year, _month);
		}
		return *this;
	}

	Date& operator--()//前置减减操作
	{
		return *this -= 1;
	}

	Date operator--(int)//后置减减操作
	{
		Date ret = *this;
		//Date ret(*this);
		*this -= 1;
		return ret;
	}
	// +, -运算符:不能修改操作数的内容
	// c = a + b
	Date operator+(int day)//加运算符
	{
		Date ret = *this;
		ret += day;
		return ret;
	}

	Date operator-(int day)//减运算符
	{
		Date ret = *this;
		ret -= day;
		return ret;
	}

	bool operator==(const Date& date)//比较相等
	{
		return _year == date._year
			&& _month == date._month
			&&_day == date._day;
	}

	bool operator>(const Date& date)//大于
	{
		if (_year > date._year)
			return true;
		else if (_year == date._year)
		{
			if (_month > date._month)
				return true;
			else if (_month == date._month)
			{
				if (_day > date._day)
					return true;
			}
		}
		return false;
	}

	bool operator>=(const Date& date)//大于等于
	{
		return (*this > date) || (*this == date);
	}

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

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

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


	void printD()  // 等价于 printD(Date* const this)
	{
		cout << _year << " " << _month << " " << _day << endl;
		//可以修改内容
		//this->_year = 100;
		//可以调用const成员函数
		fun();
	}
	//const成员函数中的const修饰的为第一个参数,即this指针
	//const成员函数内部补能修改成员变量的值
	void printD() const  //等价于 printD(const Date* const this)
	{
		cout << _year << " " << _month << " " << _day << endl;
		//不能修改内容
		//this->_year = 100;
		//不能调用非const成员函数,读写的权限不能被放大
		//++*this;

	}

	void fun()const
	{

	}

	int operator-(Date& date)//两个日期进行相减
	{
		Date d1(*this);
		Date d2(date);
		//d1 - d2
		int num = 0;
		if (d1 > d2)
		{
			while (d1 > d2)
			{
				--d1;
				++num;
			}
			return num;
		}
		else
		{
			//d1 <= d2
			while (d1 < d2)
			{
				++d1;
				++num;
			}
			return -num;
		}
	}

	/*Date operator-(Date& date)
	{

	}*/

	//取地址运算符重载函数: operator&
	//一般不需要显示定义,直接用默认即可
	Date* operator&()
	{
		//return (Date*) 0x1234;
		return this;
	}

	const Date* operator&() const
	{
		//return nullptr;
		return this;
	}

//private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& _cout, const Date& date)
{
	_cout << date._year << " " << date._month << " " << date._day << endl;
	return _cout;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章