欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2023-12-24 16:48:45
...

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;
}
相关标签: #C++ c++

上一篇:

下一篇: