施磊c++基础3

面向对象(OOP)

3.1 类与对象基础

OOP的四大特征是什么?
抽象 — 封装 — 继承 — 多态

类 -》 实体
访问限定符:
public (公共),private (私有),protected (保护)

对于成员属性

#include<iostream>
using namespace std;

const int NAME_LEN = 20;
class CGOODS
{
private:
	char _name[NAME_LEN];
	double _price;
	int _amount;
};
int main()
{

	CGOODS good;
	cout << good._price << endl;
	return 0;
}

报错
在这里插入图片描述
所以,属性一般都是私有的,能具有更高的安全性

而为了能访问属性,一般会提供公有的方法

public://公有的成员方法
	void init(char* name, double price, int amount);
	void show();

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

类体内实现的方法,自动处理成inline内联函数

而在类外定义的成员方法,则会和一般函数一样有函数调用过程


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()
{

	CGOODS good1;
	//cout << good._price << endl;
	good1.init("面包", 10.0, 200);
	good1.show();
	good1.setPrice(20.5);
	good1.show();

	CGOODS good2;

	good2.init("空调", 10000,50);
	good2.show();
	return 0;
}

计算对象的内存大小,对象的内存大小只和成员变量有关。(与struct结构体相同)

类可以定义无数个对象,每一个对象都有自己的成员变量,但是共用一套成员方法。

那么成员方法怎么知道处理哪个对象的信息?
init(name,price,amount)=》怎么知道初始化哪个对象?
答:会把调用的对象的地址传进该成员方法
即,类的成员方法一经编译,所有的方法参数,都会加一个this指针,接收调用该方法的对象的地址

3.2 构造函数与析构函数

先来一个顺序栈代码

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


	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;
	}
private:
	int* _pstack;
	int _top;
	int _size;


};

int main()
{
	SeqStack s;
	s.init(20);//初始化

	for (int i = 0; i < 15; i++)
	{
		s.push(rand()% 100);

	}

	while (!s.empty())
	{
		cout << s.top() << " ";
		s.pop();
	}

	s.release();//释放对象成员变量的外部堆内存

	return 0;

}

此时就会发现,我们很容易吧初始化或者最后释放内存操作忘记

这也是构造和析构的意义

3.3 深拷贝和浅拷贝

之前提到,成员方法是通过this指针来选择要操作的对象,所有对象都共用一套成员方法

构造函数:调用对象时自动调用;可重载
析构函数:不带参数,不能重载,只有一个析构函数;析构完成,对象就不存在了

浅拷贝创建一个新的对象,但是对于对象中的引用类型成员变量,它仅仅复制了引用,而不是引用所指向的对象。也就是说,新对象和原对象中的引用类型成员变量指向同一个内存地址。

深拷贝不仅复制对象本身,还复制对象所包含的所有引用类型成员变量所指向的对象,即创建一个完全独立的副本。这样,新对象和原对象之间没有任何共享的内存。

案例:
在这里插入图片描述
上图的浅拷贝中,s2仅仅是复制了一份s1对象的内容的一个新对象,但他们里面指针指向的仍然是同一块内存,当s1析构之后,这块内存就被释放,此时s2析构时去释放一块不存在的内存,就会出错。

所以,当对象占用外部资源时,浅拷贝就容易出差。
默认的拷贝构造函数就是浅拷贝

此时就要自己写拷贝构造函数(深拷贝)

SeqStack(const SeqStack& src) : _top(src._top), _size(src._size) {
        _pstack = new int[_size];
        for (int i = 0; i <= src._top; i++) {
            _pstack[i] = src._pstack[i];
        }
    }

~SeqStack() {
    delete[] _pstack;
    _pstack = nullptr;
}

值得注意的是,如果使用了深拷贝,那么=运算符也要重载,因为=运算符使用的也是浅拷贝

	//赋值重载运算符
	void operator=(const SeqStack& src)
	{
		_pstack = new int[src._size];

		//先释放当前对象占用的外部资源
		delete[]_pstack;

		for (int i = 0; i <= src._top; i++)
		{
			_pstack[i] = src._pstack[i];
		}

		_top = src._top;
		_size = src._size;

	}
3.4 类和对象代码应用实践

示例代码:

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

	}

	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)
	{

		if (this == &other)
		{
			return *this;
		}

		delete[] m_data;


		m_data = new char[strlen(other.m_data) + 1];
		strcpy(m_data, other.m_data);

	}

private:

	char* m_data;


};

int main()
{
	//调用const *char参数的构造函数 
	String str1;
	String str2("hello");
	String str3 = "world";

	//调用拷贝构造函数
	String str4 = str3;
	String str5(str3);
	
	//调用赋值重载函数
	str1 = str2;

	
}

实现一个循环队列

#include<iostream>
using namespace std;
# if 0
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';
		}

	}

	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)
	{

		if (this == &other)
		{
			return *this;
		}

		delete[] m_data;


		m_data = new char[strlen(other.m_data) + 1];
		strcpy(m_data, other.m_data);

	}

