程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 明明白白c++ effective c++ 條目11-15

明明白白c++ effective c++ 條目11-15

編輯:C++入門知識

條款11: 為需要動態分配內存的類聲明一個拷貝構造函數和一個賦值操作符
這個條款的原因在哪裡呢?
就是如果你創建一個類,什麼都不做,那麼類會給你創建一個默認構造函數,默認析構函數,默認拷貝函數和默認賦值函數。


所以出問題就出在默認上面去了,尤其是默認拷貝函數和默認賦值函數出的問題最多。
默認拷貝函數會怎麼做呢,對於a=b,它會將b中的成員逐位拷貝給另一個a,如果通過指針動態分配內存,則僅僅將指針的值賦給a。
這會導致至少兩個問題:
第一,b曾指向的內存永遠不會被刪除,因而會永遠丟失。這是產生內存洩漏的典型例子。第二,現在a和b包含的指針指向同一個字符串,那麼只要其中一個離開了它的生存空間,其析構函數就會刪除掉另一個指針還指向的那塊內存。

看下面代碼:


[cpp]
#include <iostream>  
#include <stdlib.h>  
#include <string.h>  
using namespace std; 
 
class MyString 

public: 
      MyString(const char* value); 
      ~MyString(); 
      friend ostream& operator << (ostream& os,MyString &c); 
private: 
      char *data; 
 
}; 
 
MyString::MyString(const char* value) 

        if(value){ 
                data = new char[strlen(value)+1]; 
                strcpy(data,value); 
        } 
        else{ 
                data = new char[1]; 
                data = '\0'; 
        } 

 
ostream& operator << (ostream& os,MyString &c) 

        os<<c.data; 
        return os; 

MyString::~MyString() 

        delete []data; 

 
int main() 

        MyString a("hello"); 
        { 
            MyString b("world"); 
                b  = a; 
        } 
        MyString c = a; 
         
        cout<<c<<endl; 
 

#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;

class MyString
{
public:
      MyString(const char* value);
   ~MyString();
      friend ostream& operator << (ostream& os,MyString &c);
private:
   char *data;

};

MyString::MyString(const char* value)
{
  if(value){
    data = new char[strlen(value)+1];
    strcpy(data,value);
  }
  else{
    data = new char[1];
    data = '\0';
  }
}

ostream& operator << (ostream& os,MyString &c)
{
  os<<c.data;
  return os;
}
MyString::~MyString()
{
  delete []data;
}

int main()
{
  MyString a("hello");
  {
      MyString b("world");
             b  = a;
  }
  MyString c = a;
  
  cout<<c<<endl;

}

看到輸出的結果是這樣的
▒▒#a▒▒#a
Aborted (core dumped)
可以看到c得不到正確的值,同時析構函數調用的時候出現崩潰的問題。


所以,如果你會調用拷貝構造函數和賦值函數,那麼一定要顯示的聲明他們,否則將他們定義為私有的。


那麼下面就看看如何顯示的聲明他們,如下
[cpp] 
MyString::MyString() 

    data = NULL; 

 
MyString::MyString(MyString &myString) 

    if (this == &myString) 
    { 
        return; 
    } 
    else 
    { 
        if(myString.data != NULL) 
        { 
            data = new char[strlen(myString.data)]; 
            strcpy(data,myString.data); 
        } 
        else 
        { 
            data = new char[1]; 
            data = '\0'; 
        } 
    } 
 

 
MyString & MyString::operator= (const MyString &myString) 

    if (this == &myString) 
    { 
        return *this; 
    } 
    else 
    { 
        delete []data; 
        if(myString.data != NULL) 
        { 
            data = new char[strlen(myString.data)]; 
            strcpy(data,myString.data); 
        } 
        else 
        { 
            data = new char[1]; 
            data = '\0'; 
        } 
    } 
    return *this; 

MyString::MyString()
{
 data = NULL;
}

MyString::MyString(MyString &myString)
{
 if (this == &myString)
 {
  return;
 }
 else
 {
  if(myString.data != NULL)
  {
   data = new char[strlen(myString.data)];
   strcpy(data,myString.data);
  }
  else
  {
   data = new char[1];
   data = '\0';
  }
 }

}

MyString & MyString::operator= (const MyString &myString)
{
 if (this == &myString)
 {
  return *this;
 }
 else
 {
  delete []data;
  if(myString.data != NULL)
  {
   data = new char[strlen(myString.data)];
   strcpy(data,myString.data);
  }
  else
  {
   data = new char[1];
   data = '\0';
  }
 }
 return *this;
}

