VC++入門經典學習筆記--結構和類

1.自定義數據類型
C++中的結構:結構是使用關鍵字struct定義的用戶定義類型。結構起源於C語言,C++繼承並擴展了結構。C++中的結構在功能上可以由類代替,因爲任何使用結構能夠做到的事情都可以使用類做到。但是因爲Windows是在廣泛應用C++之前用C語言編寫的,所以結構遍佈在Windows編程的各個方面。今天,結構仍然被廣泛使用,因此我們確實需要 瞭解結構。

2.結構的概念
考慮一下要描述像一本書這樣簡單的事物需要多少信息。我們首先可能想到書名,作者,出版社,出版日期,頁數,定價,主題或分類以及ISBN號,還可能不太困難地就想到一些其他信息。我們可以指定相互獨立的變量來容納描述一本書所需的每項信息,但更希望能夠使用一種數據類型來包含所有這些信息。這正是結構能爲我們做的事情。

3.定義結構
假設只需要在書的定義中包括書名,作者,出版社和出版年份。

 struct Book
  {
    char title[80];
    char author[80];
    char publisher[80];
    int year;
  };

這裏的代碼沒有定義任何變量,但實際上創建了一種新的類型,該類型的名稱是BOOK。關鍵字struct將BOOK定義成結構,構成本類型對象的元素是大括號內定義的。結構體內的元素可以是除所定義的結構類型以外的任何類型。例如,BOOK的結構定義中不能包括類型爲BOOK的元素。我們可以認爲這是一種侷限性,但BOOK定義中可以包括BOOK類型變量的指針。
創建其他類型變量的方式來創建BOOK類型的變量:
BOOK novbel; //聲明一個BOOK類型的變量novbel.

4.初始化結構
第一種將數據存入結構成員的方法,是在聲明語句中爲結構體成員定義初始化。

   Book novel
   {
     “Paneless Programing”,
     "I.c. Fingers",
     "Gutter Press",
     1981
   };

這些初始化值位於初始化列表內,相互之間以逗號分開,這種方式與爲數組成員定義初始值的方式完全相同。

5.訪問結構的成員
爲了訪問結構各個成員,可以使用成員選擇操作符”.”,有時稱之爲成員訪問操作符。
如果想修改Novel結構的Year成員可以這樣寫:

   novel.year=1988;

6.RECT結構在windows程序中,矩形用的很多
因此,包含在windows.h的windef.h頭文件中有一個預定義的RECT結構,,其定義本質是

   struct RECT
   {
     LONG left;
     LONG top;
     LONG right;
     LONG bottom;
   };

MFC(微軟基礎類庫(英語:Microsoft Foundation Classes,簡稱MFC)是一個微軟公司提供的類庫(class libraries),以C++類的形式封裝了Windows API,並且包含一個應用程序框架,以減少應用程序開發人員的工作量。其中包含的類包含大量Windows句柄封裝類和很多Windows的內建控件和組件的封裝類。) MFC也定義了等價於RECT結構的CCRect類。當我們理解類之後,將優先使用CRect大類而非RECT結構。CRect類提供了很多處理矩形的函數,在使用MFC編寫Windows程序時將大量使用這些函數。

7.使用指針處理結構
可以創建指向結構類型對象的指針。windows.h中聲明的許多處理RECT對象的函數都要求實參是指向RECT的指針,因爲這樣可以避免給函數傳遞RECT實參時複製整個結構的系統開銷。

  RECT* pRect{};                 //聲明一個指針指向RECT

假設已經定義了一個RECT對象&aRect,那麼可以使用通常的方式將aRect變量的地址賦予pRect指針:

  pRect = &aRect;

struct不能包含與被定義結構類型相同的成員,但可以包含指向struct的指針,其中包括指向相同類型struct的指針。

  struct ListElement
  {
    RECT aRect;
    ListElement* pNext;
  }

8.通過指針訪問結構成員

  RECT aRect{0,0,100,100};
  RECT* pRect{&aRect};

第一條語句聲明並定義了一個RECT類型的對象aRect,將第一對成員初始化爲{0,0},將第二對成員初始化爲{100,100}。第二條語句將pRect聲明爲指向RECT類型的指針,並將其初始化爲aRect的地址。現在,可以用下面的語句,通過指針訪問aRect的成員:

    (*pRect).top+=10;

