[C++ Primer Plus] Capítulo 12 Classes e alocação dinâmica de memória

12.1 Memória dinâmica e classes

  1. Deixe o programa decidir a alocação de memória em tempo de execução, não em tempo de compilação.
  2. C++ usa operadores new e delete para controlar a memória dinamicamente. Para usar esses operadores em uma classe, um destruidor será essencial.
  3. Strings definidas por objetos de classe não são armazenadas no objeto. As strings são armazenadas separadamente na memória heap e os objetos contêm apenas informações que indicam onde encontrar a string.
  4. Ao usar new no construtor para alocar memória, você deve usar delete no destruidor correspondente para liberar a memória. Se você usar new[] (incluindo colchetes) para alocar memória, deverá usar delete[] (incluindo colchetes) para liberar memória.

stringbad.h

#ifndef PRIMERPLUS_STRINGBAD_H
#define PRIMERPLUS_STRINGBAD_H
#include <iostream>
class StringBad
{
    
    
private:
    char *str;      				// 类声明没有为字符串本身分配存储空间
    int len;
    static int num_strings; 		// 无论创建了多少对象,程序都只创建一个静态类变量副本。
public:
    // 在构造函数中使用new来为字符串分配空间。
    StringBad();                    // 默认构造函数
    StringBad(const char * s);      // 自定义构造函数
    StringBad(const StringBad & s); // 复制构造函数
    StringBad & operator=(const StringBad & st);    // 赋值构造函数
    ~StringBad();

    friend std::ostream & operator<<(std::ostream & os, const StringBad & st);
};
#endif //PRIMERPLUS_STRINGBAD_H

stringbad.cpp

#include <cstring>
#include "stringbad.h"
using std::cout;

int StringBad::num_strings = 0; // 初始化静态变量,用于记录创建的类数量

StringBad::StringBad(const StringBad & st)
{
    
    
    num_strings++; 				// handle static member update
    len = st.len; 				// same length
    str = new char [len + 1]; 	// allot space
    std::strcpy(str, st.str); 	// copy string to new location
    cout << num_strings << ": \"" << str
         << "\" object created\n"; // For Your Information
}

StringBad & StringBad::operator=(const StringBad & st)
{
    
    
    if (this == &st) 			// object assigned to itself
        return *this; 			// all done
    delete [] str; 				// free old string
    len = st.len;
    str = new char [len + 1]; 	// get space for new string
    std::strcpy(str, st.str); 	// copy the string
    return *this; 				// return reference to invoking object
}

StringBad::StringBad()          // 在构造函数中使用new来为字符串分配空间
{
    
    
    len = 6;
    str = new char[6];
    std::strcpy(str, "happy");
    num_strings++;
    cout << num_strings << " : \"" << str << "\" object created.\n";
}

StringBad::StringBad(const char * s)
{
    
    
    // str = s;             // 这只保存了地址,而没有创建字符串副本。
    len = std::strlen(s);   // 不包括末尾的空字符
    str = new char[len+1];  // 使用new分配足够的空间来保存字符串,然后将新内存的地址赋给str成员。
    std::strcpy(str, s);
    num_strings++;
    cout << num_strings << " : \"" << str << "\" object created.\n";
}

StringBad::~StringBad()
{
    
    
    cout << "\"" << str << "\" object delete, ";
    --num_strings;
    cout << num_strings << " left.\n";
    delete [] str;
}

std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
    
    
    os << st.str;
    return os;
}

usestringbad.cpp

#include "stringbad.h"
using std::cout;

void callme1(StringBad & rsb);
void callme2(StringBad sb);

int main(void)
{
    
    
    using std::endl;
    {
    
    
        cout << "Starting an inner block.\n";
        StringBad headline1("Celery Stalks at Midnight");
        StringBad headline2("Lettuce Prey");
        StringBad sports("Spinach Leaves Bowl for Dollars");
        cout << "headline1: " << headline1 << endl;
        cout << "headline2: " << headline2 << endl;
        cout << "sports: " << sports << endl;
        callme1(headline1);
        cout << "headline1: " << headline1 << endl;
        callme2(headline2); // 复制构造函数被用来初始化 callme2()的形参
        cout << "headline2: " << headline2 << endl;
        cout << "Initialize one object to another:\n";
        StringBad sailor = sports;  // 复制构造函数,StringBad sailor = StringBad(sports);
        cout << "sailor: " << sailor << endl;
        cout << "Assign one object to another:\n";
        StringBad knot;
        knot = headline1;   // 赋值构造函数,knot.operator=(headline1);
        cout << "knot: " << knot << endl;
        cout << "Exiting the block.\n";
    }   // 该代码块执行完调用析构函数,否则要main函数执行完调用析构函数。
    cout << "End of main()\n";
    return 0;
}
void callme1(StringBad & rsb)
{
    
    
    cout << "String passed by reference:";
    cout << " \"" << rsb << "\"\n";
}
void callme2(StringBad sb)
{
    
    
    cout << "String passed by value:";
    cout << " \"" << sb << "\"\n";
}

fora:

Starting an inner block.
1 : "Celery Stalks at Midnight" object created.
2 : "Lettuce Prey" object created.
3 : "Spinach Leaves Bowl for Dollars" object created.
headline1: Celery Stalks at Midnight
headline2: Lettuce Prey
sports: Spinach Leaves Bowl for Dollars
String passed by reference: "Celery Stalks at Midnight"
headline1: Celery Stalks at Midnight
4: "Lettuce Prey" object created
String passed by value: "Lettuce Prey"
"Lettuce Prey" object delete, 3 left.
headline2: Lettuce Prey
Initialize one object to another:
4: "Spinach Leaves Bowl for Dollars" object created
sailor: Spinach Leaves Bowl for Dollars
Assign one object to another:
5 : "happy" object created.
knot: Celery Stalks at Midnight
Exiting the block.
"Celery Stalks at Midnight" object delete, 4 left.
"Spinach Leaves Bowl for Dollars" object delete, 3 left.
"Spinach Leaves Bowl for Dollars" object delete, 2 left.
"Lettuce Prey" object delete, 1 left.
"Celery Stalks at Midnight" object delete, 0 left.
End of main()

12.1.1 Características dos membros da classe estática

  1. Não importa quantos objetos sejam criados, o programa cria apenas uma cópia da variável de classe estática.
  2. Todos os objetos da classe compartilham o mesmo membro estático
  3. As variáveis ​​de membro estático não podem ser inicializadas em uma declaração de classe porque a declaração descreve como alocar memória, mas não a aloca.
  4. A inicialização é feita no arquivo de método, não no arquivo de declaração de classe , pois a declaração de classe está no arquivo de cabeçalho, que o programa pode incluir em vários outros arquivos. Se a inicialização for feita em um arquivo de cabeçalho, haverá várias cópias da instrução de inicialização, gerando um erro.
  5. Os membros de dados estáticos são declarados na declaração de classe e inicializados no arquivo que contém os métodos de classe.
  6. O operador de escopo é usado na inicialização para indicar a classe à qual o membro estático pertence.
  7. Mas se o membro estático for integral ou enum const, ele pode ser inicializado na declaração da classe.

12.1.2 Funções especiais dos membros

C++ fornece automaticamente as seguintes funções de membro:

  1. construtor padrão, se nenhum construtor for definido;
  2. destruidor padrão, se não definido;
  3. construtor de cópia, se não definido;
  4. operador de atribuição, se não definido; (atribui um objeto a outro)
  5. Operador de endereço, se não definido. O operador de endereço implícito retorna o endereço do objeto de chamada (ou seja, o valor do ponteiro this).

1. Construtor padrão

C++ não definirá um construtor padrão se um construtor for definido. Se você deseja criar um objeto sem inicializá-lo explicitamente, deve definir explicitamente um construtor padrão.
Um construtor parametrizado também pode ser um construtor padrão, desde que todos os parâmetros tenham valores padrão. Mas só pode haver um construtor padrão.

2. Copie o construtor

O construtor de cópia é usado para copiar um objeto em um objeto recém-criado.
Por exemplo: o protótipo do construtor de cópias da classe StringBad:StringBad(const StringBad &);

Quando chamar : o construtor de cópia será chamado ao criar um novo objeto e inicializá-lo como um objeto existente do mesmo tipo.

// 以下四种情况都将调用复制构造函数,假设motto是一个StringBad对象
StringBad ditto(motto);
StringBad metoo = motto; 			// 可能直接创建,也可能生成一个临时对象
StringBad also = StringBad(motto);	// 可能直接创建,也可能生成一个临时对象
StringBad * pStringBad = new StringBad(motto);	// 初始化一个匿名对象,并将新对象的地址赋给pstring指针。

Sempre que um programa faz uma cópia de um objeto, o compilador usará o construtor de cópia. (O construtor de cópia é usado quando uma função passa um objeto por valor ou quando uma função retorna um objeto.)
Como passar um objeto por valor invoca o construtor de cópia, o objeto deve ser passado por referência. Isso economiza tempo para chamar o construtor e espaço para armazenar o novo objeto.

O que ele faz : o construtor de cópia padrão copia membros não estáticos um por um (a cópia de membro também é conhecida como cópia superficial), copiando o valor do membro.
Se o próprio membro for um objeto de classe, o objeto de membro será copiado usando o construtor de cópia dessa classe. Funções estáticas como num_strings não são afetadas porque pertencem à classe inteira, não a objetos individuais.

Razões pelas quais um construtor de cópia deve ser definido:

  1. Se sua classe contiver membros de dados estáticos cujo valor será alterado quando um novo objeto for criado, você deverá fornecer um construtor de cópia explícito para lidar com a contagem.
  2. A razão pela qual um construtor de cópia deve ser definido é que alguns membros da classe são ponteiros para dados inicializados com new, em vez dos próprios dados.
  3. Se a classe contiver membros de ponteiro inicializados com new, um construtor de cópia deve ser definido para copiar os dados apontados em vez do ponteiro.Isso é chamado de cópia profunda .
  4. Outra forma de cópia (cópia de membro ou cópia rasa) simplesmente copia valores de ponteiro. Uma cópia superficial apenas copia as informações do ponteiro superficialmente, sem "cavar" fundo o suficiente para copiar a estrutura referenciada pelo ponteiro.
