What would you like to have for breakfast, lunch and dinner next month? Drink how many ounces of milk at dinner on the third day. On the 15th day of breakfast you need to add as many raisins to the cereal. If you're like most people, you'll have to wait until you decide to eat. C + + has some of the same policies that it takes when allocating memory, so that the program determines memory allocations at run time, rather than at compile time. In this way, memory can be used based on the needs of the program rather than a series of strict storage type rules. C + + uses the new and delete operators to dynamically control memory. Unfortunately, using these operators in a class will cause many new programming problems. In this case, the destructor is essential, and is no longer optional. Sometimes, you must also overload the assignment operator to ensure that the program runs correctly. Let's take a look at these questions.
In the following program we will use new and delete. Also, a new type of storage is introduced: Static class members. First, design a Stringbad class, and then design a slightly more powerful mystring class. The Stringbad class object will contain a string pointer and a value that represents the length of the string. The Stringbad class is used here primarily to gain insight into how new, delete, and static class members work. As a result, some information is displayed when constructors and destructors are called so that you can follow the prompts to do some things.
So why name it Stringbad? This is to indicate a reminder that Stringbad is a well-developed example. This is the first step in developing with dynamic memory allocation, which correctly accomplishes some of the obvious work, such as using new and delete correctly in constructors and destructors. It does not actually perform harmful actions, but omits some useful features that are required, but are not obvious. By explaining the problems with this class, it is helpful to understand and keep in mind some of the changes that are not obvious when you later convert it to a more powerful mystring class.
#ifndef stringbad_h_
#define Stringbad_h_
#include <iostream>
using namespace std;
Class Stringbad
{
private:
char * STR; Pointer to string
int len; Length of string
static int num_strings; Number of objects public
:
Stringbad (const char * s); Constructor
Stringbad (); Default constructor
~stringbad (); destructor
//friend function
friend Ostream & operator << (ostream & OS, const Stringbad & ST);
};
#endif
For this statement, there are two points to note:
First, it uses a char pointer (instead of a char array) to represent the name. This means that the class declaration does not allocate storage space for the string itself, but instead uses new in the constructor to allocate space for the string. This avoids pre-defining the string length in the class declaration.
Second, declare the Num_strings member as a static storage class. Static class members have one feature: no matter how many objects are created, the program creates only one copy of the static class variable. That is, all objects of a class share a static member, just as a home phone can be shared by all family members. This is handy for private data that has the same value for all class objects. For example, Num_strings members can record the number of objects that are created.
The following program manifests the implementation of the class method, which demonstrates how to use pointers and static members.
#include "stringbad.h"
#include <cstring>
int stringbad::num_strings = 0;
Stringbad::stringbad (const char * s)
{
len = strlen (s);
str = new char [len+1];
strcpy (str, s);
num_strings++;
cout << num_strings << ": \" "<< str <<" \ "Object created.\n";
}
Stringbad::stringbad ()
{
len = 4;
str = new char [len+1];
strcpy (str, "C + +");
num_strings++;
cout << num_strings << ": \" "<< str <<" \ "Default object Created.\n";
}
Stringbad::~stringbad ()
{
cout << "\" "<< str <<" \ "Object deleted,";
num_strings--;
cout << num_strings << "left.\n";
delete [] str;
Ostream & operator << (ostream & OS, const Stringbad & st)
{
os << st.str;
return OS;
}
First, note the following statement:
This statement initializes the value of the static member num_strings to 0. Note that static member variables cannot be initialized in a class declaration, because the declaration describes how to allocate memory, but does not allocate memory. You can use this format to create objects that allocate and initialize memory. For static class members, you can use separate statements outside of the class declaration for initialization because static class members are stored separately, not part of an object. Note that the initialization statement indicates the type and uses the scope operator, but does not use the keyword static.
Initialization is done in the method file, not in the header file of the class, because the class declaration is in the header file, and the program may include the header file in several other files. If you initialize in the header file, multiple initialization statement copies will occur, causing an error.
Note: Static data members are declared in the class declaration and initialized in the file that contains the method of the class. Initializes the class that the static member belongs to by using the scope operator. However, if the static member is an integer const or enumerated const, you can initialize it in the class declaration.
Next, notice that each constructor contains the expression num_strings++, which ensures that each new object is created, the value of the shared variable is added 1, and the total number of objects is recorded. In addition, the destructor contains the expression num_strings--, so the class will also keep track of where the object is being deleted, thus making the value of the Num_strings member current.
Warning: When using new in a constructor to allocate memory, you must use delete in the corresponding destructor to free memory. If you use new[] to allocate memory, you should use delete[] to free up memory.
The following program demonstrates when Stringbad's constructors and destructors run and how they run. The program places the object declaration in an internal code block, because the destructor is called when the block of code that defines the object finishes executing. If you do not, the destructor will be called when the main () function finishes executing, leaving you unable to see the information displayed by the destructor before the execution window closes.
#include "stringbad.h" void Callme1 (Stringbad &); Pass by reference void Callme2 (Stringbad);
pass by value int main (int argc, char** argv) {{cout << ' starting an inner block:\n\n ';
Stringbad headline1 ("Celery stalks at midnight");
Stringbad headline2 ("lettuce Prey");
Stringbad Sports ("Spinach Leaves Bowl for Dollars");
cout << Endl;
cout << "headline1:" << headline1 << Endl;
cout << "Headline2:" << headline2 << Endl;
cout << "Sports:" << sports << Endl;
cout << Endl;
Callme1 (HEADLINE1);
cout << "headline1:" << headline1 << Endl;
cout << Endl;
Callme2 (Headline2);
cout << "Headline2:" << headline2 << Endl;
cout << Endl;
cout << "Initialize one object to another: \ n";
Stringbad sailor = sports;
cout << "Sailor:" << sailor << Endl;
cout << Endl;
cout << "Assign one object to another: \ n";
Stringbad knot;
knot = headline1;
cout << "knot:" << knot << Endl;
cout << Endl; cout << "Exiting The block.
\ n ";
} cout << "End of Main (). \ n";
return 0;
} void Callme1 (Stringbad & RSB) {cout << "String passed by reference: \ n";
cout << "\" "<< rsb <<" \ "\ n";
} void Callme2 (Stringbad sb) {cout << "String passed by value: \ n";
cout << "\" "<< sb <<" \ "\ n"; }
Note: The first version of Stringbad has a number of intentionally flawed flaws that make the output indeterminate. For example, some compilers cannot compile it. Although the content of the output differs, the basic problem and resolution are the same.
The following is the output of the program when compiling with the dev C + + compiler:
The program interrupts before displaying information about 1 objects, and reports a generic protection error (GPF). GPF indicates that the program is trying to access a memory unit that it is forbidden to access, which is another bad signal.
As normal at the beginning of the procedure, but gradually become abnormal, resulting in disastrous results.
Callme2 () passing headline2 by value, rather than by reference, is a serious problem.
First, Headline2 is passed as a function parameter, which causes the destructor to be called. Second, although passing by value prevents the original parameter from being modified, the function actually makes the original string unrecognized, causing some non-standard characters to be displayed (the text that is displayed depends on what is in memory).
Why is that so?
In fact, a count exception is a clue. Because each object is constructed and refactored once, the number of times the constructor and destructor should be called should be the same.
In fact, when you use an object to initialize another object, the compiler automatically generates the copy constructor. The auto-generated constructor does not know that the static variable num_strings needs to be updated, so the counting scheme is messed up. In fact, all of the problems that this example illustrates are caused by the compiler's auto-generated member functions, which are described below.
1. Special member functions
The problem with the Stringbad class is caused by a special member function. These member functions are automatically defined, and for Stringbad, the behavior of these functions does not conform to the class design. Specifically, C + + automatically provides the following member functions:
The default constructor, if no constructor is defined;
Default destructor, if not defined;
Copy constructor, if not defined;
Assignment operator, if not defined;
The address operator, if not defined.
To be more precise, the compiler generates the definition of the last three functions above-if the program uses the object in a way that requires it. For example, if you assign an object to another object, the compiler provides the definition of the assignment operator.
The result shows that the problem in the Stringbad class is caused by an implicit copy constructor and an implicit assignment operator.
(1) Default constructor
If no constructors are provided, C + + creates a default constructor. If a constructor is defined, C + + will not define a default constructor. If you want to not explicitly initialize an object when it is created, you must explicitly define a default constructor. This constructor does not have any parameters, but it can be used to set a specific value.
A constructor with parameters can also be a default constructor, as long as all parameters have default values.
But there can only be one default constructor, otherwise, it can cause ambiguity of the error.
(2) Copy constructor
The copy constructor is used to copy an object into the newly created object. That is, it is used during initialization, including passing parameters by value, rather than during regular assignment. The copy constructor prototype for a class is usually as follows:
Class_name (const class_name &);
It takes a constant reference to a class object as a parameter.
For a copy constructor, you need to know two points: when to invoke and what functionality to use.
(3) When to call the copy constructor
When you create a new object and initialize it as an existing object of the same type, the copy constructor is called. This can happen in many cases, and the most common scenario is to explicitly initialize the new object to an existing object. For example, assuming that motto is a Stringbad object, the following 4 declarations call the copy constructor:
Stringbad ditto (motto); Calls Stringbad (const Stringbad &)
Stringbad metoo = Metto; Calls Stringbad (const Stringbad &)
Stringbad also = Stringbad (motto); Calls Stringbad (const Stringbad &)
Stirngbad * Pstringbad = new Stringbad (motto); Calls Stringbad (const Stringbad &)
The compiler uses the copy constructor whenever the program generates a copy of the object. Specifically, the copy constructor is used when the function returns an object by value. Remember, passing by value means creating a copy of the original variable. The copy constructor is also used when the compiler generates a temporary object.
Because passing objects by value calls the copy constructor, some of the objects should be passed by reference. This saves time in calling the constructor and the space to store the new object.
(4) The default copy constructor function
The default copy constructor replicates non-static members individually (also known as shallow copy), and copies the values of the members. The following statement:
Stringbad sailor = sports;
is equivalent to the following code:
Stringbad sailor;
Sailor.str = Sports.str;
Sailor.len = Sports.len;
2. Go back to the Stringbad class: There is a problem with the copy constructor.
First, the output of the program shows that the number of calls to the destructor is 2 more than the number of calls to the constructor, possibly because the program did create two additional objects using the default copy constructor. The default copy constructor does not describe its behavior, so it does not indicate the creation process, nor does it increase the value of the counter num_strings. However, the destructor updates the counter and is called when any object expires, regardless of how the object was created. The workaround is to provide an explicit copy constructor that updates the count.
The second exception is more subtle, and one of its symptoms is that the contents of the string are garbled. The reason for this is that the implicit copy constructor is replicated by value.
Sailor.str = Sports.str;
The copy here is not a string, but a pointer to a string. Get two pointers to the same string. This raises the problem when the destructor is called: the memory that sports.str points to has been freed by the Sailor Destructor, which results in an indeterminate, potentially harmful consequence.
Another symptom is that attempting to free memory two times may cause the program to terminate abnormally.
The way to solve this problem in class design is deep copy. In other words, the copy constructor should copy the string and assign the address of the copy to the STR member, not just the string address. This allows each object to have its own string, rather than a string that references another object. When a destructor is called, a different string is freed without attempting to release a string that has already been freed. You can write the Stringbad copy constructor like this:
Stringbad::stringbad (const Stringbad & SB)
{
num_strings++;
len = Sb.len;
str = new char [len+1];
strcpy (str, sb.str);
cout << num_strings << ": \" "<< str <<" \ "Object created. \ n ";
}
Warning: If a class contains pointer members that are initialized with new, you should define a copy constructor to copy the data pointed to, not the pointer, which is called deep copy. Another form of replication (member copy or shallow copy) is just a copy of the pointer value. Shallow copy copies pointer information only lightly, rather than digging in to copy the structure of the pointer reference.
3. Other issues with Stringbad: assignment operators
The problem with the above program is that not all problems are attributed to the default copy constructor, and you need to look at the default assignment operator. ANSI C allows for structural replication, while C + + allows class objects to be assigned values, which are implemented automatically by overloading the assignment operator for the class. The prototype of this operator is:
Class_name & class_name::operator = (const class_name &);
It accepts and returns a reference to the class object.
(1) The function of the assignment operator and how it works
When an existing object is assigned to another object, an overloaded assignment operator is used.
The assignment operator is not necessarily used when initializing an object, or a copy constructor may be used.
Like the copy constructor, the implicit implementation of the assignment operator also replicates the members individually. If the member itself is a class object, the program copies the member using the assignment operator defined for the class, but the static data is not affected.
(2) Where the problem of assignment arises
The problem occurs with the same implicit copy constructor: data is compromised. This is also a problem with member replication.
(3) Solve the assignment problem
For problems caused by the inappropriate default assignment operator, the solution is to provide the assignment operator definition (deep copy). The implementation is similar to the copy constructor, but there are some differences.
Since the target object may reference the previously allocated data, the function should use delete[] to release the data;
The function should avoid assigning the object to itself, otherwise releasing the memory operation may delete the object's contents before the object is re-assigned;
The function returns a reference to the calling object.
The following code shows how to write an assignment operator for the Stringbad class:
Stringbad & stringbad::operator = (const Stringbad & SB)
{
if (this = = &SB)
return *this;
delete [] str;
len = Sb.len;
str = new char [len+1];
strcpy (str, sb.str);
return *this;
}
With a richer knowledge, you can revise the Stringbad class to rename it to MyString. The following list of programs lists the revised class declarations:
Mystring.h--Fixed and augmented string class definition #ifndef mystring_h_ #define MYSTRING_H_ #include <iostre
Am> using namespace std; Class MyString {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 MyString (const char * s); Constructor MyString (); Default constructor MyString (const MyString & S); Copy constructor ~mystring ();
destructor int Length () const {return len;}
Overloaded operator methods MyString & operator = (const MyString &);
MyString & operator = (const char *);
Char & operator [] (int i);
const char & operator [] (int i) const;
Overloaded operator Friends friend BOOL operator < (const MyString & st1, const MyString & ST2); friend bool operator > (const MyString & St1, const MyString & ST2);
friend bool operator = = (Const MyString & st1, const MyString & ST2);
Friend Ostream & operator << (ostream & OS, const MyString & ST);
Friend IStream & operator >> (IStream & is, MyString & ST);
static function static int howmany ();
}; #endif
The following list of programs shows the revised method definition:
Mystring.cpp-MyString class methods #include <cstring> #include "mystring.h" using namespace std;
Initializing static class member int mystring::num_strings = 0;
static method int Mystring::howmany () {return num_strings;
}//class methods Mystring::mystring (const char *s) {len = strlen (s);
str = new char [len+1];
strcpy (str, s);
num_strings++;
} mystring::mystring () {len = 4;
str = new char [1];
Str[0] = ' + ';
num_strings++;
} mystring::mystring (const MyString & st) {len = St.len;
str = new char [len+1];
strcpy (str, ST.STR);
num_strings++;
} mystring::~mystring () {num_strings--;
delete [] str; }//Overloading operator methods//Assign a MyString to a MyString MyString & mystring::operator = (const Mystrin
G & St) {if (this = &st) return *this;
delete [] str;
len = St.len;
str = new char [len+1];
strcpy (str, ST.STR);
return *this; }//Assign a C string to a MyString MyString & MySTring::operator = (const char * s) {delete [] str;
Len = strlen (s);
str = new char [len+1];
strcpy (str, s);
return *this;
} Char & Mystring::operator [] (int i) {return str[i];
} const char & Mystring::operator [] (int i) const {return str[i]; } BOOL operator < (const MyString & str1, const MyString & str2) {return (strcmp (STR1.STR, Str2.str) < 0)
;
} BOOL operator > (const MyString & str1, const MyString & str2) {return str2 < str1;
} bool operator = = (Const MyString & str1, const MyString & str2) {return (strcmp (str1.str, str2.str) = = 0);}
Ostream & operator << (ostream & OS, const MyString & st) {OS << st.str;
return OS;
} IStream & Operator >> (IStream & is, MyString & St) {char temp [mystring::cinlim];
Is.get (temp, mystring::cinlim);
if (IS) st = temp;
while (is && is.get ()! = ' \ n ') continue;
return is; }
For the improved Stringbad class, we explain the following mystring:
1. Static class member functions
The member function can be declared static (the function declaration must contain the keyword static, but if the function definition is independent, it cannot contain the keyword static), there are two important consequences.
First, you cannot call a static member function through an object: In fact, a static member function cannot even use the this pointer. If a static member function is declared in the public part, you can invoke it using the class name and the scope resolution operator.
Second, because static member functions are not related to a particular object, you can only use static data members.
2. Things to consider when using new in constructors
If you use new in the constructor to initialize a pointer member, you should use delete in the destructor.
New and delete must be compatible with each other. New corresponds to delete,new[] corresponds to delete[].
If you have more than one constructor, you must use new in the same way, either with brackets or without. Because there is only one destructor, all constructors must be compatible with it. However, you can use the new initialization pointer in one constructor, and the pointer to null in another constructor, because the delete can be used with a null pointer.
You should define a copy constructor that initializes an object to another object through deep replication.
An assignment operator should be defined to copy an object to another object through deep replication.