最重要的OOP特性: 抽象; 封装和数据隐藏; 多态; 继承; 代码的可重用性。
抽象 是通往用户定义类型的捷径,在C++中,用户定义类型指的是实现抽象接口的类设计。
类 是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。
表示股票的类:
操纵方法:获得股票;增持;卖出股票;更新股票价格;显示关于所持股票的信息。
数据表示:公司名称;所持股票的数量;每股的价格;股票总值。
一般来说,类规范由两个部分组成:
private:(这个关键字可以省略),组成类接口的成员函数放在公有部分public:void Stock::update(double price)
stock00.h // 类的声明头文件
// 类的声明:类的变量和成员函数,数据和操纵数据的方法
#ifndef PRIMERPLUS_STOCK00_H
#define PRIMERPLUS_STOCK00_H
#include
class Stock // 首字母一般大写
{
private: // 这个关键字可以省略,防止其他源代码访问里面的数据,凡是在private里面的数据,只有public里面的方法可以调用。
std::string company; // 成员
long shares;
double share_val;
double total_val;
void set_total() {total_val = shares * share_val;} // 内联函数
public: // 这个关键字不能省略
Stock(); // 默认构造函数,在声明和定义时不加形参,但是定义时给每个成员初始化
// 函数重载,自定义构造函数,没有返回值,部分形参使用默认参数
Stock(const std::string &co, long n = 1, double pr = 1.0);
~Stock(); // 析构函数的声明
void buy(long num, double price); // 成员函数
void sell(long num, double price);
void update(double price);
void show() const; // const成员函数,保证函数不会修改调用对象
const Stock & topval(const Stock &s) const;
const string &company_name() const {return company;} // 返回公司的名字且不希望被修改
};
#endif //PRIMERPLUS_STOCK00_H
stock00.cpp // 类的定义源文件
// 定义类的成员函数
#include
#include "stock00.h"
using namespace std;
Stock::Stock() // 默认构造函数
{
company = "stock";
shares = 0;
share_val = 0.0;
set_total();
}
Stock::Stock(const std::string &co, long n, double pr) // 自定义构造函数
{
company = co;
if (n<0)
{
cout << "Number of shares can't be negative; "
<< company << " shares set to be 0." << endl;
shares = 0;
}
else
shares = n;
share_val = pr;
set_total();
}
Stock::~Stock() // 析构函数定义,没有参数,没有返回值,自动调用
{
cout << "Bye " << company << endl; // 类储存是以栈的方式:先进后出,后进先出
}
void Stock::buy(long num, double price)
{
if (num < 0)
cout << "Number of shares can't be negative; " << endl;
else
{
shares += num;
share_val = price;
set_total();
}
}
void Stock::sell(long num, double price)
{
if (num < 0)
cout << "Number of shares can't be negative; " << endl;
else if (num > shares)
cout << "You can't sell more than you have!" << endl;
else
{
shares -=num;
share_val = price;
set_total();
}
}
void Stock::update(double price)
{
share_val = price;
set_total();
}
void Stock::show() const
{
cout << "Company : " << company << endl;
cout << "Shares : " << shares << endl;
cout << "Share price : " << share_val << endl;
cout << "Total worth : " << total_val << endl;
}
const Stock & Stock::topval(const Stock &s) const
{
if (s.total_val > total_val) // total = this->total_val
return s;
else
return *this; // this指针指向调用成员函数的对象,this是该对象的地址。
}
usestock00.cpp // 类的使用源文件
#include
#include "stock00.h"
using namespace std;
int main(void)
{
{
Stock wind1 = Stock{"wind1"}; // 显示调用构造函数
Stock wind2{"wind2", 20, 20.2}; // 隐式调用构造函数
Stock wind3; // 自动调用默认构造函数
wind3 = wind2; // 可以将一个对象赋给同类型的另一个对象
wind3 = Stock("wind3", 30, 30.3); // 构造函数可对已存在的对象赋值
const Stock wind4 = Stock{"wind4"}; // wind4只能调用const成员函数,show()
Stock top;
top = wind1.topval(wind2);
top.show();
const int STKS = 4;
Stock mystuff[STKS]; // 创建对象数组,自动调用默认构造函数
Stock sand[STKS] = { // 创建对象数组,对不同的元素使用不同的构造函数
Stock("sand1", 11, 11.1), // 用括号括起,以逗号分割
Stock(), // 使用默认构造函数
Stock("sand3", 33) // 只初始化了三个元素,剩下的自动调用默认构造函数
};
int i;
for (i=0; i<STKS; i++)
sand[i].show();
const Stock *topsand = &sand[0]; // 定义一个类的const指针
for (i=1; i<STKS; i++)
topsand = &(topsand->topval(sand[i]));
topsand->show(); // 显示价格最高的一个对象
// Stock fluffy_the_cat;
// fluffy_the_cat.acquire("Mooncake", 20, 12.5);
// fluffy_the_cat.show(); // 通过类的对象来访问类的公有成员函数
// fluffy_the_cat.buy(15, 18.125);
// fluffy_the_cat.show();
// fluffy_the_cat.sell(400, 20.0);
// fluffy_the_cat.update(15.2);
// fluffy_the_cat.show();
} // 添加这个大括号后,析构函数调用将在到达返回语句前执行。
return 0;
}
为什么需要构造函数?数据部分的访问状态是私有的,这意味着程序不能直接访问数据成员。程序只能通过成员函数来访问数据成员,因此需要设计合适的成员函数,才能成功地将对象初始化。C++提供了一个特殊的成员函数——类构造函数,专门用于构造新对象、将值赋给它们的数据成员。
// 默认构造函数,在定义时不加形参,但是给每个成员初始化
Stock();
Stock::Stock() {...} // Stock类的默认构造函数的定义,没有形参,但是给每个成员初始化
// 函数重载,自定义构造函数,没有返回值,部分形参使用默认参数
Stock(const std::string &co, long n=1, double pr=1.0);
Stock::Stock(const std::string &co, long n, double pr) {...} // Stock类的构造函数的定义
Stock fluffy_the_cat = Stock{"Mooncake"}; // 显示调用自定义构造函数
Stock garment{"apple", 30, 123.45}; // 隐式调用自定义构造函数
Stock first; // 自动调用默认构造函数
~Stock();析构函数何时被调用:
this指针指向调用成员函数的对象,this是该对象的地址。
比较两个对象中哪个total_val值大并返回大的对象:
函数原型:const Stock & topval(const Stock & s) const;
函数调用:top = stock1.topval(stock2); // top也是一个对象
函数定义:
const Stock & Stock::topval(const Stock & s) const
{
if (s.total_val > total_val) // total = this->total_val
return s; // argument object
else
return *this; // this 指针指向调用对象
}
const int STKS = 4;
Stock mystuff[STKS]; // 创建对象数组,自动调用默认构造函数
Stock sand[STKS] = { // 创建对象数组,对不同的元素使用不同的构造函数
Stock("sand1", 11, 11.1), // 用括号括起,以逗号分割
Stock(), // 使用默认构造函数
Stock("sand3", 33) // 只初始化了三个元素,剩下的自动调用默认构造函数
};
int i;
for (i=0; i<STKS; i++)
sand[i].show();
const Stock *topsand = &sand[0]; // 定义一个类的const指针
for (i=1; i<STKS; i++)
topsand = &(topsand->topval(sand[i]));
topsand->show(); // 显示价格最高的一个对象
声明类只是描述了对象的形式,并没有创建对象。因此,在创建对象前,将没有用于存储值的空间。尽管是一个const变量。
第一种方式是在类中声明一个枚举:enum {Month = 12}; // 只是为了创建符号常量,Months只是一个符号名称,不需要枚举名。作用域为整个类。
第二种方式是在类中使用关键字static: static const int Month = 12; // 静态全局变量,作用域为整个类。
ADT以通用的方式描述数据类型,而没有引入语言或实现细节。
例如,通过使用栈,可以以这样的方式存储数据,即总是从堆顶添加或删除数据。
例如,C++程序使用栈来管理自动变量。当新的自动变量被生成后,它们被添加到堆顶;消亡时,从栈中删除它们。
例程:用类的成员函数实现对栈的操作:。。。。。。编程练习5
5、编写一个程序,它从栈中添加和删除customer结构(栈用Stack类声明表示)。每次customer结构被删除时,其payment的值都被加入到总数中,并报告总数。注意:应该可以直接使用Stack类而不作修改;只需修改typedef声明,使Item的类型为customer,而不是unsigned long即可。
(下面的程序包含第八题的题意)
p5.h
#ifndef PRIMERPLUS_P5_H
#define PRIMERPLUS_P5_H
#include
using namespace std;
struct customer
{
char fullname[35];
double payment;
};
typedef customer Item; // 起别名,为存放不同的数据类型
void visit_item(Item &item);
class Stack
{
private: // 私有部分放成员变量
enum {MAX = 10}; // 枚举类型的符号常量
Item items[MAX]; // holds stack items
int top; // 顶部堆栈项的索引,栈顶指针
public:
Stack(); // 默认构造函数
bool isempty() const; // 判断是否为空
bool isfull() const; // 判断是否满了
// push() returns false if stack already is full, true otherwise
bool push(const Item & item); // 入栈
// pop() returns false if stack already is empty, true otherwise
bool pop(Item & item); // 出栈
void visit(void (*pf)(Item &)); // 访问数据项以及执行操作
// pf指向一个将Item引用作为参数的函数(不是成员函数)
// visit( )函数将该函数用于列表中的每个数据项。
};
#endif //PRIMERPLUS_P5_H
p5.cpp
#include "p5.h"
Stack::Stack() // create an empty stack
{
top = 0; // 初始化栈顶指针
}
bool Stack::isempty() const
{
return top == 0; // 是否等于最底层
}
bool Stack::isfull() const
{
return top == MAX; // 是否等于最高层
}
bool Stack::push(const Item & item)
{
if (top < MAX) // 入栈条件
{
items[top++] = item;
return true;
}
else
return false;
}
bool Stack::pop(Item & item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
void Stack::visit(void (*pf)(Item &))
{
for (int i=0; i<top; i++)
pf(items[i]);
}
void visit_item(Item &item)
{
cout << "fullname:" << item.fullname << endl;
cout << "payment:" << item.payment << endl;
}
usep5.cpp
#include
#include // or ctype.h
#include "p5.h"
int main()
{
using namespace std;
Stack st; // create an empty stack
char ch;
customer cust;
double sum = 0.0;
cout << "Please enter A/a to add a purchase order, "
<< "P/p to process a PO, or Q/q to quit.\n";
while (cin >> ch && toupper(ch) != 'Q')
{
while (cin.get() != '\n') // 消耗回车
continue;
if (!isalpha(ch))
{
cout << '\a';
continue;
}
switch(ch)
{
case 'A':
case 'a': cout << "Enter a customer's fullname you want to push to stack (string):";
cin.getline(cust.fullname, 35);
cout << "Enter a customer's payment (double):";
cin >> cust.payment;
if (st.isfull())
cout << "stack already full\n";
else
{
st.push(cust);
st.visit(visit_item); // 显示
}
break;
case 'P':
case 'p': if (st.isempty())
cout << "stack already empty\n";
else
{
st.pop(cust);
sum += cust.payment;
cout << cust.fullname << " is popped\n";
cout << cust.payment << " is popped\n";
cout << "sum panyment :" << sum << endl;
}
break;
}
cout << "Please enter A/a to add a purchase order, "
<< "P/p to process a PO, or Q/q to quit.\n";
}
cout << "Bye\n";
return 0;
}