一、类和对象、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; }
输出为: