没有输出的输入是不完整的

0%

Class-with-pointer-mString-Houjie-Bilibili

在Bilibili跟着侯捷老师学C++系列第二篇,带指针的类mString类。
人生得遇良师,实属大幸,侯捷老师就属于这种老师,再夸一遍!!

因为C++的效率是最高的,所以对于C++自己一定要好好掌握,个人看法,你如果能学会C++,其他语言都将不在话下。然后刚好碰到一个非常好的教程,有一位非常棒的老师-侯捷老师,有一个好的平台-Bilibili,还有非常好的训练场地——leetcode,所以此时不学什么时候学呢?

教程

侯捷C++手把手教学(上),适合新手

代码文件

mstring.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#ifndef mstring_hpp
#define mstring_hpp

#include <stdio.h>
#include <cstring>
#include <iostream>
using namespace std;
class mstring{
public:
mstring(const char* a = 0);
mstring(const mstring& str);
mstring& operator =(const mstring& str);
~mstring();
char* get_c_str() const {return mdata;}

private:
char* mdata;

};

inline
mstring::mstring(const char* a){
if(a){
mdata = new char[strlen(a)+1];
strcpy(mdata, a);
}else{
mdata = new char[1];
*mdata = '\0';
}
};

inline
mstring::mstring(const mstring& str){
mdata = new char[std::strlen(str.mdata)+1];
strcpy(mdata, str.mdata);
};

inline
mstring& mstring::operator=(const mstring& str){
if(this == &str){
return *this;
}
this->mdata = new char[strlen(str.mdata)+1];
strcpy(this->mdata,str.mdata);
return *this;
}

inline
mstring::~mstring(){
delete[] mdata;
};

ostream& operator << (ostream& os,const mstring& str){
return os<<str.get_c_str();
};

#endif /* mstring_hpp */

mstring.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "mstring.hpp"
#include <iostream>
using namespace std;
int main(){
mstring s1;
cout<<"s1 = "<<s1<<endl;
mstring s2("hello");
cout<<"s2 = "<<s2<<endl;
s1 = s2;
cout<<"s1 = "<<s1<<endl;
mstring s3(s2);
cout<<"s3 = "<<s3<<endl;

mstring *p = new mstring("world");
cout<<"*p = "<<*p<<endl;
delete p;
}

运行结果

1
2
3
4
5
6
s1 = 
s2 = hello
s1 = hello
s3 = hello
*p = world
Program ended with exit code: 0

细节剖析

  1. 构造函数
1
mstring(const char* a = 0);

构造函数的名称和类名一致,没有返回值,参数可以设置默认参数(实际实现的时候就不需要再说明默认参数了)
2. 拷贝构造

1
mstring(const mstring& str);

接收一个同种类型的对象作为自己的参数,然后产生一个新的对象。

这里要注意深复制和浅复制的区别,深复制就是会新创建一个对象,这个对象有属于自己的内存空间,只是值和原来的是一致的。浅复制是说两个对象指向同一个内存空间,所以一旦有一个更改,另一个肯定会被更改,很危险。
3. 拷贝赋值

1
mstring& operator =(const mstring& str);

这里首先要注意的是我们重载了运算符=,然后是当前类的方法。其次是我们传入的参数,我们不会修改,所以设置为const,是通过引用传进来的,返回值同样是一个mstring对象,我们通过引用返回。

对于成员函数,其实每个函数都含有一个隐藏的参数this,我们可以通过this和参数引用的地址来确定是否是自我赋值,要进行判断,否则会报错。因为具体赋值的过程是先删除本身的数据,然后创建一个能容纳str的空间的内存,然后将str内容拷贝到内存中。如果this和str指向同一个地址,那么刚开始删除掉就已经没有了,之后的创建空间就会出错。所以一定要注意这个拷贝赋值时自我赋值的问题。
4. 析构函数

1
~mstring();

析构函数是指当对象生命周期结束之后会自动调用当前对象的析构函数,主要负责释放通过new关键字请求得到的内存空间。如果不自行解决,那么new出来的这块空间就会变成无主空间,会造成内存泄露。
5. 对于一个带有指针的类,必须要有拷贝构造和拷贝赋值还有析构函数。
6. 其他成员函数的编写

1
char* get_c_str() const {return mdata;}

首先这个函数可以认为是一个辅助函数,方便之后重载<< 符号的,因为ostream已经被重载过所以可以输出char类型的指针所指向的数据,所以我们这里单独写一个用于输出。
其次要注意这个函数有个const,对于类对象本身没有更改的方法都要写上这个const。
然后要注意这里的返回值类型,是一个char类型的指针。
7. array new 一定要搭配一个array delete

1
2
3
4
5
6
7
8
9
10
inline
mstring::mstring(const char* a){
if(a){
mdata = new char[strlen(a)+1];
strcpy(mdata, a);
}else{
mdata = new char[1];
*mdata = '\0';
}
};
1
2
3
4
inline
mstring::~mstring(){
delete[] mdata;
};

在构造函数中是创建了一个char类型的数组,那么在析构函数中也要以同样的方式进行释放。
8. 关于栈存储
Stack,是存在于作用域的一块内存空间,例如当你调用函数,函数本身就会形成一个stack来放置它所接收的参数,以及对应的返回地址。在函数本体中声明的任何变量,其所使用的内存块都会在上述stack中。当作用域结束,所有内存都会被回收,内存中的对象也就再也找不到了,所以这就是我们上一节所讲的不能返回一个local对象的引用或者指针而要直接返回他们本身。
9. 关于堆存储
Heap,或者叫做system heap,是指由操作系统提供的一块global的内存空间,程序可以通过new来动态分配获得若干区块。这样就需要自己来动手释放掉。
10. stack object的生命周期

1
2
3
{
complex c1(1,2);
}

在一个大括号(作用域)中创建的对象也就是所谓的local对象,比如c1,在作用域结束的时候就会被自动清理。
11. static local object的生命周期

1
2
3
{
static complex c2(1,2);
}

当一个局部变量被static关键字修饰的时候,比如c2,它就是一个静态对象,其生命在作用域结束之后仍然存在,直到整个程序结束。
12. global objects的生命周期

1
2
3
4
complex c3(1,2);
int main(){

}

全局对象,写在{}之外的对象,比如c3,其生命周期是整个程序。这个经常在算法题目中出现。
13. heap objects的生命周期

1
2
3
{
complex* p = new Complex(1,2);
}

这里的p就是一个堆对象,是由动态分配生成的,在其被deleted之际才会结束。
14. new关键字:先分配内存,然后调用构造函数
15. delete关键字:先调用析构函数,然后再释放内存。