// StringBad类的显式复制函数
StringBad::StringBad(const StringBad & s)
{
    
    
    num_strings++;	// 静态数据成员
    ...// important stuff to go here
}

3. Construtor de atribuição

  1. Ao atribuir um objeto existente a outro objeto, o operador de atribuição sobrecarregado é usado.
  2. Quando um objeto é inicializado, o operador de atribuição não é necessariamente usado (também é possível usar o construtor de cópia para criar um objeto temporário e, em seguida, copiar o valor do objeto temporário para o novo objeto por meio de atribuição).
  3. Assim como o construtor de cópia, a implementação implícita do operador de atribuição também copia os membros um por um.
  4. Se o próprio membro for um objeto de classe, o programa usará o operador de atribuição definido para a classe para copiar o membro, mas os membros de dados estáticos não serão afetados.

As características do construtor de atribuição:

  1. Como o objeto de destino pode se referir a dados alocados anteriormente, a função deve usar delete[ ] para liberar os dados.
  2. As funções devem evitar atribuir objetos a si mesmas; caso contrário, as operações de liberação de memória podem excluir o conteúdo do objeto antes de reatribuir o objeto.
  3. A função retorna uma referência ao objeto de chamada.
// 赋值操作并不创建新的对象,因此不需要调整静态数据成员num_strings的值。
StringBad & StringBad::operator=(const StringBad & st)
{
    
    
    if (this == &st) 			// object assigned to itself
    return *this; 				// all done
    delete [] str; 				// free old string
    len = st.len;
    str = new char [len + 1]; 	// get space for new string
    std::strcpy(str, st.str); 	// copy the string
    return *this; 				// return reference to invoking object
}

12.2 Classe String melhorada

12.2.1 Ponteiros Nulos

  1. delete[] é compatível com ponteiros inicializados com new[] e com ponteiros nulos.
  2. Em C++98, o valor literal 0 tem dois significados: pode representar o valor numérico zero ou pode representar um ponteiro nulo.
  3. C++11 apresenta uma nova palavra-chave nullptr para representar um ponteiro nulo.
char *str;
str = nullptr;	// 空指针
str = 0;		// 空指针
str = NULL;		// 空指针

12.2.2 Comparando funções de membro

Para implementar uma função de comparação de strings, a maneira mais fácil é usar a função trcmp() padrão:

  1. Se o primeiro argumento preceder o segundo em ordem alfabética, a função retornará um valor negativo;
  2. Retorna 0 se as duas strings forem iguais;
  3. Retorna um valor positivo se o primeiro argumento vier depois do segundo.
bool operator<(const String &st1, const String &st2)
{
    
    
	return (std::strcmp(st1.str, st2.str) < 0);
}
bool operator>(const String &st1, const String &st2)
{
    
    
	return st2 < st1;
}
bool operator==(const String &st1, const String &st2)
{
    
    
	return (std::strcmp(st1.str, st2.str) == 0);
}

Uma função de comparação como amiga é útil para comparar objetos String com strings C regulares.

12.2.3 Acessando caracteres usando a notação de colchetes

Para operadores de colchetes, um operando precede o primeiro colchete e o outro operando está entre os dois colchetes.

char & String::operator[](int i)
{
    
    
	return str[i];
}

// 调用
String opera("The Magic Flute");
cout << opera[4];	// cout << opera.operator[](4);

// 将返回类型声明为char &,便可以给特定元素赋值。
String means("might");
means[0] = 'r';		// means.operator[](0) = 'r';
means.str[0] = 'r';

// 提供另一个仅供const String对象使用的operator版本:
const char & String::operator[](int i) const
{
    
    
	return str[i];
}

// 有了const版本可以读/写常规String对象,对于const String对象,则只能读取其数据:
String text("Once upon a time");
const String answer("futile");
cout << text[1]; 	// ok, uses non-const version of operator[]()
cout << answer[1]; 	// ok, uses const version of operator[]()
cin >> text[1]; 	// ok, uses non-const version of operator[]()
//cin >> answer[1]; 	// compile-time error

12.2.4 Funções de membro de classe estática

  1. As funções de membro podem ser declaradas estáticas (declarações de função devem conter a palavra-chave static, mas não podem conter a palavra-chave static se a definição da função for independente)
  2. Primeiro, você não pode chamar uma função-membro estática por meio de um objeto; na verdade, uma função-membro estática não pode nem usar o ponteiro this.
  3. Se uma função de membro estática for declarada na seção pública, ela poderá ser chamada usando o nome da classe e o operador de resolução de escopo.
  4. Como as funções de membro estático não estão associadas a um objeto específico, somente membros de dados estáticos na classe podem ser usados.
// 在String类声明中添加如下原型
static int HowMany() {
    
     return num_strings; }
// 调用
int count = String::HowMany(); // invoking a static member function

12.2.5 Sobrecarga adicional de operadores de atribuição

Sobrecarregue o operador de atribuição para possibilitar a cópia direta de uma string regular em um objeto String. Isso elimina a necessidade de criar e excluir objetos temporários.

String & String::operator=(const char * s)
{
    
    
    delete [] str;
    len = std::strlen(s);
    str = new char[len + 1];
    std::strcpy(str, s);
    return *this;
}

string1.h

#ifndef PRIMERPLUS_STRING1_H
#define PRIMERPLUS_STRING1_H
#include <iostream>
using std::ostream;
using std::istream;
class String
{
    
    
private:
    char * str;             // pointer to string
    int len;                // length of string
    static int num_strings; // number of objects
    static const int CINLIM = 80; // cin input limit
public:
// constructors and other methods
    String(const char * s);     // constructor
    String();                   // default constructor
    String(const String &);     // copy constructor
    ~String();                  // destructor
    int length () const {
    
     return len; }
// overloaded operator methods
    String & operator=(const String &);
    String & operator=(const char *);
    char & operator[](int i);
    const char & operator[](int i) const;
// overloaded operator friends
    friend bool operator<(const String &st, const String &st2);
    friend bool operator>(const String &st1, const String &st2);
    friend bool operator==(const String &st, const String &st2);
    friend ostream & operator<<(ostream & os, const String & st);
    friend istream & operator>>(istream & is, String & st);
// static function
    static int HowMany();
};
#endif //PRIMERPLUS_STRING1_H

string1.cpp

// string1.cpp -- String class methods
#include <cstring> // string.h for some
#include "string1.h" // includes <iostream>
using std::cin;
using std::cout;
// initializing static class member
int String::num_strings = 0;
// static method
int String::HowMany()
{
    
    
    return num_strings;
}
// class methods
String::String(const char * s) // construct String from C string
{
    
    
    len = std::strlen(s); // set size
    str = new char[len + 1]; // allot storage
    std::strcpy(str, s); // initialize pointer
    num_strings++; // set object count
}
String::String() // default constructor
{
    
    
    len = 4;
    str = new char[1];
    str[0] = '\0'; // default string
    num_strings++;
}
String::String(const String & st)
{
    
    
    num_strings++; // handle static member update
    len = st.len; // same length
    str = new char [len + 1]; // allot space
    std::strcpy(str, st.str); // copy string to new location
}
String::~String() // necessary destructor
{
    
    
    --num_strings; // required
    delete [] str; // required
}
// overloaded operator methods
// assign a String to a String
String & String::operator=(const String & st)
{
    
    
    if (this == &st)
        return *this;
    delete [] str;
    len = st.len;
    str = new char[len + 1];
    std::strcpy(str, st.str);
    return *this;
}
// assign a C string to a String
String & String::operator=(const char * s)
{
    
    
    delete [] str;
    len = std::strlen(s);
    str = new char[len + 1];
    std::strcpy(str, s);
    return *this;
}
// read-write char access for non-const String
char & String::operator[](int i)
{
    
    
    return str[i];
}
// read-only char access for const String
const char & String::operator[](int i) const
{
    
    
    return str[i];
}
// overloaded operator friends
bool operator<(const String &st1, const String &st2)
{
    
    
    return (std::strcmp(st1.str, st2.str) < 0);
}
bool operator>(const String &st1, const String &st2)
{
    
    
    return st2 < st1;
}
bool operator==(const String &st1, const String &st2)
{
    
    
    return (std::strcmp(st1.str, st2.str) == 0);
}
// simple String output
ostream & operator<<(ostream & os, const String & st)
{
    
    
    os << st.str;
    return os;
}
// quick and dirty String input
// 重载>>运算符提供了一种将键盘输入行读入到String对象中的简单方法。
// 它假定输入的字符数不多于String::CINLIM的字符数,并丢弃多余的字符。
// 在if条件下,如果由于某种原因
// (如到达文件尾或get(char *, int)读取的是一个空行)导致输入失败,istream对象的值将置为 false。
istream & operator>>(istream & is, String & st)
{
    
    
    char temp[String::CINLIM];
    is.get(temp, String::CINLIM);
    if (is)
        st = temp;
    while (is && is.get() != '\n')
        continue;
    return is;
}

usestring1.cpp

