侯捷C++面向对象编程(上)课程打卡 Day3

5、操作符重载与临时对象

5.1操作符重载

在C++中,我们可以定义加法等操作符,比如我们可以定义两个是石头的加法

5.1.1成员函数实现

成员函数: complex :: function …… 前面带有class的名称(在class里事先声明了的)

inline complex& complex::operator += (const complex& r)
{
    return __doapl(this,r);
}

img

所有的成员函数都带有一个隐藏的参数this(是一个指针),this指向调用这个函数的调用者

  • 定义函数的时候,在参数列中不能写出来this,直接用即可
  • 函数里可写可不写,但当传入参数与成员变量名相同时要写
public:
	double real () const { return this->re; }  //这里的this->可省略 
c3 += c2 += c1 //c2加了c1后如果返回void就无法进行c3的操作了

将操作符写为void函数也可以,但为了可以兼容c3+=c2+=c1的形式,写成返回引用更好。

5.1.2 非成员函数实现

img

非成员函数没有this

应对三种使用方法,写出三种方式

  • 非成员函数是global函数——为了后面两种使用方法

  • 这些函数不能返回引用,必须值传递

    在函数中创建的新变量 (local 变量),要返回

5.1.3 output函数 « 的重载

cout不认识新定义的这种复数,因此也需要对<<进行操作符重载

只能全局函数,不能成员函数——导致使用时方向相反

#include <iostream.h>
ostream&
operator<<(ostream& os, const complex& x)
{
    return os << '(' << real(x) << ',' << imag(x) << ')';  //自定义输出
}

ostream& 是 cout 的 classname

参数传递:os 在函数中会变化,所以不能加 const

返回值传递:为了避免 cout « c1 « conj(c1); 连续输出,不用 void

cout « c1 返回值需要与 cout 类型一致

5.2 临时对象

img

classname () 创建一个classname类型的临时对象——不需要名称,生命只有一行

6、带指针的类:三大函数

img

  • 析构函数:~String();

  • 拷贝构造函数 copy ctor:String (const String& str);——string s3(s1)

  • 拷贝赋值函数copy op= :String& operator=(const String& str);—–s3=s2

    编译器默认的是拷贝构造赋值,编译器默认的方式只是拷贝了指针(浅拷贝),而不是指针指向的数据。

    img

alias(别名)和 memory leak(内存泄漏)都是十分危险的

因此,如果类中有指针,一定自己写这两个函数

6.1ctor和dtor(构造和析构函数)
6.1.1ctor构造函数

这里的new是申请的字符串的空间

inline
String::String(const char* cstr = 0)
{
    if (cstr) {       // 指定了初值—— String s2("hello");
        m_data = new char[strlen(cstr) + 1];  // 字符串长度 + /0
        strcpy(m_data, cstr);
    }
    else {            // 未指定初值—— String s1();
        m_data = new char[1];
        *m_data = '\0';
    }
}

这里的 new 是申请的指针的空间,String()里面还有一个 new

String* p = new String("hello");  
delete p;
6.1.2dtor析构函数
inline  
String::~String()
{
    delete[] m_data;
}

每一个new都对应一个delete—–一定要释放申请的内存

类对象死亡的时候(离开作用域),析构函数会被自动调用

例:这里结束会调用三次 dtor

{
    String s1(),
    String s2("hello");
    String* p = new String("hello");
    delete p;
}
6.2 copy ctor 拷贝构造函数
inline
String::String(const String& str)
{
    m_data = new char[strlen(str.m_data) + 1]; // “str.m_data” 兄弟之间互为友元 
    strcpy(m_data, str.m_data); // 深拷贝
}
String s1("hello ");
String s2(s1);
6.3 copy op= 拷贝赋值函数
  1. 先杀死调用者
  2. 重新申请指定大小的空间
  3. 复制字符串内容到调用者
inline
String& String::operator=(const String & str)
{
    if (this == &str)  // 检测自我赋值 self assignment
        return *this;

    delete[] m_data;                               // 第一步
    m_data = new char[strlen(str.m_data) + 1];     // 第二步
    strcpy(m_data, str.m_data);                    // 第三步
    return *this;
}

一定要在开始就检测自我赋值,因为a=a时第一步 delete 了后,会使第三步出现问题

7、堆、栈,内存管理

7.1堆和栈

Stack 栈,是存在于某一个作用域的一块内存空间。

例如当你调用函数时,函数本身就会产生一个stack用于放置它所接受的参数,以及返回地址;在函数本体内声明的任何变量其所使用的内存块都取自上述的stack

Heap 堆,或者称为system heap,是指由操作系统提供的一块global内存空间,程序可以动态分配从中获得若干区块。

可以用new来动态取得

stack 中的是自动生成的空间,作用域结束空间会自动释放

在 heap 中的是自己申请的空间,需要自己释放

{
   complex c1(1,2);              
   /*c1空间来自stack*/
   complex* p = new complex(3);  
   /*complex(3) 是个临时对象
   其所用的空间是以new从heap动态分配而得,并由p指向*/
}
7.2对象生命周期
  • stack objects的生命周期

    c1就是所谓stack object,其生命在作用域结束之际结束。这种作用域内的object,又称为auto object,因为它会被自动清理(结束自动调用析构函数)

    {
    	complex c1(1,2);
    }
    
  • static local objects 的生命期

    若在前面加上 static 后,其会存在到整个程序结束

    {
        static complex c2(1,2);
    }
    
  • global objects 的生命期

    写在任何作用域之外的对象,其生命在整个程序结束之后才结束,你也可以把它视为一种 static object,其作用域是整个程序

    ...
    complex c3(1,2);
    
    int main()
    {
        ...
    }
    
  • heap objects 的生命期

    p 所指的便是 heap object,其生命在它被 delete 之际结束

    {
        complex* p = new complex;
        ...
        delete p;
    }
    
7.3new和delete
7.3.1 new

new: 先分配memory,再调用ctor

image-20230731092332395

  1. 分配内存:先用一个特殊函数,按 class 的定义分配了两个 double 的大小
  2. 转型(忽视)
  3. 调用构造函数,赋值(1,2)
7.3.2 delete

delete:先调用 dtor, 再释放 memory

image-20230731092947259

  1. 调用析构函数——释放的是 m_date 指向的字符串 Hello 的空间(即构造函数中 new 申请的空间)
  2. 释放内存:用一个特殊函数释放了 ps 指向的空间(即String* ps = new String("Hello");new 申请的空间)
7.4内存动态分配
7.4.1 array new/delete

array new 一定要搭配 array delete

new后有**[ ]—> delete后加[ ]**

普通的delete只调用一次析构函数——剩下两个指针的指向的空间没有调用析构函数,内存泄漏

这种情况发生在有指针的类,但最好都这样写

7.4.2 对象的动态内存分配

对象与简单的数据类型没有什么不同