private:

	char* m_data;


};

int main()
{
	//调用const *char参数的构造函数 
	String str1;
	String str2("hello");
	String str3 = "world";

	//调用拷贝构造函数
	String str4 = str3;
	String str5(str3);
	
	//调用赋值重载函数
	str1 = str2;

	
}
#endif


class Queue 
{
public:
	Queue(int size = 20)
	{
		_pQue = new int[size];
		_front = _rear = 0;
		_size = size;
	}

	~Queue()
	{
		delete[]_pQue;
		_pQue = nullptr;
	}

	void addQue(int val) {
		if (full())
			resize();

		_pQue[_rear] = val;
		_rear = (_rear + 1) % _size;

	}

	void pop()
	{
		if (empty())
			return;
		_front = (_front + 1) % _size;
	}

	int top()
	{
		return _pQue[_front];
	}

	bool full() {
		return (_rear + 1) % _size == _rear;
	}

	bool empty() {
		return _front == _rear;
	}

	void resize() {

		int* ptmp = new int[2 * _size];
		int index = 0;

		for (int i = _front; i != _rear; i = (i + 1) % _size)
		{
			ptmp[index++] = _pQue[i];
		}

	}
private:
	int* _pQue;
	int _front;
	int _rear;
	int _size;

};


3.5 掌握构造函数的初始化列表

原始类:

class CGoods
{
public:
	CGoods(char* n, int a, double p)
	{
		strcpy(_name, n);
		_amount = a;
		_price = p;
	}


	void show()
	{
		cout << "name:" << _name << endl;
		cout << "amount:" << _amount << endl;
		cout << "price:" << _price << endl;

	}

private:
	char _name[20];
	int _amount;
	double _price;
};

现在,想给该类添加一个日期的属性,我们用一个类Cdate来表示:

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

如何将他们结合到一起呢?

问题来了 — 如果直接在CGoods类的成员变量里添加一个Cdate _date;
那么就会出错,原因是,在创建一个CGoods类时,由于在构造函数中并没有指明_date要如何构造,使用会使用Cdate的默认构造函数,但在Cdate中,我们重写了他的构造函数,那么他就不会提供默认的构造函数了。

使用初始化列表对_date进行初始化

	CGoods(char* n, int a, double p,int y,int m,int d)
		:_date(y,m,d)//先执行
	{
	//后执行
		strcpy(_name, n);
		_amount = a;
		_price = p;
	}

也可以都写到初始化列表上

	CGoods(char* n, int a, double p,int y,int m,int d)
		:_date(y, m, d), _amount(a),_price(p)
	{
		strcpy(_name, n);
	}

测试无误:

int main() {
	CGoods good("商品", 100, 35.0, 2019, 5, 12);
	good.show();

	return 0;
}
3.6 指向类成员的指针

示例代码:

#include<iostream>
using namespace std;

class Test {
public:
	void func() { cout << "call Test::func" << endl; }
	static void static_func() { cout << "Test::static_func" << endl; }
	int ma;
};


int main() {


	int* p = &Test::ma;

	return 0;
}

此时会出错
因为调用类的成员变量需要依赖于对象,没有对象的情况下,根本没有一块内存存在,更无法用指针指向。

所以添加一个对象t1

	Test t1;
	int Test::* p = &Test::ma;
	t1.*p = 20;
	cout << t1.*p << endl;

成功

而对于类中的静态变量

class Test {
public:
	void func() { cout << "call Test::func" << endl; }
	static void static_func() { cout << "Test::static_func" << endl; }
	int ma;
	static int mb;
};
int Test::mb;
int main() {
	*p1 = 40;
	cout << *p1 << endl;
	return 0;
}

首先,类内静态变量要在类外声明
其次,静态变量有自己的一块内存,所以可以被指针指向,就不需要依赖于对象

指向成员方法的指针

	void(*pfunc)() = &Test::func();
	(*pfunc)();

错误 — 需要依赖对象
同上,创建一个t1对象来访问

3.7 补充
  1. 普通成员方法特点 =》编译器会添加一个this形参变量
    1.属于类的作用域
    2.调用该方法时,需要依赖一个对象
    3.可以任意访问对象的私有成员变量

  2. static静态成员方法 =》不会产生this形参
    1.属于类的作用域
    2.用类名作用域来调用方法
    3.可以任意访问对象的私有成员,仅限于不依赖对象的成员
    (只能调用其他static静态成员)

  3. const常成员方法=》const CGoods *this
    1.属于类的作用域
    2.调用依赖一个对象,普通对象或者常对象都可以
    3.可以任意访问对象的私有成员,但是只能读而不能写

 const CGoods good5("非卖品", 10, 45, 2019, 5, 12);//一个常对象
good5.show();//报错
//常对象调用普通方法=》const CGoods* => CGoods *this是不正确的

必须形参也是const类型
需要复制一份show方法
然后在后面加const

void show() const
{...
}

这样的话就是
const CGoods* => const CGoods *this
那就没问题了

所以,只读操作的成员方法,最好一律实现为const常成员方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值