[C++ Primer Plus] Chapter 12 Classes and Dynamic Memory Allocation

12.1 Dynamic memory and classes

  1. Let the program decide memory allocation at runtime, not at compile time.
  2. C++ uses new and delete operators to dynamically control memory. To use these operators in a class, a destructor will be essential.
  3. Strings defined by class objects are not stored in the object. Strings are stored separately in heap memory, and objects only hold information indicating where to find the string.
  4. When using new in the constructor to allocate memory, you must use delete in the corresponding destructor to release the memory. If you use new[] (including square brackets) to allocate memory, you should use delete[] (including square brackets) to release memory.

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";
}

out:

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 Characteristics of static class members

  1. No matter how many objects are created, the program only creates one copy of the static class variable.
  2. All objects of the class share the same static member
  3. Static member variables cannot be initialized in a class declaration because the declaration describes how to allocate memory but does not allocate it.
  4. The initialization is done in the method file, not in the class declaration file , because the class declaration is in the header file, which the program may include in several other files. If initialization is done in a header file, there will be multiple copies of the initialization statement, raising an error.
  5. Static data members are declared in the class declaration and initialized in the file containing the class methods.
  6. The scope operator is used at initialization to indicate the class to which the static member belongs.
  7. But if the static member is integral or enum const, it can be initialized in the class declaration.

12.1.2 Special member functions

C++ automatically provides the following member functions:

  1. default constructor, if no constructor is defined;
  2. default destructor, if not defined;
  3. copy constructor, if not defined;
  4. assignment operator, if not defined; (assigns one object to another)
  5. Address operator, if not defined. The implicit address operator returns the address of the calling object (that is, the value of the this pointer).

1. Default constructor

C++ will not define a default constructor if a constructor is defined. If you want to create an object without explicitly initializing it, you must explicitly define a default constructor.
A parameterized constructor can also be a default constructor, as long as all parameters have default values. But there can only be one default constructor.

2. Copy constructor

The copy constructor is used to copy an object into a newly created object.
For example: the copy constructor prototype of the StringBad class:StringBad(const StringBad &);

When to call : The copy constructor will be called when creating a new object and initializing it as an existing object of the same type.

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

Whenever a program makes a copy of an object, the compiler will use the copy constructor. (The copy constructor is used when a function passes an object by value or when a function returns an object.)
Since passing an object by value invokes the copy constructor, the object should be passed by reference. This saves the time to call the constructor and the space to store the new object.

What it does : The default copy constructor copies non-static members one by one (member copying is also known as shallow copying), copying the value of the member.
If the member itself is a class object, the member object will be copied using the copy constructor of this class. Static functions such as num_strings are not affected because they belong to the entire class, not individual objects.

Reasons why a copy constructor must be defined:

  1. If your class contains static data members whose value will change when a new object is created, you should provide an explicit copy constructor to handle counting.
  2. The reason a copy constructor must be defined is that some class members are pointers to data initialized with new, rather than the data itself.
  3. If the class contains pointer members initialized with new, a copy constructor should be defined to copy the pointed-to data instead of the pointer. This is called deep copying .
  4. Another form of copying (member copy or shallow copy) simply copies pointer values. A shallow copy only copies the pointer information shallowly, without "digging" deep enough to copy the structure referenced by the pointer.
// StringBad类的显式复制函数
StringBad::StringBad(const StringBad & s)
{
    
    
    num_strings++;	// 静态数据成员
    ...// important stuff to go here
}

3. Assignment constructor

  1. When assigning an existing object to another object, the overloaded assignment operator is used.
  2. When an object is initialized, the assignment operator is not necessarily used (it is also possible to use the copy constructor to create a temporary object, and then copy the value of the temporary object to the new object through assignment.)
  3. Like the copy constructor, the implicit implementation of the assignment operator also copies members one by one.
  4. If the member is itself a class object, the program will use the assignment operator defined for the class to copy the member, but static data members are not affected.

