C++友元+内存管理

Posted 山舟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++友元+内存管理相关的知识,希望对你有一定的参考价值。


一、友元

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


1.友元函数

如果想要用cout直接输出日期而不是用成员函数来间接输出,就需要重载>>来实现。
注意cout的类型是ostream,调用的时候传入cout。

代码如下:

class Date
{
public:
	void operator<<(ostream& out)//void operator<<(Date* this, ostream& out)
	{
		out << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

但是由于重载时第一个参数一定是隐含的this指针,所以在使用重载后的<<时需要如下使用。

代码如下:

int main()
{
	Date d1;
	d1 << cout;//也即d1.operator<<(cout);
	return 0;
}

这显然不符合cout的使用习惯,同时代码的可读性很差,甚至不如一个间接的print函数。

于是有了一个问题:<<的重载写在类内会导致代码可读性下降,写在类外又无法访问类的成员变量。

那么怎样解决这个问题呢——用友元。

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

代码如下:

class Date
{
	//定义友元函数(在类内)
	//只需在函数声明前加上friend即可
	friend void operator<<(ostream& out, const Date& d);
public:
	Date(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

void operator<<(ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
}

int main()
{
	Date d1;
	cout << d1;//即operator<<(cout, d1);
	return 0;
}

但是由于operator<<返回值是空,所以无法实现连续输出,只需将operator<<的返回值改为ostream&,即继续返回out,这样每次输出后的返回值都是out,可支持连续输出。

代码如下:

class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
public:
	Date(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}

int main()
{
	Date d1;
	Date d2(2021, 7, 21);
	cout << d1 << d2;//可连续输出
	return 0;
}

输入重载>>同理。

代码如下:

class Date
{
public:
	friend istream& operator>>(istream& in, Date& d);
private:
	int _year;
	int _month;
	int _day;
};

//cin类型为istream
//返回值为istream可支持连续输入多个Date对象
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

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


2.友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
如下面的代码中Date类中的成员函数想要访问Time类型的对象_t的成员变量,则需要将Date类定义为Time类的友元。

代码如下:

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

	void SetTime()
	{
		_t._hour = 0;
		_t._minute = 1;
		_t._second = 1;
	}

private:
	int _year;
	int _month;
	int _day;

	Time _t;
};

class Time
{
	friend class Date;
public:
	Time(int hour = 0, int minute = 1, int second = 1)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

注意:
(1)友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time
类的私有成员变量,但不能在Time类中访问Date类中私有的成员变量。
(2)友元关系不能传递
如果B是A的友元,C是B的友元,不能说明C是A的友元。


二、C语言动态内存管理的方式

关于C语言动态内存管理的相关知识请移步C语言动态内存管理


三、 C++内存管理方式

通过new和delete操作符进行动态内存管理。

注意C语言中与动态内存相关的malloc,realloc,calloc和free是函数,而C++中的new和delete是操作符。

1.new/delete操作内置类型

new/delete和malloc/free针对内置类型没有任何差别,只是写法不一样。

代码如下:

void Test()
{
	//注意new和delete的格式

	//动态申请一个int类型的空间,不初始化
	int* ptr1 = new int;

	//动态申请一个double类型的空间并初始化为3.14
	//注意可以直接初始化
	double* ptr2 = new double(3.14);

	//动态申请10个char类型的空间
	char* ptr3 = new char[3];

	delete ptr1;
	delete ptr2;
	delete[] ptr3;//不要忘记[]
}

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]。 new和delete、new[]和delete[]一定要匹配,否则可能出问题。


2.new和delete操作自定义类型

代码如下:

class ListNode
{
public:
	ListNode(int val = 0)
		:_val(val)
		, _prev(nullptr)
		, _next(nullptr)
	{
		cout << "调用构造函数" << endl;
	}

	~ListNode()
	{
		cout << "调用析构函数" << endl;
	}

private:
	int _val;
	ListNode* _prev;
	ListNode* _next;
};

int main()
{
	ListNode* c = (ListNode*)malloc(sizeof(ListNode));
	free(c);
	
	ListNode* cpp = new ListNode;
	delete cpp;
	
	return 0;
}

调试如下,可以观察到malloc仅仅开出所需的空间,并不会对指针指向地址的成员变量进行初始化;而new不仅开出所需的空间,还会会对指针指向地址的成员变量进行初始化。

调试结果如下:

在这里插入图片描述


运行如下,可以观察到delete会调用自定义类型的析构函数,而free不会。

运行如下:

在这里插入图片描述

在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。


四、 malloc/free和new/delete

1.共同点

从堆上申请空间,并且需要手动释放。


2.不同点

(1)malloc和free是函数,new和delete是操作符
(2)malloc申请的空间不会初始化,new可以初始化
(3)malloc申请空间时,需要手动计算空间大小,new只需在其后跟上空间的类型即可
(4)malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
(5)malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
(6)申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理


感谢阅读,如有错误请批评指正

以上是关于C++友元+内存管理的主要内容,如果未能解决你的问题,请参考以下文章

C++友元

C++入门不能重载为友元函数的4个运算符(=, ->, [ ], ( ))

嵌入式c++学习全局 友元

关于C++运算符重载和友元的概念

C++友元

C++ 友元类,友元函数