1. Trang chủ
  2. » Công Nghệ Thông Tin

C++ by Dissection 2002 phần 5 doc

51 214 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 51
Dung lượng 504,9 KB

Nội dung

Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 187 This initializes counter variable’s value to 0 by default, unless the user provides in an argument list an explicit initial value. 5.1.2 Constructor Initializer A special syntax is used for initializing subelements of objects with constructors. Con- structor initializers for structure and class members are specified by a colon and a comma-separated list that follows the constructor parameter list and that precedes the code body. A constructor initializer is a data member identifier followed by a parenthe- sized expression. Using this syntax, the counter constructor can be recoded as // Default constructor for counter inline counter::counter(int i = 0) : value(i % 100) { } The member variable value is initialized by the expression i%100. The constructor definition has a compound statement that is empty. Notice that initialization replaces assignment. The individual members must be initializable as member-name (expression list) It is not always possible to assign values to members in the body of the constructor. An initializer list is required when a nonstatic member is either a const or a reference type. 5.1.3 Constructors as Conversions Constructors of a single parameter are used automatically for conversion unless declared with the keyword explicit. For example, Metal::Metal(Ore) provides code that can be used to convert an Ore object to a Metal object. Consider the following class, whose purpose is to print invisible characters with their ASCII designation; for example, the code 07 (octal) is alarm or bel. (See Appendix A, ASCII Character Codes for the full character set.) In file printable.cpp // ASCII printable characters class pr_char { public: pr_char(int i = 0) : c(i % 128) { } void print() const { cout << rep[c]; } private: int c; static const char* rep[128]; }; Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 188 const char* pr_char::rep[128] = { "nul", "soh", "stx", ·····// filled in with table of ASCII characters "w", "x", "y", "z","{", "|", "}", "~", "del" }; int main() { pr_char c; for (int i = 0; i < 128; ++i) { c = i; // or: c = static_cast<pr_char>(i); c.print(); cout << endl; } } Dissection of the printable Program ■ class pr_char { public: pr_char(int i = 0) : c(i % 128) { } The constructor creates an automatic conversion from integers to pr_char. Its signature is of type int. ■ static const char* rep[128]; }; const char* pr_char::rep[128] = { "nul", "soh", “stx”, ·····// filled in with table of ASCII characters "w", "x", "y", "z","{", "|", "}", "~", "del" }; The table of characters is declared static. This is important here. We want the representation to not be attached to a given object. Being declared static means that there is only one such array rep[] and it is independent of any given class variable. ■ pr_char c; The declaration invokes the default constructor and is equivalent to pr_char c(0). ■ for (int i = 0; i < 128; ++i) { c = i; // or: c = static_cast<pr_char>(i); c.print(); The integer value of i is converted implicitly by calling the construc- tor of signature int, namely pr_char::pr_char(int), to produce the equivalent pr_char value and assign it to c. You need to be care- ful with such conversions and assignments. In cases where the objects that are converted use significant resources, there can be con- siderable overhead in this technique. Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 189 5.1.4 Improving the point Class The class point from Section 4.5, Classes, on page 148, is readily improved by adding constructors. It is also the case that usually there are several constructors per class. Each constructor signature represents a useful way to declare and initialize an object of that type. Notice that the class contains the ordinary member function point::set(), which can be used to change the value of a point object but cannot be used to create a point object. In file parabola.cpp class point { public: point() : x(0), y(0) { } // default point(double u) : x(u), y(0) { } // double to point point(double u, double v) : x(u), y(v) { } void print() const { cout << "(" << x << "," << y << ")"; } void set(double u, double v) { x = u; y = v; } void plus(point c); private: double x, y; }; // Offset existing point by point c void point::plus(point c) { x += c.x; y += c.y; } This class has three individually coded constructors. They could be combined using default arguments as follows: %$#\n\b !!! Gee, we’d better use printable characters in a family publication. Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 190 inline point::point(double u = 0, double v = 0) : x(u), y(v) { } Many scientific problems require producing a table of points or a graph by using a func- tion. For example, a parabola can be coded as double parabola(double x, double p) { return(x * x) / p; } We produce a table of points graphing the parabola from 0 to 2 in increments of 0.1. In file parabola.cpp void graph(double a, double b, double incr, double f(double, double), double p, point gr[]) { double x = a; for (int i = 0; x <= b; ++i, x += incr) gr[i].set(x, f(x, p)); } const int no_of_pts = 20; int main() { point g[no_of_pts]; // uses default ctor graph(0, 2, 0.1, parabola, 5, g); cout << "First 20 samples:" << endl; for (int i = 0; i < no_of_pts; ++i) { g[i].print(); if (i % 5 == 4) cout << endl; else cout << " "; } } 5.1.5 Constructing a Stack A constructor can also be used to allocate space from the heap also known as free store. We shall modify the ch_stack type from Section 4.11, A Container Class Example: ch_stack, on page 164, so that its maximum length is initialized by a constructor. The length of the stack is a parameter to a constructor. This parameter is used to call the operator new, which can allocate storage dynamically. The design of the object ch_stack includes hidden implementation detail. Data mem- bers are placed in the private access region of class ch_stack. The public interface provides clients with the expected stack abstraction. These are all public member func- tions, such as push() and pop(). Some of these functions are accessor functions that Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 191 do not change the stack object, such as top_of() and empty(). It is usual to make these const member functions. Some of these functions are mutator functions that do change the ch_stack object, such as push() and pop(). The constructor member func- tions have the job of creating and initializing ch_stack objects. In file ch_stack2.h class ch_stack { public: // public interface for ch_stack explicit ch_stack(int size) : max_len(size), top(EMPTY) { assert(size > 0); s = new char[size]; assert(s != 0); } void reset() { top = EMPTY; } void push(char c) { s[++top]= c; } char pop() { return s[top ]; } char top_of() const { return s[top]; } bool empty() const { return (top == EMPTY); } bool full() const { return (top == max_len-1); } private: enum { EMPTY = -1 }; char* s; // changed from s[max_len] int max_len; int top; }; Dissection of ch_stack Class ■ explicit ch_stack(int size) : max_len(size),top(EMPTY) { assert(size > 0); s = new char[size]; assert(s != 0); } The keyword explicit is used with a constructor of one argument. Normally, this would be a conversion constructor, but the keyword explicit disables this feature. It is clear that we do not want an int type to be inadvertently turned into a stack. For example, if this constructor did not have the keyword explicit, then ch_stack s(200); // s is size 200 int n = 5; ····· s = n; // s assigned a stack of size 5; ····· This would be an unwanted behavior, which is prevented by using the keyword explicit. Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 192 Constructors are important because they create possibilities for conveniently initializ- ing the abstract data type. For example, we can code two additional constructors for ch_stack. One would be a default constructor to allocate a specific-length ch_stack, and a second would be a two-parameter constructor whose second parameter would be a char* to initialize the ch_stack. The two constructors are as follows: // Default constructor for ch_stack ch_stack::ch_stack() : max_len(100), top(EMPTY) { s = new char[100]; assert(s != 0); } // Copy a char* string into the ch_stack ch_stack:: ch_stack(int size, const char str[]) : max_len(size) { int i; assert(size > 0); s = new char[size]; assert(s != 0); for (i = 0; i < max_len && str[i] != 0; ++i) s[i] = str[i]; top = i; } The corresponding function prototypes would be included as members of the class ch_stack. We show the use of these constructors: In the preceding code and in the rest of this chapter, we use asser- tions to test whether a pointer value is 0. This is done after calling new and indicates that new has failed. The assert technique requires that a debug option be turned on for the compiler. Also, we are assuming that memory allocation exception handling is turned off. An alternative scheme is to have the bad_alloc exception thrown. This is discussed in detail in Section 10.9, Standard Exceptions and Their Uses, on page 409. This code has no destructor and leads to memory leaks. We show an appropriate destructor in Section 5.1.6, Classes with Destructors, on page 195. It should also have a copy con- structor and assignment operator overloaded. ■ enum { EMPTY = -1 }; char* s; // changed from s[max_len] int max_len; int top; Here is the ch_stack implementation for a dynamically sized array. We use a base pointer s rather than a fixed-length array. Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 193 ch_stack data; // creates data.s[100] ch_stack d[N]; // creates N 100 element ch_stacks ch_stack w(4, "ABCD"); // w.s[0]='A'·····w.s[3]='D' 5.1.6 The Copy Constructor The semantics of call-by-value for functions require that a local copy of the argument type be created and initialized from the value of the expression passed as the actual argument. For example: int cube_plus(int i) { i = i + 1; return i * i * i; } when called as in int j = 2; cout << cube_plus(j + 2) << endl; is equivalent to placing a block of code { int i_local = j + 2; // call by value copy i_local = i_local + 1; return i_local * i_local * i_local; } In this example, the local variable i_local is initialized to 4. One is then added to the local variable, and a value of 125, or 5 cubed, is returned. For a native type, a local copy is made and the value inside the block of the local copy is not passed back after function execution. For class types, call-by-value requires a copy constructor. The compiler provides a copy constructor whose signature is class_name::class_name(const class_name&); The compiler copies by memberwise initialization. This is usually correct for simple classes that have nonpointer data members, such as class point. This is incorrect in other circumstances, such as for classes with members that are pointers. In many cases, the pointer is the address of an object. The act of duplicating the pointer value but not the object pointed at can lead to buggy code. This form of copying is called shallow copying. Shallow copying is wrong for classes such as ch_stack. In these cases, deleting the original object may cause the copied object to incorrectly disappear. The class ch_stack explicitly defines its own copy constructor, as is appropriate. Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 194 In file ch_stack2.h // Copy ctor for ch_stack of characters ch_stack::ch_stack(const ch_stack& stk) : max_len(stk.max_len), top(stk.top) { s = new char[stk.max_len]; assert(s != 0); memcpy(s, stk.s, max_len); } The stdlib routine memcpy() copies max_len characters from the base address stk.s into memory, starting at base address s. This is called a deep copy. The character arrays are distinct because they refer to different memory locations. If instead the body of this routine were s = stk.s; this would be a shallow copy, with ch_stack variables shar- ing the same representation. Any change to one variable would change the other. Suppose that we wish to examine our stack and count the number of occurrences of a given character. We can repeatedly pop the stack, testing each element in turn, until the stack is empty. But what if we want to preserve the contents of the stack? Call-by-value parameters accomplish this. In file ch_stack2.cpp // Count the number of c’s found in s int cnt_char(char c, ch_stack s) { int count = 0; while (!s.empty()) // done when empty count += (c == s.pop()); // found a c return count; } In this case, the explicitly written copy constructor does a deep copy. If we had allowed the compiler to provide a default copy constructor, we would have potentially buggy code. A copy constructor is invoked when there is call-by-value of the object, return-by- value for the object, or initialization of one object by another of the same type. Shallow Copy Needs to Copy Deep Copy Ira Pohl’s C++ by Dissection 5.2 Classes with Destructors 195 5.2 Classes with Destructors A destructor is a member function whose name is the class name preceded by a tilde, ~. Destructors are almost always called implicitly, usually at the exit of the block in which the object was declared. They are also invoked when a delete operator is called on a pointer to an object having a destructor or where needed to destroy a subobject of an object being deleted. Let us augment our ch_stack example with a destructor: In file ch_stack2.h // Implementation with ctors and dtor class ch_stack { public: ch_stack(); // default ctor explicit ch_stack(int size) : max_len(size), top(EMPTY) { assert(size > 0); s = new char[size]; assert(s != 0); } ch_stack(const stack& stk); // copy ctor ch_stack(int size, const char str[]); ~ch_stack() { delete []s; } // dtor // rest of the methods ····· private: enum { EMPTY = -1 }; char* s; int max_len; int top; }; The addition of the destructor allows the class to return unneeded heap-allocated mem- ory during program execution. All of the public member functions perform in exactly the same manner as before. However, the destructor is implicitly invoked on block and function exit to clean up storage no longer accessible. 5.3 Members That Are Class Types In object-oriented programming (OOP) methodology, complicated objects are built from simpler objects. For example, a house is built with a foundation, rooms, and a roof. The house has a roof as a subobject. This part-whole relationship is called in OOP the HASA relationship. Complicated objects can be designed from simpler ones by incorporating them with the HASA relationship. In this section, the type address is used as a member of the class person. 5.2 5.3 Ira Pohl’s C++ by Dissection 5.4 Example: A Singly Linked List 196 In file address.cpp class address { public: address(string street, string city) :street_name(street),city_name(city) { } void print() const; string get_street() const { return street_name; } string get_city() const { return city_name; } private: string city_name; string street_name; }; class person { public: person(string n, address h); void print() const; void set_address(); private: address home; const string name; }; person::person(string n, address h) : name(n), home(h) { } Notice that the person constructor is a series of initializers. The initializers of the address member invoke the address copy constructor. Also, the methods get_street() and get_city() could be written to return a const reference as fol- lows: const string& get_street() const { return street_name; } For large objects, this is more efficient, as it does not require making a copy. Making the return type const also keeps the user of the class from altering the data member with- out an access function. 5.4 Example: A Singly Linked List The singly linked list data type is the prototype of many useful dynamic abstract data types (ADTs) called self-referential structures. These data types have pointer members that refer to objects of their own type and are the basis of many useful container classes. The following declaration implements such a type: 5.4 [...]... overwritten at block exit by the deletion routine Ira Pohl’s C++ by Dissection 5. 5 5. 5 5. 5 Strings Using Reference Semantics 201 Strings Using Reference Semantics Allocation at runtime of large aggregates can readily exhaust memory resources The list example in Section 5. 4, Example: A Singly Linked List, on page 198, shows one scheme for handling this: The system reclaims memory by traversing each list... "; zmax.print(); cout . objects that are used by my_string. For now, we leave the data members ref_cnt and s public. They are needed in some of the methods of class 5. 5 Ira Pohl’s C++ by Dissection 5. 5 Strings Using Reference. 200 int n = 5; ····· s = n; // s assigned a stack of size 5; ····· This would be an unwanted behavior, which is prevented by using the keyword explicit. Ira Pohl’s C++ by Dissection 5. 1 Classes. call -by- value of the object, return -by- value for the object, or initialization of one object by another of the same type. Shallow Copy Needs to Copy Deep Copy Ira Pohl’s C++ by Dissection 5. 2

Ngày đăng: 12/08/2014, 12:20