The characteristics of the assignment constructor:

  1. Since the target object may refer to previously allocated data, the function should use delete[ ] to release the data.
  2. Functions should avoid assigning objects to themselves; otherwise, freeing memory operations may delete the contents of the object before reassigning the object.
  3. The function returns a reference to the calling object.
// 赋值操作并不创建新的对象,因此不需要调整静态数据成员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 Improved String class

12.2.1 Null Pointers

  1. delete[] is compatible with both pointers initialized with new[] and with null pointers.
  2. In C++98, the literal value 0 has two meanings: it can represent the numeric value zero, or it can represent a null pointer.
  3. C++11 introduces a new keyword nullptr to represent a null pointer.
char *str;
str = nullptr;	// 空指针
str = 0;		// 空指针
str = NULL;		// 空指针

12.2.2 Comparing member functions

To implement a string comparison function, the easiest way is to use the standard trcmp() function:

  1. If the first argument precedes the second in alphabetical order, the function returns a negative value;
  2. Returns 0 if the two strings are the same;
  3. Returns a positive value if the first argument comes after the second.
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);
}

A comparison function as a friend is useful for comparing String objects with regular C strings.

12.2.3 Accessing characters using bracket notation

For bracket operators, one operand precedes the first bracket and the other operand is between the two brackets.

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 Static class member functions

  1. Member functions can be declared static (function declarations must contain the keyword static, but cannot contain the keyword static if the function definition is self-contained)
  2. First, you cannot call a static member function through an object; in fact, a static member function cannot even use the this pointer.
  3. If a static member function is declared in the public section, it can be called using the class name and the scope resolution operator.
  4. Since static member functions are not associated with a specific object, only static data members in the class can be used.
// 在String类声明中添加如下原型
static int HowMany() {
    
     return num_strings; }
// 调用
int count = String::HowMany(); // invoking a static member function

12.2.5 Further overloading of assignment operators

Overload the assignment operator to make it possible to directly copy a regular string into a String object. This eliminates the need to create and delete temporary objects.

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 Things to note when using new in constructors

  1. If you use new in the constructor to initialize pointer members, you should use delete in the destructor.
  2. new and delete must be compatible with each other. new corresponds to delete, and new[] corresponds to delete[].
  3. If there are multiple constructors, new must be used in the same way, either with brackets or none. Since there is only one destructor, all constructors must be compatible with it. However, it is possible to initialize a pointer with new in one constructor and to nothing (0 or nullptr in C++11) in another, because delete (whether with brackets or without square brackets) can be used for null pointers.
  4. A copy constructor should be defined to initialize one object to another by deep copying. Specifically, the copy constructor should allocate enough space to store the copied data, and copy the data, not just the address of the data. Also, all affected static class members should be updated.
  5. An assignment operator should be defined to copy one object to another via deep copy. Specifically, the method should check for self-assignment, free the memory previously pointed to by the member pointer, copy the data rather than just the address of the data, and return a reference to the calling object.

12.3.1 Default constructor example

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 Member-by-member copying of a class containing class members

Assuming the class member is of type String class or standard string class:

  1. If you copy or assign one Magazine object to another Magazine object, member-by-member copying uses the copy constructor and assignment operator defined by the member type.
  2. The situation is more complicated if the Magazine class needs to define copy constructors and assignment operators because of other members; in this case, these functions must explicitly call the copy constructor and assignment operator of String and string.
class Magazine
{
    
    
private:
    String title;
    string publisher;
    ...
};

12.4 Notes on returned objects

When a member function or stand-alone function returns an object: it can return a reference to an object, a const reference to an object, or a const object.

  1. If a method or function is to return a local object, it should return the object, not a reference to the object. In this case, a copy constructor is used to generate the returned object.
  2. If a method or function returns an object of a class that does not have a public copy constructor (such as the ostream class), it must return a reference to such an object.
  3. Finally, some methods and functions (such as overloaded assignment operators) can return an object or a reference to an object, in which case a reference should be preferred because it is more efficient.

12.4.1 Returning a reference to a const object

