C++中的类
类是一种将抽象转换为用户定义类型的C++工具,他将数据和操作数据的方法组合成一个整洁的包。下面以抽象表示股票的类来做一个说明(应该学习的是这里的抽象过程和方法):
首先应该考虑如何表示股票?可以将一股作为基本单位,定义一个表示一股股票的类。如下:
class Stock {
public:
Stock(std::string name, double prices, time_t time) :
name_(anme),
proces_(prices),
time_(time){
// 获取购买时间
time_t getTime() { return time_; };
// 获取当前价格
double getPrices() { return prices_; };
// 更新股票价格
void setPrices(double prices) {
prices_ = prices;
}
// 获取股票名称
std::string getName() { return name_; };
private:
// 购买时间
time_t time_;
// 当前价格
double prices_;
// 股票名称
std::string name_;
}
然而这意味着需要100个对象才能表示100股,这不现实。换一个角度来考虑,我们可以对一类股票进行抽象,什么叫一类呢?也就是购买时间和购买价格相同的同一种股票。这个时候需要考虑对于此类股票的可执行操作限制:
- 增持
- 卖出股票
- 更新股票价格
- 显示关于所持股票的信息
- 股票总值
同时还需要考虑该类需要存储的信息:
- 股票名称(公司名称)
- 所持股票的数量
- 每股的价格
- 购买日期
类定义如下所示:
class Stock {
public:
Stock(std::string name, double prices, time_t time, int count) :
name_(anme),
proces_(prices),
time_(time),
count_(count){
// 获取购买时间
time_t getTime() { return time_; };
// 获取当前价格
double getPrices() { return prices_; };
// 更新股票价格
void setPrices(double prices) {
prices_ = prices;
}
// 获取总值
double getSumPrices() {
return prices_ * count_;
}
// 获取股票名称
std::string getName() { return name_; };
// 卖出股票
double saleStock(int count) {
count = count <= count_ ? count : count_;
count_ -= count;
return count * prices_;
}
// 增持股票
void addStock(int count) {
count_ += count;
}
private:
// 购买时间
time_t time_;
// 当前价格
double prices_;
// 股票名称
std::string name_;
// 股票数量
int count_;
}
什么是接口?
接口是一种共享框架,供两个系统交互时使用。其一般的使用模式如下:
其使得类定义和具体实现分离,用户在不知道具体实现类的情况下可以正常使用所定义接口。
enum class InterfaceType {
TCPSTREAM,
UDPSTREAM,
......
};
class DataStream {
public:
virtual int recv(int8_t *buf, int32_t size) = 0;
virtual int send(int8_t *buf, int32_t size) = 0;
};
// 对于接口的使用者来说, 这里使用的时候不用关注接口的具体实现细节
// 所以从另一个角度来说提高了扩展性和重用性
class User {
public:
int process(DataStream *data) {
...
int size1 = data->recv(...);
...
int size2 = date->write(...);
...
}
private:
...
};
// 实际的具体交互系统
class TcpStream : public DataStream {
public:
virtual int recv(int8_t *buf, int32_t size) {
return recv(fd_, buf, size);
}
virtual int send(int8_t *buf, int32_t size) {
return send(fd_, buf, size);
}
private:
int fd_;
...
};
class UdpStream : public DataStream {
public:
virtual int recv(int8_t *buf, int32_t size) {
return recv(fd_, buf, size);
}
virtual int send(int8_t *buf, int32_t size) {
return send(fd_, buf, size);
}
private:
int recv(int fd, int8_t *buf, int32_t size){
// 这里是封装了udp的可靠通信接口
......
}
int send(int fd, int8_t* buf, int32_t size) {
// 这里是封装了udp的可靠通信接口
......
}
private:
int fd_;
...
};
// 具体交互类
class Interaction {
public:
Interaction(User *user) : user_(user) {}
void Init(InterfaceType type) {
switch(type) {
case InterfaceType::TCPSTREAM:
stream_ = new TcpStream(...);
break;
case InterfaceType::UDPSTREAM:
stream_ = new UdpStream(...);
break;
default:
break;
}
}
int process(...) {
...
return user->(stream_);
}
private:
User *user_;
DataStream *stream_;
...
};
访问控制
类的设计应该尽可能将共有接口和实现细节分开。共有接口表示设计的抽象组件。将实现细节放在一起并将他们与抽象分开被称为封装,数据隐藏(将数据放在类的私有部分)是一种封装,将实现的细节隐藏在私有部分也是一种封装。封装的另一个例子是:将类的定义和类的声明放到不同的文件中。
数据隐藏不仅可以防止直接访问数据,还让开发者无需了解数据是如何被表示的,例如:show()成员将显示某支股票的总价格,这个值可以存储在对象中,也可以在调用时通过计算得到。从类的使用角度看,使用哪种方法没有什么区别。所需要知道的只是各种成员函数的功能;也就是说需要知道成员函数接受什么样的参数以及返回什么类型的值以及其表示的含义。原则是将实现细节从接口设计中分离出来。如果以后找到了更好的、实现数据表示或成员函数细节的方法,可以对这些细节进行修改,而无需改程序接口,这使程序维护起来更加容易。
控制对成员的访问:共有还是私有
无论类成员是数据成员还是成员函数,都可以在类的共有接口或者私有部分中声明它。但由于隐藏数据是OOP的主要目标之一,因此数据项通常放在私有部分,组成类接口的成员函数放在共有部分;否则就无法从程序中调用这些函数。通常,程序使用私有成员函数来处理不属于共有接口的实现细节。