第十二天(类和动态内存分配·一)

来源:互联网 发布:android 网络请求 编辑:程序博客网 时间:2024/04/29 15:08

       今天的笔记有点特别——用英文写的。原因是我想强迫自己看英文版教材,并且想提高英语水平。此时的我还不能看中文写英文,看英文写英文倒是勉强可以。

        但是,这是一个花费巨多时间的工程,下面这个笔记,除了吃饭睡觉,出去校外3个小时买书外,当然还有小部分时间是发呆,聊Q(这段时间不长),都在写。写了这一章的大部分,实在没欲望写下去了,过些时候再把它完成吧。而且,教材这一章的最后部分是讲队列,数据结构已学过,应该很快过一遍。恩,一定要坚持。


2011-11-12(Classes and Dynamic Memory Allocation I)

1. Static Members of Classes.

       You cannot initialize a static member static member variable inside the class declaration.That is because the declaration is a description of how memory is to be allocated, but it doesn't allocate memory.

      The static class member is stored separately rather than as part of an object, so you can initialize the static class member independently.

      All in all, a static member is declared in class declaration and is initialized in the file containing the class methods. The scope operator(“::”) is used in the initialization to indicate to which class the static member belongs. The exception is const and an enumeration type.

2. Implicit Member Function.

When “finishing” declaring a user-defined class, check your class whether it contains the following necessary functions:

           A constructor to create an object    

           A copy constructor to create a copy of one object     

           A assignment operator to assign one object to anther     

           A destructor to destroy an object  

           An address operator to get the address of an object

These functions are necessary when programmer do something ordinary(like basic data type) with object. If your class does not include them, compiler will add the default one, but the defaults are not always suitable. So if you feel your compiler can't deal with some operation of object without your help, your should define the explicit one.

      The implicit address operator returns the address of the invoking object(that is the value of this pointer), which is our purpose. So we won't discuss this member function;

       Following listed below is a program, which is convenient for us to discuss the remaining function. For convenience,class declaration, definition and application are put in one single source file.  

#include <iostream>#include <cstring> using std::cout;using std::endl;class StringBad{private:char* str;int len;public:StringBad(const char * s);    StringBad();                   ~StringBad();friend std::ostream& operator<<(std::ostream & os, const StringBad & st);};StringBad::StringBad(const char * s){    len = std::strlen(s);                      str = new char[len + 1];              std::strcpy(str, s);                       cout << "\"" << str << "\" object created\n";  }StringBad::StringBad() {   len = 4;    str = new char[4];    std::strcpy(str, "C++");                   cout << "\"" << str << "\" default object created\n";  }StringBad::~StringBad()              {    cout << "\"" << str << "\" object deleted\n";                    delete [] str;    str = NULL;}std::ostream & operator<<(std::ostream & os, const StringBad & st){    os << st.str;    return os; }void callTest1(StringBad& rsb){    cout << "Call " << "\"" << rsb << "\" by reference;" << endl; }void callTest2(StringBad sb){    cout << "Call " << "\"" << sb << "\" by value;" << endl;}int main(){    cout << "Declaration & Initialization:" << endl;    StringBad s1("string one");    StringBad s2("String two");    cout << "\nCall test:\n";    callTest1(s1);    callTest2(s2);    cout << endl;    cout << "Initialize is by using s2\n";    StringBad is = s2;    cout << "is = " << is << endl;    cout << "s2 = " << s2 << endl;    cout << endl;    cout << "Assign s1 to ds:\n";    StringBad ds;    cout << "Before: " << "ds = " << ds << "; s1 = " << s1 << endl;    ds = s1;    cout << "After: " << "ds = " << ds << "; s1 = " << s1 << endl;    cout << endl;                                                             return 0;}
Output:

             Declaration & Initialization:

             "string one"object created

             "String two"object created

 

             Call test:

             Call "string one" by reference;

             Call "String two" by value;

             "String two"object deleted

 

             Initialize is by using s2

             is = 葺葺葺葺葺葺葺葺

             s2 = 葺葺葺葺葺葺葺葺

 

             Assign s1 to ds:

             "C++"default object created

             Before: ds = C++; s1 =string one

             After: ds = string one; s1 = string one

 

             "string one"object deleted

             "C++" object deleted

             "葺葺葺葺? object deleted