If a function returns the object passed to it (either by calling a method on the object or passing the object as a parameter), you can improve its efficiency by returning a reference.

  1. First, returning an object will invoke the copy constructor, whereas returning a reference will not. So the second version does less work and is more efficient.
  2. Second, the object the reference points to should exist at the time the calling function executes.
  3. Third, both version 2 v1 and v2 are declared as const references, so the return type must be const to match.
// 例如,假设要编写函数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 Returning a reference to a non-const object

Two common cases of returning a non-const object are overloading the assignment operator and overloading the << operator for use with cout. The former does this to improve efficiency, while the latter must do so.
The return value of Operator<<() is used for concatenating output: the return type must be ostream &, not just ostream. If you use the return type ostream, you will be required to call the copy constructor of the ostream class, and ostream has no public copy constructor. Fortunately, returning a reference to cout doesn't cause any problems because cout is already in scope of the calling function.

12.4.3 Returning Objects

If the returned object is a local variable in the called function, it should not be returned by reference, because the local object will have its destructor called when the called function finishes executing. Therefore, when control returns to the calling function, the object pointed to by the reference will no longer exist. In this case, an object should be returned instead of a reference.
The following example:
the constructor call Vector(x + bx, y + by) creates an object that the method operator+() can access;
whereas the implicit call to the copy constructor induced by the return statement creates an object that the caller can access.

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 Returning a const object

The above overloaded function of the + operator that returns an object can be used like this:

net = force1 + force2;			// 1: three Vector objects
force1 + force2 = net; 			// 2: dyslectic programming
cout << (force1 + force2 = net).magval() << endl; // 3: demented programming
  1. The copy constructor will create a temporary object to represent the return value. Therefore, the result of the expression force1 + force2 is a temporary object. In statement 1, the temporary object is assigned to net; in statements 2 and 3, net is assigned to the temporary object.
  2. When the temporary object is finished using it, it is discarded. For example, for statement 2, the program calculates the sum of force1 and force2, copies the result into the temporary return object, overwrites the content of the temporary object with the content of net, and then discards the temporary object. The original vectors all remain the same. Statement 3 displays the length of the temporary object and then deletes it.
  3. If the return type of Vector::operator+() was declared as const Vector, statement 1 would still be legal, but statements 2 and 3 would be illegal.

12.5 Using pointers to objects

If Class_name is a class and value is of type Type_name, then the following statement:
Class_name * pclass = new Class_name(value);
will call the following constructor:
Class_name(Type_name);or Class_name(const Type_name &);
Otherwise, if there is no ambiguity, a conversion caused by prototype matching (such as from int to double) will occur. The following initialization method will call the default constructor:
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 Talk about new and delete again

String * favorite = new String(sayings[choice]);
This is not to allocate memory for the string to be stored, but to allocate memory for the object; that is, to allocate memory for the str pointer and the len member that hold the address of the string (the program does not allocate memory for the num_string member, because the num_string member is a static member, it is stored independently of the object). Creating the object calls the constructor, which allocates memory to hold the string and assigns the address of the string to str. Then, when the program no longer needs the object, delete it using delete. Objects are single, so the program uses delete without the brackets. The same as the previous introduction, this will only release the space used to save the str pointer and the len member, and will not release the memory pointed to by str, and this task will be completed by the destructor.

The destructor will be called in the following cases (see Figure 12.4):

  1. If the object is a dynamic variable, the object's destructor will be called when the block in which the object is defined has been executed. Therefore, in program listing 12.3, when main() is executed, the destructors of headline[0] and headline[1] will be called; when callme1() is executed, the destructor of grub will be called.
  2. If the object is a static variable (external, static, staticexternal, or from namespace), the object's destructor will be called at the end of the program. This is what happens to the sports object in Listing 12.3.
  3. If an object was created with new, its destructor will only be called if you explicitly delete the object with delete.

12.5.2 Summary of Pointers and Objects

It's all in these two pictures.

12.5.3 Revisiting the positioning new operator

The positioned new operator allows you to specify a memory location when allocating memory.
The following program uses the positional new operator and the regular new operator to allocate memory for an object.

