侯捷C++面向对象编程上day3

侯捷C++面向对象编程上day3

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

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

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

5.1操作符重载

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

5.1.1成员函数实现

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

1
2
3
4
5
inline complex& complex::operator += (const complex& r)
{
return __doapl(this,r);
}

img

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

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

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

5.1.2 非成员函数实现

img

非成员函数没有this

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

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

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

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

5.1.3 output函数 << 的重载

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

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

1
2
3
4
5
6
7
#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是申请的字符串的空间

1
2
3
4
5
6
7
8
9
10
11
12
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

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

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

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

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

1
2
3
4
5
6
{
String s1(),
String s2("hello");
String* p = new String("hello");
delete p;
}
6.2 copy ctor 拷贝构造函数
1
2
3
4
5
6
inline
String::String(const String& str)
{
m_data = new char[strlen(str.m_data) + 1]; // “str.m_data” 兄弟之间互为友元
strcpy(m_data, str.m_data); // 深拷贝
}
1
2
String s1("hello ");
String s2(s1);
6.3 copy op= 拷贝赋值函数
  1. 先杀死调用者
  2. 重新申请指定大小的空间
  3. 复制字符串内容到调用者
1
2
3
4
5
6
7
8
9
10
11
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 中的是自己申请的空间,需要自己释放

1
2
3
4
5
6
7
{
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,因为它会被自动清理(结束自动调用析构函数)

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

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

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

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

    1
    2
    3
    4
    5
    6
    7
    ...
    complex c3(1,2);

    int main()
    {
    ...
    }
  • heap objects 的生命期

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

    1
    2
    3
    4
    5
    {
    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 对象的动态内存分配

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

侯捷C++面向对象编程上day3

http://example.com/2024/07/07/C++/C-03/

作者

Pluto

发布于

2024-07-07

更新于

2026-03-26

许可协议

评论

:D 一言句子获取中...

加载中,最新评论有1分钟缓存...