// sayings1.cpp -- using expanded String class
// compile with string1.cpp
// 程序首先提示用户输入,然后将用户输入的字符串存储到 String对象中,并显示它们,
// 最后指出哪个字符串最短、哪个字符串按 字母顺序排在最前面。
#include <iostream>
#include "string1.h"
const int ArSize = 10;
const int MaxLen = 81;
int main()
{
    
    
    using std::cout;
    using std::cin;
    using std::endl;
    String name;
    cout <<"Hi, what's your name?\n>>";
    cin >> name;
    cout << name << ", please enter up to " << ArSize
         << " short sayings <empty line to quit>:\n";
    String sayings[ArSize];     // array of objects
    char temp[MaxLen];          // temporary string storage
    int i;
    for (i = 0; i < ArSize; i++)
    {
    
    
        cout << i+1 << ":";
        cin.get(temp, MaxLen);
        while (cin && cin.get() != '\n')
            continue;
        if (!cin || temp[0] == '\0')    // empty line?
            break;                      // i not incremented
        else
            sayings[i] = temp;          // overloaded assignment
    }
    int total = i;                      // total # of lines read
    if ( total > 0)
    {
    
    
        cout << "Here are your sayings:\n";
        for (i = 0; i < total; i++)
            cout << sayings[i][0] << ": " << sayings[i] << endl;
        int shortest = 0;
        int first = 0;
        for (i = 1; i < total; i++)
        {
    
    
            if (sayings[i].length() < sayings[shortest].length())
                shortest = i;
            if (sayings[i] < sayings[first])
                first = i;
        }
        cout << "Shortest saying:\n" << sayings[shortest] << endl;;
        cout << "First alphabetically:\n" << sayings[first] << endl;
        cout << "This program used "<< String::HowMany()
             << " String objects. Bye.\n";
    }
    else
        cout << "No input! Bye.\n";
    return 0;
}

12.3 Coisas a observar ao usar new em construtores

  1. Se você usar new no construtor para inicializar os membros do ponteiro, deverá usar delete no destruidor.
  2. new e delete devem ser compatíveis entre si. new corresponde a delete e new[] corresponde a delete[].
  3. Se houver vários construtores, new deve ser usado da mesma maneira, com colchetes ou nenhum. Como existe apenas um destruidor, todos os construtores devem ser compatíveis com ele. No entanto, é possível inicializar um ponteiro com new em um construtor e nada (0 ou nullptr em C++11) em outro, porque delete (seja com colchetes ou sem colchetes) pode ser usado para ponteiros nulos.
  4. Um construtor de cópia deve ser definido para inicializar um objeto para outro por cópia profunda. Especificamente, o construtor de cópia deve alocar espaço suficiente para armazenar os dados copiados e copiar os dados, não apenas o endereço dos dados. Além disso, todos os membros de classe estática afetados devem ser atualizados.
  5. Um operador de atribuição deve ser definido para copiar um objeto para outro por meio de cópia profunda. Especificamente, o método deve verificar a autoatribuição, liberar a memória anteriormente apontada pelo ponteiro do membro, copiar os dados em vez de apenas o endereço dos dados e retornar uma referência ao objeto de chamada.

12.3.1 Exemplo de construtor padrão

String::String()
{
    
    
    len = 0;
    str = new char[1]; 	// uses new with []
    str[0] = '\0';
}
String::String()
{
    
    
    len = 0;
    str = 0; 			// or, with C++11, str = nullptr;
}
String::String()
{
    
    
    static const char * s = "C++"; 	// initialized just once
    len = std::strlen(s);
    str = new char[len + 1]; 		// uses new with []
    std::strcpy(str, s);
}

12.3.2 Cópia membro a membro de uma classe contendo membros de classe

Supondo que o membro da classe seja do tipo String class ou classe string padrão:

  1. Se você copiar ou atribuir um objeto Magazine a outro objeto Magazine, a cópia membro por membro usará o construtor de cópia e o operador de atribuição definidos pelo tipo de membro.
  2. A situação é mais complicada se a classe Magazine precisar definir construtores de cópia e operadores de atribuição por causa de outros membros; nesse caso, essas funções devem chamar explicitamente o construtor de cópia e o operador de atribuição de String e string.
class Magazine
{
    
    
private:
    String title;
    string publisher;
    ...
};

12.4 Notas sobre objetos devolvidos

Quando uma função de membro ou função autônoma retorna um objeto: ela pode retornar uma referência a um objeto, uma referência const a um objeto ou um objeto const.

  1. Se um método ou função deve retornar um objeto local, ele deve retornar o objeto, não uma referência ao objeto. Nesse caso, um construtor de cópia é usado para gerar o objeto retornado.
  2. Se um método ou função retornar um objeto de uma classe que não tenha um construtor de cópia pública (como a classe ostream), ele deverá retornar uma referência a tal objeto.
  3. Finalmente, alguns métodos e funções (como operadores de atribuição sobrecarregados) podem retornar um objeto ou uma referência a um objeto, caso em que uma referência deve ser preferida porque é mais eficiente.

12.4.1 Retornando uma referência a um objeto const

Se uma função retorna o objeto passado para ela (seja chamando um método no objeto ou passando o objeto como um parâmetro), você pode melhorar sua eficiência retornando uma referência.

  1. Primeiro, retornar um objeto invocará o construtor de cópia, enquanto retornar uma referência não. Portanto, a segunda versão funciona menos e é mais eficiente.
  2. Em segundo lugar, o objeto para o qual a referência aponta deve existir no momento em que a função de chamada é executada.
  3. Em terceiro lugar, ambas as versões 2 v1 e v2 são declaradas como referências const, portanto, o tipo de retorno deve ser const para corresponder.
// 例如,假设要编写函数Max(),它返回两个Vector对象中较大的一个,
// 其中Vector 是第11章开发的一个类。
Vector force1(50,60);
Vector force2(10,70);
Vector max;
max = Max(force1, force2);

// version 1
Vector Max(const Vector & v1, const Vector & v2)
{
    
    
    if (v1.magval() > v2.magval())
    	return v1;
    else
    	return v2;
}
// version 2
const Vector & Max(const Vector & v1, const Vector & v2)
{
    
    
    if (v1.magval() > v2.magval())
    	return v1;
    else
    	return v2;
}

12.4.2 Retornando uma referência a um objeto não const

Dois casos comuns de retorno de um objeto não const são sobrecarregar o operador de atribuição e sobrecarregar o operador << para uso com cout. O primeiro faz isso para melhorar a eficiência, enquanto o último deve fazê-lo.
O valor de retorno de Operator<<() é usado para concatenar a saída: o tipo de retorno deve ser ostream &, não apenas ostream. Se você usar o tipo de retorno ostream, será necessário chamar o construtor de cópia da classe ostream e ostream não tem construtor de cópia pública. Felizmente, retornar uma referência para cout não causa nenhum problema porque cout já está no escopo da função de chamada.

12.4.3 Retornando Objetos

Se o objeto retornado for uma variável local na função chamada, não deve ser retornado por referência, pois o objeto local terá seu destruidor chamado quando a função chamada terminar de executar. Portanto, quando o controle retornar à função de chamada, o objeto apontado pela referência não existirá mais. Nesse caso, um objeto deve ser retornado em vez de uma referência.
O exemplo a seguir:
a chamada do construtor Vector(x + bx, y + by) cria um objeto que o método operator+() pode acessar;
enquanto a chamada implícita para o construtor de cópia induzida pela instrução return cria um objeto que o chamador pode acessar .

Vector force1(50,60);
Vector force2(10,70);
Vector net;
net = force1 + force2;
// 返回的不是force1,也不是force2,force1和force2在这个过程中应该保持不变。
// 因此,返回值不能是指向在调用函数中已经存在的对象的引用。

// 在Vector::operator+( )中计算得到的两个矢量的和被存储在一个新的临时对象中,
// 该函数也不应返回指向该临时对象的引用,
// 而应该返回实际的Vector对象,而不是引用。
Vector Vector::operator+(const Vector & b) const
{
    
    
	return Vector(x + b.x, y + b.y);
}

12.4.4 Retornando um objeto const

A função sobrecarregada acima do operador + que retorna um objeto pode ser usada assim:

net = force1 + force2;			// 1: three Vector objects
force1 + force2 = net; 			// 2: dyslectic programming
cout << (force1 + force2 = net).magval() << endl; // 3: demented programming
  1. O construtor de cópia criará um objeto temporário para representar o valor de retorno. Portanto, o resultado da expressão force1 + force2 é um objeto temporário. Na instrução 1, o objeto temporário é atribuído a net; nas instruções 2 e 3, net é atribuído ao objeto temporário.
  2. Quando o objeto temporário termina de usá-lo, ele é descartado. Por exemplo, para a instrução 2, o programa calcula a soma de force1 e force2, copia o resultado no objeto de retorno temporário, sobrescreve o conteúdo do objeto temporário com o conteúdo de net e então descarta o objeto temporário. Todos os vetores originais permanecem os mesmos. A instrução 3 exibe o comprimento do objeto temporário e o exclui.
  3. Se o tipo de retorno de Vector::operator+() fosse declarado como const Vector, a instrução 1 ainda seria legal, mas as instruções 2 e 3 seriam ilegais.

12.5 Usando ponteiros para objetos

Se Class_name for uma classe e o valor for do tipo Type_name, a seguinte instrução:
Class_name * pclass = new Class_name(value);
chamará o seguinte construtor:
Class_name(Type_name);ou Class_name(const Type_name &);
Caso contrário, se não houver ambigüidade, ocorrerá uma conversão causada pela correspondência de protótipo (como de int para double). O seguinte método de inicialização chamará o construtor padrão:
Class_name * ptr = new Class_name;

usestring2.cpp

