• 三、C++面向对象-类和对象那些你不知道的细节原理


    一、类和对象、this指针

    OOP语言的四大特征是什么?

    • 抽象
    • 封装、隐藏
    • 继承
    • 多态

    类体内实现的方法会自动处理为inline函数。

    类对象的内存大小之和成员变量有关

    类在内存上需要对齐,是为了减轻cup在内存上的io次数

    查看类对象的大小的指令:cl className.cpp /d1reportSingleClassLayout类名

    一个类可以定义无数个对象,每个对象都有自己的成员变量,但是他们共享一套成员方法。

    有一个问题:Q1:类中的成员方法是怎么知道要处理哪个对象的信息的?

    A1:在调用成员方法的时候会在参数列表里隐式的给定对象内存的地址。如下所示:

    类的成员方法一经编译,所有方法参数都会加一个this指针,接收调用该方法的对象的地址,即下图中的CGoods *this

    二、掌握构造函数和析构函数

    定义一个SeqStack类:

    class SeqStack
    {
    
    public:
    	SeqStack(int size = 10) :_top(-1), _size(size) {
    		_pstack = new int[size];
    	}
    	~SeqStack() {
    		cout << this << "~SeqStack()" << endl;
    		delete[] _pstack;
    		_pstack = nullptr;
    	}
    
    	void push(int val) {
    		if (full()) {
    			resize();
    		}
    		_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];
    		}
    		delete[] _pstack;
    		_pstack = ptmp;
    		_size *= 2;
    	}
    };
    /**
    	运行过程
    */
    int main() {
    	SeqStack sq1;
    
    	for (int i = 0; i < 15; i++) {
    		sq1.push(rand() % 100);
    	}
    
    	while (!sq1.empty()) {
    		cout << sq1.top() << " ";
    		sq1.pop();
    	}
    
    	return 0;
    }
    

    三、掌握对象的深拷贝和浅拷贝

    .data段的对象是程序启动的时候构造的,程序结束的时候析构的

    heap堆上对象是new的时候构造的,delete的时候析构的

    stack栈上的对象是在调用函数的时候构造的,执行完函数时析构的

    如果对象占用外部资源,浅拷贝就会出现问题:会导致一个对象指向的内存释放,从而造成另一个对象中的指针成为野指针。所以就要对这样的对象进行深拷贝,在新的对象中重新开辟一块空间,使两者互不干涉。

    注意:在面向对象中,要避免使用memcpy进行拷贝,因为对象的内存占用不确定,会因为对象中保存指针而造成浅拷贝。需要拷贝的时候只能用for循环逐一拷贝。

    深拷贝:

    	SeqStack& operator=(const SeqStack& src) {
    		cout << "operator=" << endl;
    		//防止自赋值
    		if (this == &src) {
    			return *this;
    		}
    		delete[] _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;
    		return *this;
    	}
    
    	SeqStack(const SeqStack& src) {
    		cout << this << "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;
    	}
    

    四、类和对象应用实践

    类Queue:

    #pragma once
    class CirQueue
    {
    public:
    
    	CirQueue(int size = 10) {
    		_pQue = new int[size];
    		_front = _rear = 0;
    		_size = size;
    	}
    
    	CirQueue(const CirQueue& src) {
    		_size = src._size;
    		_front = src._front;
    		_rear = src._rear;
    		_pQue = new int[_size];
    		for (int i = _front; i != _rear; i = (i + 1) % _size) {
    			_pQue[i] = src._pQue[i];
    		}
    	}
    
    	~CirQueue() {
    		delete[] _pQue;
    		_pQue = nullptr;
    	}
    
    
    
    	CirQueue& operator=(const CirQueue& src) {
    		if (this == &src) {
    			return *this;
    		}
    		delete[] _pQue;//需要释放掉自身占用的外部资源
    		_size = src._size;
    		_front = src._front;
    		_rear = src._rear;
    		_pQue = new int[_size];
    		for (int i = _front; i != _rear; i = (i + 1) % _size) {
    			_pQue[i++] = src._pQue[i];
    		}
    		return *this;
    	}
    
    	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() {
    		return _pQue[_front];
    	}
    
    	bool full() {
    		return (_rear + 1) % _size == _front;
    	}
    	
    	bool empty () {
    		return _front == _rear;
    	}
    
    
    
    private:
    	int* _pQue;
    
    	int _front;
    
    	int _rear;
    
    	int _size;
    
    	void resize() {
    		int* ptmp = new int[_size * 2];
    		int index = 0;
    		for (int i = _front; i != _rear; i=(i+1)%_size) {
    			ptmp[index++] = _pQue[i];
    		}
    		delete[] _pQue;
    		_pQue = ptmp;
    		_front = 0;
    		_rear = index;
    		_size *= 2;
    	}
    };
    
    
    

    类String:

    #pragma once
    #include <algorithm>
    class String
    {
    public:
    
    	String(const char* str = nullptr) {
    		if (str != nullptr) {
    			_pChar = new char[strlen(str) + 1];
    			strcpy(_pChar, str);
    		}
    		else {
    			_pChar = new char[1];
    			*_pChar = '\0';
    		}
    	}
    
    	String(const String& str) {
    		_pChar = new char[strlen(str._pChar)+1];
    		strcpy(_pChar, str._pChar);
    	}
    
    	~String() {
    		delete[] _pChar;
    		_pChar = nullptr;
    	}
    
    	String& operator=(const String& str) {
    		if (this == &str) {
    			return *this;
    		}
    		delete[] _pChar;//需要释放掉自身占用的外部资源
    
    		_pChar = new char[strlen(str._pChar) + 1];
    		strcpy(_pChar, str._pChar);
    		return *this;
    	}
    
    private:
    	char* _pChar;
    	
    };
    

    五、掌握构造函数的初始化列表

    初始化列表和写在构造体里有什么区别:

    初始化列表会直接定义并且赋值;放在构造体里会先执行定义操作,在对定义好的对象赋值。

    对象变量是按照定义的顺序赋值的,与构造函数中初始化列表的顺序无关。上图中的ma是0xCCCCCCCC,mb是10,ma未赋值。

    六、掌握类的各种成员方法及其区别

    普通成员方法和常成员方法,是可以重载的,常成员方法可以在对象声明为const的时候调用。

    对象声明为const的时候,调用成员方法是通过const对象的指针调用的,而普通的成员方法默认生成的是普通的指针对象,不能直接赋值。

    只要是只读操作的成员方法,一律实现成const常成员方法

    三种成员方法:

    七、指向类成员的指针

    class Test {
    public:
    	void func() { cout << "call Test::func" << endl; }
    	static void static_func() { cout << "call Test::static_func" << endl; }
    
    	int ma;
    	static int mb;
    };
    
    int Test::mb=0;
    
    int main() {
    
    	Test t1;
    	Test *t2 = new Test();//在堆上生成对象,并用指针指向
    
    	//使用指针调用类成员方法(前面要加类的作用域Test::)
    	void (Test:: * pfunc)() = &Test::func;
    	(t1.*pfunc)();
    	(t2->*pfunc)();
    
    	//定义指向static的类成员方法
    	void(*pfunc1)() = &Test::static_func;
    	(*pfunc1)();
    
    	//使用指针指向类成员变量,前面要加类的作用域Test::
    	int Test::* p = &Test::ma;
    	t1.*p = 20;
    	cout << t1.*p << endl;
    
    	t2->*p = 30;
    	cout << t2->*p << endl;
    
    	int* p1 = &Test::mb;
    	*p1 = 40;
    	cout << *p1 << endl;
    
    	delete t2;
    	return 0;
    }
    

    输出为:

  • 相关阅读:
    【分布式能源的选址与定容】基于非支配排序多目标粒子群优化算法求解分布式能源的选址与定容附Matlab代码
    数据库基础入门 — SQL
    1137. 第N个泰波那契数- 力扣
    使用原生div制作table表格
    期权开户流程、交易时间和规则详解清晰易懂
    MySQL 8 - 处理 NULL 值 - is null、=null、is not null、<> null 、!= null
    kafka的java客户端-生产者
    通过媒体查询来实现 WPF 响应式设计
    IntelliJ IDEA 左侧Commit栏不见了
    [含毕业设计论文+PPT+源码等]ssm后勤服务|会议室预约|办公管理管理系统小程序+Java后台管理系统|前后分离VUE
  • 原文地址:https://www.cnblogs.com/woden3702/p/16273541.html