// 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;
}

out:

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

There are two problems when using the positional new operator:

  1. First, the positioned new operator overwrites the memory location used for the first object with a new object when creating the second object. Obviously, this will cause problems if the class dynamically allocates memory for its members.
  2. Second, when delete is used for pc2 and pc4, the destructor will be called automatically for the objects pointed to by pc2 and pc4; however, when delete[] is used for buffer, the destructor will not be called for the object created with the positional new operator. constructor.

Solution:
The programmer must be responsible for locating the buffer memory location used by the new operator.
To use different memory locations, the programmer needs to provide two different addresses in the buffer and make sure that the two memory locations do not overlap.

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

If you use the positioned new operator to allocate memory for an object, you must ensure that its destructor is called. That is, explicitly calling the destructor for objects created with the positioned new operator.
The address pointed to by the pointer pc1 is the same as buffer, but buffer is initialized with new [], so it must be released with delete [] instead of delete. Even if buffer was initialized with new instead of new[], delete pc1 will free buffer, not pc1. This is because the new/delete system knows about the allocated 512-byte block buffer, but has no idea what the positioned new operator did to that memory block.
When calling a destructor explicitly, you must specify the object to be destroyed. Since there are pointers to objects, these can be used:

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

The following is an improved program:
manage the memory unit used by the positioning new operator, and add appropriate delete and explicit destructor calls.
Objects created with the positional new operator should be deleted in the reverse order of creation. The reason is that objects created later may depend on objects created earlier. Also, the buffers used to store these objects are only freed when all objects have been destroyed.

// 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;
}

out:

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 Review of various techniques

12.6.1 Overloading the << operator

To redefine the << operator so that it can be used with cout to display the contents of an object, define the following friend operator function:

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

where c_name is the class name. If the class provides public methods that return what you want, you can use those methods in the operator function so you don't have to make them friend functions.

12.6.2 Conversion functions

  1. To convert a single value to a class type, you need to create a class constructor whose prototype is as follows: c_name(type_name value);
    where c_name is the class name and type_name is the name of the type to be converted.
  2. To convert a class to another type, you need to create a class member function whose prototype is as follows: operator type_name();
    Although the function does not declare a return type, it should return a value of the desired type.
  3. Be careful when using conversion functions. You can use the keyword explicit when declaring a constructor to prevent it from being used for implicit conversions.

12.6.3 A class whose constructor uses new

  1. For all class members whose memory is allocated by new, delete should be used in the destructor of the class, which will release the allocated memory.
  2. If the destructor frees memory by using delete on the pointer class member, every constructor should initialize the pointer with new, or set it to a null pointer.
  3. Either use new [] or new in the constructor, and they cannot be mixed. If the constructor uses new[], the destructor should use delete[]; if the constructor uses new, the destructor should use delete.
  4. A copy constructor should be defined that allocates memory instead of pointing a pointer to existing memory. This way the program will be able to initialize a class object to another class object. Prototypes are usually as follows:className(const className &)
  5. A class member function that overloads the assignment operator should be defined, and its function is defined as follows (where c_pointer is a class member of c_name, and its type is a pointer to type_name). The following example assumes that new[] is used to initialize the variable 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 Queue Simulation

A queue is an abstract data type (Abstract Data Type, ADT) that can store an ordered sequence of items.
New items are added at the end of the queue, and items at the head of the queue can be removed. First in first out (FIFO).

Example: Heather Bank wants to estimate how long customers wait in line.
Typically, one-third of customers will be served in one minute, one-third in two minutes, and another third in three minutes. Also, the timing of customer arrival is random, but the number of customers using the ATM each hour is fairly constant. The other two tasks of the project are: designing a class to represent the customer; and writing a program to simulate the interaction between the customer and the queue.

12.7.1 Queue class Queue class