這裏使用的通過指針訪問struct成員的方法看起來相當笨拙。這種操作在C++中出現得相當頻繁,因此C++提供了一個特殊操作符,間接成員選擇操作符(->)。

   pRect->top +=10;  

該語句更清楚地表示自己的用途。在本書中經常會看到該操作符。

9.數據類型,對象,類和實例
基本類型的變量不能充分模擬現實世界中的對象或虛擬的對象。例如,很難用int模擬一個箱子,但可以使用struct成員爲這樣的對象定義一組特性。如下所示,可以定義length,width和height這3個變量來表示箱子的尺寸,並將它們作爲Box結構的成員捆綁到一起:

  struct Box
  {
    double length;
    double width;
    double height;
  };

有了名爲Box的新數據類型的定義之後,就可以像定義基本類型變量那樣定義該類型的變量。在程序中,可以創建,處理和銷燬任意數量的Box對象。這意味着可以使用struct來模擬對象,並 圍繞對象編寫程序。
因此,這就是面向對象編程,對嗎?對,但不完全對。面向對象編程(OOP)基於與對象類型相關的3個基本概念,即封裝,多態性和繼承性,而我們目前所看到的不完全與之吻合。
在C++中,struct的概念遠遠超出了C語言中原來的struct概念,它現在合併了類的面向對象思想。類的思想–可以創建數據類型並像使用已有類型那樣使用,對C++而言非常重要,因此該語言引入了一個新關鍵字class來描述類這一概念。在C++中,除了成員的訪問控制以外,關鍵字struct和class幾乎是等同的。保留關鍵字struct是爲了向後兼容C語言,但使用struct能實現的一切都可以用類來實現,而且類可以比struct實現更多的功能。
定義表示箱子的類:

  class CBox
  {
    public:
      double m_length;
      double m_width;
      double m_height;
  };

與定義Box結構的情況類似,將CBox定義成類時,實質上是在定義新的數據類型。僅有的區別是使用了關鍵字class代替struct,還在類成員的定義前面使用了後跟冒號的關鍵字public。作爲類組成部分被定義的變量稱爲類的數據成員,因爲他們是存儲數據的變量。

public關鍵字提供了區別結構和類之間的線索。以該關鍵字定義的類成員通常是可以訪問的,其訪問方式與訪問結構成員相同。默認情況下,類成員一般是不可訪問的,而是私有的(private)。爲了使類成員可訪問,就必須在他們的定義前面使用關鍵字public。
結構成員默認是共有的。類成員默認情況下之所以是私有的,一般是因爲類對象應該是自包含實體,這樣的數據使對象應該被封裝起來,並且只能在受控制的情形下修改。公共的數據成員是非常少見的例外情況。
MFC採用了在所有類名稱以C作爲前綴的約定,因此我們也要養成這樣的習慣。MFC還給類的數據成員添加m_前綴,以便將他們與其他變量區別開來。
可以像下面這樣,聲明一個表示CBox類類型的實例bigBox

  CBox bigBox;

10.類的起源:
類的概念是以爲英國人爲使普通人保持心情愉快而發明的,它源於”知道自己在社會中的地位和作用的人們,生活將比那些對此茫然無知的人更感到安全和舒適”這一理論。著名的C++發明者丹麥人Bjarne Stroustrup在劍橋大學期間學到了深奧的類概念,並非常成功地將之應用到他的新語言中。
類通常都有非常精確的任務和一組可以執行的動作,從這一點來講,C++中的類與英語原意相同。但是,C++中的類與英語原意是有區別的,因爲他們很大程度上專注於實現工作類的價值。實際上,在某些方面他們甚至與英語原意相反,因爲C++中的工作類往往位於那些完全不做任何事情的類背後。

11.類的操作
在c++中,可以創建新的數據類型–類,來表示任何希望表示的對象。類(以及結構)不僅限於容納數據,還可以定義成員函數,甚至可定義在類對象之間使用標準C++運算符執行操作。還可以在CBox類中實現使box相加,相減乃至相乘的操作。事實上,幾乎任何在box上下問中有實際意義的操作都可以實現。
這裏談論的是令人難以置信的強大技術,它使我們採用的編程方法產生了重大變化。我們不在根據本質上與計算機相關的數據類型(整數,浮點數等)分解問題,然後編寫程序,而是根據與問題相關的數據類型(換一種說法就是類)進行編程。

