文章目录
- 前言
- 一、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); realloc
delete[]_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; // #1
SeqStack 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 = str2
str1.operator=(str2) => str1
str3 = str1
*/
str3 = str1 = str2;
return 0;
}
关于赋值重载函数中返回值为引用的情况:
如果一个方法的返回值是一个引用,那么它返回的是某个对象的引用,而不是对象本身的副本。这意味着通过引用返回的值与原始对象共享同一块内存,对返回值的修改将直接影响原始对象。
返回引用的方法有以下几个作用:
- 避免对象的复制:通过返回引用而不是副本,可以避免在函数调用中进行大量的复制操作,提高性能。特别是对于大型对象或复杂的数据结构,避免复制可以节省时间和内存。
- 允许链式操作:返回引用可以使多个方法调用可以连接在一起形成链式操作。这种方法通常用于实现流畅的、易读的代码,比如在输入/输出流中使用<<和>>运算符。
- 允许对返回值进行修改:返回引用允许在函数外部对返回值进行修改。这可以用于实现函数返回某个对象的引用,以便可以直接修改该对象的状态。
需要注意的是,返回引用时需要确保返回的引用在函数调用结束后仍然有效。一般来说,应该返回指向静态、全局、或动态分配的对象的引用,而不是指向局部对象的引用,以避免出现悬垂引用(dangling references)的问题。
完整的Mystring重写见:参考链接