Define the characteristics of a Queue class:

  1. Queues store ordered sequences of items;
  2. There is a limit to the number of items the queue can hold;
  3. Should be able to create empty queues;
  4. It should be possible to check if the queue is empty;
  5. It should be possible to check if the queue is full;
  6. It should be possible to add items at the end of the queue;
  7. It should be possible to remove items from the head of the queue;
  8. It should be possible to determine the number of items in the queue.

1. Design the public interface of the Queue class

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. Represent queue data: linked list

A linked list consists of a sequence of nodes. Each node contains the information to be saved in the linked list and a pointer to the next node.

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

Singly linked list , each node contains only a pointer to other nodes.
After knowing the address of the first node, you can find each subsequent node along the pointer.
Usually, the pointer in the last node of the linked list is set to NULL (or 0) to indicate that there are no more nodes to follow.

3. Design the private part of the Queue class

Let a data member of the Queue class point to the starting position of the linked list.
You can also use data members to keep track of the maximum number of items the queue can store and the current number of items.

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:
//...
};

Nested Structures and Classes
A structure, class or enumeration declared within a class declaration is said to be nested within the class and its scope is the entire class. This declaration does not create data objects, but only specifies the types that can be used in the class. If the declaration is made in the private part of the class, the declared type can only be used in this class; if the declaration is made in the public part, the declared type can be used from outside the class through the scope resolution operator. For example, if Node is declared in the public part of the Queue class, a variable of type Queue::Node can be declared outside the class.

4. Class method*

Constructor—member initialization list :

  1. For const data members, they must be initialized before the body of the constructor is executed, that is, when the object is created.
  2. Only constructors can use this initializer-list syntax.
  3. For non-static const class data members, this syntax must be used. (static not in class memory)
  4. This syntax must also be used for class members that are declared as references. (Because it is necessary to assign a value when defining a reference)
  5. The initial value can be a constant or a parameter in the parameter list of the constructor.
  6. Data members are initialized in the order in which they appear in the class declaration, regardless of the order in which they are listed in the initializer.
  7. For members that are themselves class objects, it is more efficient to use member initialization lists.
// 队列最初是空的,因此队首和队尾指针都设置为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)
{
    
    }

Why do you need a member initializer list?
Normally, when a constructor is called, the object will be created before the code in the parentheses is executed. Therefore, calling Queue(int qs)the constructor will cause the program to first allocate memory for the member variables (equivalent to initialization). Then, the program flow enters the parentheses, using the normal assignment to store the value in memory. At this time, you can no longer assign values ​​​​to const constants.

Add items to the end of the queue (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;
}

Delete the first item in the queue (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;
}

Explicit destructor:
Adding an object to the queue will call new to create a new node. By deleting the node, the dequeue( ) method can indeed clear the node, but this does not guarantee that the queue will be empty when it expires (the queue will be disbanded when it is not empty). Therefore, the class needs an explicit destructor - a function that deletes any remaining nodes.

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
    } 
}

Finally, to clone or copy a queue, a copy constructor and an assignment constructor that performs a deep copy must be provided.

12.7.2 Customer class

When a customer enters the queue and how long it takes for a customer transaction.
When the simulation generates a new customer, the program creates a new customer object and stores the customer's arrival time and a randomly generated transaction time in it. When the customer arrives at the head of the queue, the program will record the time at this time and subtract it from the time of entering the queue to obtain the customer's waiting time.

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 ATM Simulation

The program allows the user to enter 3 numbers: the maximum length of the queue, the duration of the program simulation (in hours), and the average number of customers per hour. The program will use loops - each loop represents one minute. In each minute of the loop, the program will do the following:

  1. Determine whether a new customer has come. If it comes, and the queue is not full at this time, add it to the queue, otherwise reject the customer to enqueue.
  2. If no customer is transacting, the first customer in the queue is picked. Determine how long this customer has been waiting, and set the wait_time counter to the processing time required for new customers.
  3. If the client is in progress, decrement the wait_time counter by 1.
  4. Record various data, such as the number of customers served, the number of customers rejected, the cumulative time of waiting in line, and the cumulative queue length.

queue.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

queue.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;
}

out:

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

bank.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);
}