12.類的相關術語
類是用戶定義的數據類型。
面向對象編程(OOP)是一種編程風格,它基於將自己的數據類型定義成類的思想,這些數據類型專用於打算解決的問題的問題域。

 聲明類的對象有時稱作實例化,因爲這是在創建類的實例。
 類的實例稱爲對象,
 對象在定義中隱式地包含數據和操作數據的函數,這種思想稱爲封裝。

13.理解類
類是用戶定義的數據類型的說明,其包含的數據元素可以是基本類型或其他用戶定義類型的變量。類的數據元素是單數據元素,數組,指針,幾乎任何種類的指針數組或其他類的對象,因此在類類型可以包括的數據方面有非常大的靈活性。類還包含通過訪問類內的數據元素來處理本類對象的函數。因此,類組合了構成對象的元素數據的定義和處理奔雷對象中數據的方法。
類中的數據和函數稱爲類的成員。奇怪的是類的數據項稱爲數據成員,函數的類成員稱爲函數成員或成員函數。類的成員函數有時也稱作方法。

 定義類時,就是在定義某種數據類型的藍圖。我們沒有實際定義任何數據,但確實定義了類名的意義--即類對象將由那些數據組成,對這樣的對象可以執行什麼操作。如果要編寫基本類型double的說明,則情況完全相同。但我們編寫的不是double類型的實際變量,而是這種變量的構成和操作的定義。爲了創建基本數據類型的變量,需要使用聲明語句。創建變量的情形完全相同。

14.定義類:定義表示箱子的類:

  class CBox
      {
        public:
          double m_length;
          double m_width;
          double m_height;
      };
  類名跟在class關鍵字後面,3個數據成員在大括號內定義的。數據成員的定義使用我們已經很熟悉的聲明語句,整個類定義以分號結束。所有類成員的名稱都是該類的局部變量。因此,可以在程序中的其他地方使用相同的名稱,包括其他類定義。

  類的訪問控制:
  public關鍵字決定着後面那些類成員的訪問屬性。將數據成員指定爲public,意味着在包含這些成員的類對象的作用於內的任何位置都可以訪問它們。還可以將類成員指定爲private或protected,此時這些成員不能在類的外部訪問。事實上,如果完全省略訪問說明,則成員的默認訪問屬性是private(這是類和結構之間的唯一區別--結構的默認訪問說明符是public)。

15.聲明類的對象
聲明類的對象的方法與聲明基本類型對象的方法完全相同。因此,可以用下面的語句聲明類類型CBox的對象:

      CBox Box1;
      CBox box2;
  box1和box2這兩個對象都擁有各自的數據成員。

16.訪問類的數據成員
可以使用訪問結構成員時用過的直接成員選擇操作符來引用類對象的數據成員。

17.對象成員的初始化
因爲CBox對象的數據成員是public,所以在創建對象時,可以在初始化列表中指定他們的值:

      CBox box1{2.5,3.5,4.5};
  box1的成員按順序獲取列表的值,所以m_Length是2.5,m_Height是4.5。如果在列表中提供的值少於數據成員的個數,未獲取值的成員就設置爲0。
 CBox box2{2.5,3.5};
  也可以提示一個空列表,把所有成員都初始化爲0:
      CBox box3{};

18.初始化類成員

   class CBox
      {
        public:
          double m_length{1.0};
          double m_width{1.0};
          double m_height{1.0};
      };
  初始化類成員的語法與普通變量相同,也是使用初始化列表。初始化應用於所創建的任何CBox對象的成員,除非成員的值通過其他方式設置。

19.類的成員函數是其定義或原型在類定義內部的函數,他們可以處理本類的任何對象,有權訪問本類對象的所有成員。

