【C++的探索路8】运算符重载为友元以及可变长数组程序编写

本篇文章内容首先对运算符重载为友元进行介绍,由于运算符重载这部分内容相对比较多,在结束友元的介绍后,通过一个综合性的小练习对前面的重载部分内容进行一个总结回顾。

运算符重载为友元

由前面关于友元的内容,可知:友元的目的在于实现更便捷的编程,但是会一定程度的破除C++的封装性质。

英雄不问出路,运算符重载同样可以利用友元,实现较为便捷的编程而至于比较严重的破坏程序的封装性。

这里由一个Complex类的实现例子,对运算符重载为友元所需要做的工作进行讲解:

class Complex{
	int real, imag;
public:
	Complex():real(0),imag(0){}
	Complex&operator+(int n) {
		real = real + n;
		return*this;
	}
	friend Complex&operator+(int n, Complex c);
	const void printComplex() const {
		cout << real << "+" << imag << "i" << endl;
	}
};
Complex&operator+(int n,const Complex&c) {
	c.real = c.real + n;
	return c;
	/*
	return Complex(c.real + n, c.imag);
	*/
}
int main() {
	Complex c1;
	c1 + 5;
	c1.printComplex();
	5 + c1;
	c1.printComplex();
	return 0;
}

整个代码片段包括一个Complex的类以及对应的主函数。

Complex作为复数类,需要定义实部real和虚部imag,出于良好的编程习惯,需要将这两个变量进行私有化。

主函数方面,主要有两种运算情况需要注意:一种是Complex的对象c1+5,另外一种是5+Complex的对象c1。

我们知道,c1要直接和5进行操作时,由于类型不匹配,一定需要编写+重载函数。

如果定义为单参的运算符重载函数(重载为成员函数),则运算符重载在程序运算过程中执行的是c.operator+(5)的后置操作,也就是c+5;却对5+c这个操作无能为力。

我们只能再编写一个重载为全局函数的+运算符重载函数:Complex&operator+(int n,const Complex&c),对这一功能进行实现。【注意:参数表中在Complex c的前后分别加上const和&的原因已经在前面讲过,分别用于解决怕外界更改以及避免构造函数调用引起内存花销的问题】。

当运算符+重载为全局函数时,由于受到程序封装性的限制,参数表位置将导致出现无法访问内部成员的错误。但放开private成员又不是咱的风格;怎么办?让他们变成好基友:

在Complex类的内部把Complex&operator+(int n,const Complex&c )变成Complex的好朋友,则所有问题迎刃而解。

至此,总结一下,运算符重载为友元的目的和普通的友元一样,部分放开程序的封装性,实现更友好的编程。


练习:可变长数组

这一个编程练习的目的在于温习前面的内容,具备一定的难度。

现在贴出代码要求:

1,可以在初始化对象时指定数组
2,可以往数组内部添加元素
3,不用担心动态内存分配、释放的问题
4,能够像使用数组一样使用动态数组类对象:通过下标访问

主程序代码如下:

int main()
{
	CArray a;//开始的数组是空的
	for (int i = 0; i < 5; ++i)
		a.push_back(i);
	CArray a2, a3;
	a2 = a;
	for (int i = 0; i < a.length(); ++i)
		cout << a2[i] << " ";
	a2 = a3;//a2是空的
	for (int i = 0; i < a2.length(); ++i)//a2.length()返回0
		cout << a2[i] << " ";
	cout << endl;
	a[3] = 100;
	CArray a4(a);
	for (int i = 0; i < a4.length(); ++i)
		cout << a4[i] << " ";
    return 0;
}

程序编写探索:

主程序第3行为CArray的对象a,因此第一步:编写无参构造函数CArray();

第四、五行通过for循环,调用push_back()成员函数,将0~5的值输入给动态数组。

因此首先需要定义push_back()成员函数,第二,参数表的数据类型为int,返回值可以为void(因为这部分仅需要实现一个简单的插入)。

第六行与第三行一样,是那么的波澜不惊,因此:暂时搁置争议。

第七行:将a的值赋值给a2,必然需要定义赋值运算符重载函数,CArray& operator=(const CArray&a)。关于返回值类型,前面一章已经有详细陈述,不再解释。

