C++ 編程習慣編程要點

      網友投稿 716 2025-04-03

      以良好的方式編寫C++ class


      假設現在我們要實現一個復數類complex,在類的實現過程中探索良好的編程習慣。

      ① Header(頭文件)中的防衛式聲明

      complex.h:

      # ifndef __COMPLEX__

      # define __COMPLEX__

      class complex

      {

      }

      # endif

      防止頭文件的內容被多次包含。

      ② 把數據放在private聲明下,提供接口訪問數據

      # ifndef __COMPLEX__

      # define __COMPLEX__

      class complex

      {

      public:

      double real() const {return re;}

      double imag() const {return im;}

      private:

      doubel re,im;

      }

      # endif

      ③ 不會改變類屬性(數據成員)的成員函數,全部加上const聲明

      例如上面的成員函數:

      double real () `const` {return re;}

      double imag() `const` {return im;}

      既然函數不會改變對象,那么就如實說明,編譯器能幫你確保函數的const屬性,閱讀代碼的人也明確你的意圖。

      而且,const對象才可以調用這些函數——const對象不能夠調用非const成員函數。

      ④ 使用構造函數初始值列表

      class complex

      {

      public:

      complex(double r = 0, double i =0)

      : re(r), im(i) { }

      private:

      doubel re,im;

      }

      在初始值列表中,才是初始化。在構造函數體內的,叫做賦值。

      ⑤如果可以,參數盡量使用reference to const

      為complex 類添加一個+=操作符:

      class complex

      {

      public:

      complex& operator += (const complex &)

      }

      使用引用避免類對象構造與析構的開銷,使用const確保參數不會被改變。內置類型的值傳遞與引用傳遞效率沒有多大差別,甚至值傳遞效率會更高。例如,傳遞char類型時,值傳遞只需傳遞一個字節;引用實際上是指針實現,需要四個字節(32位機)的傳遞開銷。但是為了一致,不妨統一使用引用。

      ⑥ 如果可以,函數返回值也盡量使用引用

      以引用方式返回函數局部變量會引發程序未定義行為,離開函數作用域局部變量被銷毀,引用該變量沒有意義。但是我要說的是,如果可以,函數應該返回引用。當然,要放回的變量要有一定限制:該變量的在進入函數前,已經被分配了內存。以此條件來考量,很容易決定是否要放回引用。而在函數被調用時才創建出來的對象,一定不能返回引用。

      說回operator +=,其返回值就是引用,原因在于,執行a+=b時,a已經在內存上存在了。

      而operator +?,其返回值不能是引用,因為a+b的值,在調用operator +的時候才產生。

      下面是operator+=?與’operator +’ 的實現:

      inline complex & complex :: operator += (const complex & r)

      {

      this -> re+= r->re;

      this -> im+= r->im;

      return * this;

      }

      inline complex operator + (const complex & x , const complex & y)

      {

      return complex ( real (x)+ real (y), //新創建的對象,不能返回引用

      imag(x)+ imag(y));

      }

      在operator +=中返回引用還是必要的,這樣可以使用連續的操作:

      c3 += c2 += c1;

      ⑦ 如果重載了操作符,就考慮是否需要多個重載

      就我們的復數類來說,+可以有多種使用方式:

      complex c1(2,1);

      complex c2;

      c2 = c1+ c2;

      c2 = c1 + 5;

      c2 = 7 + c1;

      為了應付怎么多種加法,+需要有如下三種重載:

      inline complex operator+ (const complex & x ,const complex & y)

      {

      return complex (real(x)+real(y),

      imag(x+imag(y););

      }

      inline complex operator + (const complex & x, double y)

      {

      return complex (real(x)+y,imag(x));

      inline complex operator + (double x,const complex &y)

      {

      return complex (x+real(y),imag(y));

      }

      ⑧ 提供給外界使用的接口,放在類聲明的最前面

      這是某次面試中,面試官大哥告訴我的。想想確實是有道理,類的用戶用起來也舒服,一眼就能看見接口。

      Class with pointer member(s):記得寫Big Three

      C++的類可以分為帶指針數據成員與不帶指針數據成員兩類,complex就屬于不帶指針成員的類。而這里要說的字符串類String,一般的實現會帶有一個char *指針。帶指針數據成員的類,需要自己實現class三大件:拷貝構造函數、拷貝賦值函數、析構函數。

      class String

      {

      C++ 編程習慣與編程要點

      public:

      String (const char * cstr = 0);

      String (const String & str);

      String & operator = (const String & str);

      ~String();

      char * get_c_str() const {return m_data};

      private:

      char * m_data;

      }

      如果沒有寫拷貝構造函數、賦值構造函數、析構函數,編譯器默認會給我們寫一套。然而帶指針的類不能依賴編譯器的默認實現——這涉及到資源的釋放、深拷貝與淺拷貝的問題。在實現String類的過程中我們來闡述這些問題。

      ①析構函數釋放動態分配的內存資源

      如果class里有指針,多半是需要進行內存動態分配(例如String),析構函數必須負責在對象生命結束時釋放掉動態申請來的內存,否則就造成了內存泄露。局部對象在離開函數作用域時,對象析構函數被自動調用,而使用new動態分配的對象,也需要顯式的使用delete來刪除對象。而delete實際上會調用對象的析構函數,我們必須在析構函數中完成釋放指針m_data所申請的內存。下面是一個構造函數,體現了m_data的動態內存申請:

      /*String的構造函數*/

      inline

      String ::String (const char *cstr = 0)

      {

      if(cstr)

      {

      m_data = new char[strlen(cstr)+1]; // 這里,m_data申請了內存

      strcpy(m_data,cstr);

      }

      else

      {

      m_data= new char[1];

      *m_data = '\0';

      }

      }

      這個構造函數以C風格字符串為參數,當執行

      String *p = new String ("hello");

      m_data向系統申請了一塊內存存放字符串hello:

      析構函數必須負責把這段動態申請來的內存釋放掉:

      inline

      String ::~String()

      {

      delete[]m_data;

      }

      ②賦值構造函數與復制構造函數負責進行深拷貝

      來看看如果使用編譯器為String默認生成的拷貝構造函數與賦值操作符會發生什么事情。默認的復制構造函數或賦值操作符所做的事情是對類的內存進行按位的拷貝,也稱為淺拷貝,它們只是把對象內存上的每一個bit復制到另一個對象上去,在String中就只是復制了指針,而不復制指針所指內容。現在有兩個String對象:

      String a("Hello");

      String b("World");

      a、b在內存上如圖所示:

      如果此時執行

      b = a;

      淺拷貝體現為:

      存儲World\0的內存塊沒有指針所指向,已經成了一塊無法利用內存,從而發生了內存泄露。不止如此,如果此時對象a被刪除,使用我們上面所寫的析構函數,存儲Hello\0的內存塊就被釋放調用,此時b.m_data成了一個野指針。來看看我們自己實現的構造函數是如何解決這個問題的,它復制的是指針所指的內存內容,這稱為深拷貝

      /*拷貝賦值函數*/

      inline String &String ::operator= (const String & str)

      {

      if(this == &str) //①

      return *this;

      delete[] m_data; //②

      m_data = new char[strlen(str.m_data)+1]; //③

      strcpy(m_data,str.m_data); //④

      return *this

      }

      這是拷貝賦值函數的經典實現,要點在于:

      ① 處理自我賦值,如果不存在自我賦值問題,繼續下列步驟:

      ② 釋放自身已經申請的內存

      ③ 申請一塊大小與目標字符串一樣大的內存

      ④ 進行字符串的拷貝

      對于a = b,②③④過程如下:

      同樣的,復制構造函數也是一個深拷貝的過程:

      inline String ::String(const String & str )

      {

      m_data = new char[ strlen (str) +1];

      strcpy(m_data,str.m_data);

      }

      另外,一定要在operator = 中檢查是否self assignment?假設這時候確實執行了對象的自我賦值,左右pointers指向同一個內存塊,前面的步驟②delete掉該內存塊造成下面的結果。當企圖對rhs的內存進行訪問是,結果是未定義的。

      static與類

      ① 不和對象直接相關的數據,聲明為static

      想象有一個銀行賬戶的類,每個人都可以開銀行賬戶。存在銀行利率這個成員變量,它不應該屬于對象,而應該屬于銀行這個類,由所有的用戶來共享。static修飾成員變量時,該成員變量放在程序的全局區中,整個程序運行過程中只有該成員變量的一份副本。而普通的成員變量存在每個對象的內存中,若把銀行利率放在每個對象中,是浪費了內存。

      ② static成員函數沒有this指針

      static成員函數與普通函數一樣,都是只有一份函數的副本,存儲在進程的代碼段上。不一樣的是,static成員函數沒有this指針,所以它不能夠調用普通的成員變量,只能調用static成員變量。普通成員函數的調用需要通過對象來調用,編譯器會把對象取地址,作為this指針的實參傳遞給成員函數:

      obj.func() ---> Class :: fun(&obj);

      而static成員函數即可以通過對象來調用,也可以通過類名稱來調用。

      ③在類的外部定義static成員變量

      另一個問題是static成員變量的定義。static成員變量必須在類外部進行定義:

      class A

      {

      private:

      static int a; //①

      }

      int A::a = 10; //②

      注意①是聲明,②才是定義,定義為變量分配了內存。

      ④static與類的一些小應用

      這些可以用來應付一下面試,在實現單例模式的時候,static成員函數與static成員變量得到了使用,下面是一種稱為”餓漢式“的單例模式的實現:

      class A

      {

      public:

      static A& getInstance();

      setup(){...};

      private:

      A();

      A(const A & rhs);

      static A a;

      }

      這里把class A的構造函數都設置為私有,不允許用戶代碼創建對象。要獲取對象實例需要通過接口getInstance。”餓漢式“缺點在于無論有沒有代碼需要a,a都被創建出來。下面是改進的單例模式,稱為”懶漢式“:

      class A

      {

      public:

      static A& getInstance();

      setup(){....};

      private:

      A();

      A(const A& rsh);

      ...

      };

      A& A::getInstance()

      {

      static A a;

      return a;

      }

      “懶漢式”只有在真正需要a時,調用getInstance才創建出唯一實例。這可以看成一個具有拖延癥的單例模式,不到最后關頭不干活。很多設計都體現了這種拖延的思想,比如string的寫時復制,真正需要的時候才分配內存給string對象管理的字符串。

      C++ 面向對象編程

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:如何在Excel刪除單元格中單詞之間的多余空格?
      下一篇:WPS表格怎樣對數據進行排序(wps怎么對表格進行排序)
      相關文章
      免费在线观看亚洲| 亚洲爆乳无码专区www| 色噜噜噜噜亚洲第一| 亚洲精品日韩专区silk| 亚洲爆乳无码专区| 亚洲AV永久无码精品一百度影院| 亚洲黄黄黄网站在线观看| 亚洲AV无码一区二区三区网址| 亚洲熟妇AV一区二区三区宅男| 亚洲人成小说网站色| 亚洲人成伊人成综合网久久| 亚洲国产精品久久人人爱| 亚洲男女性高爱潮网站| 亚洲欧洲日产韩国在线| 亚洲最大黄色网址| 激情综合亚洲色婷婷五月| 亚洲日本人成中文字幕| 亚洲中文无码mv| 亚洲av无码专区在线电影天堂| 337P日本欧洲亚洲大胆艺术图| 激情婷婷成人亚洲综合| 亚洲国产婷婷香蕉久久久久久| 亚洲A丁香五香天堂网| 中文字幕亚洲一区二区三区| 亚洲日韩小电影在线观看| 亚洲成av人片天堂网| 亚洲视频在线免费观看| 亚洲成A∨人片在线观看无码| 亚洲av永久综合在线观看尤物| 亚洲中文字幕日本无线码| 亚洲国产精品美女久久久久| 国产AV无码专区亚洲AV蜜芽| 亚洲精品岛国片在线观看| 亚洲线精品一区二区三区影音先锋| 亚洲成a人片在线观看日本| 亚洲一区免费观看| 亚洲乱人伦精品图片| 亚洲午夜成人精品无码色欲| 欧洲亚洲综合一区二区三区| 国产av无码专区亚洲国产精品| 亚洲精品午夜无码电影网|