20.類的成員函數
類的成員函數是其定義或原型在類定義內部的函數,它們可以處理本類的任何對象,有權訪問本類對象的所有成員,而不管訪問指定符是什麼。在成員函數體中使用的類成員名稱自動引用用於調用函數的特定對象的成員,且只能給該類類型的特定對象調用該函數。如果嘗試在調用成員函數時不指定對象名,程序就不會編譯。

      class CBox
      {
        public:
          double m_length{1.0};
          double m_width{1.0};
          double m_height{1.0};
          double volume()
          {
            return m_length*m_width*m_width;
          }
      };
      int main()
      {
            double boxVolume{box1.volume()};
      }
  在成員函數內訪問成員時,不需要以任何方式限定這些成員的名稱。未定義的成員名自動引用調用該成員函數時當前對象的成員。

21.在類的外部定義成員函數
可以在類的外部定義成員函數。此時只需要將函數原型放在類內部。如果這樣重寫前面的CBox類,並將函數定於放在類的外部,則類定義如下:

        class CBox
      {
        public:
          double m_length{1.0};
          double m_width{1.0};
          double m_height{1.0};
      };
      double CBox::volume()
      {
            return m_length*m_width*m_width;
      }
 因爲volume()成員函數的定義在類的外部,所以必須以某種方式告訴編譯器:該函數屬於CBox類--即給函數名加上類名作爲前綴,並用作用域解析運算符::(兩個冒號)將兩者分開。

22.內聯函數
在內聯函數中,編譯器設法以函數體代碼代替函數調用。這樣可以避免調用函數時的大量系統開銷,加速代碼的運行。
注意:當然,編譯器將確保展開內聯函數時不引起任何與變量名衝突或作用域有關的問題。
把函數指定爲內聯,並不能保證函數是內聯的。編譯器不一定總能插入內聯函數的代碼(如遞歸函數或返回地址的函數),但通常應該沒有問題。內聯函數最適用於那些非常短的簡單函數,如CBox類的volume()函數,因爲這樣的函數執行的更快,而且插入其函數體的代碼不會顯著增加可執行模塊兒的大小。編譯器不能把函數變成內聯函數內時,代碼仍能編譯運行。
在函數定義位於類定義外部時,也可以告訴編譯器將函數視爲內聯函數–即在函數頭前面加上關鍵字inline:

    inline double CBox::Volume()
     {
        return m_length*m_width*m_height;
     }

使用上面的函數定義,程序將與原來完全相同。因此可以把成員函數的定義放在類的外部,且仍然保留內聯函數在執行性能方面的優勢。也可以對程序中與類好不相干的普通函數應用關鍵字inline,並得到相同的效果。但請記住,該關鍵字最適合用於短小的簡單的程序。
如前所述,給內聯函數使用關鍵字inline不是必須的。即使該函數沒有標記爲inline,編譯器有時也可以確定內倆函數是否有意義。

23.類構造函數:當定義不具有public屬性的類數據成員時,不能以任何方式從類外不訪問這些成員。必須有更好的方法,當然–那就是類構造函數。
類構造函數是類的特殊函數,在創建新的類對象時調用它。因此,該函數提供了創建對象時進行初始化的機會,並確保數據成員只包含有效值。構造函數是一個成員函數,所以無論成員的訪問特性是什麼,都可以設置成成員的值。
在命名類構造函數方面,我們沒有迴旋餘地,它們總是與所屬的類的名稱相同,甚至類有兩個或多個構造函數時,也是如此。構造函數沒有任何返回類型。給構造函數指定返回類型錯誤的,即使寫成void也不允許。類構造函數的首要目的是給類的數據成員賦予初始值,因此任何返回類型都是不必要或不允許的。

       class CBox
      {
        public:
          double m_length{1.0};
          double m_width{1.0};
          double m_height{1.0};

          CBox(double lv,double wv,double hv)
          {
            m_length = lv;
            m_width = wv;
            m_height = hv;
          }
      };

24.默認的構造函數
默認構造函數如下所示:CBox(){}
因爲沒有爲數據成員提供任何初始值。默認構造函數既可以是定義中沒有指定任何形參的構造函數,也可以是實參全部有默認值的構造函數。

25.在構造函數中使用初始化列表:
可以在構造函數定義的開頭使用構造函數的初始化列表初始化數據成員。

CBox(double lv=1.0,double wv = 1.0,double hv = 1.0):m_length{lv},m_width{wv},m_height{hv}
{
    cout<<"Constructor called."<<endl;
}

