天天看點

C++類和動态記憶體配置設定

動态記憶體和類

如果有這樣的strbad類:

#include<iosream>
#ifdef STRBAD_H_
#define SREBAD_H_
class strbad{
  private:
    char * str;
    int len;
    static int num;
  public:
    strbad(const char *s);
    strbad();
    ~strbad();
    friend std::ostream & operator<<(std::ostream &os,const strbad &st);
  }
#endif
           
#include<cstring>
#include"stringbad.h"
using std::cout;

int strbad::num=0;//靜态成員變量不能在聲明中初始化
//除非靜态變量是const或者枚舉型

strbad::strbad(const char *s){
  len=std::strlen(s);
  str=new char[len+1];//str是指針,是以必須提供記憶體
  std::strcopy(str,s);//不能用str=s,這樣的話沒有建立副本,而隻是複制了指針
  num++;
}

strbad::strbad(){
  len=4;
  str=new char[4];
  std::strcopy(str,"C++");
  num++;
}

strbad::~strbad(){
  num--;
  delete[] str;
  //new對應delete,new[]對應delete[]
}


std::ostream & operator<<(std::ostream &os,const strbad &st){
  os<<st.str;
  return os;
}

           

看似設計沒有什麼問題,但下面的語句證明它其實大有問題:

{
strbad a=srebad("a");
call(a);
cout<<a;//事實證明在這裡a已經被析構了
strbad b=a;
}
cout<<strbad::num;//發現num為負數

void call(strbad s);//這裡是值傳遞
           

所有的原因在于忽略了類的複制構造函數。

strbad(const strbad &);
           

編譯器自動生成的複制構造函數不知道如何處理num,導緻num被弄亂了。

下面一些建立對象的情況會導緻調用複制構造函數:

strbad ditto(motto);
strbad metto=motto;
strbad also=strbad(motto);
strbad * p=new strbad(motto);
           

當程式生成了對象副本時,會調用複制構造函數。具體比如:當函數按值傳遞對象(call方法)或函數傳回對象時,都将使用複制構造函數。是以我們應該盡可能按引用傳遞對象。

預設複制構造函數逐個複制非靜态成員,複制的是成員的值。

是以我們可以發現,在調用call方法時,s被a指派,但由于類中存儲的值為指針類型,是以使用在調用s的析構函數時會把s的指針(同樣是a的指針)指向的動态記憶體釋放,是以我們會發現a的動态記憶體已經被delete了。

我們可以定義一個顯式複制構造函數:

strbad:strbad(const strbad & st){
  num++;
  len=st.len;
  str=new char[len+1];
  std::strcpy(str,st.str);
}
           

有些類成員是使用new初始化的、指向資料的指針,而不是資料本身,是以我們需要深度複制(改寫預設複制構造函數)。

當然我們也要重載指派運算符。

strbad b;
b=a;//不會觸發複制構造函數,需要重載=
           

有以下注意點:

(1)目标對象可能引用了以前配置設定的資料,函數應使用delete來釋放它們。

(2)函數應當避免将對象賦給自身,否則可能會導緻在指派前删除了自身的内容。

(3)函數傳回一個指向調用對象的引用。

strbad & strbad::operator=(const strbad& st){
  if (this==&st)  return *this;
  delete[] str;
  len=st.len;
  str=new char[len+1];
  std::strcpy(str,st.str);
  return *this;
}
           

如果指派為str=0,這說明str為空指針,此時使用delete[] str是可行的。

可以将成員函數聲明為靜态的,這樣就不能通過對象調用靜态成員函數,甚至不能用this,靜态成員函數也不與特定的對象相關聯,是以隻能使用靜态資料成員。

構造函數中new的注意事項

有如下注意點:

(1)如果在構造函數中用new,應該在析構函數中用delete。

(2)new和delete必須相容,new對應delete,new[]對應delete[]

(3)如果有多個構造函數,則必須用相同的方法new,要麼都帶中括号,要麼都不帶,因為隻有一個析構函數。delete(無論有沒有中括号)都可以用于空指針(0,NULL)

(4)應定義一個複制構造函數。

(5)應重載一個指派運算符。

對于定位new運算符,有如下注意點:

pc1=new(buffer) strbad;
pc2=new(buffer) strbad;//no
pc2=new(buffer+sizeof(strbad)) strbad("aaa");//yes
           

再執行buffer的delete操作前,需要顯式地調用析構函數:

pc1->~strbad();
pc2->~strbad();
delete[] buffer;//這樣才算完全銷毀
           

有關傳回對象的說明

1.傳回指向const對象的引用

如果函數傳回傳遞給它的對象,可以通過傳回引用來提高效率,因為傳回引用不會調用複制構造函數。而且,引用指向的對象應該在調用函數執行時存在。除此以外,傳回的類型必須為const才能比對。

2.傳回指向非const對象的引用

兩種常見的情形是,重載指派運算符和cout一起使用的<<。前者是提高效率,後者是必須那麼做。

3.傳回對象

如果被傳回的對象是被調用函數中的局部變量,則不應按引用方式傳回它。此時将使用複制構造函數來生成傳回的對象。

4.傳回const對象

net=force1+force2;
force1+force2=net;//傳回const對象将不允許那麼做
           

使用const能夠防止那樣奇怪的錯誤,當然傳回的對象将會是const的。

繼續閱讀