OOP语言的四大特征是什么?
抽象、封装/隐藏、继承、多态
一、类和对象、this指针
1.类(属性->成员变量 行为->成员方法)(实例化)-> 对象
类 =>商品实体
访问限定符:pubilic公有的 private私有的 protected保护的
#include<iostream>
using namespace std;
const int NAME_LEN = 20;
class CGoods
{
public://给外部提供公有的成员方法,来访问私有的属性
void init(const char *name, double price, int amount);
void show();
//给成员变量提供一个getXXX或setXXX的方法,类体内实现的方法,自动处理成inline内联函数
void setName(char *name){strcpy(_name, name);}
void setPrice(double price){_price = price;}
void setAmount(int amount){_amount = amount;}
const char* getName(){return _name;}
double getPrice(){return _price;}
int getAmount(){return _amount;}
private://属性一般是私有的成员变量
char _name[NAME_LEN];
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<<endl;
cout<<"price:"<<_price<<endl;
cout<<"amount:"<<_amount<<endl;
}
int main()
{
//对象内存大小,只与成员变量有关,与成员方法无关
//工具=》vs命令提示符=》切到代码所在文件路径=》/dlreportSingleClassLayoutCGoods
CGoods good;
good.init("面包",10.0,200);
good.show();
good.setPrice(20.0);
good.setAmount(100);
good.show();
CGoods good2;
good2.init("空调",10000.0,20);
good2.show();
return 0;
}
2.this指针
?show()=>怎么知道处理哪个对象的信息?
?void init(const char *name, double price, int amount)=>怎么知道把信息初始化给哪一个对象?
类的成员方法一经编译,所有的方法参数,都会加一个this指针(CGood *this),接收调用该方法的对象的地址。
二、构造函数与解析函数
1.OOP实现一个顺序栈:
#include<iostream>
using namespace std;
class SeqStack
{
public:
void init(int size = 10)
{
_pstack = new int[size];
_top = -1;
_size = size;
}
void release()
{
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 s;
s.init(5);
for(int i = 0; i < 15; i++)
{
s.push(rand() % 100);
}
while(!s.empty())
{
cout<< s.top()<<" "; //输出15个值,说明扩容成功!
s.pop();
}
s.release();
return 0;
}
2.构造函数和析构函数
函数的名字和类名一样,没有返回值。
析构函数是不带参数的,所有析构函数只能有一个,但构造函数可以带参数,因此可以提供多个构造函数,叫做析构函数的重载。
先构造的后析构,相当于入栈出栈操作!!!(见下面代码输出)
#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;
}
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()
{
//1.开辟内存 2.调用构造函数
SeqStack s; //第一种构造方式,size为10
//s.init(5);
for(int i = 0; i < 15; i++)
{
s.push(rand() % 100);
}
while(!s.empty())
{
cout<< s.top()<<" "; //输出15个值,说明扩容成功!
s.pop();
}
//s.release();
SeqStack s1(50); //第二种构造方式,size为20
//先构造的后析构,相当于入栈出栈操作!!!
//打印结果:
//0093F868 SeqStack()
//61 27 81 45 5 64 62 58 78 24 69 0 34 67 41 0093F848 SeqStack()
//0093F848 ~SeqStack()
//0093F868 ~SeqStack()
return 0;
}
以上是对于“栈stack”的对象,还有“数据端.data”,“堆heap”:
对于全局对象global,定义的时候构造,程序结束的时候析构,因为全局变量在数据端上。
堆上的对象,需要手动析构。
SeqStack gs;
int main()
{
SeqStack *ps = new SeqStack(60);
ps->push(70);
ps->push(80);
cout<<ps->top()<<endl;
delete ps;
三、对象的深拷贝和浅拷贝
对象默认的拷贝构造是做内存的数据拷贝,关键是对象如果占用外部资源,那么浅拷贝就出现问题了!
浅拷贝一定要自定义拷贝构造函数和赋值重载函数!
#include<iostream>
using namespace std;
class SeqStack
{
public:
//构造函数(开辟资源)
SeqStack(int size = 10)
{
cout<<this<<" SeqStack()"<<endl;
_pstack = new int[size];
_top = -1;
_size = size;
}
//自定义拷贝函数《= 对象的浅拷贝出现问题(使用指针占用外部资源)
//因此为深拷贝!!!
//只能for循环,使用memcpy(ptmp, _pstack, sizeof(int)*_size)相当于是浅拷贝
SeqStack(const SeqStack &src)
{
cout<<"SeqStack(const SeqStack &src)"<<endl;
_pstack = new int[src._size];
for(int i = 0; i<= src._size; i++)
{
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._size;
}
//析构函数(释放资源)
~SeqStack()
{
cout<<this<<" ~SeqStack()"<<endl;
delete []_pstack;
_pstack = nullptr;
}
void operator=(const SeqStack &src)
{
cout<<"operator="<<endl;
//需要先释放当前对象占用的外部资源
delete []_pstack;
_pstack = new int[src._size];
for(int i = 0; i<= src._size; 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;//数组扩容的总大小
void resize()
{
int *ptmp = new int[_size * 2];
for(int i = 0; i < _size; i++)
{
ptmp[i] = _pstack[i];
} //memcpy(ptmp, _pstack, sizeof(int)*_size)
delete []_pstack;
_pstack = ptmp;
_size *= 2;
}
};
int main()
{
SeqStack s; //没有提供让任何构造函数的时候,会为你生成默认构造和默认析构,是空函数
SeqStack s1(10);
SeqStack s2 = s1; //第一种,默认拷贝构造函数(浅拷贝)=》做直接的内存+数据拷贝
SeqStack s3(s1); //第二种
s2=s1;//默认赋值函数=》做直接的内存+数据拷贝,因此需要先释放当前对象占用的外部资源,后续步骤与深拷贝相同
}
四、类和对象的应用实践
1.String类型
#pragma warning( disable : 4996)
#include<iostream>
using namespace std;
class String
{
public:
String(const char* str = nullptr) //普通构造函数
{
if(str != nullptr) //先判断外部字符串是否为空
{
m_data = new char[strlen(str) + 1]; //再对底层字符串开辟堆内存
strcpy(this->m_data, str);
}
else
{
m_data = new char[1];
*m_data = '\0'; // 0
}
}
String(const String &other) //拷贝构造函数
{
m_data = new char[strlen(other.m_data) + 1];
strcpy(m_data, other.m_data);
}
~String(void) //析构函数
{
delete[]m_data;
m_data = nullptr;
}
String& operator=(const String &other) //赋值重载函数
//返回值String& 支持连续赋值操作,比如str3=str2=str1;
//可以返回void,但不能连续赋值了
{
if (this == &other)
{
return *this;
}
delete[]m_data;
m_data = new char[strlen(other.m_data) + 1];
strcpy(m_data, other.m_data);
return *this;
}
private:
char* m_data; //用于保存字符串
};
int main()
{
String str1;
String str2("HELLO");
String str3 = "word";
//初始化,等号左边对象正在构造,调用拷贝构造函数
String str4 = str3;
String str5(str3);
//赋值,等号两边对象都已存在,调用赋值重载函数
str3 = str1 = str2;
return 0;
}
2.循环队列
新插入的元素只能添加到队尾,被删除的只能是排在队头的元素。
遵守“队列先入先出”原则。
#include<iostream>
using namespace std;
class Queue
{
public:
Queue(int size = 5)
{
_pQue = new int[size];
_front = _rear = 0;
_size = size;
}
~Queue()
{
delete[]_pQue;
_pQue = nullptr;
}
Queue(const Queue& 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];
}
}
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 = (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];
index++;
}
delete[]_pQue;
_pQue = ptmp;
_front = 0;
_rear = index;
_size = _size * 2;
}
};
int main()
{
Queue queue;
for (int i = 0; i < 20; i++)
{
queue.push(rand() % 100);
}
while (!queue.empty())
{
cout << queue.front() << " ";
queue.pop();
}
cout << endl;
Queue queue1 = queue;
queue1 = queue;
return 0;
}
五、构造函数的初始化列表
作用:可以指定当前对象成员变量的初始化方式。
但需注意:成员变量的初始化和它们定义的顺序有关,和构造函数初始化列表中出现的先后顺序无关!最好以定义的顺序来初始化列表
以下面例子为例,CDate是CGoods商品信息的一部分, a part of ...组合的关系:
#pragma warning( disable : 4996)
#include<iostream>
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 _month;
int _year;
int _day;
};
class CGoods
{
public:
CGoods(const char* n, int a, double p, int y, int m, int d)
:_date(y, m, d) //#1 构造函数的初始化列表
,_amount(a) // == int _amount = a;
,_price(p) // == int _price = p;
{
// #2 当前类类型构造函数体
strcpy(_name, n);
//_amount = a; //int _amount; _amount = a;
//_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; //成员对象
};
int main()
{
CGoods good("商品", 100, 35.0, 2025, 10, 10);
good.show();
return 0;
}
六、类的各种成员方法与区别
1.普通成员方法
①属于类的作用域
②本质区别:编译器会添加一个this指针,调用该方法是,需要依赖一个对象(常对象是无法调用的,实参是const CGoods* => CGoods* ,无法转化)
③可以任意访问对象的私有成员变量
2.static静态成员方法
①属于类的作用域
②本质区别:编译器不会添加this指针,不需要依赖一个对象,用类名作用域(类名::成员方法名)来调用方法
③可以任意访问对象的私有成员变量,仅限于static静态变量
3.常成员方法
①属于类的作用域
②调用依赖一个对象,普通对象或者常对象都可以
③可以任意访问对象的私有成员变量,但是只能读,不能写(因为是const)
#pragma warning( disable : 4996)
#include<iostream>
using namespace std;
class CDate
{
public:
CDate(int y, int m, int d)
{
_year = y;
_month = m;
_day = d;
}
void show()const //因为CGoods::show()是常成员,所以此处也需要成为常成员
{
cout << _year << "/" << _month << "/" <<_day << endl;
}
private:
int _month;
int _year;
int _day;
};
class CGoods
{
public:
CGoods(const char* n, int a, double p, int y, int m, int d)
:_date(y, m, d) //#1 构造函数的初始化列表
,_amount(a) // == int _amount = a;
,_price(p) // == int _price = p;
{
// #2 当前类类型构造函数体
strcpy(_name, n);
//_amount = a; //int _amount; _amount = a;
//_price = p;
_count++;
}
// 普通的成员方法
void show()
{
cout << "name:" << _name << endl;
cout << "amount:" << _amount << endl;
cout << "price:" << _price << endl;
_date.show();
}
//常成员方法 只要是只读操作的成员方法,一律实现成const常成员方法
void show()const //const CGoods *this
{
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; //成员对象
static int _count; // static不属于对象,属于类级别 声明 用来记录商品对象的总数量
};
//static成员变量一定要在类外进行定义并且初始化
int CGoods::_count = 0;
int main()
{
CGoods good1("商品1", 100, 35.0, 2025, 10, 10);
good1.show();
CGoods good2("商品2", 100, 35.0, 2025, 10, 10);
good2.show();
CGoods good3("商品3", 100, 35.0, 2025, 10, 10);
good3.show();
CGoods good4("商品4", 100, 35.0, 2025, 10, 10);
good4.show();
CGoods::showCGoodsCount();
const CGoods good5("非卖商品5", 100, 35.0, 2025, 10, 10);
good5.show();
return 0;
}
七、指向类成员的指针
1.指向成员变量的指针
#include<iostream>
using namespace std;
class Test
{
public:void func() { cout << "call Test::func" << endl; }
static void static_func() { cout << "Test::static_fun" << endl; }
int ma;
static int mb;
};
int Test::mb;
int main()
{
Test t1;
Test* t2 = new Test();
int Test::* p = &Test::ma; //ma依赖于对象,所以指针需要添加类名::
t1.*p = 20; //同时,通过对象来调用指针
cout << t1.*p << endl;
t2->*p = 30;
cout << t2->*p << endl;
int *p1 = &Test::mb; //mb是static,不依赖于对象,指针不需要添加类名::
*p1 = 40;
cout << *p1 << endl;
return 0;
}
2.指向成员方法的指针
#include<iostream>
using namespace std;
class Test
{
public:void func() { cout << "call Test::func" << endl; }
static void static_func() { cout << "Test::static_fun" << endl; }
int ma;
static int mb;
};
int Test::mb;
int main()
{
Test t1;
Test* t2 = new Test();
void (Test:: * pfunc)() = &Test::func;
(t1.*pfunc)();
(t2->*pfunc)();
void(* pfunc1)() = &Test::static_func; //静态成员函数的指针
pfunc1(); //不用加*
return 0;
}