[4]面向对象程序设计(Object oriented programming):

第六节 复习Complex类的实现过程 编程内容 略

第七节 三大函数:拷贝构造、拷贝复制、析构

class String
{
public:
    String(const char* cstr = 0);
    String(const String& str);
    String& operator = (const String&);
    ~String();
    char* get_c_str() const { return m_data;}
private:
    char* m_data;

};

因为字符串的长度并不是确定的,所以我们考虑动态分配整个字符串。将private处构造的数据定义为指针。Public部分的内容是构造函数。

第一个是一个构造函数,传入进来指针并赋值为0;

第二个也是构造函数,比较特殊的是它接受的是自己本身的引用。(所以叫拷贝构造)

第三个是一个操作符重载。特别的在于右手边赋值的是自己的东西,所以叫拷贝赋值。

第四个函数,和class名称相同,前面有~,叫析构函数。当类本身死亡的时候,会被调用。

拷贝构造、拷贝赋值、析构三种函数被称为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;

这个函数的意义是,传入一个字符串。若字符串指针为空,则为其申请一片空间,并且设置最后一个元素为'\0'。而对于非空的指针,就为其申请连续的一片空间。并且将cstr这个字符串传递到m_data里面。

最后的析构函数,将m_data中的所有字符都删去,这样可以回收内存,防止内存泄漏。

因此,如果class有指针,那么一般而言需要在类的最后写析构函数来清理内存。这样可以将侵占的内存释放掉。

离开作用域的时候自然而然析构函数就会被调用起来。

class with pointer members 必须有 copy cotr 和copy op(assignment operator) 拷贝构造和拷贝赋值。

若用default赋值和构造,那么只不过是将指针之间地数据进行了转移,而空着的地方,就会造成内存泄漏;此外,两个指针指向同一个位置也是十分危险的。可能会造成报错.

我们应当完成:深拷贝

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);
   
}

拷贝构造函数copy ctor

接口应当传引用,传进来不会被改动。拷贝构造应当创建一份足够的空间来存储字符。其次,应当进行字符串的复制。

alias:别名,两个指针指向同一个地址,可能会造成空间非法。

拷贝赋值函数copy assignment operator

inline
String& String::operator = (const String& str)
{
    if(this == &str)//检测自我赋值
        return *this;
    delete[] m_data;//清空s2原有空间。
    m_data = new char[ strlen(str.m_data) + 1];//开s2新空间和赋值,从s1完成赋值拷贝
    strcpy(m_data, str.m_data);
    return *this;
}





{
    String s2 = s1;//赋值操作,执行的是s2,那么对s2进行原有内存清空,根据s1的清空进行内存分配和赋值即可。



}

若没有检测自我赋值,那么复制过程中,先删除自身。则,在后续的strcpy函数中,我们无法访问已经删除的字符内存段了。综上所述,自我复制不仅是为了效率,还是为了安全性。

第八节 堆、栈与内存管理

#include <iostream.h>
ostream& operator << (ostream& os, const String& str)
{
    os << str.get_c_str();
    return os;
}

{
    String s1("Hello");
    cout << s1;
}

由于在测试程序中想要对字符串做输出,我们一样要写一个操作符的重载。不可以写成一个成员函数,这是因为输出的时候cout会相反。(在右边)所以要写成全局函数。

因为cout可以直接接受一个指针并且打印出其对应的字符串,所以我们提前构造了一个返回指针函数。

Stack栈

Stack是存在于某作用域(scope)的一块内存空间(memory space)。例如当你调用函数,函数本身就会形成一个stack用来防止它所接受的参数,以及返回值地址。在函数本体内声明的任何变量,其所使用的内存块都取自上述stack。

Heap堆

或称为system heap,是指由操作系统分配的一块global内存空间。程序可以动态分配(dynamic allocated)从其中取得若干区块(blocks)

可以通过new获得全局分配的堆空间。而在函数内部声明的,占用的是栈的空间。另外,因为new最后占用的是堆空间,所以有必要手动去delete它。

栈和堆是两种对象获得内存的方式。

栈对象的生命期:只要离开作用域,那么这个对象的声明就结束了,会自动调用其析构函数实现内存释放。

static local 对象的生命期

class Complex(..);
...
{
   static Complex c2(1,2);
    Complex* p = new Complex;
    ...
    delete p;
}

c2就是所谓static object,其声明在作用域结束之后仍然存在,直到整个程序结束。

global 对象的生命期:

在全局环境中,任何大括号中都可以调用。在int main之前就有了,而在main函数之后才消失。

heap 对象的生命期:所指的便是heap object,其生命在它被delete之际被结束。

若没有delete,出现内存泄漏。因为作用域结束后,p所致的heap对象仍然存在,而指针的声明就结束了。作用域之外再也看不到p了。

Complex* pc = new Complex(1,2);
编译器转化为:
Complex *pc;
void* mem = operator new(sizeof(Complex));//分配内存,其中o,n是一个名称比较特别的函数,函数本身调用malloc(n)
pc = static_cast<Complex*>(mem);//转型,void->Complex
pc->Complex::Complex(1,2);//构造函数

new的具体步骤:先分配内存,再调用构造函数。

相应的:delete的具体步骤,先调用dtor析构函数,再释放内存。本质调用free。

综上所述,new的本质就是malloc;而delete的本质是free

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值