這種編寫方式嘉定構造函數位於類定義內部。構造函數的初始化列表與形參列表之間以冒號分開,各個成員的初始化器之間以逗號分開。成員初始化列表總是在函數體之前執行,所以可以在構造函數體重使用已在列表中初始化的成員值。

對於const或引用類型的類成員,其初始化方式是無法選擇的。唯一的方式是在構造函數中使用成員初始化列表。構造函數體中的賦值語句是無效的。還要注意,成員初始化的順序不同於它們在構造函數初始化列表中的順序,而與它們類在定義中的順序相同。

25.類的友元函數:
由於某種原因,我們希望某系雖然不是類成員的函數能夠訪問類的所有成員–它們擁有特殊權限。這樣的函數稱爲類的友元函數,使用關鍵字friend來定義。可以在類定義中添加友元函數的原型,也可以添加整個函數定義。在類定義內定義的友元函數默認也是內聯函數。
友元函數不是類的成員,因此訪問特性不適用於它們。這些函數只是擁有特殊權限的普通全局函數。

26.默認複製構造函數

    CBox box1{78.0,24.0,18.0};

現在,要創建另一個與box1相同的CBox對象,希望能夠用box1將其初始化。

    int main()
    {
        CBox box1{78.04.018.0};
        CBox box2{box1};

        cout<<box1.m_length<<endl;
        cout<<box2.m_length<<endl;
    }

該程序像我們期望的那樣工作,結果是兩個箱子具有相同的體積。但從輸出可以看出,僅在創建box1時調用了一次構造函數。那麼,box2是如何創建的呢?
這裏的機制類似於前面沒有定義構造函數時所使用的機制–即編譯器提供默認的構造函數,以允許創建對象。本示例中,編譯器生成一個默認的賦值構造函數。

複製構造函數做的就是我們在這裏做的事情–即通過用同類的現有對象進行初始化來創建類對象。複製構造函數的默認版本通過諸葛成員地複製現有的對象來創建新對象。

*注意:默認構造函數對CBox這樣的簡單類工作得不錯,但對許多類來說--如擁有指針成員的類,該函數將不能正常工作。實際上,對這些類應用默認複製構造函數進行逐個成員地複製,可能產生嚴重的程序錯誤。這種情況下,必須創建自己的複製構造函數,來替代默認的複製構造函數。*

27.this指針
在CBox類中,用類定義中的類成員名編寫volume()函數。當然,創建的所有CBox類型的對象都包含這些成員,因此必須有某種機制使該函數能夠引用調用它的具體對象的成員。
任何成員函數執行時,都自動包含一個名爲this的隱藏指針,它指向調用該函數時使用的對象。因此,m_Length成員名出現在volume()函數體中時,實際上是this->m_length–用於調用函數對象成員的全稱。編譯器負責在函數中給成員名添加必要的指針名this。
如果需要,也可以在成員函數中顯式的使用this指針。

        class CBox
      {
        public:
          double m_length{1.0};
          double m_width{1.0};
          double m_height{1.0};
          double volume()
          {
            return m_length*m_width*m_width;
          }
          bool compare(CBox& xBox)
          {
            return this->volume()>xBox.volume();
          }
      };
      int main()
      {
        CBox match{2.2,1.1,0.5};
        CBox cigar{8.0,5.0,1.0};
        if(cigar.compare(match))
          cout<<"match is smaller than cigar"<<endl;
        else 
        cout<<"matcj is equal to or larger than cigar"<<endl;
        return 0;
      }

28.類的靜態成員
靜態數據成員是程序啓動時自動創建的,並且初始化爲0–除非將其初始化爲其他值。因此,僅當希望類的靜態數據成員最初是非零值時,才需要初始化它們。但仍需要定義它們,例如:

     int CBox::objectCount;

該語句定義了objectCount,但沒有顯式初始化它,所以其默認值爲0。

