string类的定义
string类定义
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr = 0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
String::function(...) ...
Global-function(...) ...
#endif
Big Three, 三個特殊函數
所谓的Big Three 指的是拷贝构造函数,复制构造函数,析构函数。
先介绍构造函数和析构函数:
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';
}
}
inline
String::~String()
{
delete[] m_data;
}
调用时:
{
String s1(),
String s2("hello");
String* p = new String("hello");
delete p;
}
类一般会提供默认的拷贝构造函数和复制构造函数,但是对于成员变量中含有指针的类必须重新定义拷贝构造函数和复制构造函数。
如果使用类默认的构造函数:
String a("Hello");
String b("World");
b = a;
在这种情况下将执行浅拷贝,所谓的浅拷贝指的就是简单的对应赋值,
这个时候将a赋值给b由于执行的是浅拷贝,所以实际上a,b指向的将是同一个区域,这个时候不仅导致b原来的m_data的内存泄漏,而且一旦在a中修改m_data,b中的m_data将随之修改,产生连锁反应。
因此,对于成员变量含有指针的类不能使用浅拷贝,而应该采取深拷贝:
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
复制构造函数:
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
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);
s2 = s1;
}
结果是:
注意,这种赋值的自我检测的意义不仅仅在于提高效率,更在于防止错误的发生,如果,没有自我赋值检测,this ths将指向同一个内存区域,此时往下执行delete之后将会产生不知道ths指向的是什么:
output 函數
#include <iostream.h>
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
//调用时
{
String s1("hello ");
cout << s1;
}
调用get_c_str()将返回一个char* 类型,cout支持此类型输出,将输出一个字符串。
栈和堆
栈(stack)存是存在某个作用域的一块内存空间,例如调用函数时,函数本身就会形成一个栈,用来存放接收的参数,返回地址,以及临时变量。在函数本体内声明的任何变量其所使用的内存空间都在栈上。
堆(heap)就是system heap,是指由操作系统提供的一块global内存空间,程序可能动态分配若干个区块。
class Complex { … };
...
{
Complex c1(1,2);
Complex* p = new Complex(3);
}
c1所占用的空间来自stack,complex(3)是个临时对象,其所占用的空间是以new自heap分配而得,并由p指向。
c1是所谓的stack object,其生命在作用域结束之后就结束。这个作用域内的object也被称为是auto object,因为他们会被自动清理。
class Complex { … };
...
{
static Complex c2(1,2);
}
c2是一个static object,其生命在作用域之后仍然存在,直到整个程序结束。
class Complex { … };
...
Complex c3(1,2);
int main()
{
...
}
class Complex { … };
...
Complex c3(1,2);
int main()
{
...
}
c3是所谓的global object,其生命在整个程序结束之后才结束。
class Complex { … };
...
{
Complex* p = new Complex;
...
delete p;
}
p是所谓的heap object,其生命在被delete之后才结束,如果这样使用:
class Complex { … };
...
{
Complex* p = new Complex;
...
}
将会出现内存泄漏,因为当作用域结束,p所指向的heap object仍然存在,但p的生命周期已经结束了,作用域之外再也看不到p,也就没有机会delete p。
先分配内存,再调用构造函数:
Complex* Complex* pc pc == new new Complex(1,2);
编译器将上面的这行代码转换成三个步骤:
void* mem = operator new( sizeof(Complex) ); //分配内存
pc = static_cast<Complex*>(mem); //转型
pc->Complex::Complex(1,2); //构造函数
new的内部实际上调用了malloc()。
示意图如下:
String* String* ps ps == new new String("Hello String("Hello");
编译器将其转换为:
void* mem = operator new( sizeof(String) ); //分配內存
ps = static_cast<String*>(mem); //转型
ps->String::String("Hello"); //构造函数
先调用析构函数,再释放内存:
Complex* Complex* pc pc == new new Complex(1,2);
。。。
delete pc;
编译器将delete pc的过转换成:
Complex::~Complex(pc); // 析构函数
operator delete(pc); // 释放内存
delete内部调用了free()。
String* ps = new String("Hello");
...
delete ps;
编译器将delete操作转换为:
String::~String(ps); // 析构函数
operator delete(ps); // 释放内存
VC中动态分配所得的内存块
在调试状态下,对于complex,本身占用8byte,如图中绿色部分所示,图中灰色部分为调试相关信息,固定的(32+4)byte红色部分为为cookie,它的作用是指示了这块内存的大小以及分配类型,头尾各有一个,共计8tyte,如此计算分配一个double应该占用8+32+4+8=52byte,但是要求在内存分配的过程中大小必须是16的倍数,于是增加了12byte的pad,所以应该分配64byte,cookie中的0x41,即表示分配了4*16byte,最后个bit用于表示分配形式当为1表示系统分配出去,当为0表示系统回收。
在非调试状态下,对于complex,只占16byte。
同理可得,调试状态下string分配48byte,非调试状态下分配16byte。
动态分配的array
在分配数组的上面会加上4byte用于表示分配的arrray元素个数:
array new 一定要搭配 array delete
在delete时加上[]会使得在delete的过程中读取需要进行析构的次数,正常情况下会进行3次析构,再删除p指针。
但是如果不加[],默认只有一个对象,只会调用一次析构函数,然后删除p指针,此时就会造成内存泄漏。
虽然对于某些情况下,如:
Complex * pc = new Complex[3];
delete pc;
这种成员中不包含指针的变量的类可以不用delete array的形式,但是为了保持一致和防止错误,还是要求array new 和 array delete一起使用。
string类的完整定义
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
转载于:https://www.cnblogs.com/xiaojianliu/articles/9495840.html