深入C++03:面向对象

📕面向对象

类和对象、this指针#

不用做太多笔记,都可以看初识C++的笔记;

记住👀:声明后面都要加“”,比如声明方法和变量还有class结束的地方;而实现函数出来的地方是不需要加“

为什么要按最大字节对齐?和内存池相关

掌握构造函数和析构函数#

看初始C++笔记enough;

掌握对象得深拷贝和浅拷贝#

浅拷贝:直接内存数据拷贝

Copy
SeqStack s;//默认构造函数; SeqStack s2 = s;//调用拷贝构造函数; SeqStack s3(s);//上下都是调用拷贝构造函数,类会有默认拷贝函数,如默认构造函数相同,不写会自己生成; //(什么时候调用什么函数查看下面代码实践);

image-20220320165217418

容易错点:类对象含有对象内存之外的资源,比如说在堆中有内存资源,那么调用析构函数会发生错误;

image-20220320164636644

深拷贝:自定义拷贝构造函数和重载=运算符

深拷贝原型例子:

image-20220320165439878

问题一:为什么C++面向对象里面拷贝数据的时候用for循环,不用memcpy(ptmp, _pstack, sizeof(int)* _size)或者relloc()?

因为memcpy和relloc都是浅拷贝,如果数据只是int、float这种简单类型,不会用到外部资源就没有什么问题,还是可以用这两个函数拷贝数据的,但是如果数据内是对象,对象内有指向外部内存,那么就会有浅拷贝的问题出现(delete了可以放弃的数据,会发现转移的数据也被释放了)。

image-20220320170432269

问题二:直接赋值不是浅拷贝操作吗?采用直接赋值操作怎么实现深拷贝呢?

一开始说两者SeqStack s2 = s; SeqStack s3(s);都是有默认拷贝函数和默认的赋值函数,两个效果是相同的,都是直接拷贝内存数据;

Copy
void operator=(const SeqStack &src) { _pstack = src._pstack; _top = src._top; _size = src._size; } //这个默认赋值函数还有一个问题:假设s, s2都初始化,这时候将s2 = s, s2中_pstack指向的内存(在构造函数时申请的空间)会没有指针指到,因为s2中的_pstack指向了 s中_pstack指向的空间(因为是浅拷贝啊!) //所以我们重载赋值函数时要注意这个问题!

所以,赋值函数=我们需要运算符重载

实例:

