侯捷C++面向对象编程(上)--string部分

string部分分析(Class with pointer member)

//string.h
#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=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;
}
​
//拷贝赋值函数
inline
String& String::operator=(const String& str)
{
    //一定要检查 self assignment
   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);
}
​
//output 函数
#include <iostream>
using namespace std;
​
ostream& operator<<(ostream& os, const String& str)
{
   os << str.get_c_str();
   return os;
}
​
#endif
//string_test.cpp
#include "string.h"
#include <iostream>
​
using namespace std;
​
int main()
{
  String s1(); 
  String s2("hello");
    
  String s3(s1);//拷贝
  cout << s3 << endl;
  
  s3 = s2;//赋值
  cout << s3 << endl;     
  cout << s2 << endl;  
  cout << s1 << endl;      
}

1、Big Three(拷贝构造、拷贝赋值、析构函数)

析构函数:
inline
String::~String()
{
   delete[] m_data;
}

与complex类不同的是,我们自定义了string类的析构函数(即使我们没有在complex中写出析构函数,但是编译器会提供默认析构函数)。但在string类中,存在动态分配的内存(上述构造函数中),需要我们在析构函数中手动的释放内存,而默认析构函数不会这样做。这也是Class with pointer member和Class without pointer member的显著区别。

拷贝构造(copy ctor)和 拷贝赋值(copy op=):

Class with pointer member 必须有copy ctor 和 copy op=!

默认拷贝构造函数:

如果程序员没有为类定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数。默认拷贝构造函数会逐个成员地拷贝源对象的成员变量到新对象。对于基本数据类型,这通常是足够的,但对于指针(动态分配的资源,如动态数组或动态内存)逐个成员拷贝会出现一些问题

  • 新对象的指针成员会复制源对象的指针成员的值(即指针的地址)。

  • 结果是两个指针指向同一块内存区域。

这种逐个成员拷贝的行为被称为“浅拷贝”。对于指针,浅拷贝会导致以下问题:

  • 悬挂指针:如果源对象被销毁,其指针成员指向的内存可能被释放,而新对象的指针仍然指向这块内存,这将导致悬挂指针问题。

  • 双重释放:如果两个对象的指针都尝试释放同一块内存,可能会导致运行时错误或程序崩溃。

自定义拷贝构造函数:

为了避免这些问题,通常需要为包含指针的类定义自定义拷贝构造函数,实现深拷贝!

拷贝构造(copy ctor):
inline
String::String(const String& str)
{
   m_data = new char[ strlen(str.m_data) + 1 ];
   strcpy(m_data, str.m_data);
}
拷贝赋值(copy op=):
inline
String& String::operator=(const String& str)
{
    //一定要检查 self assignment
   if (this == &str)//检测自我赋值
      return *this;
    //如果没有上述自我检测,当执行 a=a; 时,无法new一个合适大小的空间(m_data已经释放内存)
   delete[] m_data;
   m_data = new char[ strlen(str.m_data) + 1 ];
   strcpy(m_data, str.m_data);
   return *this;
}

2、堆、栈与内存管理

出现一种创建对象的新的方式:

Complex c1(1,2);//隐式调用构造函数
Complex c2= Complex(1,2);//显式的调用构造函数
Complex * p =new Complex(3);//新的创建对象的方式
stack objects 的生命期:

在stack objects 的作用域结束之际,自动地调用析构函数进行清理,因此又称为auto objects。

static local objects 的生命期:

global objects 的生命期:

写在任何作用域之外的对象(任何{}之外的对象)即为global objects。

heap objects的生命期:

注:通常在C++中,我们可以显式地或隐式地调用构造函数,以Qt中的QLabel为例:

QLabel label1 = QLabel("Hello word!");//显式地调用构造函数
QLabel label2("Hello word!");//隐式地调用构造函数

然而,当我们隐式地调用默认构造函数时,需要特别小心,可能一不小心就声明了一个函数 :

QLabel label();//declares a function
QLabel label;//calls default constructor

但当你使用 new 关键字创建对象时,可以省略括号也可以不省略,如果构造函数接受默认参数。对于 QLabel 类,其默认构造函数没有参数,因此你可以写:

QLabel *label = new QLabel;

这里,new QLabel 相当于调用了 QLabel 类的默认构造函数 QLabel(),即使没有显式地写出括号。

然而,如果你想要调用一个接受参数的构造函数,或者想要更明确地表示你正在调用构造函数,你可以包含括号,即使里面没有参数:

QLabel *label = new QLabel();

这样做可以提高代码的可读性,特别是在调用接受参数的构造函数时,它清楚地表明了构造函数的调用。

new 与 delete:

new 在这里被编译器分为三步:

  1. 分配内存,调用operator new 函数(内部为malloc函数);

  2. 转型<Complex *>

  3. 调用构造函数,通过指针pc调用构造函数

delete 在这里被编译器分为两步:

  1. 调用析构函数

  2. 释放内存,内部为free函数

动态分配所得到的内存块:

在new 与 delete 部分我们提到其底层是c语言中的malloc和free函数,在这里我们了解一下使用malloc到底的到多大的空间。

如果去创建一个complex对象,理论上我们会申请到8bit内存空间,但事实是在debug模式和release模式下分别得到64bit和16bit内存空间。string类型也是这样。

与上述一样,分配数组时也不是理论上的空间大小,在VC编译器下,分配的空间还存储了数组的大小以及其他内容。

当使用new 数组但delete没有[]时,造成内存泄露的空间并不是数组的空间,而是数组元素所指对象空间的泄露,在此处即string对象中的构造函数为m_data申请的空间。如果你对complex类进行array new,即时没有array delete 也不会出错,因为数组元素所指对象中没有动态分配到空间。 但我们要养成一个好习惯:array new 一定要搭配array delete。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值