侯捷C++面向对象编程(上)课程打卡 Day3
5、操作符重载与临时对象
5.1操作符重载
在C++中,我们可以定义加法等操作符,比如我们可以定义两个是石头的加法
5.1.1成员函数实现
成员函数: complex :: function …… 前面带有class的名称(在class里事先声明了的)
inline complex& complex::operator += (const complex& r)
{
return __doapl(this,r);
}
所有的成员函数都带有一个隐藏的参数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 非成员函数实现
非成员函数没有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 临时对象
classname ()
创建一个classname类型的临时对象——不需要名称,生命只有一行
6、带指针的类:三大函数
析构函数:~String();
拷贝构造函数 copy ctor:String (const String& str);——string s3(s1)
拷贝赋值函数copy op= :String& operator=(const String& str);—–s3=s2
编译器默认的是拷贝构造赋值,编译器默认的方式只是拷贝了指针(浅拷贝),而不是指针指向的数据。
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= 拷贝赋值函数
- 先杀死调用者
- 重新申请指定大小的空间
- 复制字符串内容到调用者
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
- 分配内存:先用一个特殊函数,按 class 的定义分配了两个
double
的大小 - 转型(忽视)
- 调用构造函数,赋值
(1,2)
7.3.2 delete
delete:先调用 dtor, 再释放 memory
- 调用析构函数——释放的是
m_date
指向的字符串Hello
的空间(即构造函数中new
申请的空间) - 释放内存:用一个特殊函数释放了
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 对象的动态内存分配
对象与简单的数据类型没有什么不同