C++:类和对象

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值