這裡需要注意幾點:
1 MyString c = a; 實際上調用的是MyString::MyString(MyString &myString), 而不是重載 =號的函數。
   你可以用vs或者gdb來單步調試。原因在於,這種形式是初始化,而不是賦值。
2 下面兩個才是賦值
             MyString b("world");
     MyString c;
     b  = a;
     c = a;

     這個時候,你需要先刪除之前的data數據,考慮到有的時候參數為0,所以就定義一個參數為0的構造函數,或者將上面的構造函數寫成MyString::MyString(const char* value = NULL)的缺省參數的形式。
3 拷貝構造函數的時候,不需要刪除data,因為拷貝構造函數的時候,this->data肯定沒有申請空間,刪除會引起錯誤。
4 上面代碼其實有二個錯誤,就是應該申請的空間為strlen(data)+1,最後一位'\0';
   我在cygwin 下面運行是沒問題的,估計是僥幸,但是 vs裡面運行就會崩潰。


另外,大家可以練習怎麼寫這個函數,面試經常會問到的。真正的理解,需要一個過程,主要很多細節:要判斷是否相等,要注意什麼時候可以刪除data,什麼時候不可以, 要注意new [] 和delete[] 的對應。


條款12: 盡量使用初始化而不要在構造函數裡賦值
書上介紹的很清楚了,我這裡主要總結一下、
初始化的好處:
1 const 和引用必須使用初始化,而不能用賦值。
2 效率高。
     初始化的流程,就一步,將類成員初始化。
     而構造函數內賦值則有兩步:第一,調用默認構造函數,第二,調用賦值構造函數。
    類的構造函數的本質上是函數,函數就有形參和實參。
   例如
 [cpp]
   class A 
   {  
   pulic: 
          A(B &bInput); 
   private: 
          B b; 
  } 
  A::A(B &bInput) 
  { 
       b = bInput; 

   class A
   {
   pulic:
          A(B &bInput);
   private:
          B b;
  }
  A::A(B &bInput)
  {
       b = bInput;
}

    它的流程是什麼呢?先調用B()生成b,然後在調用operator = 賦值函數,效率當然無法保證了。這也是第一條的原因,如果是 const或者引用則等於是聲明了一下,而沒有初始化,那麼編譯的時候肯定報錯。


條款13: 初始化列表中成員列出的順序和它們在類中聲明的順序相同
舉個簡單例子:
[cpp] 
#include <iostream>  
using namespace std; 
 
class A 

public: 
    A(int value); 
    void print(); 
private: 
    int i; 
    int j; 
}; 
A::A(int value):j(value),i(j) 


void A::print() 

        cout<<i<<" "<<j<<endl; 

int main() 

    
        A a(10); 
        a.print(); 

#include <iostream>
using namespace std;

class A
{
public:
 A(int value);
 void print();
private:
 int i;
 int j;
};
A::A(int value):j(value),i(j)
{
}
void A::print()
{
  cout<<i<<" "<<j<<endl;
}
int main()
{
  
  A a(10);
  a.print();
}

這個輸出的結果是什麼?
有些人以為是10 10
我電腦上的結果是
2281060 10

第一個數是隨機數,因為執行的順序是j(i) 然後才是j(10)


條款14: 確定基類有虛析構函數
我之前有篇文章專門將這個,大家可以看看
http://blog.csdn.net/mlkiller/article/details/8884321  
這裡就不展開了。


條款15: 讓operator=返回*this的引用
剛才在寫前面的例子,重載符號<<和=的時候,我還想返回值怎麼去寫。
<<符號是和=都是二元操作符,但是後面跟的參數不一樣,<<跟了兩個參數,流對象和操作對象,它最後返回流對象。
而=只有自己,它的返回值呢應該是這個對象本身,我就在糾結&引用怎麼返回,它和this指針之間什麼關系。
等知道答案的時候,還是有些疑惑*this和&引用相等麼?看個例子


[cpp]
int p = 1; 
int *q = &p; 
int &t = p; 
int &s = *q; 

int p = 1;
int *q = &p;
int &t = p;
int &s = *q;

你看到引用本身怎麼用指針初始化。


關於文中返回const類型,我沒有搞得太清楚,時間比較晚了,以後弄明白在寫進來。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved