本博客重新复习类,首先分析OOP程序员常常依照的客户/服务器模型,继而以服务器设计人员身份从类的声明、类的定义两方面实现,最后以客户者身份进行类的使用。
参考自《C++ Primer Plus》第六版
思想:客户/服务器模型
OOP程序员常常依照客户/服务器模型讨论程序设计。客户通过了解该接口使用类的程序,服务器包括类的声明及实现方法等资源。客户只能通过公有方式定义的接口使用服务器程序,服务器设计者根据该接口设计、实现并后期修改类实现细节,而不能修改接口。这样程序对应二者是独立的,对服务器的修改不会对客户的行为造成影响。
步骤一:提供类声明
指定类设计的第一步是提供类声明。类声明类似结构声明,可以包括数据成员和函数成员。声明有私有部分,在其中声明的成员只能通过成员函数进行访问;声明还包括公有部分,在其中声明的成员可被使用类对象的成员直接访问。通常,数据成员被放在私有部分,成员函数被放在公有部分,因此典型的类声明的格式如下:
class className
{
private:
data member declarations
public:
member function prototypes
};
公有部分的内容构成了设计的抽象部分-公有接口。将数据封装到私有部分中可以保护数据的完整性,这被称为数据隐藏。因此,C++通过类使得实现抽象、数据隐藏和封装等OOP特性非常容易。
步骤二:实现类成员函数
指定类设计的第二步是实现类成员函数。可以在类声明中提供完整的函数定义,而不是函数原型,但是通常的做法是单独提供函数定义(除非函数很小可被作为内联函数)。在这种情况下,需要使用作用域解析运算符来指出成员函数属于哪个类。
步骤三:类的使用
当创建同一个类的多个对象,创建的每个新对象都有自己的存储空间,用于存储其内部变量和类成员,但同一个类的所有对象共享同一组类方法,即每种方法只有一个副本。
调用成员函数时,它将使用被用来调用它的对象的数据成员。
实例分析
C++程序员将接口(类定义)放在头文件中,并将实现(类方法的代码)放在源代码文件中。
**类的声明:**参考stock.h
- C++关键字class指出了这些代码定义了一个类设计,Stock是这里新类的类型名。该声明能够声明Stock类型的变量—成为对象或者实例。
- 存储的数据以类成员的形式出现,成员函数可以直接定义(内联函数),也可以用原型表示(如其他成员函数)。对于描述函数接口而言,原型足够了,将数据和方法结合成一个单元是类最吸引人的特性。
- 访问控制:关键字private和public描述了对类成员的访问控制。使用类对象的程序都可以直接访问公有部分,但只能通过公有函数(或友元函数)来方位对象的私有成员。因此,公有成员函数时程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。
- 数据隐藏:防止程序之间访问数据的机制成为数据隐藏。数据隐藏不仅可以防止直接访问数据,还让开发者(类的用户)无需了解数据是如何表示的。
- 类设计尽可能将公有接口与实现细节分开。公有接口表示设计的抽象组件。将实现细节放在一起并将它们与抽象分开成为封装。
- 在类声明中默认访问控制是private,这和C语言结构体中默认是public是相反的。
类的定义:参考stock.cpp
类的定义为类声明中的原型表示的成员函数提供代码。成员函数定义与常规函数定义非常相似,他们有函数头和函数体,也可以有返回类型和参数。但是它们还有两个特殊的特征:
- 定义成员函数时,使用作用域解析运算符(::)俩标识函数所属的类;
- 类方法可以访问类的私有成员
了解了这两个特性后,就可以实现类方法了。
- 私有数据仅限于公有函数访问,这样起到了安全保护作用。
- 私有成员函数供编写这个类的人使用,而实例化这个类的人不能使用。这种方法可以省去许多重复性工作并节省空间,修改时仅在一处修改即可。
- 内联方法,定义位于类声明中的函数都将自动成为内联函数,类声明常将短小的成员函数作为内联函数; 在类声明定义的成员函数也可以为内联函数,只需在类实现部分中定义函数时使用inline限定符即可。
类的调用:参考main.cpp
Stock stock1("百度",12,20.0); //syntax 1
stock1.show();
Stock stock2 = Stock ("阿里",2,2.0); //syntax 2
stock2.show();
如上
语法1,创建一个名为stock1的Stock对象,并将其数据成员初始化为指定的值。
语法2:创建并初始化一个名为stock2的对象。
C++标准允许编译器使用者两种语法,本质完全相同。
cout <<"Assigning stock1 to stock2:\n";
stock2 = stock1;
cout << "Listing stock1 and stock2:\n";
如上, 允许调用构造函数创建一个临时对象,然后将该临时对象复制到stock2中,然后丢弃它。
Stock stock2 = Stock ("阿里",2,2.0); //方式一
stock1 = Stock("腾讯",10,50.0); //方式二(temp object)
如上,方式一创建有指定值的对象,可能会创建临时对象(也可能不会);方式2赋值的方式总会在赋值前使用构造函数创建一个临时对象。因此使用方式一效率更高。
赋值:在默认情况下,将类对象赋值给同类型的另一个对象时,C++将源对象的每个数据成员的内容复制到目标对象中的相应数据成员中。
stock1 = Stock("腾讯",10,50.0); //stock1对象已经存在,构造函数赋值
构造函数不仅仅可以用于初始化新对象,例如stock1对象已经存在,因此这条语句并不是对stock1初始化,而是赋值。通过构造程序创建一个新的、临时的对象,然后将内容复制给stock1实现的。随后程序调用析构函数,以删除该临时对象。
函数main()结束时,其局部变量(stock1和stock2)将消失。由于自动变量存放于栈中,因此最后创建的对象将最先被删除,最先创建的对象将最后被删除。
代码:stock.h
#ifndef STOCK\_H\_
#define STOCK\_H\_
#include <string>
class Stock
{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot(){ total_val = shares * share_val;}
public:
//two constuctors
Stock(); //default consructor
Stock(const std::string &co, long n = 0,double pr =0.0);
~Stock(); //default destructor
void buy(long num, double price);
void sell(long num,double price);
void update(double price);
void show() ;
};
#endif
代码:stock.cpp
#include <iostream>
#include "stock.h"
//constructors
Stock::Stock()
{
std::cout << "Default constructor called\n";
company = "null name";
shares = 0;
share_val = 0.0;
total_val = 0.0;
}
Stock::Stock(const std::string &co, long n, double pr)
{
std::cout << "\nConstructor using"<< co <<"called\n";
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();
}
//class destructor
Stock::~Stock()
{
std::cout << "Bye,"<<company <<"!\n";
}
void Stock::buy(long num,double price)
{
if(num<0)
{
std::cout<< "Number of shares purchased can't be negative;"
<< "Trancation is aborted.\n";
}
else
{
shares += num;
share_val = price;
set_tot();
}
}
void Stock::sell(long num, double price)
{
using std::cout;
if(num<0)
{
std::cout<< "Number of shares sold can't be negative;"
<< "Trancation is aborted.\n";
}
else if(num >shares)
{
cout << "you can't sell more than you have!"
<< "Trancation is aborted.\n";
}
else
{
shares -= num;
share_val = price;
set_tot();
}
}
void Stock::update(double price)
{
share_val = price;
set_tot();