// compile with string1.cpp
// 最初,shortest指针指向数组中的第一个对象。
// 每当程序找到比指向的字符串更短的对象时,就把shortest重新设置为指向该对象。
// 同样,first指针跟踪按字母顺序排在最前面的字符串。
// 这两个指针并不创建新的对象,而只是指向已有的对象。因此,这些指针并不要求使用new来分配内存。
#include <iostream>
#include <cstdlib>  // (or stdlib.h) for rand(), srand()
#include <ctime>    // (or time.h) for time()
#include "string1.h"
const int ArSize = 10;
const int MaxLen = 81;
int main()
{
    
    
    using namespace std;
    String name;
    cout <<"Hi, what's your name?\n>>";
    cin >> name;
    cout << name << ", please enter up to " << ArSize
         << " short sayings <empty line to quit>:\n";
    String sayings[ArSize];
    char temp[MaxLen]; // temporary string storage
    int i;
    for (i = 0; i < ArSize; i++)
    {
    
    
        cout << i+1 << ":";
        cin.get(temp, MaxLen);
        while (cin && cin.get() != '\n')
            continue;
        if (!cin || temp[0] == '\0') // empty line?
            break; // i not incremented
        else
            sayings[i] = temp; // overloaded assignment
    }
    int total = i; // total # of lines read
    if (total > 0)
    {
    
    
        cout << "Here are your sayings:\n";
        for (i = 0; i < total; i++)
            cout << sayings[i] << "\n";
// use pointers to keep track of shortest, first strings
        String * shortest = &sayings[0]; // initialize to first object
        String * first = &sayings[0];
        for (i = 1; i < total; i++)
        {
    
    
            if (sayings[i].length() < shortest->length())
                shortest = &sayings[i];
            if (sayings[i] < *first) // compare values
                first = &sayings[i]; // assign address
        }
        cout << "Shortest saying:\n" << * shortest << endl;
        cout << "First alphabetically:\n" << * first << endl;
        srand(time(0));
        int choice = rand() % total; // pick index at random
// use new to create, initialize new String object
        String * favorite = new String(sayings[choice]);    // 指针favorite指向new创建的未被命名对象。
        cout << "My favorite saying:\n" << *favorite << endl;
        delete favorite;
    }
    else
        cout << "Not much to say, eh?\n";
    cout << "Bye.\n";
    return 0;
}

12.5.1 Falar sobre novo e deletar novamente

String * favorite = new String(sayings[choice]);
Isso não é para alocar memória para a string a ser armazenada, mas para alocar memória para o objeto; ou seja, para alocar memória para o ponteiro str e o membro len que contém o endereço da string (o programa não aloca memória para o membro num_string, porque o membro num_string é um membro estático, ele é armazenado independentemente do objeto). A criação do objeto chama o construtor, que aloca memória para manter a string e atribui o endereço da string a str. Então, quando o programa não precisar mais do objeto, exclua-o usando delete. Os objetos são únicos, então o programa usa delete sem os colchetes. Igual à introdução anterior, isso apenas liberará o espaço usado para salvar o ponteiro str e o membro len, e não liberará a memória apontada por str, e essa tarefa será concluída pelo destruidor.

O destruidor será chamado nos seguintes casos (veja a Figura 12.4):

  1. Se o objeto for uma variável dinâmica, o destruidor do objeto será chamado quando o bloco no qual o objeto está definido for executado. Portanto, na listagem de programa 12.3, quando main() for executado, os destruidores de headline[0] e headline[1] serão chamados; quando callme1() for executado, o destruidor de grub será chamado.
  2. Se o objeto for uma variável estática (externa, estática, estática externa ou do namespace), o destruidor do objeto será chamado no final do programa. É o que acontece com o objeto esportivo na Listagem 12.3.
  3. Se um objeto foi criado com new, seu destruidor só será chamado se você excluir explicitamente o objeto com delete.

12.5.2 Resumo de Ponteiros e Objetos

Está tudo nessas duas fotos.

12.5.3 Revisitando o novo operador de posicionamento

O operador new posicionado permite especificar um local de memória ao alocar memória.
O programa a seguir usa o operador new posicional e o operador new normal para alocar memória para um objeto.

// placenew1.cpp -- new, placement new, no delete
// 使用了定位new运算符和常规new运算符给对象分配内存.
// 该程序使用new运算符创建了一个512字节的内存缓冲区,
// 然后使用new运算符在堆中创建两个JustTesting对象,
// 并试图使用定位new运算符在内存缓冲区中创建两个JustTesting对象。
// 最后,它使用delete来释放使用new分配的内存。
#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF = 512;
class JustTesting
{
    
    
private:
    string words;
    int number;
public:
    JustTesting(const string & s = "Just Testing", int n = 0)
    {
    
    words = s; number = n; cout << words << " constructed\n"; }
    ~JustTesting() {
    
     cout << words << " destroyed\n";}
    void Show() const {
    
     cout << words << ", " << number << endl;}
};
int main()
{
    
    
    char * buffer = new char[BUF]; // get a block of memory
    JustTesting *pc1, *pc2;
    pc1 = new (buffer) JustTesting; // place object in buffer
    pc2 = new JustTesting("Heap1", 20); // place object on heap
    cout << "Memory block addresses:\n" << "buffer: "
         << (void *) buffer << " heap: " << pc2 <<endl;
    cout << "Memory contents:\n";
    cout << pc1 << ": ";
    pc1->Show();
    cout << pc2 << ": ";
    pc2->Show();
    JustTesting *pc3, *pc4;
    pc3 = new (buffer) JustTesting("Bad Idea", 6);
    pc4 = new JustTesting("Heap2", 10);
    cout << "Memory contents:\n";
    cout << pc3 << ": ";
    pc3->Show();
    cout << pc4 << ": ";
    pc4->Show();
    delete pc2; // free Heap1
    delete pc4; // free Heap2
    delete [] buffer; // free buffer
    cout << "Done\n";
    return 0;
}

fora:

Just Testing constructed
Heap1 constructed
Memory block addresses:
buffer: 0x1ff73f715b0 heap: 0x1ff73f71260
Memory contents:
0x1ff73f715b0: Just Testing, 0
0x1ff73f71260: Heap1, 20
Bad Idea constructed
Heap2 constructed
Memory contents:
0x1ff73f715b0: Bad Idea, 6
0x1ff73f717c0: Heap2, 10
Heap1 destroyed
Heap2 destroyed
Done

Existem dois problemas ao usar o novo operador posicional:

  1. Primeiro, o novo operador posicionado substitui o local de memória usado para o primeiro objeto por um novo objeto ao criar o segundo objeto. Obviamente, isso causará problemas se a classe alocar memória dinamicamente para seus membros.
  2. Em segundo lugar, quando delete for usado para pc2 e pc4, o destruidor será chamado automaticamente para os objetos apontados por pc2 e pc4; no entanto, quando delete[] for usado para buffer, o destruidor não será chamado para o objeto criado com o novo operador posicional.construtor.

Solução:
O programador deve ser responsável por localizar a localização da memória buffer utilizada pelo novo operador.
Para usar locais de memória diferentes, o programador precisa fornecer dois endereços diferentes no buffer e certificar-se de que os dois locais de memória não se sobreponham.

// 其中指针pc3相对于pc1的偏移量为JustTesting对象的大小。
pc1 = new (buffer) JustTesting;
pc3 = new (buffer + sizeof (JustTesting)) JustTesting("Better Idea", 6);

Se você usar o operador new posicionado para alocar memória para um objeto, deverá garantir que seu destruidor seja chamado. Ou seja, chamar explicitamente o destruidor para objetos criados com o novo operador posicionado.
O endereço apontado pelo ponteiro pc1 é o mesmo que o buffer, mas o buffer é inicializado com new [], portanto deve ser liberado com delete [] em vez de delete. Mesmo que o buffer tenha sido inicializado com new em vez de new[], deletar pc1 liberará buffer, não pc1. Isso ocorre porque o sistema new/delete conhece o buffer de bloco de 512 bytes alocado, mas não tem ideia do que o novo operador posicionado fez com esse bloco de memória.
Ao chamar um destruidor explicitamente, você deve especificar o objeto a ser destruído. Como existem ponteiros para objetos, eles podem ser usados:

pc3->~JustTesting(); // destroy object pointed to by pc3
pc1->~JustTesting(); // destroy object pointed to by pc1

O seguinte é um programa aprimorado:
gerencie a unidade de memória usada pelo novo operador de posicionamento e adicione as chamadas de destruidor explícitas e de exclusão apropriadas.
Os objetos criados com o novo operador posicional devem ser excluídos na ordem inversa da criação. A razão é que objetos criados posteriormente podem depender de objetos criados anteriormente. Além disso, os buffers usados ​​para armazenar esses objetos só são liberados quando todos os objetos forem destruídos.

// placenew2.cpp -- new, placement new, no delete
// 该程序使用定位new运算符在相邻的内存单元中创建两个对象,并调用了合适的析构函数。
#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF = 512;
class JustTesting
{
    
    
private:
    string words;
    int number;
public:
    JustTesting(const string & s = "Just Testing", int n = 0)
    {
    
    words = s; number = n; cout << words << " constructed\n"; }
    ~JustTesting() {
    
     cout << words << " destroyed\n";}
    void Show() const {
    
     cout << words << ", " << number << endl;}
};
int main()
{
    
    
    char * buffer = new char[BUF]; // get a block of memory
    JustTesting *pc1, *pc2;
    pc1 = new (buffer) JustTesting; // place object in buffer
    pc2 = new JustTesting("Heap1", 20); // place object on heap
    cout << "Memory block addresses:\n" << "buffer: "
         << (void *) buffer << " heap: " << pc2 <<endl;
    cout << "Memory contents:\n";
    cout << pc1 << ": ";
    pc1->Show();
    cout << pc2 << ": ";
    pc2->Show();
    JustTesting *pc3, *pc4;
// fix placement new location
    pc3 = new (buffer + sizeof (JustTesting))
            JustTesting("Better Idea", 6);
    pc4 = new JustTesting("Heap2", 10);
    cout << "Memory contents:\n";
    cout << pc3 << ": ";
    pc3->Show();
    cout << pc4 << ": ";
    pc4->Show();
    delete pc2; // free Heap1
    delete pc4; // free Heap2
// explicitly destroy placement new objects
    pc3->~JustTesting(); // destroy object pointed to by pc3
    pc1->~JustTesting(); // destroy object pointed to by pc1
    delete [] buffer; // free buffer
    cout << "Done\n";
    return 0;
}

fora:

