文章目录
- 前言
- 一、this指针
- 二、构造和析构
- 三、深拷贝浅拷贝
- 浅拷贝
- 深拷贝
- 编程实践
前言
什么是OOP思想?
OOP语言的四大特征:
抽象,封装/隐藏,继承,多态
一、this指针
this指针=》类=》很多对象
一套成员方法是如何处理多个对象的?具体而言:
成员方法show() => 怎么知道处理哪个对象的信息? 答:具体对象的this指针。
成员方法init (name,price,amount) => 怎么知道把信息初始化给哪一个对象的? 答:具体对象的this指针。
类的成员方法一经编译,所有的方法参数,都会加一个this指针,接收调用该方法的对象的地址。
当有一实例化对象good1,good1调用成员方法show时,编译器会自动加入参数:
show(&good1);
当有一实例化对象good2,good2调用成员方法init时,编译器会自动加入参数:
init(&good2,name,price,amount);
二、构造和析构
OOP实现顺序栈。
要想实现一个类,共有一般放成员方法,私有一般放成员变量。
因为顺序栈要实现内存的扩充,所以需要使用数组指针。
class SeqStack
{
public:// 构造函数 SeqStack(int size = 10){cout << " SeqStack() Ptr" << this << endl;_pstack = new int[size];_top = -1;_size = size;}// 自定义的拷贝构造函数 《= 对象的浅拷贝现在有问题了SeqStack(const SeqStack &src){cout << "SeqStack(const SeqStack &src)" << endl;_pstack = new int[src._size];for (int i = 0; i <= src._top; ++i){_pstack[i] = src._pstack[i];}_top = src._top;_size = src._size;}// 析构函数~SeqStack() {cout << this << " ~SeqStack()" << endl;delete[]_pstack;_pstack = nullptr;}void push(int val){if (full()) resize(); // resize不想要用户调他_top++;_pstack[top] = val;}void pop(){if (empty()) return;--_top;}int top() { return _pstack[_top]; }bool empty() { return _top == -1; }bool full() { return _top == _size - 1; }private:int *_pstack; // 动态开辟数组,存储顺序栈的元素int _top; // 指向栈顶元素的位置int _size; // 数组扩容的总大小void resize(){int *ptmp = new int[_size * 2];for (int i = 0; i < _size; ++i){ptmp[i] = _pstack[i];} // memcpy(ptmp, _pstack, sizeof(int)*_size); reallocdelete[]_pstack;_pstack = ptmp;_size *= 2;}
}
析构函数如果需要释放一个指向数组的指针,需要中括号[]
:
delete[]_pstack;
调用:
SeqStack ps1; // .data段开辟内存,程序结束才进行析构
int main()
{SeqStack *ps2 = new SeqStack(60); //heap段开辟内存ps2->push(70);ps2->pop();cout << ps2->top() << endl;delete ps2; // 1、在stack段开辟内存 2、调用构造SeqStack ps3;
}
调用总结:
1、全局对象
全局对象定义时进行构造,程序结束才析构。
2、heap段对象
new时:1、malloc内存开辟;2、构造函数。
delete时:1、析构函数;2、free(ps2)
3、stack段对象
定义时构造,出作用域析构。
三、深拷贝浅拷贝
浅拷贝
内存的拷贝
int main()
{SeqStack s1(10);SeqStack s2 = s1; // #1SeqStack s3(s1); // #2// #1和#2都是调用拷贝构造// #1相当于 s2.operator=(s1)// void operator=(const SeqStack& src)
}
什么时候不能浅拷贝:
对象中的成员变量指针,指向了对象外的资源。所以在浅拷贝时,两个对象的指针就指向了同一块资源。
深拷贝
当对象占用外部资源时,使用深拷贝,使得其各自占有各自的资源。
class SeqStack
{
public:// 构造函数 SeqStack(int size = 10) {}// 自定义的拷贝构造函数 《= 对象的浅拷贝现在有问题了SeqStack(const SeqStack &src){cout << "SeqStack(const SeqStack &src)" << endl;_pstack = new int[src._size];for (int i = 0; i <= src._top; ++i){_pstack[i] = src._pstack[i];}_top = src._top;_size = src._size;}void operator= (const SeqStack &src){if(this == &src) return; // 防止自赋值delele[] _pstack; // 重要,需要先释放当前对象的内存_pstack = new int[src.size];for (int i = 0; i <= src._top; ++i){_pstack[i] = src._pstack[i];}_top = src._top;_size = src._size;}// 析构函数~SeqStack() {}void push(int val){}void pop(){}int top() {}bool empty() {}bool full() {}
private:int *_pstack; // 动态开辟数组,存储顺序栈的元素int _top; // 指向栈顶元素的位置int _size; // 数组扩容的总大小void resize(){}
}
注意:
1、为什么拷贝时要使用for循环,而不是直接
memcpy(ptmp, _pstack, sizeof(int)*_size);
使用memcpy仍然是浅拷贝。
2、赋值构造函数时,有一个释放当前对象的内存的操作。对应shared_ptr引l用计数在赋值的时候也会减1(随即会加1)
3、赋值构造函数应该防止s1 = s1
这种自赋值的情况。
编程实践
Mystring:
注意:
1、在普通构造函数中,无论如何也要开辟一块内存。保证对象是有一个有效的对象。
2、普通构造函数的输入为const char *str
,因为在新版编译器中不让普通的指针指向常量字符串。
char *p = "hello world"; // 不能修改*p
现在编译器都不让普通的指针指向常量字符串,应该这么写:
const char *p = "hello world";
#include <bits/stdc++.h>using namespace std;class String
{
public:String(const char *str = nullptr) {if (str != nullptr){m_data = new char[strlen(str) + 1]; // 加上'/0'strcpy(this->m_data, str);}else{m_data = new char[1]; // new char;*m_data = '\0'; // 0}}String(const String &other){m_data = new char[strlen(other.m_data) + 1]; // 深拷贝strcpy(m_data, other.m_data);}~String(void) // 析构函数{delete[]m_data;m_data = nullptr;}// String& 是为了支持连续的operator=赋值操作String& operator=(const String &other) // 赋值重载函数{if (this == &other){return *this; // str1}delete[]m_data; // 析构m_data = new char[strlen(other.m_data) + 1];strcpy(m_data, other.m_data);return *this; // str1}
private:char *m_data; // 用于保存字符串
};
int main()
{// 调用带const char*参数的构造函数String str1;String str2("hello");String str3 = "world";// 调用拷贝构造函数String str4 = str3;String str5(str3);// 调用赋值重载函数/*str1 = str2str1.operator=(str2) => str1str3 = str1*/str3 = str1 = str2;return 0;
}
关于赋值重载函数中返回值为引用的情况:
如果一个方法的返回值是一个引用,那么它返回的是某个对象的引用,而不是对象本身的副本。这意味着通过引用返回的值与原始对象共享同一块内存,对返回值的修改将直接影响原始对象。
返回引用的方法有以下几个作用:
- 避免对象的复制:通过返回引用而不是副本,可以避免在函数调用中进行大量的复制操作,提高性能。特别是对于大型对象或复杂的数据结构,避免复制可以节省时间和内存。
- 允许链式操作:返回引用可以使多个方法调用可以连接在一起形成链式操作。这种方法通常用于实现流畅的、易读的代码,比如在输入/输出流中使用<<和>>运算符。
- 允许对返回值进行修改:返回引用允许在函数外部对返回值进行修改。这可以用于实现函数返回某个对象的引用,以便可以直接修改该对象的状态。
需要注意的是,返回引用时需要确保返回的引用在函数调用结束后仍然有效。一般来说,应该返回指向静态、全局、或动态分配的对象的引用,而不是指向局部对象的引用,以避免出现悬垂引用(dangling references)的问题。
完整的Mystring重写见:参考链接