C++ Primer Plus学习笔记10-对象和类

1. 过程性编程和面向对象编程

采用OOP方法时,首先从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作。完成对接口的描述后,需要确定如何实现接口和数据存储。最后,使用新的设计方案创建程序。

2. 抽象和类

将问题的本质特征抽象出来,并根据特征来描述解决方案。抽象是通往用户定义类型的捷径。

提供类声明(类似结构声明,包括数据成员和函数成员);实现类成员函数。

2.1 C++中的类

类是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。

一般来说,类规范由两部分组成:

  • 类声明:以数据成员的方式描述数据部分,以成员函数(方法)的方式描述公有接口
  • 类方法定义:描述如何实现类成员函数。

什么是接口
接口是一个共享框架,供两个系统交互时使用。对于类,称公共接口。公共指的是使用类的程序。接口让程序员能够编写与类对象交互的代码,从而让程序能够使用类对象。

访问控制
关键字privatepublic。使用类对象的程序都可以访问公有部分,但只能通过公有成员函数访问对象的私有成员。C++还提供了第三个访问控制关键字protected
因此公有成员函数是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。

类设计尽可能将公有接口与实现细节分开。数据隐藏不仅可以防止直接访问数u,还让开发者无需了解数据是如何被表示的。

数据项通常放在私有部分,组成类接口的成员函数放在公有部分。通常,程序员使用私有成员函数来处理不属于公有接口的实现细节。不必在类声明中使用关键字private,这是类对象的默认访问控制。

只要类方法不修改调用对象,就应将其声明为const

void Stock::show() const // promises not to change invoking object
// stock00.h -- Stock class interface
// version 00
#ifndef STOCK00_H_
#define STOCK00_H_

#include <string>

class Stock // class definition
{
  private:
    std::string company;
    long shares;
    double share_val;
    double total_val;
    void set_tot() {total_val = shares * share_val;}
  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();
}; // note semicolon at the end

#endif

2.2 实现类成员函数

成员函数定义与常规函数定义相似,但是它们还有两个特殊的特征:

  • 定义成员函数时,使用作用域解析运算符::来标识函数所属的类。
  • 类方法可以访问类的private组件。

定义位于类声明中的函数都将自动称为内联函数。

调用成员函数时,它将使用被用来调用它的对象的数据成员。所创建的每个新对象都有自己的存储空间,用于存储其内部变量和类成员。但同一个类的所有对象共享一组类方法。

// stock00.cpp -- implementing the Stock class
// version 00
#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){
  if (num < 0)
  {
    std::cout << "Number of shares sold can't be negative. "
	          << "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(){
  std::cout << "Company: " << company << endl;
  std::cout << "\tShares: " << shares << endl;
  std::cout << "\tShare Price: $" << share_val
  			<< "\tTotal Worth: $" << total_val << endl;
}

2.3 使用类

C++的目标是使得使用类与使用基本的内置类型尽可能相同。

// usestck0.cpp -- the client program
// compile with stock00.cpp
#include <iostream>
#include "stock00.h"

int main(){
  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();
  return 0;
}

OOP程序员常依照客户/服务器模型来讨论程序设计。客户是使用类的程序;类声明构成服务器,它是程序可以使用的资源。客户只能通过以公有方式定义的接口使用服务器,这意味着客户唯一的责任是了解接口。服务器的责任是确保服务器根据该接口可靠并准确地运行。

2.4 修改实现

修改方法的实现时,不应影响客户程序的其他部分。

3. 类的构造函数和析构函数

常规的初始化语法不适用于类型,原因在于数据部分的访问状态是私有的。一般来说,最好在创建对象时对它进行初始化。为此,C++提供了一个特殊的成员函数——类构造函数,专门用于构造新对象并赋值,其名称与类名相同。构造函数实际上没有声明类型。
程序声明对象时,将自动调用构造函数。

构造函数的参数表示的不是类成员,而是赋给类成员的值。因此参数名不能与类成员相同。为避免以上混乱,一种常见的做法是在数据成员名中使用m_前缀;另一种做法是在成员名中使用后缀_

3.1 声明和定义构造函数

// constructor prototype with some default arguments
Stock(const string & co, long n=0, double pr=0.0);
// constructor definition
Stock::Stock(const 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();
}

3.2 使用构造函数

C++提供了两种使用构造函数初始化对象的方式:

Stock food = Stock("World Cabbage", 250, 1.25); // 显式调用
Stock garment("Furry Mason", 50, 2.5);			// 隐式调用
Stock *pstock = new Stock("Electroshock Games", 18, 19.0);

每次创建类对象,C++都使用类构造函数。但是无法使用对象来调用构造函数。

3.3 默认构造函数

在未提供显示初始值时,用来创建对象的构造函数。当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。为类定义了构造函数后,程序员就必须为它提供默认构造函数。

3.4 析构函数

对象过期时,程序将自动调用一个特殊的成员函数——析构函数。它完成清理工作。

析构函数的名称是类名前加~,它没有返回值和声明类型且没有参数。

什么时候调用析构函数由编译器决定,通常不应在代码中显式地调用析构函数。如果程序员没有提供析构函数,编译器将隐式地声明一个默认析构函数,并在发现导致对象被删除的代码后,提供默认析构函数的定义。

3.5 改进Stock