Just Testing constructed
Heap1 constructed
Memory block addresses:
buffer: 0x231c1ea15b0 heap: 0x231c1ea1260
Memory contents:
0x231c1ea15b0: Just Testing, 0
0x231c1ea1260: Heap1, 20
Better Idea constructed
Heap2 constructed
Memory contents:
0x231c1ea15d8: Better Idea, 6
0x231c1ea17c0: Heap2, 10
Heap1 destroyed
Heap2 destroyed
Better Idea destroyed
Just Testing destroyed
Done

12.6 Revisão de várias técnicas

12.6.1 Sobrecarregando o operador <<

Para redefinir o operador << para que possa ser usado com cout para exibir o conteúdo de um objeto, defina a seguinte função de operador amigo:

ostream & operator<<(ostream & os, const c_name & obj)
{
    
    
    os << ... ; // display object contents
    return os;
}

onde c_name é o nome da classe. Se a classe fornece métodos públicos que retornam o que você deseja, você pode usar esses métodos na função do operador para não precisar torná-los funções amigas.

12.6.2 Funções de conversão

  1. Para converter um único valor em um tipo de classe, você precisa criar um construtor de classe cujo protótipo seja o seguinte: c_name(type_name value);
    onde c_name é o nome da classe e type_name é o nome do tipo a ser convertido.
  2. Para converter uma classe em outro tipo, você precisa criar uma função membro de classe cujo protótipo seja o seguinte: operator type_name();
    Embora a função não declare um tipo de retorno, ela deve retornar um valor do tipo desejado.
  3. Tenha cuidado ao usar funções de conversão. Você pode usar a palavra-chave explicit ao declarar um construtor para evitar que ele seja usado para conversões implícitas.

12.6.3 Uma classe cujo construtor usa new

  1. Para todos os membros da classe cuja memória é alocada por new, delete deve ser usado no destruidor da classe, que liberará a memória alocada.
  2. Se o destruidor liberar memória usando delete no membro da classe de ponteiro, todo construtor deverá inicializar o ponteiro com new ou defini-lo como um ponteiro nulo.
  3. Use new [] ou new no construtor e eles não podem ser misturados. Se o construtor usar new[], o destruidor deve usar delete[]; se o construtor usar new, o destruidor deve usar delete.
  4. Um construtor de cópia deve ser definido para alocar memória em vez de apontar um ponteiro para a memória existente. Dessa forma, o programa poderá inicializar um objeto de classe para outro objeto de classe. Os protótipos são geralmente os seguintes:className(const className &)
  5. Uma função de membro de classe que sobrecarrega o operador de atribuição deve ser definida e sua função é definida da seguinte maneira (onde c_pointer é um membro de classe de c_name e seu tipo é um ponteiro para type_name). O exemplo a seguir assume que new[] é usado para inicializar a variável c_pointer:
c_name & c_name::operator=(const c_name & cn)
{
    
    
    if (this == & cn)
    	return *this; // done if self-assignment
    delete [] c_pointer;
    // set size number of type_name units to be copied
    c_pointer = new type_name[size];
    // then copy data pointed to by cn.c_pointer to
    // location pointed to by c_pointer
    ...
    return *this;
}

12.7 Simulação de fila

Uma fila é um tipo de dado abstrato (Tipo de dado abstrato, ADT) que pode armazenar uma sequência ordenada de itens.
Novos itens são adicionados no final da fila e itens no início da fila podem ser removidos. Primeiro a entrar primeiro a sair (FIFO).

Exemplo: Heather Bank quer estimar quanto tempo os clientes esperam na fila.
Normalmente, um terço dos clientes será atendido em um minuto, um terço em dois minutos e outro terço em três minutos. Além disso, o tempo de chegada do cliente é aleatório, mas o número de clientes que usam o caixa eletrônico a cada hora é razoavelmente constante. As outras duas tarefas do projeto são: projetar uma classe para representar o cliente e escrever um programa para simular a interação entre o cliente e a fila.

12.7.1 Classe da fila Classe da fila

Defina as características de uma classe Queue:

  1. As filas armazenam sequências ordenadas de itens;
  2. Há um limite para o número de itens que a fila pode conter;
  3. Deve ser capaz de criar filas vazias;
  4. Deve ser possível verificar se a fila está vazia;
  5. Deve ser possível verificar se a fila está cheia;
  6. Deve ser possível adicionar itens no final da fila;
  7. Deve ser possível remover itens do início da fila;
  8. Deve ser possível determinar o número de itens na fila.

1. Projete a interface pública da classe Queue

class Queue
{
    
    
    enum {
    
    Q_SIZE = 10};
private:
// private representation to be developed later
public:
    Queue(int qs = Q_SIZE); 		// create queue with a qs limit
    ~Queue();
    bool isempty() const;
    bool isfull() const;
    int queuecount() const;			// 返回队列中节点的个数
    bool enqueue(const Item &item); // 入队
    bool dequeue(Item &item); 		// 出队
};

2. Representar dados da fila: lista encadeada

Uma lista encadeada consiste em uma sequência de nós. Cada nó contém as informações a serem salvas na lista encadeada e um ponteiro para o próximo nó.

struct Node
{
    
    
    Item item; 			// 当前节点的数据信息
    struct Node * next; // 指向下一个节点的位置
};

Lista ligada individualmente , cada nó contém apenas um ponteiro para outros nós.
Depois de saber o endereço do primeiro nó, você pode localizar cada nó subseqüente ao longo do ponteiro.
Normalmente, o ponteiro no último nó da lista vinculada é definido como NULL (ou 0) para indicar que não há mais nós a seguir.

3. Projete a parte privada da classe Queue

Deixe um membro de dados da classe Queue apontar para a posição inicial da lista encadeada.
Você também pode usar membros de dados para controlar o número máximo de itens que a fila pode armazenar e o número atual de itens.

typedef Customer Item;	// Customer 是一个类
class Queue
{
    
    
private:
// class scope definitions
// Node is a nested structure definition local to this class
    struct Node {
    
     Item item; struct Node * next;};
    enum {
    
    Q_SIZE = 10};
// private class members
    Node * front; 		// pointer to front of Queue
    Node * rear; 		// pointer to rear of Queue
    int items; 			// current number of items in Queue
    const int qsize; 	// maximum number of items in Queue
    ...
public:
//...
};

Estruturas e classes aninhadas
Uma estrutura, classe ou enumeração declarada dentro de uma declaração de classe é considerada aninhada dentro da classe e seu escopo é a classe inteira. Essa declaração não cria objetos de dados, mas apenas especifica os tipos que podem ser usados ​​na classe. Se a declaração for feita na parte privada da classe, o tipo declarado só pode ser usado nesta classe; se a declaração for feita na parte pública, o tipo declarado pode ser usado de fora da classe por meio do operador de resolução de escopo. Por exemplo, se Node for declarado na parte pública da classe Queue, uma variável do tipo Queue::Node poderá ser declarada fora da classe.

4. Método de aula*

Construtor — lista de inicialização de membros :

  1. Para membros de dados const, eles devem ser inicializados antes que o corpo do construtor seja executado, ou seja, quando o objeto é criado.
  2. Somente construtores podem usar essa sintaxe de lista de inicializadores.
  3. Para membros de dados de classe const não estáticos, essa sintaxe deve ser usada. (estático não na memória de classe)
  4. Essa sintaxe também deve ser usada para membros de classe declarados como referências. (Porque é necessário atribuir um valor ao definir uma referência)
  5. O valor inicial pode ser uma constante ou um parâmetro na lista de parâmetros do construtor.
  6. Os membros de dados são inicializados na ordem em que aparecem na declaração de classe, independentemente da ordem em que são listados no inicializador.
  7. Para membros que são objetos de classe, é mais eficiente usar listas de inicialização de membros.
// 队列最初是空的,因此队首和队尾指针都设置为NULL(0或nullptr),
// 并将items设置为0。另外,还应将队列的最大长度qsize设置为构造函数参数qs的值。
Queue::Queue(int qs) : qsize(qs) // initialize qsize to qs
{
    
    
    front = rear = NULL;
    items = 0;
}
// 初值可以是常量或构造函数的参数列表中的参数。
Queue::Queue(int qs) : qsize(qs), front(NULL), rear(NULL), items(0)
{
    
    }

Por que você precisa de uma lista inicializadora de membros?
Normalmente, quando um construtor é chamado, o objeto será criado antes que o código entre parênteses seja executado. Portanto, chamar Queue(int qs)o construtor fará com que o programa primeiro aloque memória para as variáveis ​​de membro (equivalente à inicialização). Em seguida, o fluxo do programa entra entre parênteses, usando a atribuição normal para armazenar o valor na memória. Neste momento, você não pode mais atribuir valores a constantes const.

Adicionar itens ao final da fila (enqueue):

bool Queue::enqueue(const Item & item)
{
    
    
    if (isfull())
        return false;
    Node * add = new Node; // create node
// on failure, new throws std::bad_alloc exception
    add->item = item; // set node pointers
    add->next = NULL; // or nullptr;
    items++;
    if (front == NULL) // if queue is empty,
        front = add; // place item at front
    else
        rear->next = add; // else place at rear
    rear = add; // have rear point to new node
    return true;
}

Exclua o primeiro item da fila (dequeue):

bool Queue::dequeue(Item & item)
{
    
    
    if (front == NULL)
        return false;
    item = front->item; // set item to first item in queue
    items--;
    Node * temp = front; // save location of first item
    front = front->next; // reset front to next item
    delete temp; // delete former first item
    if (items == 0)
        rear = NULL;
    return true;
}

Destruidor explícito:
adicionar um objeto à fila chamará new para criar um novo nó. Excluindo o nó, o método dequeue( ) pode de fato limpar o nó, mas isso não garante que a fila estará vazia quando expirar (a fila será desfeita quando não estiver vazia). Portanto, a classe precisa de um destruidor explícito - uma função que exclui todos os nós restantes.