Obviously, something wrong with this program. But what problem? It would be run well when replace the StringBad to other basic data type like int, double. The key problem is your give a command but don't offer some necessary and suitable tools to complete it. In this case,the tools are copy constructor and assignment function.

I. Copy Constructor

A copy constructor is used to copy an object to a newly created object. That is, it's used during initialization, not during assignment. Its prototype:

                       class_name(const class_name &);

For example,the copy constructor of StringBad is:

                       StringBad (const StringBad&);

i. When It Is Used

A copy constructor is invoked whenever a new object is created and initialized to an existing object of the same kind, which happens in several situations. In particularly, if str is an object of StringBad the following 4 defining declarations invoke the copy constructor:

StringBad newStr(str);StringBad newStr = str;StringBad newStr = StringBad(str);StringBad* newStr = new StringBad(str);
Less obviously, when a function passes an object by value(like callTest2() in this example). Because passing by value means creating a copy of the original variable.

ii. What It Does

If the class creator didn't define a suitable copy constructor, compiler will add the default one to user-defined class. Precisely, the method of copy default copy constructor used is called shallow copying. The statement

                      StringBad is = s3;

is theoretically equals to:

StringBad is;is.str = s3.str;is.len = s3.len;
Note that these statements are invalid, but they make it easy to describe what is shallow copying: copy the address of older one'sstr to the new one rather than its content.

iii. Where It Goes Wrong

The statement is.str= s3.str; copies the pointer to a string, which results in what is.str is point to become useless. And destructor will be invoked. The function of destructor is frees the memory pointed to by thestr pointer. Finallymain function is going to terminate, memory pointed to by pointer s3.str is “still here”. So, destructor is invoked again, freeing this chunk of memory again. Freeing the same chunk of memory is an undefined, possibly harmful behavior.

iv. Fix The Problem

The cure for the problems in the class design is to make a deep copy.That is, rather than just copy the address of the string, the copy constructor should duplicate the string and assign the address of the duplicate to thestr member. That way, each object gets its own string rather than referring to another object's string. Here are the new copy constructor:

StringBad::StringBad(const StringBad& s){str = new char[s.len + 1];std::strcpy(str,s.str);cout << "\"" << str << "\" object created\n"; }
Note that a copy constructor, like constructor, creates a new object.

II.Assignment Operator

After adding copy constructor toStringBad problems still here(OMG!!). Cool down, let's check the program line by line.

       Line 66, passing by reference, no problem; Next, line 67 passing by value, copy constructor involved, no problem; Next, line 71, is similar to line 67, no problem. So the bug of program point to line 79. Let's scan part of output after defining copy constructor:     

                Declaration& Initialization:

          Assign s1 to ds:

          "C++" default object created

          Before: ds = C++; s1 = string one

          After: ds = string one; s1 = string one

 

          "string one" object deleted

          "String three" object deleted

          "String three" object deleted

          "String two" object deleted

          "葺葺葺葺葺葺葺葺"object deleted

Again, garbled string wasdelete ,and again, a chunk memory is freed twice.

       The statementds = s1; ,has invoked the assignment operator, this operator has the following prototype:

                                     className& className::operator =(const className&);

Every expression has its return value,assign expressions are not the exception. So the assignment operator return the reference of object. For example, here's the prototype for the StringBad class:

                    StringBad& StringBad::operator =(const StringBad&);

i. When Is It Used

An overload assignment operator is used when you assign one object to another existing object. This description is similar to copy constructor, but “existing” here is different from the there.Precisely, “existing object” means the object's address and its member(s) had been declared and initialized. And “existing object” in the copy constructor description means only the object's address had been declared. So the statement:

                                   StringBad is = s2;

