类和对象、this指针
OOP语言的四大特征是什么?:
抽象 封装/隐藏 继承 多态访问限定符:
public 公有的 给外部提供公有方法,来访问私有的属性
private 私有的 不能在类外访问 属性都是私有的
protected 保护的1.类体内实现的方法,自动处理成inline内联函数
2.常量字符串是不允许让普通指针接收
3.类的内存大小计算:
只和成员变量有关,和成员方法无关
char _name[20]; 占1字节
double _price; 占8字节
int _amount; 占4字节
首先找最长的占用字节类型,最长的是8字节,按照最长的补位:char 类型有20个就是20字节,能整除最长的字节也就是24;所以要补4个字节对齐
double 类型有8字节
int 类型也补了4空白字节对齐
所以CGoods类的大小就是40字节4.可以定义无数个对象,每个对象都有自己的成员变量,但是它们共享一套成员方法
那么问题来了:
show() => 怎么知道处理哪个对象的信息的呢?
init(name, price, amount) =>怎么知道把信息初始化给哪一个对象的呢?
this 指针就是做这个事的:
类的成员方法一经编译,所有的方法参数,都会加一个this指针 比如:
init(&CGoods *this,const char * name,double price, int amount)
所以调用init的时候 比如::
goods.init(&goods,"面包",10.0,200); //这会把对象地址传了进去class CGoods{ public: //可以用一个函数初始化私有变量. void init(const char *name,double price, int amount); //打印 void show(); const char* getName() { return _name; } double getPrice() { return _price; } int getAmount() { return _amount; } private: char _name[20]; double _price; int _amount; }; //类外定义函数 void CGoods::init(const char *name, double price, int amount) { strcpy(_name, name); _price = price; _amount = amount; } void CGoods::show() { cout << "name:" << _name << ", _price:" << _price << ", amount:" << _amount <<endl; } int main() { CGoods goods; //初始化对象, goods.init("面包", 10.2, 100); goods.show(); return 0; }
构造函数和析构函数以及深拷贝和浅拷贝
构造函数和析构函数 :
构造函数: SeqStack()
定义对象时,自动调用的;可以重载的,构造完成,对象就产生了
析构函数: ~SeqStack()
不带参数,不能重载,只有一个析构函数;出了当前作用域,就会自动调用析构函数,析构完成,这个对象就不存在了
注意:没有提供任何构造函数的时候,会为你生成默认构造和默认析构,是空函数深拷贝和浅拷贝
SeqStack s1(10);
SeqStack s2 = s1; //拷贝
浅拷贝 : 直接拷贝过去,是堆内存的数据也是直接拷贝内存的地址,所以s1,s2 指向的是同一块内存
代码:SeqStack(const SeqStack &src) { _pstack = src._pstack; _price = src.price; _amount = src._amount; }
解释:
s1
int *_pstack; ----> new 0x61feb4
int _top ; ----> 10
int _size; ----> 50
s2
int *_pstack; ----> new 0x61feb4
int _top ; ----> 10
int _size; ----> 50
1.析构的时候先是析构的s2,然后s2的_pstack被delete,s1的_pstack还是指向0x61feb4,所以到s1析构的时候,0x61feb4地址成野指针,释放野指针就会程序出现异常报错
2.对象默认的拷贝构造是做内存的数据拷贝
3.关键是对象如果占用外部资源,那么浅拷贝就出现问题了!深拷贝:
SeqStack(const SeqStack &src) { //防止自赋值 if (this == &src) return ; _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.cpp
//OOP 实现顺序栈 #include <iostream> using namespace std; class SeqStack { public: //构造函数 SeqStack(int size = 10) { cout << this << " SeqStack()" <<endl; _pstack = new int[size]; _top = -1; _size = size; } //析构函数 ~SeqStack() { cout << this << " ~SeqStack()" <<endl; 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) { 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; } //成员函数 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; //总容器大小 private: 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 s(5); //栈上调用成员的方法 s.push(1); //堆上开辟内存必须要自己释放 SeqStack *ps = new SeqStack(60); ps->push(70); ps->push(80); ps->pop(); cout << ps->top() <<endl; delete ps; //这里的delete先调用类的析构函数,然后再把内存释放 SeqStack s1(10); SeqStack s2 = s1; //拷贝构造函数 SeqStack s3(s1); s2 = s1; //赋值操作,默认的赋值操作也是做浅拷贝 }
深拷贝例子:
#include <iostream> #include <cstring> using namespace std; class String { public: String(const char *str = nullptr) //普通构造函数 { if (nullptr == str) { m_data = new char[1]; *m_data = '\0'; return ; } m_data = new char[strlen(str) + 1]; strcpy(m_data,str); } String(const String &thor) //拷贝构造函数 { if(this == &thor) return; m_data = new char[strlen(thor.m_data) + 1]; strcpy(m_data,thor.m_data); } ~String(){ delete []m_data; m_data = nullptr; } //赋值重载函数 String& 是为了支持连续的operator= 赋值操作 String& operator=(const String &thor){ if(this != &thor) { delete []m_data; m_data = new char[strlen(thor.m_data) + 1]; strcpy(m_data,thor.m_data); } return *this; } private: char *m_data;//用于保存字符串 }; int main(){ String s; String s1("Hello"); String s2 = s1; String s3 = s2; s2 = s1; s3 = s2 = s1; //连续赋值 return 0; }
#include <iostream> using namespace std; class Queue { public: Queue(int size = 10){ _pQue = new int[size]; _front = _rear = 0; _size = size; } //禁用拷贝构造和赋值构造 // Queue(const Queue &src) = delete; // Queue& operator=(const Queue &src) = delete; Queue(const Queue &src) { if(this == &src) return ; _size = src._size; _front = src._front; _rear = src._rear; _pQue = new int[_size]; for (int i = _front; i != _rear; i = (_rear + 1) % _size ) { _pQue[i] = src._pQue[i]; } } Queue& operator=(const Queue &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 = (_rear + 1) % _size) { _pQue[i] = src._pQue[i]; } return *this; } ~Queue(){ delete []_pQue; _pQue = nullptr; } bool full() { return (_rear + 1) % _size == _front; } bool empty() { return _front == _rear; } void push(int val) { // cout << val <<endl; if (full()) resize(); _pQue[_rear] = val; _rear = (_rear + 1) % _size; } void pop() { if(!empty()) _front = (_front + 1) % _size; } int front() { return _pQue[_front]; } private: int *_pQue; // 申请队列的数组空间 int _front; //指向队头的位置 int _rear; //指向队尾的位置 int _size; //队列扩容的总大小 void resize() { int *temp = new int[_size * 2]; int index = 0; for (int i = _front; i != _rear; i = ( i + 1) % _size) { temp[index ++] = _pQue[i]; } delete []_pQue; _pQue = temp; _front = 0; _rear = index; _size *= 2; } }; int main() { Queue queue; for(int i = 0; i < 20; ++i) { queue.push(rand() % 100); } while (!queue.empty()) { cout << queue.front() << "\t"; queue.pop(); } return 0; }
构造函数和初始化列表
构造函数的初始化列表
1.初始化列表给成员变量是直接初始化的,比如 int a = 1;
2.构造的函数体内初始化变量是先定义,然后再赋值;
3.特殊变量必须要用初始化列表,比如:
一个类中包含成员对象,成员对象是自定义的构造函数:CDate _date;
1)如果在函数体内初始化就会发生这样的事情:先定义 CDate _date;就会调用默认构造,我们自定义的构造函数,所以找不到默认构造就会出错,就会编译错误
2)如果在初始化列表中就会直接初始化:CDate _date = CDate(y,m,d)
4.成员变量的初始化和它们定义的顺序有关,和构造函数初始化列表中的出现的先后顺序无关!#include <iostream> #include <cstring> using namespace std; //构造函数的初始化列表 class CDate { public: CDate(int y, int m, int d) //自定义构造函数,编译器就不会产生默认构造了 { _year = y; _month = m; _day = d; } void show() { cout << _year <<"-" << _month <<"-" << _day <<endl; } private: int _year; int _month; int _day; }; class CGoods { public: CGoods(const char *n,int a,double p,int y,int m,int d) :_date(y,m,d) //#1 构造函数的初始化列表 ==> CDate(y,m,d) ,_amount(a) //相当于 int _amount = a; 直接初始化 { //#2 当前类类型构造函数体 strcpy(_name,n); //相当于 int _price, _price = p; 先定义后赋值 _price = p; } void show() { cout << "name:" << _name << endl; cout << "amount:" << _amount << endl; cout << "price:" << _price << endl; _date.show(); } private: char _name[20]; int _amount; double _price; CDate _date; //成员对象 1.分配内存 2.调用构造函数 }; int main() { CGoods goods1("面包1",100,15.0,2022,1,21); goods1.show(); CGoods goods2("面包2",200,25.0,2022,1,21); goods2.show(); CGoods goods3("面包3",300,35.0,2022,1,21); goods3.show(); CGoods goods4("面包4",400,45.0,2022,1,21); goods4.show(); CGoods goods5("面包5",500,55.0,2022,1,21); goods5.show(); return 0; }
类的各种成员方法以及区别
假设上面的CGoods类商品需要统计一下它的数量,该怎么统计呢?
1.如果在类里边直接定义一个_count() 就会变成每个类都有一个了,所以这个行不通
2.虽然全局变量可以,但是我们现在需要用类成员来统计,所以也不行
3.所以这个应该使用静态的成员变量
静态成员变量:在类内部定义,必须要在类外进行定义并且初始化
不属于对象,而是属于类级别的全局变量
4.静态方法没有this指针,不需要接受对象的地址,调用的时候用类名的作用域来调用就行
注意:静态方法可以任意访问对象的私有成员,仅限于不依赖对象的成员(只能调用其他的static静态成员)
普通变量和静态成员变量的核心区别就是:
普通的方法 ===》编译器会添加一个this行参变量
static静态成员方法===〉 不会生成this行参
总结:
普通成员方法:
1.属于类的作用域
2.调用该方法时,需要依赖一个对象(常对象是无法调用的,实参const CGoods* CGoods *this)
3.可以任意访问对象的私有方法 protected继承。public private
static静态成员方法:
1.属于类的作用域
2.用类名作用域来调用方法
3.可以任意访问对象的私有成员,仅限于不依赖对象的成员(只能调用其他static静态成员)
const常方法:
1.属于类的作用域
2.调用依赖一个对象,普通对象或者常对象都可以
3.可以任意访问对象的私有成员,但是只能读不能写。
class CDate { public: CDate(int y, int m, int d) //自定义构造函数,编译器就不会产生默认构造了 { _year = y; _month = m; _day = d; } void show() { cout << _year <<"-" << _month <<"-" << _day <<endl; } void show() const { cout << _year <<"-" << _month <<"-" << _day <<endl; } private: int _year; int _month; int _day; }; class CGoods { public: CGoods(const char *n,int a,double p,int y,int m,int d) :_date(y,m,d) //#1 构造函数的初始化列表 ==> CDate(y,m,d) ,_amount(a) //相当于 int _amount = a; 直接初始化 { //#2 当前类类型构造函数体 strcpy(_name,n); //相当于 int _price, _price = p; 先定义后赋值 _price = p; _count ++;//构造的时候增加一个商品统计 } void show() { cout << "name:" << _name << endl; cout << "amount:" << _amount << endl; cout << "price:" << _price << endl; _date.show(); } //常成员方法 const CGoos *this void show() const { cout << "name:" << _name << endl; cout << "amount:" << _amount << endl; cout << "price:" << _price << endl; _date.show(); } //静态成员方法,只需要用类名调用 static void showCGoodsCount() { cout << "所有商品的数量:" << _count << endl; } private: char _name[20]; int _amount; double _price; CDate _date; //成员对象 1.分配内存 2.调用构造函数 //静态成员变量,在这里只是一个声明,必须要要在类外去定义,并且初始化 //存在于数据段上,只有一个 static int _count; }; //定义静态变量 int CGoods::_count = 0; int main() { CGoods goods1("面包1",100,15.0,2022,1,21); goods1.show(); CGoods goods2("面包2",200,25.0,2022,1,21); goods2.show(); CGoods goods3("面包3",300,35.0,2022,1,21); goods3.show(); CGoods goods4("面包4",400,45.0,2022,1,21); goods4.show(); CGoods goods5("面包5",500,55.0,2022,1,21); goods5.show(); //只能调用常方法 //只要是只读操作的成员方法,一律实现成const常成员方法 const CGoods goods6("样品面包",500,55.0,2022,1,21); goods6.show(); CGoods::showCGoodsCount(); return 0; }
指向类成员的指针
class Test { public: void func() { cout << "call Test::func" << endl; } static void static_func() { cout << "Test::static_func" << endl; } int ma; };
指向成员变量的指针:
int main() { Test t1; Test *t2 = new Test(); int Test::*p = &Test::ma; t1.*p = 20; cout << t1.*p << endl; t2->*p = 30; cout << t2->*p << endl; return 0; }
指向成员方法的指针:
int main() { Test t1; Test *t2 = new Test(); void (Test::*pfunc)() = &Test::func; (t1.*pfunc)(); (t2->*pfunc)(); delete t2; return 0; }