C++类和对象


一.类的定义

1.类定义格式:

(1)class为定义类的关键字,Date为类的名字,{}中为类的主体,类定义结束时后面分号不能省略。 类体中内容称为类的成员:变量称为类的属性成员变量;函数称为类的方法成员函数

class Date
{
private:
	// 为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如_ 或者 m开头
	int _year; // year_ ,m_year
	int _month;
	int _day;
};

(2)为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如_ 或者 m开头。C++中这个并不是强制的,只是⼀些惯例。

(3)C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化时struct中可以定义函数,但是一般还是推荐使用class。

typedef struct ListNodeC
{
	struct ListNodeC* next;
	int val;
}LTNode;

struct ListNodeCPP
{
	void Init(int x)
	{
		next = nullptr;
		val = x;
	} 
	ListNodeCPP* next;
	int val;
};

(4)定义在类里面的成员函数默认为inline

2.访问限定符:

(1)C++一种实现封装的方式,用类将对象的属性与方法结合在一起,使对象更完善,通过访问权限选择性的将接口提供给外部的用户使用。

(2)public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是一样的,后面的继承才能体现区别。

(3)访问权限作用域从该访问限定符出现的位置开始知道下一个访问限定符出现为止,如果后面没有访问限定符,作用域到 ‘ } ’ 即类结束。

(4)class定义成员没有被访问限定符修饰时默认private,struct默认为public。

(5)⼀般成员变量都会被限制为private/protected,需要给别⼈使用的成员函数会放为public。

在这里插入图片描述

3.类域:

(1)类定义了一个新的作用域,类的所有成员都在类的作用域中,在类外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。

(2)类域影响的是编译的查找规则,下面程序中 Init 如果不指定类域Stack,编译器就会把 Init 当成全局函数,编译时找不到 array 等成员的声明 / 定义,就会报错。指定类域Stack,就是知道 Init 是成员函数当前域找不到array等成员,就会到类域中去找。

using namespace std;
class Stack
{
public :
	// 成员函数
	void Init(int n = 4);
	// 成员变量
	int* array;
	size_t capacity;
	size_t top;
};
// 声明和定义分离,需要指定类域
void Stack::Init(int n)
{
	array = (int*)malloc(sizeof(int) * n);
	if (nullptr == array)
	{
		perror("malloc申请空间失败");
		return;
	} 
	capacity = n;
	top = 0;
}

二.实例化

1.实例化概念:

(1)用类类型在物理内存中创建对象的过程,称为类实例化出对象

(2)类是对象的一种抽象描述,是一个模型,这个模型限定了类有哪些成员变量,这些成员变量只有声明没有分配空间,只有用类实例化出对象时,才会分配空间

(3)一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。如同是房屋的设计图,设计图中规划了房屋的数量、大小、功能,但并没有实体的建筑,不占空间对象如同通过设计图建造出来的房屋,才占据空间,这些房屋同时具有设计图中设计的数量、大小、功能。

在这里插入图片描述

#include<iostream>
using namespace std;
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	// 这⾥只是声明,没有开空间
	int _year;
	int _month;
	int _day;
};
int main()
{
	// Date类实例化出对象d1和d2
	Date d1;
	Date d2;
	d1.Init(2005, 6, 20);
	d1.Print();
	d2.Init(2004, 9, 26);
	d2.Print();
	return 0;
}

在这里插入图片描述

2.对象大小:

类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量

那么对象是否包含成员函数呢?

首先函数被编译后是一段指令,对象中无法存储,这些指令单独存储在一个单独的区域(代码段),因此对象中非要存储函数的话,只能是成员函数的指针

对象是否有存储指针的必要呢?现在假设我们有一个Date类,Date实例化d1和d2两个对象,d1和d2都有各自独立成员变量存储各自的数据,但是因为是同一个类,其成员函数指针是相同的,因此d1和d2的成员函数指针是一样的,这样存储在对象中就会很浪费(假如我们使用同一个类实例化100个对象,成员函数指针就重复存储100次)。

因此函数指针不需要存储,下面两种存储方式我们使用方式二

在这里插入图片描述

对象中只存储成员变量,C++规定类实例化的对象也要符合内存对齐的规则。

内存对齐规则
https://blog.csdn.net/W_XiangYu/article/details/137369376?spm=1001.2014.3001.5501
大部分内容和上链接文章中结构体内存对齐规则相同

特例:如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

#include<iostream>
using namespace std;
// 计算⼀下A/B/C实例化的对象是多⼤?
class A
{
public :
	void Print()
	{
		cout << _ch << endl;
	}
private:
	char _ch;
	int _i;
};
class B
{
public :
	void Print()
	{
		//...
	}
};
class C
{};

int main()
{
	A a;
	B b;
	C c;
	cout << sizeof(a) << endl;
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
	return 0;
}

在这里插入图片描述

上面程序运行后,B和C类对象并没有成员函数,但是大小为1,这是为什么呢?因为如果一个字节都不给,怎么表示这个对象存在过呢?因此这里要给1字节,进行占位表示对象存在过。

三.this指针

(1)Date类中有 Init 和 Print 两个成员函数,函数体中没有关于不同函数对象的区分,当d1调用 Init 和 Print 函数时,为了知道应该访问d1对象还是d2对象,C++给了一个隐含的this指针

(2)编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this指针。
例如:Date类的 Init 函数的原型为:

void Init(Date* const this, int year,int month, int day)

(3)类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值。

this->_year = year;

(4)C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。
在这里插入图片描述
在这里插入图片描述

#include<iostream>
using namespace std;
class Date
{
public:
	// void Init(Date* const this, int year, int month, int day)
	void Init(int year, int month, int day)
	{
		// this->_year = year;
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	// Date类实例化出对象d1和d2
	Date d1;
	Date d2;
	// d1.Init(&d1, 2024, 3, 31);
	d1.Init(2005, 6, 20);
	d1.Print();
	d2.Init(2004, 9, 26);
	d2.Print();
	return 0;
}

在这里插入图片描述

四.类的默认成员函数

默认成员函数就是用户没有显示实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成6个默认成员函数。

前四个比较重要
在这里插入图片描述

五.构造函数

构造函数是特殊的成员函数,构造函数虽然名叫构造,但构造函数的主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Data类中的 Init 函数,构造函数自动调用的特点就完美的替代了 Init。

构造函数的特点:

(1)函数名与类名相同。

(2)无返回值。(C++规定返回值什么都不用给)

(3)对象实例化时系统会自动调用对应的构造函数。

(4)构造函数可以重载。

(5)如果类中没有显示定义构造函数,C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生成。

(6)无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。三个函数有且只有一个存在,不能同时存在。
在这里插入图片描述

(7)编译器默认生成的构造,对内置类型成员变量的初始化没有要求,因此是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化,如果自定义类型成员变量没有默认构造函数,就会报错,初始化这个成员变量,需要用初始化列表

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的原生数据类型,如:int/char/double/指针等,自定义类型就是我们使用class/struct等关键字自己定义的类型。

// 如果留下三个构造中的第⼆个带参构造,第⼀个和第三个注释掉
class Date
{
public :
	// 1.⽆参构造函数
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	} 
	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	} 
	// 3.全缺省构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

两个Stack实现队列:

typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};
// 两个Stack实现队列
class MyQueue
{
public:
	//编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化
private:
	Stack pushst;
	Stack popst;
};

初始化列表:

(1)上面实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有另一种方式,就是初始化列表,初始化列表的使用方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后面跟⼀个放在括号中的初始值或表达式。

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

(2)每个成员变量在初始化列表只能出现一次,初始化列表可以认为是每个成员变量定义初始化的地方。

(3)引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。

错误写法:

class Time
{
public:
	Time(int hour)
		: _hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1, int& x)
		:_year(year)
		, _month(month)
		, _day(day)
	{} 
	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t; // 没有默认构造
	int& _ref; // 引⽤
	const int _n; // const
};

在这里插入图片描述
正确写法:

class Time
{
public:
	Time(int hour)
		: _hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1, int& x)
		:_year(year)
		, _month(month)
		, _day(day)
		, _t(12) // 没有默认构造
		, _ref(x) // 引⽤
		, _n(1) // const
	{} 
	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t; // 没有默认构造
	int& _ref; // 引⽤
	const int _n; // const
};

(4)初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议:声明顺序和初始化列表顺序尽量保持一致

还有一种写法,在声明的时候给缺省值:

