- 过程性编程和面向对象编程
- 类概念
- 如何定义和实现类
- 公有类访问和私有类访问
- 类的数据成员
- 类方法(类函数成员)
- 创建和使用类对象
- 类的构造函数和析构函数
- const成员函数
- this指针
- 创建对象数组
- 类作用域
- 抽象数据类型
面向对象编程(OOP)的重要特性:
- 抽象
- 封装和数据隐藏
- 多态
- 继承
- 代码的可重用性
类将数据表示和操纵数据的方法组合成一个包
一般来说,类规范由两部分组成:
- 类声明:以数据成员的方式描述数据部分,以成员函数的方式描述公有接口
- 类方法定义:描述如何实现类成员函数
C++将接口(类定义)放在头文件,将实现(类方法)放在源代码
使用#ifndef等来多次包含同一个文件,遵循一种常见但不通用的约定——类名首字母大写
#pragma once #ifndef __STOCK00_H__ #define __STOCK00_H__ #includeclass 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
#include#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#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; }
- 客户只能通过公有方式定义的接口使用服务器
- 设计人员只能修改类设计的实现细节,而不能修改接口
- 类设计第一步:提供类声明。类声明可以包括数据成员和函数成员。私有部分的成员只能通过成员函数访问;公有部分的成员可以被类对象之间访问。通常,数据成员放私有,成员函数放公有。公有部分内容构成了设计的抽象部分——公有接口。数据封装到私有部分可以保护数据完整性——数据隐藏。
- 类设计第二步:实现类成员函数。使用作用域解析运算符(::)指出成员函数属于哪个类。要创建对象(类的实例),只需将类名视为类型名即可,和int i一样;类成员函数通过类对象调用,和结构成员一样。
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);
- 无法使用对象来调用构造函数,因为在构造函数构造出对象前,对象是不存在的。
- 因此构造函数被用来创建对象,而不能通过对象来调用。
默认构造函数是在未提供显式初始值时,用来创建对象的构造函数。
如果没有提供任何构造函数,C++将自动提供默认构造函数(隐式)。
定义默认构造函数的方式有两种:
- 给已有构造函数的所有参数提供默认值
- 通过函数重载定义另一个没有参数的构造函数。
- 不要同时采用这两种方式
隐式调用构造函数时,不要使用圆括号。
10.3.4 析构函数Stock first("AAA"); //调用构造函数
Stock second(); //返回值为Stock类的函数
Stock third; //调用默认构造函数
析构函数:对象过期时程序自动调用析构函数完成清理工作。
例如,如果构造函数使用new来分配内存,析构函数将使用delete释放这些内存。
Stock类的析构函数为~Stock()。
和构造函数一样,析构函数也可以没有返回值和声明类型。
和构造函数不同的是,析构函数没有参数。
类必须要有一个析构函数。如果没有定义,则编译器隐式提供一个析构函数。
10.3.5 改进Stock类//程序清单 10.4 stock10.h #pragma once #ifndef STOCK10_H_ #define STOCK10_H_ #includeclass 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#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 calledn"; 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 #include10.3.6 构造函数和析构函数小结#include "stock10.h" int main(void) { { using std::cout; cout << "Using constructor to create new objectsn"; 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 objectn"; stock1 = Stock("CCC", 10, 50.0); //创建临时对象,重新赋值 cout << "Revised stock1:n"; stock1.show(); cout << "Donen"; } return 0; }
- 构造函数在创建类对象时被调用。
- 构造函数的名称和类名相同,通过函数重载,可以创建多个同名的构造函数。
- 构造函数用于初始化类对象成员
- 默认构造函数可以没有任何参数,如果有,必须给所有参数都提供默认值。
- 每个类只能由一个析构。
有时候方法涉及两个对象,这种情况需要使用C++的this指针
//程序清单 10.7 stock20.h #pragma once #ifndef STOCK20_H_ #define STOCK20_H_ #includeclass 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#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 calledn"; 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 #include10.6 类作用域#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.1 作用域为类的常量class Bakery
{
private:
const int Months = 12; //FAILS
double costs[Months];
...
}
类声明只是描述对象的形式,并没有创建对象。
在创建对象前,没有用于存储值的空间。
10.7 抽象数据类型(abstract data type,ADT)第一种:枚举
enum{ Months = 12 };
第二种:static
static const int Months = 12;
//程序清单 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#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#include #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; }



