C++ --类和对象的知识总结

一,目录

1,类和对象的初识
2,类的六个默认成员函数
3,explicit关键字,static关键字的作用
4,友元
5,内部类

二,正文

1,类和对象的初识

(一)类的两种定义方式:

1,声明和定义全部放在类体中
2,声明放在.h头文件中,类的定义放在.cpp文件中
(注意:只在类外实现方法时,需要注意加上作用域限定符)

(二)了解封装

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

C++是如何实现封装的?

用类将对象的属性与方法结合在一起,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。而对于对象内部内的一些实现细节则外部用户并不需要知道。
在这里插入图片描述
(三)C++中的struct和class在定义类时的区别是什么?

1,C++中的struct可以定义类也可以定义结构体,但C++中的struct定义的类的成员默认访问方式是public,而class定义的类的成员默认访问方式是private
2,C语言中的struct定义的结构体中是不能有函数的

(四)类的作用域和类的实例化

类的作用域:类定义了一个新的作用域,类的所有成员和方法都在类作用域中,一旦出这个作用域就需要加::作用域限定符,在类里实现的方法,编译器可能默认为内联函数。
类的实例化:用类类型创建对象的过程。

(五)计算一个类的大小
1,一个类的大小只包含成员变量的大小之和,要考虑内存对齐问题(参照结构体的内存对齐原则)
2,空类的大小为1字节(原因:编译器要区分空类创建的不同对象)

补充:

结构体内存对齐的规则:
1,结构体变量的第一个成员不需要内存对齐,第一个成员在与结构体变量偏移量为0的地址处。
2,其他成员要对齐到某个数字(对齐数)的整数倍的地址处
对齐数是编译器指定的一个对齐数与该成员大小的较小值(如果不指定,那默认对齐数就是成员变量的大小)
对齐:当除第一个成员变量的其他成员放入结构体时,起始偏移量能整除自身对齐数,就称为对齐。
3,结构体总大小为最大对齐数的整数倍,每一个成员变量都有一个对齐数,其中最大的那被称为最大对齐数。
4,如果结构体中嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数,在没有指定对齐数的情况下,嵌套结构体的对齐数就是其自身的大小)的整数倍。

(六)this指针

概念:this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
类型: *const(不能改变指针的指向)
this指针传递方式:成员函数的调用约定—thiscall—ecx(普通函数的调用约定:—cdecl–参数压栈的方式)

this指针是否可以为nullptr?
this指针可以为空,但不能访问成员变量

2,类的六个默认成员函数

(一)构造函数

a>>概念:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用。构造函数并不是开辟空间创建对象,而是初始化对象。

b>>特征:
1,函数名与类名相同
2,无返回值(void也不行)
3,构造函数可以重载
4,如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
5,无参构造函数和全缺省的构造函数都称为默认构造函数,默认构造函数只能有一个

***c>>应用场景:***对象实例化时编译器自动调用对应的构造函数,且在对象的生命周期内只调用一次

d>>默认构造函数:
1,默认的构造函数是给对象中的每一个成员变量赋值,构造函数中的语句只能将其称作赋初值,而非初始化
2,编译器生成的默认构造函数会调用自定义类型成员的默认构造函数

e>>构造函数整体初始化

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

类中包含以下成员就必须要在初始化列表进行初始化:
1,引用成员变量
2,const成员变量
3,类类型成员变量(该类没有默认的构造函数)

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

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

(二)析构函数

a>>概念:析构函数不是完成对象的销毁,局部对象的销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

b>>特征:
1,析构函数是在类名前加上字符~
2,没有参数没有返回值,并且不能够重载(由于没有参数,无法重载)
3,一个类有且只有一个析构函数,若未显式定义,系统会自动生成默认的析构函数

c>>应用场景:在对象生周期结束时,编译系统会自动调用析构函数

d>>默认析构函数:编译器生成的默认析构函数会调用自定义类型成员的默认析构函数