// stock10.h -- Stock Class definition with constructors, destructor added
#ifndef STOCK10_H_
#define STOCK10_H_
#include <string>

class Stock // class definition
{
  private:
    std::string company;
    long shares;
    double share_val;
    double total_val;
    void set_tot() {total_val = shares * share_val;}
  public:
  // two constructors
  	Stock();	// default constructor
    Stock(const std::string & co, long n=0, double pr=0.0);
    ~Stock();	// noisy destructor
    void buy(long num, double price);
    void sell(long num, double price);
    void update(double price);
    void show();
}; // note semicolon at the end

#endif

// stock10.cpp -- Stock class with constructors, destructor added
#include <iostream>
#include "stock10.h"

// Constructors (verbose versions)
Stock::Stock() // default constructor
{
  std::cout << "Default constructor called\n";
  company = "no name";
  shares = 0;
  share_val = 0.0;
  total_val = 0.0;
}
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();
}
// class destructor
Stock::~Stock() // verbose class destructor
{
  std::cout << "Bye, " << company << "!\n";
}
//other methods
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){
  if (num < 0)
  {
    std::cout << "Number of shares sold can't be negative. "
	          << "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(){
  std::cout << "Company: " << company << endl;
  std::cout << "\tShares: " << shares << endl;
  std::cout << "\tShare Price: $" << share_val
  			<< "\tTotal Worth: $" << total_val << endl;
}


// usestok1.cpp -- using the Stock class
// compile with stock10.cpp
#include <iostream>
#include "stock10.h"

int main()
{
  using std::cout;
  cout << "Using constructors to create new objects\n";
  Stock stock1("NanoSmart", 12, 20.0);
  stock1.show();
  Stock stock2 = Stock("Boffo Objects", 2, 2.0);
  stock2.show()
  Stock hot_tip = {"Derivatives Plus Plus", 100, 34.0};

  cout << "Assigning stock1 to stock2:\n";
  stock2 = stock1;
  cout << "Listing stock1 and stock2:\n";
  stock1.show();
  stock2.show();

  cout << "Using a constructor to reset an object\n";
  stock1 = Stock("Nifty Foods", 10, 40.0); // temporary object
  cout << "Revised stock1:\n";
  stock1.show()
  cout << "Done\n";
  return 0;
}

4. this指针

使用this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法)。如果方法需要引用整个调用对象,则可以使用表达式*this;在函数的括号后面使用const限定符将this限定为const

// method prototype
const Stock & topval(const Stock & s) const;
// method implementation
const Stock & Stock::topval(const Stock & s) const{
  if (s.total_val > total_val)
    return s;
  else
    return *this;
}

5. 对象数组

声明对象数组的方法与声明标准类型数组相同。
初始化对象数组的方案:首先使用默认构造函数创建数组元素,然后花括号中的构造函数将创建临时对象,然后将临时对象的内容复制到相应的元素中。

6. 类作用域

在类中定义的名称的作用域都为整个类。类作用域意味着不能从外部直接访问类的成员,公有成员函数也是如此。要调用公有成员函数,必须通过对象。

6.1 作用域为类的常量

声明类只是描述了对象的形式,并没有创建对象。因此在创建对象前,没有用于存储值的空间。

  • 在类中声明一个枚举,可以使用枚举为整形常量提供作用域为整个类的符号名称。以这种方式声明枚举并不会创建类数据成员。
  • 使用关键字static
class Bakery{
  private:
    enum {Months=12};
    static const int Months2=12;
    double costs[Months];
    double costs2[Months2];
}

6.2 作用域内枚举(C++11)

传统枚举定义的枚举量可能发生冲突。为避免该问题,C++11提供一种新枚举,枚举量作用域为类。另外, C++11还提高了作用域内枚举的类型安全(不能隐式地转换成整型)。

enum egg {Small, Medium, Large, Jumbo};
egg choice = egg::Large //需要用枚举名限定枚举量
enum class : short pizza {Small, Medium, Large, Xlarge}; //底层类型指定

7. 抽象数据类型(ADT)

Abstract Data Type以通用的方式来描述数据类型。类概念非常适合与ADT方法。

//stack.h -- class definition for the stack ADT
#ifndef STACK_H_
#define STACK_H_

typedef unsigned long Item;

class Stack{
  private:
    enum {MAX = 10}; // constant specific to class
    Item items[MAX]; // holds stack items
    int top; 		 // index for top stack item
  public:
    Stack();
    bool isempty() const;
    bool isfull() const;
    // push() returns false if stack already is full, true otherwise
    bool push(const Item &item); // add item to stack
    // pop() returns false if stack already is empty, true otherwise
    bool pop(Item & item); // pop top into item
};
#endif

// stack.cpp -- Stack member functions
#include "stack.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)
    return false;
  else{
    item = items[--top];
    return true;
  }
}

// stacker.cpp -- testing the Stack class
#include <iostream>
#include <cctype> // or ctype.h
#include <stack.h>
int main(){
  using namespace std;
  Stack st; // create an empty stack
  char ch;
  unsigned long po;
  cout << "Please enter A to add a purchase order,\n"
       << "P to process a PO, or 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 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 << "Please enter A to add a purchase order.\n"
         << "P to process a PO, or Q to quit.\n";
  }
  cout << "Bye\n";
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值