面向对象(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 补充
-
普通成员方法特点 =》编译器会添加一个this形参变量
1.属于类的作用域
2.调用该方法时,需要依赖一个对象
3.可以任意访问对象的私有成员变量 -
static静态成员方法 =》不会产生this形参
1.属于类的作用域
2.用类名作用域来调用方法
3.可以任意访问对象的私有成员,仅限于不依赖对象的成员
(只能调用其他static静态成员) -
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常成员方法

1959

被折叠的 条评论
为什么被折叠?



