本章内容包括:
- 过程性编程和面向对象编程
- 类概念
- 如何定义和实现类
- 公有类访问和私有类访问
- 类的数据成员
- 类方法(类函数成员)
- 创建和使用类对象
- 类的构造函数和析构函数
- const成员函数
- this指针
- 创建对象数组
- 类作用域
- 抽象数据类型
面向对象编程(OOP)的重要特性:
- 抽象
- 封装和数据隐藏
- 多态
- 继承
- 代码的可重用性
10.2 抽象和类
10.2.2 C++中的类
类将数据表示和操纵数据的方法组合成一个包
一般来说,类规范由两部分组成:
- 类声明:以数据成员的方式描述数据部分,以成员函数的方式描述公有接口
- 类方法定义:描述如何实现类成员函数
C++将接口(类定义)放在头文件,将实现(类方法)放在源代码
使用#ifndef等来多次包含同一个文件,遵循一种常见但不通用的约定——类名首字母大写
#pragma once
#ifndef __STOCK00_H__
#define __STOCK00_H__
#include <string>
class Stock //关键字+类名
{
public: //公有
void acquire(const std::string& co, long n, double pr);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show(void);
private: //私有
std::string company;
long shares;
double share_val;
double total_val;
void set_tot(void) { total_val = shares * share_val; } //内联
};
#endif // !__10.1_STOCK00_H__
1. 访问控制
- 使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数或友元函数来访问对象的私有成员。
- 公有成员函数是程序和对象私有成员之间的桥梁,提供了对象和程序之间的接口。
- 防止程序之间访问数据被称为数据隐藏。
- 将实现细节(private)放在一起并将它们与抽象(public)分开称为封装。
- 将类函数定义和类声明放在不同文件是封装的另一个例子。
2. 控制对成员的访问:公有还是私有
- 数据项通常放在私有部分,组成类接口的成员函数放在公有部分
- 可以省略关键字private
10.2.3 实现类成员函数
#include <iostream>
#include "stock00.h"
void Stock::acquire(const std::string& co, long n, double pr)
{
company = co; //类成员函数可以访问私有数据成员
if (n < 0)
{
std::cout << "Number of shares can't be negative; " << company << " shares set to 0.\n";
shares = 0; //类成员函数可以访问私有数据成员
}
else
shares = n; //类成员函数可以访问私有数据成员
share_val = pr;
set_tot();
}
void Stock::buy(long num, double price)
{
if (num < 0)
std::cout << "Number of shares purchased can't be negative. Transaction is aborted.\n";
else
{
shares += num; //原来的股票书 + 新买的
share_val = price;
set_tot();
}
}
void Stock::sell(long num, double price)
{
using std::cout;
if (num < 0) //卖的股票数 < 0
cout << "Number of shares sold can't be negative. Transaction is aborted.\n";
else if (num > shares) //卖的股票数 > 现有股票数
cout << "You can't sell more than yu have. Transaction is aborted.\n";
else
{
shares -= num;
share_val = price;
set_tot();
}
}
void Stock::update(double price)
{
share_val = price;
set_tot();
}
void Stock::show(void)
{
std::cout << "Company: " << company << "\tShares: " << shares
<< "\tShare Price: $" << share_val << "\tTotal Worth: $" << total_val << std::endl;
}
成员函数有两个特殊的特征:
- 定义成员函数时,使用作用域解析运算符(::)来标识函数所属的类
- 类方法可以访问类的private组件
void Stock::update(double price)这种表示法意味着update()函数是Stock类的成员,还意味着可以将另一个类的成员函数也命名为update()。
作用域解析运算符确定了方法定义对应类的身份。标识符update()具有类作用域。Stock类的其他成员函数不必使用作用域解析运算符,就可以使用update()方法,因为它们属于同一个类,互相可见。
内联方法:定义位于类声明中的函数将自动成为内联函数。在类声明外定义的成员函数,只需在实现部分中定义函数时使用inline限定符,即可使其成为内联函数
10.2.4 使用类
#include <iostream>
#include "stock00.h"
int main(void)
{
Stock fluffy_the_cat; //类 + 对象
fluffy_the_cat.acquire("NanoSmart", 20, 12.50);
fluffy_the_cat.show();
fluffy_the_cat.buy(15, 18.125);
fluffy_the_cat.show();
fluffy_the_cat.sell(400, 20.00);
fluffy_the_cat.show();
fluffy_the_cat.buy(300000, 40.125);
fluffy_the_cat.show();
fluffy_the_cat.sell(300000, 0.125);
fluffy_the_cat.show();
return 0;
}
- 客户只能通过公有方式定义的接口使用服务器
- 设计人员只能修改类设计的实现细节,而不能修改接口
10.2.6 小结
- 类设计第一步:提供类声明。类声明可以包括数据成员和函数成员。私有部分的成员只能通过成员函数访问;公有部分的成员可以被类对象之间访问。通常,数据成员放私有,成员函数放公有。公有部分内容构成了设计的抽象部分——公有接口。数据封装到私有部分可以保护数据完整性——数据隐藏。
- 类设计第二步:实现类成员函数。使用作用域解析运算符(::)指出成员函数属于哪个类。要创建对象(类的实例),只需将类名视为类型名即可,和int i一样;类成员函数通过类对象调用,和结构成员一样。
10.3 类的构造函数和析构函数
C++的目标之一是让使用类对象就像使用标准类型一样。
类构造函数:专门用于构造新对象,为数据成员赋值。
Stock类的构造函数是名为Stock()的成员函数。
构造函数没有返回值,但没有被声明为void类型。
10.3.2 使用构造函数
- 显式调用构造函数:Stock food = Stock("AAA", 250, 1.25);
- 隐式调用构造函数:Stock garment ("BBB", 50, 2.5);
- 构造函数与new:Stock *pstock = new Stock ("CCC", 18, 19.0);
- 无法使用对象来调用构造函数,因为在构造函数构造出对象前,对象是不存在的。
- 因此构造函数被用来创建对象,而不能通过对象来调用。
10.3.3 默认构造函数
默认构造函数是在未提供显式初始值时,用来创建对象的构造函数。
如果没有提供任何构造函数,C++将自动提供默认构造函数(隐式)。
定义默认构造函数的方式有两种:
- 给已有构造函数的所有参数提供默认值
- 通过函数重载定义另一个没有参数的构造函数。
- 不要同时采用这两种方式
隐式调用构造函数时,不要使用圆括号。
Stock first("AAA"); //调用构造函数
Stock second(); //返回值为Stock类的函数
Stock third; //调用默认构造函数
10.3.4 析构函数
析构函数:对象过期时程序自动调用析构函数完成清理工作。
例如,如果构造函数使用new来分配内存,析构函数将使用delete释放这些内存。
Stock类的析构函数为~Stock()。
和构造函数一样,析构函数也可以没有返回值和声明类型。
和构造函数不同的是,析构函数没有参数。
类必须要有一个析构函数。如果没有定义,则编译器隐式提供一个析构函数。
10.3.5 改进Stock类
//程序清单 10.4 stock10.h
#pragma once
#ifndef STOCK10_H_
#define STOCK10_H_
#include <string>
class Stock
{
public:
Stock(); //默认构造函数
~Stock(); //析构函数
Stock(const std::string& co, long n = 0, double pr = 0);
//构造函数,默认参数,从右向左
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show(void) const;
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot(void) { total_val = shares * share_val; }
};
#endif // !STOCK10_H_
//程序清单 10.5
#include <iostream>
#include "stock10.h"
Stock::Stock(const std::string& co, long n, double pr)
{
company = co;
if (n < 0)
{
std::cout << "Number of shares can't be negative; " << company << " shares set to 0.\n";
shares = 0;
}
else
shares = n;
share_val = pr;
set_tot();
}
Stock::Stock()
{
std::cout << "Default constructor called\n";
company = "no name";
shares = 0;
share_val = .0;
total_val = 0.0;
}
Stock::~Stock()
{
//std::cout << "Bye, " << company << std::endl; //仅是标识析构调用时刻
}
void Stock::buy(long num, double price)
{
if (num < 0)
std::cout << "Number of shares purchased can't be negative. Transaction is aborted.\n";
else
{
shares += num;
share_val = price;
set_tot();
}
}
void Stock::sell(long num, double price)
{
using std::cout;
if (num < 0)
cout << "Number of shares sold can't be negative. Transaction is aborted.\n";
else if (num > shares)
cout << "You can't sell more than yu have. Transaction is aborted.\n";
else
{
shares -= num;
share_val = price;
set_tot();
}
}
void Stock::update(double price)
{
share_val = price;
set_tot();
}
void Stock::show(void) const
{
std::cout << "Company: " << company << "\tShares: " << shares
<< "\tShare Price: $" << share_val << "\tTotal Worth: $" << total_val << std::endl;
}
void show() const;以这种方式声明和定义的函数称为const成员函数。
只要类方法不修改调用对象,就应将其声明为const。
//程序清单 10.6
#include <iostream>
#include "stock10.h"
int main(void)
{
{
using std::cout;
cout << "Using constructor to create new objects\n";
Stock stock1("AAA", 12, 20.0); //隐式构造函数
stock1.show();
Stock stock2 = Stock("BBB", 32, 23.0); //显式构造函数
stock2.show();
cout << "Assigning stock1 to stock2:\n";
stock2 = stock1; //类对象支持赋值拷贝
stock1.show();
stock2.show();
cout << "Using constructor to reset an object\n";
stock1 = Stock("CCC", 10, 50.0); //创建临时对象,重新赋值
cout << "Revised stock1:\n";
stock1.show();
cout << "Done\n";
}
return 0;
}
10.3.6 构造函数和析构函数小结
- 构造函数在创建类对象时被调用。
- 构造函数的名称和类名相同,通过函数重载,可以创建多个同名的构造函数。
- 构造函数用于初始化类对象成员
- 默认构造函数可以没有任何参数,如果有,必须给所有参数都提供默认值。
- 每个类只能由一个析构。
10.4 this指针
有时候方法涉及两个对象,这种情况需要使用C++的this指针
//程序清单 10.7 stock20.h
#pragma once
#ifndef STOCK20_H_
#define STOCK20_H_
#include <string>
class Stock
{
public:
Stock();
~Stock();
Stock(const std::string& co, long n = 0, double pr = 0);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show(void) const; //注意这个const
const Stock& topval(const Stock& s) const;
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot(void) { total_val = shares * share_val; }
};
#endif // !STOCK20_H_
程序清单 10.8
#include <iostream>
#include "stock20.h"
Stock::Stock(const std::string& co, long n, double pr)
{
company = co;
if (n < 0)
{
std::cout << "Number of shares can't be negative; " << company << " shares set to 0.\n";
shares = 0;
}
else
shares = n;
share_val = pr;
set_tot();
}
Stock::Stock()
{
std::cout << "Default constructor called\n";
company = "no name";
shares = 0;
share_val = .0;
total_val = 0.0;
}
Stock::~Stock()
{
//std::cout << "Bye, " << company << std::endl;
}
void Stock::buy(long num, double price)
{
if (num < 0)
std::cout << "Number of shares purchased can't be negative. Transaction is aborted.\n";
else
{
shares += num;
share_val = price;
set_tot();
}
}
void Stock::sell(long num, double price)
{
using std::cout;
if (num < 0)
cout << "Number of shares sold can't be negative. Transaction is aborted.\n";
else if (num > shares)
cout << "You can't sell more than yu have. Transaction is aborted.\n";
else
{
shares -= num;
share_val = price;
set_tot();
}
}
void Stock::update(double price)
{
share_val = price;
set_tot();
}
void Stock::show(void) const
{
std::cout << "Company: " << company << "\tShares: " << shares
<< "\tShare Price: $" << share_val << "\tTotal Worth: $" << total_val << std::endl;
}
const Stock& Stock::topval(const Stock& s) const //注意这个const
{
if (s.total_val > total_val) //total_val == this->total_val
return s;
else
return *this;
}
const Stock & topval ( const Stock & s ) const;
该函数隐式地访问一个对象,而显式地访问另一个对象,并返回其中一个对象的引用。
括号中的const表明,该函数不会修改显式访问的对象;
括号后的const表明,该函数不会修改隐式访问的对象。
由于该函数返回两个const对象之一的引用,所以返回类型也为const引用,类型匹配。
top= stock1.topval(stock2);
top = stock2.topval(stock1);
const Stock& Stock::topval(const Stock& s) const
{
if (s.total_val > total_val)
return s;
else
return ????;
}
如果s.total_val大于total_val,则函数返回指向s的引用;否则,将返回调用该方法的对象。
this指针指向调用成员函数的对象,this设置为调用对象的地址。
一般来说,所有类方法都将this指针设置为调用它的对象的地址。
total_val == this->total_val
要返回的不是this,this是对象的地址,*this是对象本身。
10.5 对象数组
声明对象数组的方法与声明标准类型数组相同
//程序清单 10.9
#include <iostream>
#include "stock20.h"
const int STKS = 4;
int main(void)
{
using std::cout;
using std::endl;
Stock stocks[STKS] =
{
Stock("AAA", 12, 20.0),
Stock("BBB", 200, 2.05),
Stock("CCC", 130, 3.25),
Stock("DDD", 60, 6.5)
};
cout << "Stock holding:\n";
int st;
for (st = 0; st < STKS; st++)
stocks[st].show();
cout << endl;
const Stock* top = &stocks[0]; //类似const int * 不修改指向对象内容
for (st = 1; st < STKS; st++)
top = &top->topval(stocks[st]);
cout << "Most valuable holding:\n";
top->show();
return 0;
}
10.6 类作用域
在类中定义的名称的作用域都为整个类。
10.6.1 作用域为类的常量
class Bakery
{
private:
const int Months = 12; //FAILS
double costs[Months];
...
}
类声明只是描述对象的形式,并没有创建对象。
在创建对象前,没有用于存储值的空间。
第一种:枚举
enum{ Months = 12 };
第二种:static
static const int Months = 12;
10.7 抽象数据类型(abstract data type,ADT)
//程序清单 10.10 stack.h
//入栈出栈
#pragma once
#ifndef STACK_H_
#define STACK_H_
typedef unsigned long Item; //类型别名
class Stack
{
public:
Stack();
~Stack();
bool isempty(void) const; //判空
bool isfull(void) const; //判满
bool push(const Item& item); //入栈
bool pop(Item& item); //出栈
private:
enum { MAX = 10 }; //static const int MAX = 10;
Item items[MAX];
int top; //栈顶指针
};
#endif // !STACK_H_
//程序清单 10.11
#include <iostream>
#include "stack.h"
Stack::Stack(void) //默认构造函数 成员变量初始化
{
top = 0;
}
Stack::~Stack(void)
{
}
bool Stack::isempty(void) const
{
return top == 0;
}
bool Stack::isfull(void) 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;
}
//程序清单 10.12
#include <iostream>
#include <cctype>
#include "stack.h"
int main(void)
{
using namespace std;
Stack st;
unsigned long po;
char ch;
cout << "Enter a to add, p to pop, q to quit: ";
while (cin >> ch && toupper(ch) != 'Q')
{
while (cin.get() != '\n') //消耗回车
continue;
switch (ch)
{
case 'A':
case 'a':
cout << "Enter a po number to add: ";
cin >> po;
if (st.isfull())
cout << "Stack already full.\n";
else
st.push(po);
break;
case 'p':
case 'P':
if (st.isempty())
cout << "Stack already empty.\n";
else
{
st.pop(po);
cout << "PO #" << po << " popped.\n";
}
break;
}
cout << "Enter a to add, p to pop, q to quit: ";
}
cout << "Bye.\n";
return 0;
}