#include<malloc.h>
#include<string.h>
#include<assert.h>
typedef int DateType;
class SeqList
{
public:
	SeqList(int capacity = 10)
	{
		cout << "SeqList(size_t)" << endl;
		_array = (DateType *)malloc(sizeof(DateType)*capacity);
		if (nullptr == _array)
		{
			assert(0);
			return;
		}
		_capacity = 0;
		_size = 0;
	}
	~SeqList()
	{
		cout << "~SeqList()" << endl;
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DateType *_array;
	size_t _size;
	size_t _capacity;
};

(三)拷贝构造函数

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

Date (const Date &d)

b>>特征
1,拷贝构造函数是构造函数的一种重载形式
2,拷贝构造函数的参数只有一个且必须使用引用传参,使用传值传参的方式会导致无穷递归调用
3,若未显式定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝就做浅拷贝

c>>应用场景:在用已存在的类类型对象创建新对象时由编译器自动调用

d>>默认拷贝构造函数:如果是浅拷贝,编译器生成的默认拷贝构造函数,仅仅只是将地址拷贝到了另一个对象中,也就是两个对象指向同一份地址,如果在程序结束后,调用析构函数来对这两个对象进行释放,也就是对象中的地址,这样这个地址指针就会被释放两次,从而导致程序奔溃。

(四)运算符重载

a>>目的
1,为了增强代码的可读性引入了运算符重载
2,告诉编译器该对当前对象进行什么操作

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

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

以下五个运算符不能重载: .* :: sizeof ?: .

写一个日期类如下,里面包含了大部分常见的运算符重载:

class Date
{
public:
	//构造函数******************************
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		if (!(year > 0) &&
			(month > 0 && month < 13) &&
			(day>0 && day <= GetDaysOfMonth(year, month)))
		{
			_year = 1900;
			_month = 1;
			_day = 1;
		}
	}
	//拷贝构造函数***************************
	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;
	}
	//求增加多少天后的日期************************
	Date operator+(int days)
	{
		if (days < 0)
		{
			return *this + (0 - days);
		}
		Date temp(*this);
		temp._day += days;
		while (temp._day > temp.GetDaysOfMonth(temp._year, temp._month))
		{
			temp._day -= temp.GetDaysOfMonth(temp._year, temp._month);
			temp._month += 1;
			if (temp._month > 12)
			{
				temp._month = 1;
				temp._year += 1;
			}
		}
		return temp;
	}
	//求减少多少天后的日期*********************************************
	Date operator-(int days)
	{
		if (days < 0)
		{
			return *this + (0 - days);
		}
		Date temp(*this);
		temp._day -= days;
		while (temp._day <= 0)
		{
			temp._month--;
			if (temp._month < 0)
			{
				temp._month = 12;
				temp._year--;
			}
			temp._day += temp.GetDaysOfMonth(temp._year, temp._month);
		}
		return temp;
	}
	//输出重载***************************************************
	friend ostream& operator<<(ostream& _cout, const Date&d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
	//两个日期对象相减*****************************************
	int operator-(const Date &d)
	{
		Date max(*this);
		Date min(d);
		if (*this < d)
		{
			swap(max, min);
		}
		int count = 0;
		while (min != max)
		{
			count++;
			min++;
		}
		return count;
	}
	//重载== *****************************************************
	bool operator==(const Date& d)
	{
		if (_year == d._year&&_month == d._month&&_day == d._day)
		{
			return true;
		}
		return false;
	}
	//重载!= *****************************************************
	bool operator!=(const Date& d)
	{
		if (*this == d)
		{
			return false;
		}
		return true;
	}

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

		return false;
	}
	//前置++     ************************************************
	Date & operator++()
	{
		*this = *this + 1;
		return *this;
	}
	//后置++******************************************************
	Date operator++(int)
	{
		Date temp(*this);
		*this = *this + 1;
		return temp;
	}
	//前置--************************************
	Date & operator--()
	{
		*this = *this - 1;
		return *this;
	}
	//后置--**********************************************
	Date operator--(int)
	{
		Date temp(*this);
		*this = *this - 1;
		return temp;
	}

	
private:
	int GetDaysOfMonth(int year, int month)
	{
		int arr[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		arr[2] = 28;
		if ((0 == year % 4 && 0 != year % 100) || 0 == year % 400)
		{
			if (month == 2)
			{
				arr[2] += 1;
			}
		}
		return arr[month];
	}
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2019, 9, 18);
	Date d2(2020, 1, 1);
	cout << d2 - d1 << endl;

	d1 = d1 - 999;
	cout << d1 << endl;
	system("pause");
	return 0;
}

(五)const成员
1,const修饰类的成员函数(可通过mutale修改)
2,const对象不可以调用普通类型成员函数,
非const对象可以调用const成员函数,
const成员函数内不能调用其他的普通成员函数,
非const成员函数内可以调用其他const类型成员函数

3,explicit关键字,static关键字的作用

explicit关键字作用:
由于对于单个参数的构造函数,具有类型转换的作用,因此就需要explicit关键字来修饰构造函数,这样可以禁止单参构造函数的隐式转换

class Date
{
public:
	Date(int year) 
		:_year(year){}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2019);
	d1 = 2019;//d1是一个类类型的对象,而2019是一个整形变量
	system("pause");//会先为2019构造一个对象,再用这个临时对象来为d1赋值
	return 0;
}

static关键字的作用:
在这里插入图片描述

4,友元

(一)友元函数:

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

b>>特征:
1,友元函数可以访问类的私有成员,但不是类的成员函数
2,友元函数不能用const修饰(因为const修饰成员函数实际是修饰的this指针,而友元函数没有this指针)
3,友元函数可以在类定义的任何地方声明,不受类访问限定符的限制
4,一个函数可以是多个类的友元函数
5,友元函数的调用原理和普通函数的调用原理是一样的

c>>优缺点:
优点:能够提高效率,表达简单清晰。
缺点:破坏了封装机制,尽量不使用成员函数

d>>使用场景:
1,运算符重载的某些场合需要用到友元函数(输入输出运算符)
2,两个类需要共享数据的时候

(二)友元类:

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

b>>特点
1,友元关系是单向的,不具有交换性
2,友元关系不能传递

5,内部类

(1)概念:如果一个类定义在另一个类的内部,这个类就叫做内部类,此时的内部类是一个独立的类,它不属于外部类,更不能通过外部类去调用内部类,外部类对内部类没有任何优越的访问权限

内部类就是外部类的友元类,即内部类可以通过外部类的对象参数来访问外部类中的所有成员。

(2)特性:
1,内部类可以定义在外部类的public,private或protected都是可以的
2,内部类可以直接访问外部类中的static,枚举成员,不需要 外部类的对象或类名
3,sizeof(外部类)=外部类,和内部类没有任何关系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值