out:

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 Summary

  1. In the class constructor, you can use new to allocate memory for data, and then assign the memory address to the class member. In this way, the class can handle strings of different lengths without having to fix the length of the array in advance when the class is designed. Using new in class constructors can also cause problems when objects expire. If the object contains member pointers and the memory it points to is allocated by new, releasing the memory used to hold the object does not automatically free the memory pointed to by the object member pointers. Therefore, when using the new class in the class constructor to allocate memory, you should use delete in the class destructor to release the allocated memory. This way, when the object expires, the memory pointed to by its pointer member is automatically freed.
  2. Problems can also arise when initializing one object into another object, or assigning one object to another object, if the object contains a pointer member to new-allocated memory. By default, C++ initializes and assigns members one by one, which means that the members of the object being initialized or assigned will be exactly the same as the original object. If the members of the original object point to a data block, the copy members will point to the same data block. When the program finally deletes these two objects, the destructor of the class will try to delete the same block of memory data twice, which will error out. The solution is: define a special copy constructor to redefine the initialization, and overload the assignment operator. In either case, the new definition will create copies pointing to the data, and make new objects point to those copies. That way, both the old and new objects will refer to independent, identical data without overlapping. Assignment operators must be defined for the same reason. In each case, the ultimate goal is to perform a deep copy, that is, copy the actual data, not just a pointer to the data.
  3. When an object's storage persistence is automatic or external, its destructor is called automatically when it no longer exists. If you use the new operator to allocate memory for an object, and assign its address to a pointer, the destructor will automatically be called for the object when you use delete on the pointer. However, if you use the positioned new operator (instead of the regular new operator) to allocate memory for a class object, you must take care of explicitly calling the destructor for that object by calling the destructor method with a pointer to the object. C++ allows structures, classes, and enumeration definitions to be contained within classes. The scope of these nested types is the whole class, which means that they are confined to the class and will not conflict with structures, classes and enumerations of the same name defined elsewhere.
  4. C++ provides a special syntax for class constructors that can be used to initialize data members. This syntax consists of a colon and a comma-separated initialization list, placed after the closing parenthesis of the constructor parameters and before the opening parenthesis of the function body. Each initializer consists of the name of the member being initialized and parentheses containing the initial value. Conceptually, these initialization operations are performed when the object is created, and the statements in the function body have not yet been executed. The syntax is as follows: queue(int qs) : qsize(qs), items(0), front(NULL), rear(NULL) { }If the data member is a non-static const member or reference, this format must be used, but the new in-class initialization of C++11 can be used for non-static const members.
  5. C++11 allows in-class initialization, i.e. initialization within the class definition:
    this is equivalent to using a member initialization list. However, a constructor using a member initializer list overrides the corresponding in-class initialization.
class Queue
{
    
    
private:
    ...
    Node * front = NULL;
    enum {
    
    Q_SIZE = 10};
    Node * rear = NULL;
    int items = 0;
    const int qsize = Q_SIZE;
    ...
};

12.9 Review Questions

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. If you define a class whose pointer members are initialized with new, please point out 3 possible problems and how to correct them.

  1. First, when an object of this type expires, the data pointed to by the object's member pointer will still remain in memory, which will take up space and at the same time be inaccessible because the pointer has been lost. You can let the class destructor delete the memory allocated by new in the constructor to solve this problem.
  2. Second, after the destructor frees such memory, if the program initializes such an object to another object, the destructor will try to free these memory twice. This is because initializing one object to the default initialization of the other, will copy the pointer value but not the data pointed to, which will make both pointers point to the same data. The solution is, define a copy constructor that makes initialization copy the data pointed to.
  3. Third, assigning one object to another will also result in both pointers pointing to the same data. The solution is to overload the assignment operator so that it copies data instead of pointers.

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. Which class methods will be invoked by each of the following statements?

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 Programming Exercises

first question

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;
}

second question

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;
}

out:

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

fourth question

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;
}

out:

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.

Guess you like

Origin blog.csdn.net/qq_39751352/article/details/126922068