is a kind of copy rather than assign.

ii. Where It Goes Wrong

What a default assignment operator does is the same as default copy constructor, including the way they copy(both are shallow copy).

       After assigning, ds and s1 point to the same memory . When the main function going to terminate, compiler must delete two objects: ds ands1, bug floating.

iii. Fix the problem

The solution for the problem created by an inappropriate default assignment operator is to provide your own assignment operator definition, one that makes a deep copy. Compared to copy constructor,there are some differences: 

       Because the target object may already refer to previously allocated data,the overload function should usedelete[].

       According to the first point , this function can't assign an object to itself. Otherwise, the memory would be freed before there reassigned. But we can use theif statement to avoid this situation. 

      The function returns a reference to the invoking object.

Summarize these three points, here's the assignment operator :

String& String::operator= (const String& s){if(this == &s)return *this;//if object assigned to itselfdelete [] str;str = new char[s.len + 1];std::strcpy(str,s.str);return *this;}

III.Destructor

If your class has no explicit destructor, all of problems in this program will disappear. But the fact that your had defined explicit constructor which uses thenew operator to allocate memory makes your have to define  a destructor.

3. Better String Class

Now that your more knowledgeable, it's your responsibility to create a better string class. A perfect string class contains the following functions:

    int length()const{return len;}    String(const char * s);     String();                   ~String();    String(const String& s);    char& operator [](int i);    const char& operator [](int i)const;    String& operator =(const String& s);    friend bool operator <(const String& s1, const String& s2);    friend bool operator >(const String& s1, const String& s2);    friend bool operator ==(const String& s1, const String& s2);    friend std::ostream& operator<<(std::ostream & os,const String & st);
I. New Default Constructor

The new default constructor create a empty string:

String::String() {    str = new char[1];    str[0] = '\0';    len = 0;}
I have anther expression to allocate memory for str:

                  str = new char;

Though they allocate amount of memory, when it freed by destructor:

                  delete[] str

it would be anther boresome problem. So usingdelete[] is compatible with pointers initialized by using new[] and with theNULL pointer.

II.Overloading the [] Operator

Unlike other operators in this class(such as==, <, <<), your can,t point out what operands of bracket operator are, after glancing at this prototype:

                  char& operator[](int i);

But smart as you, you will point out the operands of bracket expression likestr[0] arestr and 0 after a short thinking.

      Declaring the return type as type char& allows you assign values to particular element. For example, you can use the following:

                  String ex("might");

                  ex[0] ='r';

The statement

               ex[0] ='r';

is equals to :

                  ex.operator[](0);

This function returns a reference toex.str[0] .We can regard a reference of variable as the variable itself. So when assign'r' to the reference of ex.str[0], you can actually change the value ofex.str[0].Suppose you have a constant object:

              const String expection("const");

Then you want print one of character in this string:

                  cout << expection[0];

this statement is labeled an error.

That is because you get the element of string, but you can't promise not to alter data. The solution is to overload antheroperator[]() by using the following prototype:

                 const char& operator[](int i)const;

4.Return a Reference to a const Object

Why we use const qualifier when define a function? The qualifier const point out which argument can't be modified explicitly. And a const argument can accept both const and non-const value when calling. The const reference take the advantage of passing by value and passing by address----that's more efficient(don't have to create a copy of original data) and less influence(don't change the original data).

5. Return a Reference to a Non-const Object

Two common examples of returning a non-const object are the assignment operator and overloading the << operator:

I. operator =().

When the return value ofoperator =() is used for chained assignment:

String s("string");String s1, s2;s1 = s2 = s;
the return value of operator =()is the reference tos2 ,which it does modify.

II. operator <<() .

The return value of operator <<() is ostream object, becauseostream class does not have a public copy constructor, your can't return the value ofostream object that must use explicit copy constructor to make deep copy.



原创粉丝点击