第11,12行,需要输出a2[i]的内容,但这是一个对象,通常怎么会存在这种操作?我们就需要定义一个int &operator[](int i)对这个问题进行解决。

此外,第11行还出现了length()成员函数,返回的是动态数组的长度,因此,需要定义成员变量len与成员函数length()相互配合,输出动态数组的内部内容。

程序第15行为CArray a4(a),这些内容显然不是先天性的,需要定义个构造函数CArray(CArray&a),防止报错。

最后,需要扫尾工作:定义析构函数~CArray()。

从需要编写的目的的角度来看,大概需要实现的就是下面的这些内容:

class CArray {
	int *p;
	int len;
public:
	CArray() :p(NULL),len(0){}
	CArray(CArray&a);
	void push_back(int i);
	int length();
	CArray&operator=(const CArray&a);
	int&operator[](int i);
	~CArray();
};

完成基本构架后,依据主程序,对程序进行逐步填充。

补充tips:

1,memcpy与strcpy

memcpy与strcpy都是C标准库中的复制语句,所不同的是strcpy只能在字符串的复制当中运用;而memcpy则适用更广,没有什么类型的限制。因此本段选用memcpy进行复制任务。除了类型上的区别,strcpy还会额外的复制结束符;memcpy则不会。

2,strcpy_s与memcpy_s

这两个都是基于安全性考虑的函数,使使用者更清晰的了解到目前我要用多少内存进行copy,具体形参可以参考相应函数声明。



第一部分:CArray的无参构造函数,需要使得内部长度为0,内容为NULL。内容已经在上述给出。


第二部分:push_back(i)成员函数;该函数的目的在于每来一个数,都把他塞到对象中去,塞进去则数组长度+1;基于这个思路,可编写程序如下:

void CArray::push_back(int i) {
	int *tmp = ptr;
	++len;
	ptr = new int[len];
	memcpy(ptr, tmp, sizeof(int)*(len - 1));
	*(ptr+len-1) = i;
}

注意,有一种错误的写法如下:

void CArray::push_back(int i) {
	++len;
	p = new int[len];
	*(p + len-1) = i;
}

为什么需要像上面那么写呢?因为每一次new都是重新分配一次内存空间,如果我们像下面这么写,则在ptr[0]~ptr[len-1]范围内都是赋了一个未知的值。


第三部分:赋值运算符重载函数,与前一讲中的CString类的赋值运算符重载编写类似:

首先判断a.p与p相等不相等,相等则直接return*this;若不相等则按照a.p存不存在进行判断:存在则清位置,不存在则设置为NULL。

存在则new出一块空间,将长度以及内容copy过去,不存在则直接NULL和0

CArray&CArray::operator=(const CArray&a) {
	if (a.ptr == ptr)
		return*this;
	if (ptr) 
		delete[]ptr;

	if (a.ptr) {
		ptr = new int[a.len];
		memcpy(ptr, a.ptr, sizeof(int)*a.len);
		len = a.len;
	}
	else
	{
		len = 0;
		ptr = NULL;
	}
	return *this;
}
第四部分:[]运算符重载的编写

int&CArray::operator[](int i){
	return *(ptr+i);
}

[]为单参重载,依样画葫芦可以得到相应的函数内部结构。

第五部分:构造函数的编写

CArray::CArray(const CArray&a) {
	if (a.ptr) {
		ptr = new int[a.len];
		memcpy(ptr, a.ptr, sizeof(int)*a.len);
		len = a.len;
	}
	else
	{
		len = 0;
		ptr = NULL;
	}
	return;
}


综上所述,可整理得到完整程序如下:

#include<iostream>
using namespace std;
class CArray {
	int *ptr;
	int len;
public:
	CArray() :ptr(NULL), len(0) {
	};
	void push_back(int i) ;
	int length() {
		return len;
	}
	CArray&operator=(const CArray&a);
	int&operator[](int i);
	CArray(const CArray&);
	~CArray() {
		len = 0;
		if(ptr)delete[]ptr;
	}
};

void CArray::push_back(int i) {
	int *tmp = ptr;
	++len;
	ptr = new int[len];
	memcpy(ptr, tmp, sizeof(int)*(len - 1));
	*(ptr+len-1) = i;
}

CArray&CArray::operator=(const CArray&a) {
	if (a.ptr == ptr)
		return*this;
	if (ptr) 
		delete[]ptr;

	if (a.ptr) {
		ptr = new int[a.len];
		memcpy(ptr, a.ptr, sizeof(int)*a.len);
		len = a.len;
	}
	else
	{
		len = 0;
		ptr = NULL;
	}
	return *this;
}
int&CArray::operator[](int i){
	return *(ptr+i);
}

CArray::CArray(const CArray&a) {
	if (a.ptr) {
		ptr = new int[a.len];
		memcpy(ptr, a.ptr, sizeof(int)*a.len);
		len = a.len;
	}
	else
	{
		len = 0;
		ptr = NULL;
	}
	return;
}
int main()
{
	CArray a;//开始的数组是空的
	for (int i = 0; i < 5; ++i)
		a.push_back(i);

	CArray a2, a3;

	a2 = a;
	for (int i = 0; i < a.length(); ++i)
		cout << a2[i] << " ";
	a2 = a3;//a2是空的
	for (int i = 0; i < a2.length(); ++i)//a2.length()返回0
		cout << a2[i] << " ";
	cout << endl;
	a[3] = 100;//这一部分还存在疑问
	CArray a4(a);
	for (int i = 0; i < a4.length(); ++i)
		cout << a4[i] << " ";
	return 0;
}

memcpy_s与我程序的救赎

由于当时检查不及时以及时间关系,在简单的将memcpy换成memcpy_s后出现了严重的内存问题;便以为是编译器欺负我。直到有人指点:你这写的是什么玩意,幡然醒悟

我们来看看memcpy_s的官方定义:

来源:memcpy_s、wmemcpy_s

errno_t memcpy_s(
  void*dest,
  size_t destSize,
  const void*src,
  size_t count
);

memcpy_s相对memcpy多了一个参数:destSize,使得程序员在使用这条语句的时候会检查内存使用情况。一般可以通过sizeof的形式进行解决


言归正传,怎么用?上面的程序有什么问题?

问题:内存

每一次我把上面的memcpy改成memcpy_s后,程序总会很执着的崩溃,然后我就更执着的坚信编译器在欺负我一个菜鸟。

但是检查以后,我发现,我程序的编写问题主要出在将长度的定义放在内存拷贝之后,而不是之前。问题就出在这里:

因为程序new了以后,这一块空间是空的,对应的长度也自然为0。也就是我们直接的将东西往不存在空间的地方写,不崩你的程序崩谁的程序?


怎么改?

改了以后,如下面

#include<cstring>
#include<iostream>
using namespace std;
class CArray {
	int size;
	int*ptr;
public:
	CArray(int s = 0);//为什么不用指定str?
	CArray(const CArray&a);
	~CArray();
	void push_back(int v);
	CArray&operator=(const CArray&a);
	int length() {
		return size;
	}
	int&operator[](int i) {
		return ptr[i];
	}
};
CArray::CArray(int s) :size(s) {
	if (s == 0)
		ptr = NULL;
	else
		ptr = new int[s];//貌似没说len!
}
CArray::CArray(const CArray&a) {
	if (!a.ptr) {
		ptr = NULL;
		size = 0;
		return;
	}
	ptr = new int[a.size];
	size = a.size;
	memcpy_s(ptr, (size) * sizeof(int), a.ptr, sizeof(int)*a.size);
	return;
}
CArray::~CArray() {
	if (ptr)
		delete[]ptr;
}

CArray&CArray::operator=(const CArray&a) {
	if (ptr == a.ptr)
		return*this;
	if (a.ptr == NULL)
	{
		if (ptr)
			delete[]ptr;
		ptr = NULL;
		size = 0;
		return*this;
	}
	if (size < a.size) {
		if (ptr)
			delete[]ptr;
		ptr = new int[a.size];
	}
	size = a.size;
	memcpy_s(ptr,  size*sizeof(int),a.ptr, sizeof(int)*a.size);
	return*this;
}

void CArray::push_back(int v) {
	if (ptr) {
		int *tmpPtr = new int[size + 1];
		memcpy_s(tmpPtr, size * sizeof(int), ptr, sizeof(int)*size);
		delete[]ptr;
		ptr = tmpPtr;
	}
	else
		ptr = new int[1];
	ptr[size++] = v;
}

养成好习惯,注意安全性,少用去除警报的操作

下课







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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值