带指针数据成员的类
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 s1("dog"); //直接初始化
String s2=s1; //拷贝初始化
拷贝初始化通常使用拷贝构造函数来完成,拷贝初始化在下列情况会发生:
1、用=定义变量时
2、将一个对象作为实参传递给一个非引用类型的形参
3、从一个返回类型为非引用类型的函数返回一个对象
4、用花括号列表初始化一个数组中的元素或一个聚合类中的成员
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
拷贝构造函数的参数最好是const的引用
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;
}
拷贝赋值函数注意:
检测自我赋值;
delete原来的指针数据成员;
new分配内存;
inline
String::~String()
{
delete[] m_data;
}
析构函数:
若构造函数中使用new,虚构函数中要delete
stack(栈)和heap(堆)
stack,栈内存,由程序自动向操作系统申请分配以及回收,速度快,使用方便,但程序员无法控制。若分配失败,则提示栈溢出错误。
注意,const局部变量也储存在栈区内,栈区向地址减小的方向增长。
heap,堆内存,比如由new分配的内存块,程序员向操作系统申请一块内存,当系统收到程序的申请时,会遍历一个记录空闲内存地址的链表,寻找第一个空间
大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。分配的速度较慢,地址不连
续,容易碎片化。此外,由程序员申请,同时也必须由程序员负责销毁,否则则导致内存泄露。
class Complex { ... };
...
Complex c3(1,2);
{
Complex c1(1,2);
static Complex c2(1,2);
Complex* p= new Complex;
...
delete p;
}
int main()
{
...
}
c1是stack object,其生命在作用域结束后结束。
c2是static object,其生命在作用域结束后仍然存在,直到整个程序结束。
c3是global object,其生命在整个程序结束后才结束。
p是heap object,其生命在它被delete时结束。使用new别忘了delete。