29.類的靜態成員函數
通過將某個函數聲明爲static可以使該函數獨立於本類的任何具體對象。因此它沒有this指針。static成員函數的優點是:即使本類的任何對象都不存在,它們也能存在並被調用。在這種情況下,靜態成員函數只能使用靜態數據成員函數,因爲後者是唯一存在的數據成員。因此,即使不能肯定是否有類對象存在,也可以調用類的靜態函數成員來檢查靜態數據成員。
靜態成員函數的原型可以如下所示:

    static void aFunction(int n);
可以用下面這條語句通過具體對象調用靜態函數:
    aBox.aFunction(10);

該函數不能訪問aBox的非靜態成員。不通過引用對象也可以調用同一個函數,這種情況下該語句的形式如下:CBox::aFunction(10);
類名CBox限定了函數。使用類名和作用域解析運算符,可以告訴編譯器aFunction()函數屬於哪個類。

30.類對象的指針和引用
使用類對象的指針和引用–特別是引用,在面向對象編程和函數形參說明方面非常重要。類對象可能涉及相當多的數據,因此對對象使用按值傳遞機制非常耗時和低效,因爲需要複製每一個實參對象。使用引用形參可以避免這個系統開銷,而且引用形參對於一些類的操作是必不可少的。
如果不使用引用形參,將不能編寫複製構造函數。

31.類對象的指針
能以生命其他指針的相同方式,聲明指向類對象的指針。

    CBox* pBox{};
現在可以使用取址運算符,按通常的方式在賦值語句中使用該指針來存儲CBox對象的地址:
    pBox = &ciger;
        cout<<pBox->volume();
該語句再次使用了間接成員訪問操作符。

32.類對象的引用
當隨ongoing類一起使用時,才能真正體驗引用的價值。如同指針一樣,在聲明和使用類對象的引用於聲明和使用基本變量的引用之間,實質上沒有任何區別。例如,爲了聲明對象cigar的引用,可以這樣寫:

    CBox& rcigar{cigar};
爲了使用引用計算對象cigar的體積,只需在應該出現對象名的位置使用引用名即可:
    cout<<rcigar.volume();
我們可能還記得,引用用作被引用對象的別名,因此其用法與原來的對象名完全相同。

實現複製構造函數:
引用重要性實際體現在函數(特別是類的成員函數)的形參和返回值等上下文中。現在仍以複製構造函數爲例。我們暫且迴避何時需要編寫自己的複製構造函數這個問題,而全神貫注與如何編寫複製構造函數。
複製構造函數是用同類的現有對象進行初始化,從而創建新對象的構造函數,因此需要接受同類的對象作爲實參。

    CBox(CBox initB);

現在考慮調用該函數時將發生什麼事情。如果編寫CBox myBox(cigar);
這樣一條聲明語句,那麼將生成如下所示對複製構造函數的調用。該語句似乎沒有任何問題,但其實參是通過按值傳遞機制傳遞的。在可以傳遞對象cigar之前,編譯器需要安排創建該對象的副本。因此,編譯器爲了處理複製構造函數的這條調用語句,需要調用複製構造函數來創建實參的副本。但是,由於是按值傳遞,第二次調用同樣需要創建實參的副本,因此還得調用複製構造函數,就這樣持續不休。最終得到的是對複製構造函數的無窮調用。
解決方法是使用const引用形參。可以將複製構造函數的原型寫成如下形式:

    CBox(const CBox& initB);

現在,不再需要複製構造函數的實參。實參是用來初始化引用形參的,因此沒有複製發生。如果函數的形參是引用,則調用該函數時不需要複製實參。函數直接訪問被調用函數中的實參變量。const限定符用來確保該函數不能修改實參。
這是const限定符的另一個重要用途。我們應該總是將函數的應用形參聲明爲const,除非該函數將修改實參。
可以像下面這樣實現這個複製構造函數:

CBox::CBox(const CBox& initB):
        m_length(initB.m_length),m_width(initB.m_Width),m_height(initB.m_height){}

該複製構造函數的定義假定其位於類定義外部,因此構造函數名用類名和作用域解析運算符加以限定。被創建對象的各個數據成員用傳遞給構造函數的實參對象的對應成員進行初始化。
有點兒亂,留作回看!本來沒怎麼系統的學過C,C++只是學cocos2d-x的時候,大致學了一下要用到的一些東西。發現,回過頭來看這些基礎的東西,學習後感覺更加的踏實了!哈哈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章