C++面向对象(三)String类

一、深浅拷贝

String.h

	———————————————————————————————————————————————————————————————————————————————
	|	#ifndef __COMPLEX__		        										  |
	|	#define __COMPLEX__		      											  |
	———————————————————————————————————————————————————————————————————————————————
0	|	#include <cmath>		          			--------------------------	  |
	|												|  forward declarations  |    |
	|	class ostream;             					|	   (前置声明 )	     |	  |
	|	class String;		              		    --------------------------	  |
	|																			  |
	|  		           		  													  |
	———————————————————————————————————————————————————————————————————————————————
1	|	class String{	          					------------------------	  |
	|		...										|  class declarations  |      |
	|	};            								|	   (类-声明 )	   |      |
	|			              		   				------------------------	  |
	———————————————————————————————————————————————————————————————————————————————
2	|	string::function(...) ...	          		----------------------	      |
	|	Global-function(...) ...					|  class definition  |        |
	|	           									|	  (类-定义 )	 |    	  |
	|			              		   				----------------------		  |
	———————————————————————————————————————————————————————————————————————————————
	|	#endif	          					 									  |
	———————————————————————————————————————————————————————————————————————————————

string-test.cpp

int main(){
	String s1();
	String s2("hello");   
	String s3(s1);  // 拷贝构造(copy ctor)
	cout << s3 << endl;
	s3 = s2;  // 拷贝赋值(copy op=)
	cout << s3 << endl;
}

注意:这里要知道 浅拷贝深拷贝 的区别。浅拷贝一般是编译器默认的构造函数,即一个位一个位地拷贝过去,前面的complex例子便是用浅拷贝。 深拷贝 则是一般用在带指针的类上面(需要自己去定义编写的构造函数),因为如果只是用编译默认的函数去拷贝,则只会将相应的指针拷贝过去,进行的是浅拷贝,这样相当于两个类对象指针相同,且指向同一地址,容易出现问题。只要类是带指针的,则一定要自己编写拷贝赋值函数。

二、Big Three(三个特殊函数)

class String{
public:
	String(const char* cstr = 0); // 构造函数应该跟class同名
	String(const String& str); // 拷贝构造
	String& operator=(cons String& str); // 拷贝赋值
	~String(); //析构
	char* ger_c_str() const{
		return m_data;
	}
private:
	char* m_data;
}

- ctor 和 dtor(构造函数和析构函数)

注意:一个字符串有多长,有两种设计方法,一为在字符串后面+结束符号 ’\0’ ;一为在前面加标志长度的数值 ’len’ 。从C以来,一直延续一个概念,字符串是一个指针指进来指着头,指着一串,然后后面再有个 ’\0’ 的结束符号(C++也是)。

inline String::String(const char* cstr = 0){
	if(cstr){
		m_data = new char[strlen(cstr) + 1];
		strcpy(m_data, cstr);
	}
	else{
		m_data = new char[1];
		*m_data = '\0';
	}
}

class里面有指针多半是要做动态分配的(即new),所以在对象死亡之前必须调用下面的析构函数,将动态分配的内存释放掉,否则将会造成内存泄漏。

*point
hello
inline String::~String(){
	delete[] m_data;
}

这因为前面对应new的是数组 ‘[ ]’ ,所以析构函数这边应该对应用 ‘delete[ ]’。其释放的是‘hello’对应的动态内存空间。而下面的例子中,因为new的不是 ‘[ ]’ ,而是动态分配了个指针内存空间,所以直接 ’delete p‘ 即可,无需 ‘delete[ ]’

{
	String s1();
	String s2("hello");
	String* p = new String("hello");
	delete p;
}


class with pointer members 必须有 copy ctor(拷贝构造)copy op=(拷贝赋值)



- copy ctor(拷贝构造函数)

在这里插入图片描述
若需要操作 b=a; 时,当使用默认的copy ctor时,进行的是浅拷贝,即只是将a的point复制到b,a和b指向的都是同一个内存地址,这样当对a进行操作改变时,会让b也受到影响。而且原先b指针指向的内存块内存泄漏。而下面因为自写了拷贝构造函数,所以进行的是深拷贝

inline String:: String(const String& str){
	m_data = new char[strlen(str.m_data) + 1];
	strcpy(m_data, str.m_data);
}
{
	String s1("hello");
	String s2(s1);
	// String s2 = s1;
}

- copy op=(拷贝赋值函数)

inline String& String::operator=(const String& str){
	if(this == &str)
		return *this;   // 检测自我赋值(self assignment)
	delete[] m_data;
	m_data = new char[strlen(str.m_data) + 1];
	strcpy(m_data, str.m_data);
	return *this;
}
{
	String s1("hello");
	String s2(s1);
	String s2 = s1;
}

正常情况下,当两个对象的指针指向不是同一个内存地址时,不用自我检测也许不会出错,如:

在这里插入图片描述
123
在这里插入图片描述
在这里插入图片描述
但当存在自我赋值的情况时,则必须要编写

if(this == &str)
return *this;