Queue::~Queue()
{
    
    
    Node * temp;
    while (front != NULL) // while queue is not yet empty
    {
    
    
        temp = front; // save address of front item
        front = front->next;// reset pointer to next item
        delete temp; // delete former front
    } 
}

Finalmente, para clonar ou copiar uma fila, um construtor de cópia e um construtor de atribuição que executa uma cópia profunda devem ser fornecidos.

12.7.2 Classe de cliente

Quando um cliente entra na fila e quanto tempo leva para uma transação do cliente.
Quando a simulação gera um novo cliente, o programa cria um novo objeto cliente e armazena nele o horário de chegada do cliente e um horário de transação gerado aleatoriamente. Quando o cliente chegar no início da fila, o programa irá registrar o tempo neste horário e subtraí-lo do tempo de entrada na fila para obter o tempo de espera do cliente.

class Customer
{
    
    
private:
    long arrive; // arrival time for customer
    int processtime; // processing time for customer
public:
    Customer() {
    
     arrive = processtime = 0; }	// 默认构造函数创建一个空客户。
    void set(long when);
    long when() const {
    
     return arrive; }
    int ptime() const {
    
     return processtime; }
};
// set()成员函数将到达时间设置为参数,并将处理时间设置为1~3中的一个随机值。
void Customer::set(long when)
{
    
    
    processtime = std::rand() % 3 + 1;
    arrive = when;
}

12.7.3 Simulação ATM

O programa permite que o usuário digite 3 números: o comprimento máximo da fila, a duração da simulação do programa (em horas) e o número médio de clientes por hora. O programa usará loops - cada loop representa um minuto. Em cada minuto do loop, o programa fará o seguinte:

  1. Determine se um novo cliente chegou. Se ele vier e a fila não estiver cheia neste momento, adicione-o à fila, caso contrário, rejeite o cliente para enfileirar.
  2. Se nenhum cliente estiver fazendo transações, o primeiro cliente da fila é selecionado. Determine quanto tempo esse cliente está esperando e defina o contador wait_time para o tempo de processamento necessário para novos clientes.
  3. Se o cliente estiver em andamento, diminua o contador wait_time em 1.
  4. Registre vários dados, como o número de clientes atendidos, o número de clientes rejeitados, o tempo cumulativo de espera na fila e o tamanho cumulativo da fila.

fila.h

#ifndef PRIMERPLUS_QUEUE_H
#define PRIMERPLUS_QUEUE_H
// This queue will contain Customer items
class Customer
{
    
    
private:
    long arrive;        // arrival time for customer
    int processtime;    // processing time for customer
public:
    Customer() {
    
     arrive = processtime = 0; }    // 内联函数
    void set(long when);
    long when() const {
    
     return arrive; }
    int ptime() const {
    
     return processtime; }
};

typedef Customer Item;
class Queue
{
    
    
private:
// class scope definitions
// Node is a nested structure definition local to this class
// 结构体表示链表的每一个节点,存放节点信息和下一个指向的位置
// 当前节点保存的是一个类的对象,链表中依次存类的对象
    struct Node {
    
     Item item; struct Node * next;};
    enum {
    
    Q_SIZE = 10};
// private class members
    Node * front;   // pointer to front of Queue
    Node * rear;    // pointer to rear of Queue
    int items;      // current number of items in Queue
    const int qsize; // maximum number of items in Queue
// preemptive definitions to prevent public copying
    Queue(const Queue & q) : qsize(0) {
    
     }
    Queue & operator=(const Queue & q) {
    
     return *this;}
public:
    Queue(int qs = Q_SIZE);         // create queue with a qs limit
    ~Queue();
    bool isempty() const;
    bool isfull() const;
    int queuecount() const;         // 返回队列中节点的个数
    bool enqueue(const Item &item); // 入队
    bool dequeue(Item &item);       // 出队
};
#endif //PRIMERPLUS_QUEUE_H

fila.cpp

// queue.cpp -- Queue and Customer methods
#include "queue.h"
#include <cstdlib> // (or stdlib.h) for rand()
// Queue methods
Queue::Queue(int qs) : qsize(qs)
{
    
    
    front = rear = NULL;    // or nullptr
    items = 0;
}
// 析构函数:把原来开辟的内存空间都释放掉,队列不为空的时候解散队列
Queue::~Queue()
{
    
    
    Node * temp;
    while (front != NULL)   // while queue is not yet empty
    {
    
    
        temp = front;       // save address of front item
        front = front->next;// reset pointer to next item
        delete temp;        // delete former front
    }
}
bool Queue::isempty() const
{
    
    
    return items == 0;
}
bool Queue::isfull() const
{
    
    
    return items == qsize;
}
int Queue::queuecount() const
{
    
    
    return items;
}
// Add item to queue
bool Queue::enqueue(const Item & item)
{
    
    
    if (isfull())
        return false;
    Node * add = new Node;  // create node,存放新的队列节点
// on failure, new throws std::bad_alloc exception
    add->item = item;       // set node pointers
    add->next = NULL;       // or nullptr;队列的最后是空的
    items++;                // 节点个数++
    if (front == NULL)      // if queue is empty,
        front = add;        // place item at front
    else
        rear->next = add;   // else place at rear
    rear = add;             // have rear point to new node
    return true;
}
// Place front item into item variable and remove from queue
bool Queue::dequeue(Item & item)
{
    
    
    if (front == NULL)
        return false;
    item = front->item;     // set item to first item in queue
    items--;                // 节点个数--
    Node * temp = front;    // save location of first item
    front = front->next;    // reset front to next item
    delete temp;            // delete former first item
    if (items == 0)
        rear = NULL;
    return true;
}
// customer method
// when is the time at which the customer arrives
// the arrival time is set to when and the processing
// time set to a random value in the range 1 - 3
void Customer::set(long when)
{
    
    
    processtime = std::rand() % 3 + 1;  // 记录操作了多长时间
    arrive = when;  // 记录何时开始操作
}

usequeue.cpp

// 入队列,队列入满就出队列
#include "queue.h"
#include <iostream>
using namespace std;
int main(void)
{
    
    
    Item temp;
    int qs;
    int i = 0;
    int customers = 0;

    cout << "Enter max size of queue:";
    cin >> qs;

    Queue line(qs);

    while(!line.isfull())
    {
    
    
        temp.set(i++);      // 填当前进入队列的时间long类型的整数
        line.enqueue(temp);
        customers++;
    }
    cout << "enqueue, customers :" << customers << endl;

    while(!line.isempty())
    {
    
    
        line.dequeue(temp);
        customers--;
    }
    cout << "dequeue, now customers :" << customers << endl;
    return 0;
}

fora:

Enter max size of queue:12
enqueue, customers :12
dequeue, now customers :0

banco.cpp

// bank.cpp -- using the Queue interface
// compile with queue.cpp
#include <iostream>
#include <cstdlib>  // for rand() and srand()
#include <ctime>    // for time()
#include "queue.h"
const int MIN_PER_HR = 60;
bool newcustomer(double x); // is there a new customer?
int main()
{
    
    
    using std::cin;
    using std::cout;
    using std::endl;
    using std::ios_base;
// setting things up
    std::srand(std::time(0)); // random initializing of rand()
    cout << "Case Study: Bank of Heather Automatic Teller\n";
    cout << "Enter maximum size of queue:";
    int qs;
    cin >> qs;
    Queue line(qs);         // line queue holds up to qs people
    cout << "Enter the number of simulation hours:";
    int hours;              // hours of simulation
    cin >> hours;
// simulation will run 1 cycle per minute
    long cyclelimit = MIN_PER_HR * hours; // # of cycles
    cout << "Enter the average number of customers per hour:";
    double perhour;         // average # of arrival per hour
    cin >> perhour;
    double min_per_cust;    // average time between arrivals
    min_per_cust = MIN_PER_HR / perhour;
    Item temp;              // new customer data
    long turnaways = 0;     // turned away by full queue
    long customers = 0;     // joined the queue
    long served = 0;        // served during the simulation
    long sum_line = 0;      // cumulative line length
    int wait_time = 0;      // time until autoteller is free
    long line_wait = 0;     // cumulative time in line
// running the simulation
    for (int cycle = 0; cycle < cyclelimit; cycle++)
    {
    
    
        if (newcustomer(min_per_cust)) // have newcomer
        {
    
    
            if (line.isfull())
                turnaways++;
            else
            {
    
    
                customers++;
                temp.set(cycle);        // cycle = time of arrival
                line.enqueue(temp);     // add newcomer to line
            } }
        if (wait_time <= 0 && !line.isempty())
        {
    
    
            line.dequeue (temp);        // attend next customer
            wait_time = temp.ptime();   // for wait_time minutes
            line_wait += cycle - temp.when();
            served++;
        }
        if (wait_time > 0)
            wait_time--;
        sum_line += line.queuecount();
    }
// reporting results
    if (customers > 0)
    {
    
    
        cout << "customers accepted: " << customers << endl;
        cout << "customers served: " << served << endl;
        cout << "turnaways: " << turnaways << endl;
        cout << "average queue size: ";
        cout.precision(2);
        cout.setf(ios_base::fixed, ios_base::floatfield);
        cout << (double) sum_line / cyclelimit << endl;
        cout << "average wait time: "
             << (double) line_wait / served << " minutes\n";
    }
    else
        cout << "No customers!\n";
    cout << "Done!\n";
    return 0;
}
// x = average time, in minutes, between customers
// return value is true if customer shows up this minute
// 值RAND_MAX是在cstdlib文件(以前是 stdlib.h)中定义的,
// 值RAND_MAX是是rand( )函数可能返回的最大值(0是最小值)。
bool newcustomer(double x)
{
    
    
    return (std::rand() * x / RAND_MAX < 1);
}

fora:

Case Study: Bank of Heather Automatic Teller
Enter maximum size of queue:10
Enter the number of simulation hours:4
Enter the average number of customers per hour:30
customers accepted: 113
customers served: 108
turnaways: 0
average queue size: 2.27
average wait time: 4.74 minutes
Done!