Copy
void operator=(const SeqStack &src) { //防止自赋值,自赋值如果不防止会出现内存访问异常,自己分析一下即可; if (this == &src) return; //需要先释放当前对象占用的外部资源 delete []_pstack; //进行拷贝,和拷贝构造一样的操作 _psatck = new int[src.size]; for (int i = 0; i <= src._top; i++) { _pstack[i] = src._pstack[i]; } _top = src._top; _size = src._size; }

浅拷贝有问题的时候再考虑深拷贝;

类和对象代码应用实践#

①简写实现String类

Copy
#include<iostream> #include<cstring> using namespace std; class String { public: String(const char * str = nullptr) {//构造函数 if (str != nullptr) { m_data = new char[strlen(str) + 1];//strlen函数不会读取'\0'; strcpy(this->m_data, str); cout << "调用有参数的构造函数" << endl; } else { m_data = new char[1]; //这样不管为不为空,strlen都可以访问 *m_data = '\0'; cout << "调用没有参数的构造函数" << endl; } } ~String() {//析构函数 delete m_data; m_data = nullptr; } String(const String &other) { //拷贝函数 m_data = new char[strlen(other.m_data) + 1]; strcpy(m_data, other.m_data); cout << "调用拷贝函数" << endl; } String& operator=(const String &other) {//=运算符重载,String& 是为了支持连续使用= if (this == &other) return *this;//注意,这里不能返回other,因为other有const修饰 cout << "调用=赋值函数" << endl; delete m_data; m_data = new char[strlen(other.m_data) + 1]; strcpy(m_data, other.m_data); return *this; } private: char *m_data; //保存字符,要用深拷贝 void resize() { } }; int main () { //调用带const char*参数的构造函数; String str1; String str2("hello"); String str3 = "world"; //一定条件下成立,刚刚好, 问题:为什么这个也可以调用构造函数?编译器优化相关 String *test = new String("lulu"); //调用拷贝构造函数 String str4 = str3; String str5(str3); //调用赋值重载函数 str1 = str2; //必须已经声明 return 0; }

image-20220320191821773

看看字符数组的操作

②循环队列

Copy
#include<iostream> using namespace std; class Queue { public: Queue(int size = 15) { //构造函数 _pQue = new int[size]; _front = _rear = 0; _size = size; } ~Queue() { //析构函数 delete []_pQue; _pQue = nullptr; } void push(int val) { if (full()) resize(); _pQue[_rear] = val; _rear = (_rear + 1) % _size; } void pop() { if (empty()) return; _front = (_front + 1) % _size; } int front() { if (empty()) return -1; return _pQue[_front]; } bool empty() { return _front == _rear; } bool full() { return (_rear + 1) % _size == _front; } // Queue(const Queue &que) = delete; //不允许使用拷贝构造函数; Queue(const Queue &que) { //拷贝构造函数 _front = que._front; _rear = que._rear; _size = que._size; _pQue = new int[_size]; // for (int i = 0; i < _size; i++ ) {//这样写有些值不存在,而且浪费时间,遍历到了不用的值 // _pQue[i] = que._pQue[i]; // } for (int i = _front; i != _rear; i = (i + 1) % _size) { //rear在的值是空的 _pQue[i] = que._pQue[i]; } } // Queue& operator=(const Queue &que) = delete; //不允许使用拷贝构造函数; Queue& operator=(const Queue &que) { //赋值函数 if (this == &que) return *this; delete []this->_pQue; _front = que._front; _rear = que._rear; _size = que._size; _pQue = new int[_size]; for (int i = _front; i != _rear; i = (i + 1) % _size) { //raer在的值是空的 _pQue[i] = que._pQue[i]; } return *this; } private: int *_pQue; //队列的数组空间, 存在堆空间,类对象外空间,需要深拷贝 int _front; //队头位置 int _rear; //队尾位置 int _size; //队列扩容总大小; void resize() { //扩容函数,注意实现 int *newQue = new int[_size * 2]; int index = 0; for (int i = _front; i != _rear; i = (i + 1) % _size) { newQue[index++] = _pQue[i]; } delete []_pQue; _pQue = newQue; _front = 0; _rear = index; _size *= 2; } }; int main() { Queue que; for (int i = 0; i < 20; i++) { que.push(i); } while(!que.empty()) { cout << que.front() << " "; que.pop(); } return 0; }

image-20220320211522752

掌握构造函数得初始化列表#

①初始化列表和和构造函数函数体内写有什么区别?

可以去看看汇编代码

image-20220320235007026

②初始化列表的顺序是以类中成员变量的声明顺序去初始化的,而不是在初始化列表中出现的顺序!

image-20220321000656508

掌握类得各种成员方法以及区别#

const、static、以及什么都不加,他们修饰的方法很花里胡哨,但是实际上就是this指针的区别;

image-20220321002820821

三者成员函数的实例:

image-20220321002544712

指向类成员的指针#

直接看实例:

Copy
#include<iostream> #include<vector> class Test { public: void fun() {std::cout << "call Test::fun" << std::endl;}; static void static_fun() {std::cout << "call Test::static_fun" << std::endl;}; int ma; static int mb; }; int main() { Test t1; Test *t2 = new Test(); //操作普通成员变量,必须要①声明有Test作用域的指针,②有对象 int Test::*p = &Test::ma; t1.*p = 20;//改变了t1中ma的值 t2->*p = 20;//改变了t2中ma的值; //操作静态成员变量,直接用同类型指针操作即可 int *p1 = &Test::mb; *p1 = 40;//改变了静态成员变量mb的值; //操作普通成员函数,必须要①声明有Test作用域的指针,②有对象 void (Test::*pfun)() = &Test::fun; (t1.*pfun)(); (t2->*pfun)(); //操作静态成员函数,直接用同类型的指针即可 void (*pstatic_fun)() = &Test::static_fun; return 0; }
posted @   D-booker  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示
CONTENTS