否则一上来直接执行 ‘delete[ ]’ 则因为同一个指针地址指向的内存被释放掉了,无法进行后续的拷贝操作。
在这里插入图片描述

三、stack(栈)和heap(堆)

class Complex {
	...
};

{
Complex c1(1,2);
Complex* p = new compelx(3);
}

c1,所占用的空间来自stack,当离开作用域{ }时,便自动释放掉了;
而 Complex(3) 是个临时对象,其所占用空间时以new自heap动态分配而得,并由p来指向。若离开作用域{ }时,此情况下,若不调用‘delete p’,则无法释放掉其动态分配的内存空间,而造成内存泄漏。(若是涉及字符串,即带指针的类的对象时,则一定要’delete[ ]’ ,再‘delete p’)

-Stack

存在于某作用域的一块内存空间。若在函数体内声明的任何变量其所使用的内存块都是存在于stack中的。

  • stack objects的生命周期

{
Complex c1(1,2);
}

c1 称stack object又称auto object,生命再作用域(scope)结束时结束,会自动被清理掉。

  • static local objects的生命周期

{
static Complex c1(1,2);
}

c2 称static object,生命再作用域(scope)结束时仍存在,直到整个程序结束(因为其析构函数在离开作用域时也没被调用,直到程序结束才调用)。

-Heap

操作系统提供的一块global内存空间,动态分配。

  • heap objects的生命周期

{
Complex* p = new Complex;

delete p;
}

p指的是heap object,生命在调用delete时结束。在调用delete时,会先调用complex的默认析构函数,所以释放了new出来的内存空间,不存在内存泄漏。

{
Complex* p = new Complex;
}

没有使用delete,存在内存泄漏。因为当作用域结束时,p所指的那块空间(即heap object)仍然存在,但指针p的生命却结束了,作用域之外再也没机会delete p了

- new:先分配memory,再调用ctor

Complex *pc = new Complex(1,2);

则编译器转化为:

  1. void* men = operator new(sizeof(Complex)); // 分配内存

内部调用malloc(n),在内存中开辟出double,double的空间:
在这里插入图片描述

  1. pc = static_cast<Complex*>(men); // 转型

将第一步的void型指针转为对应的Complex型指针。

  1. pc -> Complex::Complex(1,2); // 调用构造函数

在这里插入图片描述
注:这里的pc指针是不要写出来的,只是为了说明将其写出来而已。
---------------------------------------------------------------- ⇓ \Downarrow -------------------------------------------------
在这里插入图片描述调用complex的构造函数,通过指针p找到对应内存地址,给内存空间double,double赋值。

- delete:先调用dtor,再释放memory

String *ps = new String("Hello");
...
delete ps;

则编译器转化为:

  1. String::~String(ps); // 析构函数

这里是先将指针ps所指向的动态分配的内存空间里的’Hello’先释放掉,即动态分配内存空间里的内容已经被清除了。
在这里插入图片描述

  1. operator delete(ps); // 释放内存

其内部相当于调用 free(ps),即将指针删除掉,对应释放掉的即是ps(字符串相当于一个指针而已)。
在这里插入图片描述

四、动态分配的内存空间

在C中,对于动态分配内存,使用的是malloc和free的指令。

- 动态分配所得的内存块

左图中左图表示的是debuger模式下的,其除了有8byte的complex数据外(8),前面还有8个8byte(32)+后面的1个8byte的灰色信息部分(debugger信息部分),再加上头尾的2个8byte的cookie信息(4*2),总共为52byte,而要凑成16进制数,还需填补上深绿色的部分(4*3),所以52+12=64。另外cookie的作用是,标志内存空间的状态以及所分配的整块空间的大小,因为转成了16的倍数,即为00000040,而后面补1即00000041,表示这是系统给出去的空间,对应系统收回的即为00000040。

右图表示的是非debugger模式下的,则不许加上灰色数据的部分。下面String类对象也是类似情况:
在这里插入图片描述

- 动态分配所得的数组(array)

注意array new 一定要搭配 array delete,否则很容易存在内存泄漏 (针对指针类对象的数组而言) 的情况。

  • 非指针类对象的情况

    在这里插入图片描述这里最后面的4是表示在VC编译器下,虽有3来表示有三个(double, double),类似数组的长度。在该类对象的情况下,array new 不搭配 array delete即(‘delete [ ]’),不会内存泄漏,因为没有涉及到指针即不涉及动态分配的内存空间,在离开作用域时直接结束消亡。

  • 指针类对象的情况
    在这里插入图片描述对比下图左边(array delete)和右边(no array delete)的代码,
    在这里插入图片描述左边的因为正确使用了array delete,调用了三次dtor而将动态分配的内存空间释放掉,然后再free掉指针,不会造成内存泄漏;
    但右边的由于没有进行array delete,则只是调用一次dtor,而只释放了一个动态内存空间,剩余的其他两个动态分配的内存空间则没法释放,然后指针就被free掉了 (若在指针被free掉之前,没有将其中所分配得内存空间释放干净,则会导致内存泄漏)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值