12.8 Resumo

  1. No construtor de classe, você pode usar new para alocar memória para dados e, em seguida, atribuir o endereço de memória ao membro da classe. Dessa forma, a classe pode lidar com strings de tamanhos diferentes sem precisar fixar o comprimento do array com antecedência quando a classe é projetada. O uso de novos construtores de classe também pode causar problemas quando os objetos expiram. Se o objeto contém ponteiros de membro e a memória para a qual ele aponta é alocada por new, liberar a memória usada para manter o objeto não libera automaticamente a memória apontada pelos ponteiros de membro de objeto. Portanto, ao usar a nova classe no construtor de classe para alocar memória, você deve usar delete no destruidor de classe para liberar a memória alocada. Dessa forma, quando o objeto expira, a memória apontada por seu membro ponteiro é automaticamente liberada.
  2. Também podem surgir problemas ao inicializar um objeto em outro objeto ou atribuir um objeto a outro objeto, se o objeto contiver um membro ponteiro para a memória recém-alocada. Por padrão, C++ inicializa e atribui membros um a um, o que significa que os membros do objeto que está sendo inicializado ou atribuído serão exatamente os mesmos do objeto original. Se os membros do objeto original apontarem para um bloco de dados, os membros da cópia apontarão para o mesmo bloco de dados. Quando o programa finalmente excluir esses dois objetos, o destruidor da classe tentará excluir o mesmo bloco de dados da memória duas vezes, o que resultará em erro. A solução é: definir um construtor de cópia especial para redefinir a inicialização e sobrecarregar o operador de atribuição. Em ambos os casos, a nova definição criará cópias apontando para os dados e fará com que novos objetos apontem para essas cópias. Dessa forma, os objetos antigos e novos se referirão a dados idênticos e independentes sem sobreposição. Os operadores de atribuição devem ser definidos pelo mesmo motivo. Em cada caso, o objetivo final é realizar uma cópia profunda, ou seja, copiar os dados reais, não apenas um ponteiro para os dados.
  3. Quando a persistência de armazenamento de um objeto é automática ou externa, seu destruidor é chamado automaticamente quando ele não existe mais. Se você usar o operador new para alocar memória para um objeto e atribuir seu endereço a um ponteiro, o destruidor será chamado automaticamente para o objeto quando você usar delete no ponteiro. No entanto, se você usar o operador new posicionado (em vez do operador new normal) para alocar memória para um objeto de classe, deverá tomar cuidado ao chamar explicitamente o destruidor desse objeto chamando o método destruidor com um ponteiro para o objeto. C++ permite que estruturas, classes e definições de enumeração estejam contidas em classes. O escopo desses tipos aninhados é a classe inteira, o que significa que eles estão confinados à classe e não entrarão em conflito com estruturas, classes e enumerações de mesmo nome definidas em outro lugar.
  4. C++ fornece uma sintaxe especial para construtores de classe que podem ser usados ​​para inicializar membros de dados. Essa sintaxe consiste em dois pontos e uma lista de inicialização separada por vírgula, colocada após o parêntese de fechamento dos parâmetros do construtor e antes do parêntese de abertura do corpo da função. Cada inicializador consiste no nome do membro que está sendo inicializado e parênteses contendo o valor inicial. Conceitualmente, essas operações de inicialização são executadas quando o objeto é criado e as instruções no corpo da função ainda não foram executadas. A sintaxe é a seguinte: queue(int qs) : qsize(qs), items(0), front(NULL), rear(NULL) { }Se o membro de dados for uma referência ou membro const não estático, esse formato deve ser usado, mas a nova inicialização em classe do C++11 pode ser usada para membros const não estáticos.
  5. C++11 permite inicialização dentro da classe, ou seja, inicialização dentro da definição da classe:
    isso é equivalente a usar uma lista de inicialização de membros. No entanto, um construtor que usa uma lista de inicializadores de membros substitui a inicialização correspondente na classe.
class Queue
{
    
    
private:
    ...
    Node * front = NULL;
    enum {
    
    Q_SIZE = 10};
    Node * rear = NULL;
    int items = 0;
    const int qsize = Q_SIZE;
    ...
};

12.9 Questões de revisão

1、

#include <cstring>
using namespace std;
class String
{
    
    
private:
    char * str;
    int len;
};
// 构造函数
// 需要给str指针指向一个内存空间,不能不管它
String::String(const char *s)
{
    
    
    len = strlen(s);
    str = new char[len+1];
    strcpy(str, s);
}

2. Se você definir uma classe cujos membros ponteiros são inicializados com new, aponte 3 possíveis problemas e como corrigi-los.

  1. Primeiro, quando um objeto desse tipo expirar, os dados apontados pelo ponteiro do objeto ainda permanecerão na memória, o que ocupará espaço e ao mesmo tempo ficará inacessível porque o ponteiro foi perdido. Você pode permitir que o destruidor de classe exclua a memória alocada por new no construtor para resolver esse problema.
  2. Em segundo lugar, depois que o destruidor liberar essa memória, se o programa inicializar esse objeto para outro objeto, o destruidor tentará liberar essa memória duas vezes. Isso ocorre porque a inicialização de um objeto para a inicialização padrão do outro copiará o valor do ponteiro, mas não os dados apontados, o que fará com que os dois ponteiros apontem para os mesmos dados. A solução é definir um construtor de cópia que faça a inicialização copiar os dados apontados.
  3. Em terceiro lugar, atribuir um objeto a outro também resultará em ambos os ponteiros apontando para os mesmos dados. A solução é sobrecarregar o operador de atribuição para que ele copie dados em vez de ponteiros.

4、

#include <iostream>
#include <cstring>
using namespace std;
class Nifty
{
    
    
private:    // 可以省略
    char *personality;
    int talents;
public:     // 不可以省略
    Nifty();
    Nifty(const char * s);              // 加const不希望被修改
    ~Nifty(){
    
    delete [] personality;}    // 释放构造函数中开辟的内存空间
    friend ostream & operator<<(ostream & os, const Nifty & n);
};
Nifty::Nifty()
{
    
    
    personality = NULL;
    talents = 0;
}
Nifty::Nifty(const char * s)
{
    
    
    int len;
    len = strlen(s);
    personality = new char[len+1];
    strcpy(personality, s);
    talents = 0;
}
ostream & operator<<(ostream & os, const Nifty & n)
{
    
    
    os << "personality : " << n.personality << endl;
    os << "talents = " << n.talents << endl;
    return os;
}

5. Quais métodos de classe serão invocados por cada uma das seguintes instruções?

class Golfer
{
    
    
private:
    char * fullname; // points to string containing golfer's name
    int games; // holds number of golf games played
    int * scores; // points to first element of array of golf scores
public:
    Golfer();
    Golfer(const char * name, int g= 0);
// creates empty dynamic array of g elements if g > 0
    Golfer(const Golfer & g);
    ~Golfer();
};
// 下列各条语句将调用哪些类方法?
Golfer nancy; 					// #1
Golfer lulu(“Little Lulu”); 	// #2
Golfer roy(“Roy Hobbs”, 12); 	// #3
Golfer * par = new Golfer; 		// #4,new开辟类这么大的内存空间,调用默认构造函数。
Golfer next = lulu; 			// #5,一个类的对象初始化另一个类对象,复制构造函数。
Golfer hazzard = “Weed Thwacker”; // #6,用字符串初始化类的对象,将某一种类型转化为类的类型时将使用带字符串参数的构造函数。
*par = nancy; 					// #7,两边对象都存在不会调用构造函数,调用默认赋值运算符。
nancy = “Nancy Putter”; 		// #8,右边调用带字符串的构造函数,然后调用默认的赋值运算符。

12.10 Exercícios de Programação

primeira pergunta

12p1.h


#ifndef PRIMERPLUS_12P1_H
#define PRIMERPLUS_12P1_H
class Cow
{
    
    
    char name[20];
    char * hobby;
    double weight;
public:
    Cow();
    Cow(const char * nm, const char * ho, double we);
    Cow(const Cow & c);
    ~Cow();
    Cow & operator=(const Cow & c);
    void ShowCow() const;
};
#endif //PRIMERPLUS_P1_H

12p1.cpp

#include "12p1.h"
#include <cstring>
#include <iostream>
using namespace std;
Cow::Cow()
{
    
    
    name[0] = '\0';
    hobby = nullptr;    // NULL
    weight = 0.0;
}
Cow::Cow(const char * nm, const char * ho, double wt)
{
    
    
    strncpy(name, nm, 20);  // strncpy()和strnpy()略有不同
    if (strlen(nm) >= 20)
        name[19] = '\0';
    hobby = new char[strlen(ho) + 1];   // 深度拷贝,指针指向开辟的空间
    strcpy(hobby, ho);
    weight = wt;
}
Cow::Cow(const Cow & c)
{
    
    
    strcpy(name, c.name);
    hobby = new char[strlen(c.hobby) + 1];   // 深度拷贝,指针指向开辟的空间
    strcpy(hobby, c.hobby);
    weight = c.weight;
}
Cow::~Cow()
{
    
    
    delete [] hobby;
}
Cow & Cow::operator=(const Cow & c)
{
    
    
    if (this == &c)
        return *this;
    delete [] hobby;    // 释放成员指针以前指向的内存
    strcpy(name, c.name);
    hobby = new char[strlen(c.hobby) + 1];   // 深度拷贝,指针指向开辟的空间
    strcpy(hobby, c.hobby);
    weight = c.weight;
    return *this;
}
void Cow::ShowCow() const
{
    
    
    cout << "Name  : " << name << endl;
    cout << "Hobby : " << hobby << endl;
    cout << "Weight: " << weight << endl;
}

use12p1.cpp

#include "12p1.h"
int main(void)
{
    
    
    Cow cow1;
    Cow cow2("cow2", "cccc", 123.4);
    Cow cow3(cow2);
    cow1 = cow2;
    cow1.ShowCow();
    cow2.ShowCow();
    cow3.ShowCow();
    return 0;
}

segunda questão

string2.h

#ifndef PRIMERPLUS_STRING2_H
#define PRIMERPLUS_STRING2_H
#include <iostream>
using std::ostream;
using std::istream;
class String
{
    
    
private:
    char * str;             // pointer to string
    int len;                // length of string
    static int num_strings; // number of objects
    static const int CINLIM = 80; // cin input limit
public:
// constructors and other methods
    String(const char * s);     // constructor
    String();                   // default constructor
    String(const String &);     // copy constructor
    ~String();                  // destructor
    int length () const {
    
     return len; }
// overloaded operator methods
    String & operator=(const String &);
    String & operator=(const char *);
    char & operator[](int i);
    const char & operator[](int i) const;
// overloaded operator friends
    friend bool operator<(const String &st, const String &st2);
    friend bool operator>(const String &st1, const String &st2);
    friend bool operator==(const String &st, const String &st2);
    friend ostream & operator<<(ostream & os, const String & st);
    friend istream & operator>>(istream & is, String & st);
// static function
    static int HowMany();

    friend String operator+(const char * s, const String & st);
    String operator+(const String & st);
    void stringlow();
    void stringup();
    int has(char ch) const;
};
#endif //PRIMERPLUS_STRING1_H

string2.cpp

// string1.cpp -- String class methods
#include <cstring>      // string.h for some
#include "string2.h"    // includes <iostream>
#include <cctype>
using namespace std;
// initializing static class member
int String::num_strings = 0;
// static method
int String::HowMany()
{
    
    
    return num_strings;
}
// class methods
String::String(const char * s) // construct String from C string
{
    
    
    len = std::strlen(s); // set size
    str = new char[len + 1]; // allot storage
    std::strcpy(str, s); // initialize pointer
    num_strings++; // set object count
}
String::String() // default constructor
{
    
    
    len = 4;
    str = new char[1];
    str[0] = '\0'; // default string
    num_strings++;
}
String::String(const String & st)
{
    
    
    num_strings++; // handle static member update
    len = st.len; // same length
    str = new char [len + 1]; // allot space
    std::strcpy(str, st.str); // copy string to new location
}
String::~String() // necessary destructor
{
    
    
    --num_strings; // required
    delete [] str; // required
}
// overloaded operator methods
// assign a String to a String
String & String::operator=(const String & st)
{
    
    
    if (this == &st)
        return *this;
    delete [] str;
    len = st.len;
    str = new char[len + 1];
    std::strcpy(str, st.str);
    return *this;
}
// assign a C string to a String
String & String::operator=(const char * s)
{
    
    
    delete [] str;
    len = std::strlen(s);
    str = new char[len + 1];
    std::strcpy(str, s);
    return *this;
}
// read-write char access for non-const String
char & String::operator[](int i)
{
    
    
    return str[i];
}
// read-only char access for const String
const char & String::operator[](int i) const
{
    
    
    return str[i];
}
// overloaded operator friends
bool operator<(const String &st1, const String &st2)
{
    
    
    return (std::strcmp(st1.str, st2.str) < 0);
}
bool operator>(const String &st1, const String &st2)
{
    
    
    return st2 < st1;
}
bool operator==(const String &st1, const String &st2)
{
    
    
    return (std::strcmp(st1.str, st2.str) == 0);
}
// simple String output
ostream & operator<<(ostream & os, const String & st)
{
    
    
    os << st.str;
    return os;
}
// quick and dirty String input
// 重载>>运算符提供了一种将键盘输入行读入到String对象中的简单方法。
// 它假定输入的字符数不多于String::CINLIM的字符数,并丢弃多余的字符。
// 在if条件下,如果由于某种原因
// (如到达文件尾或get(char *, int)读取的是一个空行)导致输入失败,istream对象的值将置为 false。
istream & operator>>(istream & is, String & st)
{
    
    
    char temp[String::CINLIM];
    is.get(temp, String::CINLIM);
    if (is)
        st = temp;
    while (is && is.get() != '\n')
        continue;
    return is;
}

String operator+(const char * s, const String & st)
{
    
    
    String temp;
    temp.len = strlen(s) + st.len;
    temp.str = new char[temp.len+1];
    strcpy(temp.str, s);
    strcat(temp.str, st.str);
    return temp;
}
String String::operator+(const String & st)
{
    
    
    String temp;
    temp.len = len + st.len;
    temp.str = new char[temp.len+1];
    strcpy(temp.str, str);
    strcat(temp.str, st.str);
    return temp;
}
void String::stringlow()
{
    
    
    for (int i=0; i < len; i++)
        str[i] = tolower(str[i]);
}
void String::stringup()
{
    
    
    for (int i=0; i < len; i++)
        str[i] = toupper(str[i]);
}
int String::has(char ch) const
{
    
    
    int count = 0;
    for (int i=0; i < len; i++)
    {
    
    
        if (str[i] == ch)
            count++;
    }
    return count;
}

usestring2.cpp

#include <iostream>
using namespace std;
#include "string2.h"
int main()
{
    
    
    String s1(" and I am a C++ student.");
    String s2 = "Please enter your name: ";
    String s3;
    cout << s2; // overloaded << operator
    cin >> s3; // overloaded >> operator
    s2 = "My name is " + s3; // overloaded =, + operators
    cout << s2 << ".\n";
    s2 = s2 + s1;
    s2.stringup(); // converts string to uppercase
    cout << "The string\n" << s2 << "\ncontains " << s2.has('A')
         << " 'A' characters in it.\n";
    s1 = "red"; // String(const char *),
// then String & operator=(const String&)
    String rgb[3] = {
    
     String(s1), String("green"), String("blue")};
    cout << "Enter the name of a primary color for mixing light: ";
    String ans;
    bool success = false;
    while (cin >> ans)
    {
    
    
        ans.stringlow(); // converts string to lowercase
        for (int i = 0; i < 3; i++)
        {
    
    
            if (ans == rgb[i]) // overloaded == operator
            {
    
    
                cout << "That's right!\n";
                success = true;
                break;
            } }
        if (success)
            break;
        else
            cout << "Try again!\n";
    }
    cout << "Bye\n";
    return 0;
}

fora:

Please enter your name: Fretta Farbo
My name is Fretta Farbo.
The string
MY NAME IS FRETTA FARBO AND I AM A C++ STUDENT.
contains 6 'A' characters in it.
Enter the name of a primary color for mixing light: yellow
Try again!
BLUE
That's right!
Bye

quarta pergunta

12p4.h


#ifndef PRIMERPLUS_12P4_H
#define PRIMERPLUS_12P4_H
typedef unsigned long Item;         // 起别名,为存放不同的数据类型
class Stack
{
    
    
private:                // 私有部分放成员变量
    enum {
    
    MAX = 10};    // 枚举类型的符号常量
    Item * pitems;
    int size;
    int top;            // 顶部堆栈项的索引,栈顶指针
public:
    Stack(int n = MAX);                // 默认构造函数
    Stack(const Stack & st);
    ~Stack();
    Stack & operator=(const Stack & st);
    bool isempty() const;   // 判断是否为空
    bool isfull() const;    // 判断是否满了
    // push() returns false if stack already is full, true otherwise
    bool push(const Item & item);   // 入栈
    // pop() returns false if stack already is empty, true otherwise
    bool pop(Item & item);          // 出栈
};
#endif //PRIMERPLUS_STACK_H

12p4.cpp

// stack.cpp -- Stack member functions
#include "12p4.h"
Stack::Stack(int n) // create an empty stack
{
    
    
    pitems = new Item[n];
    size = n;
    top = 0;            // 初始化栈顶指针
}
Stack::Stack(const Stack & st)
{
    
    
    pitems = new Item[st.size];
    for (int i=0; i<st.size; i++)
        pitems[i] = st.pitems[i];
    size = st.size;
    top = st.top;
}
Stack::~Stack()
{
    
    
    delete [] pitems;
}
bool Stack::isempty() const
{
    
    
    return top == 0;    // 是否等于最底层
}
bool Stack::isfull() const
{
    
    
    return top == MAX;  // 是否等于最高层
}
bool Stack::push(const Item & item)
{
    
    
    if (top < MAX)      // 入栈条件
    {
    
    
        pitems[top++] = item;
        return true;
    }
    else
        return false;
}
bool Stack::pop(Item & item)
{
    
    
    if (top > 0)
    {
    
    
        item = pitems[--top];
        return true;
    }
    else
        return false;
}
Stack & Stack::operator=(const Stack & st)
{
    
    
    if (this == &st)
        return *this;
    delete [] pitems;
    pitems = new Item[st.size];
    for (int i=0; i<st.size; i++)
        pitems[i] = st.pitems[i];
    size = st.size;
    top = st.top;
    return * this;
}

use12p4.cpp

// stacker.cpp -- testing the Stack class
#include <iostream>
#include <cctype>   // or ctype.h
#include "12p4.h"
const int MAX = 5;
int main()
{
    
    
    using namespace std;
    Stack st(MAX);       // create an empty stack
    Item item;
    for (int i=0; i<MAX; i++)
    {
    
    
        cout << "Enter a number you want to push to stack:";
        cin >> item;
        while(cin.get() != '\n');
        st.push(item);
    }

    Stack st_new(st);   // 复制构造函数
    for (int i=0; i < MAX; i++)
    {
    
    
        st_new.pop(item);
        cout << item << " is poped." << endl;
    }

    return 0;
}

fora:

Enter a number you want to push to stack:1
Enter a number you want to push to stack:2
Enter a number you want to push to stack:3
Enter a number you want to push to stack:4
Enter a number you want to push to stack:5
5 is poped.
4 is poped.
3 is poped.
2 is poped.
1 is poped.

Acho que você gosta

Origin blog.csdn.net/qq_39751352/article/details/126922068
Recomendado
Clasificación