class Date
{
public:
	Date()
		: _month(2)
	{
		cout << "Date()" << endl;
	}
	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	// 注意这⾥不是初始化,这⾥给的是缺省值,这个缺省值是给初始化列表的
	// 如果初始化列表没有显⽰初始化,默认就会⽤这个缺省值初始化
	int _year = 1;
	int _month = 1;
	int _day;
	const int _n = 1;
	int* _ptr = (int*)malloc(12);
};

六.析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作析构函数的功能类比之前Stack中的Destroy函数,Date因为没有资源需要释放所以没有Destroy,因此Data不需要析构函数。

析构函数的特点:

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

(2)无参数无返回值。(和构造类似)

(3)一个类只能有一个析构函数。若未显示定义,编译器会自动生成默认的析构函数

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

(5)我们不写编译器自动生成的析构函数对内置类型成员不做处理自定义类型成员会调用他自己的析构函数

(6)我们显示写析构函数,对于自定义类型成员也会调用他自己的析构函数,也就是说自定义类型成员无论什么情况,都会自动调用自己的析构函数

(7)如果类中没有申请资源时(如Date),析构函数可以不写,直接使用编译器生成的默认析构函数;如果默认生成的析构函数就够用(如MyQueue),也不需要显示写析构;但有资源申请的时候(如Stack),一定要自己写析构,否则会造成资源泄漏。

Date:

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

Stack:

typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};

Queue:

class MyQueue
{
public :
	// 编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构
	// 释放的Stack内部的资源
private:
	Stack pushst;
	Stack popst;
};

(8)一个局部域的多个对象,后定义的先析构。(C++规定)

七.拷贝构造函数

如果一个构造函数的第一个参数时自身类类型的引用,且任何额外的参数都有默认值(第一个引用外的参数),则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数

拷贝构造的特点:

(1)拷贝构造函数是构造函数的一个重载。

(2)拷贝构造函数的第一个参数必须是类类型的引用,使用传值传参编译器会报错,语法逻辑上会引发无穷递归调用(如下图)。

在这里插入图片描述

(3)C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,因此自定义类型传值传参和传值返回都会调用拷贝构造。

(4)若未显示定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(单字节拷贝),对自定义类型成员变量会调用他的拷贝构造

(5)类成员变量全是内置类型没有指向什么资源(如Date),编译器自动生成的拷贝构造就可以完成拷贝,不需要显示实现拷贝构造;都是内置类型,但是有成员变量指向了资源(如Stack),编译器自动生成的拷贝构造完成的浅拷贝不符合需求,需要显示实现拷贝构造(实现深拷贝: 对指向的资源也进行拷贝);类型内部主要是自定义类型,编译器自动生成的拷贝构造调用自定义类型成员变量的拷贝构造(如MyQueue)。如果一个类显示实现了析构函数并且释放资源,那么他就需要显示写拷贝构造,否则就不需要。

在这里插入图片描述
上面程序中Stack没有显示写拷贝构造,程序崩溃,原因是进行浅拷贝,拷贝后的str2中的 _a和str1中的_a指向同一块空间,在str1,str2析构时,这块空间被析构了两次,所以程序会崩溃。

(6)传值返回会产生一个临时对象调用拷贝构造,而传引用返回,返回的是返回对象的别名(引用),没有产生拷贝。但是如果返回对象当前函数局部域中的局部对象,函数结束就销毁了,这种情况下传引用返回会出现问题,这时的引用类似野指针。因此虽然传引用返回可以减少拷贝,但是在使用时一定要确保,返回对象在当前函数结束后还存在,才能使用引用返回。

在这里插入图片描述

八.赋值运算符重载

1.运算符重载:

(1)当运算符被用于类类型的对象时,C++允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。

(2)运算符重载时具有特殊名字的函数,它的名字时由operator和后面要定义的运算符共同构造。和其他函数一样,它也具有其返回类型和参数列表以及函数体。

(3)重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。。一元运算符有一个参数,二元运算符有俩个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。

(4)如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。

(5)运算符重载后,优先级和结合性与对应的内置类型运算符保持一致。

(6)不能过连接语法中没有的符号来创建新的操作符(如 operator@)。

(7).* :: sizeof ?: . 以上5个运算符不能重载

(8)重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义。如:int operator+(int x, int y)
在这里插入图片描述

(9)一个类需要重载哪些运算符,是看哪些运算符重载后有意义,Date类重载**operator-有意义,而operator+**没有意义。

(10)重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++。C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。

(11)重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置第⼀个形参位置左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把 ostream/istream 放到第⼀个形参位置就可以了,第二个形参位置当类类型对象。

重载为全局的函数,面临对象访问私有成员变量的问题。
解决方法:

成员放公有

友元函数(在后面)

重载为成员函数

class Date
{
public :
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//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;
}
int main()
{
	Date d1(2024, 7, 5);
	Date d2(2024, 7, 6);
	// 运算符重载函数可以显示调用
	operator==(d1, d2);
	// 编译器会转换成 operator==(d1, d2);
	d1 == d2;
	return 0;
}

2.赋值运算符重载:

赋值运算符重载是⼀个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于⼀个对象拷贝初始化给另⼀个要创建的对象。

赋值运算符重载的特点:

(1)赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。参数建议写成 const 当前类类型引用,否则会传值传参会有拷贝。

(2)有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值的场景。

(3)没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),

(4)类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,不需要我们显示实现赋值运算符重载;全是内置类型,但有指向资源,需要我们自己实现深拷贝(对指向的资源也进行拷贝);主要是自定义类型成员,不需要我们显示实现赋值运算符重载。如果⼀个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		cout << " Date(const Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	// 传引⽤返回减少拷贝
	// d1 = d2;
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		// d1 = d2表达式的返回对象应该为d1,也就是 *this
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 7, 5);
	Date d2(d1); // 拷贝构造
	Date d3(2024, 7, 6);

	//赋值重载
	d1 = d3;
	//拷贝构造
	Date d4 = d1;

	return 0;
}

九.取地址运算符重载

1.const成员函数

(1)将const修饰的成员函数称之为const成员函数,const修饰成员函数,将const放到成员函数参数列表的后面。

void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}

(2)const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。const 修饰Date类的Print成员函数,Print隐含的this指针由Date * const this 变为const Date* const this

int main()
{
	// 这⾥⾮const对象也可以调用const成员函数是⼀种权限的缩小
	Date d1(2024, 7, 5);
	d1.Print();
	const Date d2(2024, 8, 5);
	d2.Print();
	return 0;
}
}

2.取地址运算符重载

取地址运算符重载分为普通取地址运算符重载const取地址运算符重载⼀般这两个函数编译器自动的就可以够我们用了,不需要去显示实现

当我们不想让别人取到当前类对象的地址,就可以自己实现一份,随便返回一个地址。

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

十.类型转换

(1)C++支持内置类型的隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数

(2)构造函数前加explicit就不再支持隐式类型转换。

在这里插入图片描述

十一.static成员

(1)用static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进行初始化

(2)静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区

(3)用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针

(4)静态成员函数中可以访问其他的静态成员,但是不能访问非静态成员,因为没有this指针

(5)非静态的成员函数,可以访问任意的静态成员变量和静态成员函数

(6)突破类域就可以访问静态成员,可以通过 类名::静态成员 / 对象.静态成员 来访问静态成员变量和静态成员函数。

(7)静态成员也是类的成员,受public、protected、private 访问限定符的限制
在这里插入图片描述

(8)静态成员变量不能在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表

在这里插入图片描述

十二.友元函数

(1)友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前⾯加friend,并且把友元声明放到一个类的里面

(2)外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,它不是类的成员函数

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

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

(5)友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有和保护成员

(6)友元类的关系是单向的不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。

(7)友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是B的友元。

(8)友元提供了便利,但是会增加耦合度,破坏封装,所以友元不宜多用

在这里插入图片描述

十三.内部类

(1)如果一个类定义在另一个类的内部,这个类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,它只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类

(2)内部类默认是外部类的友元类

(3)内部类本质也是⼀种封装,当A类跟B类紧密关联A类实现出来主要就是给B类使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类其他地方无法使用

在这里插入图片描述

十四.匿名对象

(1)用 类型(实参) 定义出来的对象叫做匿名对象,之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象

(2)匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。

在这里插入图片描述

十五.对象拷贝时的编译器优化

(1)现代编译器会为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少一些传参和传参过程中可以省略的拷贝

(2)如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新一点的编译器对于连续一个表达式步骤中的连续拷贝会进行合并优化

  • 13
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

W_XiangYu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值