主要讲:OOP编程和this指针;
类是实体的抽象类型;用OOP编程,首先要在问题场景中寻找实体,得到实体的属性和行为二方面=》ADT(abstract data type)得到抽象数据类型,就可以在编程中定义类了;
类(属性->成员变量,一般都是私有的,行为->成员方法,提供一些共有的成员方法让外部访问成员变量):
OOP语言的四大特征是:抽象;封装(隐藏-通过访问限定符实现),继承,多态;
构造函数可以有参数,构造函数可以重载多个;
析构函数没有参数,析构函数只能有一个;
创建类对象时候开辟内存并且调用构造函数;
问题:自定义实现一个可以扩容的栈
class SeqStack {
private:
int* _pstack; //动态开辟数组,存储顺序栈的元素
int _top;//指向栈顶元素的位置
int _size; //数组扩容的总大小
void resize() {
int* ptmp = new int[_size * 2]; //申请新的内存空间,是之前的2倍
for (int i = 0; i < _size; i++) {
ptmp[i] = _pstack[i];
}
//注意这里没用memcpy和realloc,它们是内存拷贝,不适合对象拷贝。涉及深拷贝,浅拷贝问题。
delete[] _pstack; //删除就的内存空间
_pstack = ptmp;//_pstack指向新的内存空间
_size *= 2; //更新size
}
public:
SeqStack(int size = 10) {
_pstack = new int[size];
_top = -1;
_size = size;
}
~SeqStack() {
delete[] _pstack;
_pstack = nullptr;
}
//因为类对象有依赖外部资源,所以默认的拷贝构造函数的浅拷贝会出现问题,所以需要深拷贝
SeqStack(const SeqStack& src) { //拷贝构造函数
_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;
}
//注意Operator =涉及:自我赋值和异常处理二个原则,这里省略,后面讲
delete[] _pstack; //先释放当前空间
_pstack = new int[src._size]; //申请新的空间,注意new可能出现异常,这里省略,后面讲
for (int i = 0; i <= src._top; i++) {//对新空间的元素赋值
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._top;
}
void push(int val) {
if (full()) {
resize();
}
_pstack[++_top] = val;
}
void pop() {
if (empty()) {
return;
}
--_top;
}
bool empty() {
return _top == -1;
}
bool full() {
return _top == _size - 1;
}
int top() {
return _pstack[_top];
//这里不用判断空,因为外部调用top通常结合empty使用
}
};
this指针干什么用的呢?一个类可以产生很多对象,类的函数是被这些对象共享的。那么成员函数是怎么区分不同对象的呢?编译器会给成员函数加上一个隐藏的形参this指针,用来接受要调用该方法的对象。
对象默认的拷贝构造函数是浅拷贝;
对象默认的拷贝赋值运算符是浅拷贝;
如果类对象有占用外部资源,比如有指针指向其他动态分配的内存,那么浅拷贝就会出现问题;需要深拷贝,
构造函数的初始化列表: 成员变量的初始化顺序和它们在类中定义的顺序有关,与初始化列表的先后顺序无关;
什么时候必须用成员初始化列表呢?
- 成员变量是引用类型(引用必须要初始化)
- 成员变量是const类型(const变量必须要初始化)
- 有基类,基类的构造函数有参数;
- 成员变量是类类型,且这个类类型有构造函数
使用成员初始化列表的好处?
成员初始化列表可以提高运行效率,成员初始化列表的代码最后会被编译器编译到大括号的用户代码之前进行执行;这样在函数体内就不需要再次给成员变量进行初始化了;
类的各种成员函数以及区别:
普通成员函数:1)属于类的作用域2)调用该方法,需要通过对象3)可以任意访问对象的私有成员变量 ;普通成员函数有一个隐藏的形参this指针,当某个对象调用普通成员函数时候,这个对象的地址会给那个隐藏的形参this指针。
静态成员变量在类中是声明,需要在类外定义;静态成员变量不属于对象,属于类的作用域之下。
比如通过一个类创建了多个对象,想统计对象有多少个?可以在类中声明一个静态成员变量(注意需要在类外定义),然后在类的构造函数中对静态成员变量加1;那么每次创建对象都会调用构造函数,而构造函数对静态成员变量加1,这样就可以通过静态成员变量来记录总共创建了多少类对象了。
静态成员函数:当访问类中的静态成员的时候,使用静态成员方法,不需要对象,直接通过类名调用。静态成员函数没有this指针,不能访问类的普通成员,只能访问类的静态成员。静态成员函数属于类的作用域;
如果不想一个对象被修改,可以将这个对象定义为const对象;const对象只能调用const成员函数;所以类中不改变调用对象的函数应该定义为const成员函数;更深层原因是如果cosnt对象调用普通成员函数,普通成员函数的隐藏形参this指针类型是 T* ,而传递给这个隐藏形参this的是const T* 类型,不能从T* 转const T* ,所以出错;而const成员函数的隐藏形参this指针类型是cont T* ,那么就可以了。所以同名的成员函数和const成员函数是重载关系,因为隐藏形参this类型不同;
非const对象可以调用const成员函数也可以调用普通成员函数;而const对象只能调用const成员函数;所以建议把类中的不改变对象的成员函数(读操作)定义为const 成员函数,这样const对象和非const对象都可以调用了。
所以这些成员函数的区别根本是this指针,静态成员函数无this指针,const成员函数是const的this指针,普通成员函数是非const 的this指针。
注:静态成员函数可以想象成一个普通全局函数,只不过是落在了类的作用域里面,它没有this指针,只能访问类中的静态成员;