C++基礎(2)

C++核心編程(面向對象編程)ios

一、內存分區模型c++

  C++程序在執行時,將內存大方向劃分爲4個區域:程序員

  • 代碼區:存放函數體的二進制代碼,由操做系統進行管理的。
  • 全局區:存放全局變量和靜態變量以及常量。
  • 棧區:由編譯器自動分配和釋放,存放函數的參數值,局部變量等。
  • 堆區:由程序員分配和釋放,若程序員不釋放,程序結束時由系統回收.

1.1 程序運行前編程

  在程序編譯後,生成了exe可執行程序,未執行該程序前分爲兩個區域數組

  代碼區:安全

    存放CPU執行的機器指令。架構

    代碼區是共享的,共享的目的是對於頻繁被執行的程序,只須要在內存中有一份代碼便可。app

    代碼區是只讀的,使其只讀的緣由是防止程序意外地修改了它的指令。函數

  全局區:工具

    全局變量和靜態變量(static關鍵字)存放在此。

    全局區還包含了常量區,字符串常量和其餘常量(const修飾的全局變量:全局常量)也存放在此。

    該區域的數據在程序結束後由操做系統釋放。

  注:局部變量、const修改的局部變量(局部常量)不在全局區中,

  全局變量:函數體外的變量。

  局部變量:函數體內的變量。

  靜態變量:在普通變量前面加static,屬於靜態變量。

  常量:字符串常量((int)&"hello world")、const修飾的變量

  總結:

  • C++中在程序運行前分爲全局區和代碼區。
  • 代碼區特色是共享和只讀
  • 全局區中存放全局變量、靜態變量、常量
  • 常量區中存放const修飾的全局常量和字符串常量

1.2  程序運行後

  棧區:由編譯器自動分配釋放,存放函數的參數值,局部變量等。

  棧區數據注意事項---不要返回局部變量的地址

  棧區的數據由編譯器管理開闢和釋放

  函數體內的局部變量,形參數據存放在棧區,棧區的數據在函數執行完後自動釋放

  堆區:由程序員分配釋放,若程序員不釋放,程序結束時由操做系統回收。在C++中主要利用new在堆區開闢內存。

  例:int *p = new int(10);

1.3 new操做符

  C++中利用new操做符在堆區開闢數據。

  堆區開闢的數據,由程序員手動開闢,手動釋放,釋放利用操做符delete。

  語法: new 數據類型

  利用new建立的數據,會返回該數據對應的類型的指針。

  例:int * p = new int(10); int * arr = new int[10];

  釋放堆區數組(要加中括號[]):delete[] arr;

2. 引用

2.1 引用的基本使用

  做用:給變量起別名。

  語法:數據類型 &別名 = 原名

  例:int a = 10; int &b=a;

2.2 引用注意事項

  • 引用必須初始化
  • 引用在初始化後,不能夠改變

2.3 引用作函數參數

  做用:函數傳參時,能夠利用引用的技術讓形參修飾實參。

  優勢:能夠簡化指針修改實參。

  值傳遞:形參不會修飾實參。

  地址傳遞:形參會修飾實參。

  引用傳遞:形參會修飾實參的。

  總結:經過引用參數產生的效果同按地址傳遞是同樣的。引用的語法更清楚簡單。

2.4 引用作函數返回值

  做用:引用是能夠做爲函數的返回值存在的。

  注意:不要返回局部變量引用

  用法:函數調用做爲左值。(test02())

2.5 引用的本質

  本質:引用的本質在c++內部實現是一個指針常量(指針的指向不能夠改,指針的值能夠改)。

  結論:C++推薦使用引用技術,由於語法方便,引用本質是指針常量,可是全部的指針操做編譯器都幫咱們作了。

2.6 常量引用

  做用:常量引用主要用來修飾形參,防止誤操做。

  在函數形參列表中,能夠加const修飾形參,防止形參改變實參。

  // 加上const以後,編譯器將代碼修改成 int temp = 10; const int & ref = temp;

  const int & ref = 10; // 引用必須引一塊合法的內存空間。

 3. 函數提升

3.1 函數默認參數

  在C++中,函數的形參列表中的形參是能夠有默認值的。

  語法:返回值類型 函數名 (參數=默認值){ }

  注:(1)若是某個位置已經有了默認參數,那麼從這個位置日後,從左到右都必須有默認值。

    (2)若是函數聲明有默認參數,函數實現就不能有默認參數。(聲明和實現只能有一個默認參數)

3.2 函數佔位參數

  C++中函數的形參列表裏能夠有佔位參數,用來作佔位,調用函數時必須填補該位置。

  語法:返回值類型 函數名 (數據類型){ }

  佔位參數還能夠有默認參數。

3.3 函數重載

3.3.1 函數重載概述

  做用:函數名能夠相同,提升複用性。

  函數重載知足條件:(1)同一個做用域下(全局做用域,main函數之外);

           (2)函數名稱相同;

           (3)函數參數類型不一樣或者個數不一樣或者順序不一樣。

  注:函數的返回值不能夠做爲函數重載的條件。

3.3.2 函數重載注意事項

  引用做爲重載條件

  函數重載碰到函數默認參數,出現二義性,報錯,避免這種狀況。

4. 類和對象

  C++面向對象的三大特性爲:封裝、繼承、多態。

  C++認爲萬事萬物都皆爲對象,對象上有其屬性和行爲。

  例如:

  人能夠做爲對象,屬性有姓名、年齡、身高、體重...,行爲有走、跑、跳、吃飯、唱歌...

  車也能夠做爲對象,屬性有輪胎、方向盤、車燈...,行爲有載人、放音樂、放空調...

  具備相同性質的對象,咱們能夠抽象爲類,人屬於人類,車屬於車類

4.1 封裝

  封裝是C++面向對象三大特性之一。

  封裝的意義:(1)將屬性和行爲做爲一個總體,表現生活中的事物。

        (2)將屬性和行爲加以權限控制。

  封裝意義一:

    在設計類的時候,屬性和行爲寫在一塊兒,表現事物。

  語法: class 類名{ 訪問權限: 屬性/ 行爲 };

  類中的屬性和行爲統稱爲成員,屬性(成員屬性、成員變量),行爲(成員函數、成員方法)

  封裝意義二:

    類在設計時,能夠把屬性和行爲放在不一樣的權限下,加以控制

    訪問權限有三種:

      1. public 公共權限:     成員 類內能夠訪問,類外能夠訪問;

      2. protected 保護權限:成員 類內能夠訪問,類外不能夠訪問 子類也能夠訪問父類中的保護內容;

      3. private 私有權限        成員 類內能夠訪問,類外不能夠訪問 子類不能夠訪問父類中的私有內容。

4.1.2 struct和class區別

  在C++中struct和class惟一的區別就在於 默認的訪問權限不一樣。 

  區別:struct默認權限爲公共, class默認權限爲私有。

4.1.3 成員屬性設置爲私有

  優勢1:將全部成員屬性設置爲私有,能夠本身控制讀寫權限。

  優勢2:對於寫權限,咱們能夠檢測數據的有效性。

 4.2 對象的初始化和清理

  生活中咱們買的電子產品都基本會有出廠設置,在某一天咱們不一樣時候也會刪除一些本身信息數據保證安全。

  C++中的面向對象來源於生活,每一個對象也都會有初始設置以及對象銷燬前的清理數據的設置。

4.2.1 構造函數和析構函數

  對象的初始化清理也是兩個很是重要的安全問題。

  一個對象或者變量沒有初始狀態,對其使用後果是未知。

  一樣的使用完一個對象或變量,沒有及時清理,也會形成必定的安全問題。

  C++利用了構造函數和析構函數例解決上述問題,這兩個函數將會被編譯器自動調用,完成對象初始化和清理工做。

  對象的初始化和清理工做是編譯器強制要咱們作的事情,所以若是咱們不提供構造和析構,編譯器會提供編譯器提供的構造函數和析構函數是空實現。

  • 構造函數:主要做用在於建立對象時爲對象的成員屬性賦值,構造函數由編譯器自動調用,無需手動調用。(進行初始化操做)
  • 析構函數:主要做用在於對象銷燬前系統自動調用,執行一些清理工做。(進行清理的操做)  

  構造函數語法:類名(){}

  1. 構造函數,沒有返回值也不寫void

  2. 函數名稱與類名相同

  3. 構造函數能夠有參數,所以能夠發生重載

  4. 程序在調用對象時候會自動調用構造,無需手動調用,並且只會調用一次。

 

  析構函數語法:~類名(){}

  1. 析構函數,沒有返回值也不寫void

  2. 函數名稱與類名相同,在名稱前加上符號~

  3. 析構函數不能夠有參數,所以不能夠發生重載

  4. 程序在對象銷燬前會自動調用析構,無需手動調用,並且只會調用一次

4.2.2 構造函數的分類和調用

  兩種分類方式:

    按參數分爲:有參構造和無參構造

    按類型分爲:普通構造和拷貝構造

  三種調用方式:

    括號法、顯示法、隱式轉換法

    注:調用默認構造函數時候,不要加(),如Person p1(),編譯器會認爲是一個函數的聲明,不會認爲在建立對象。

      Person(10) 匿名對象,特色:當前行執行結束後,系統會當即回收掉匿名對象。

      不要利用拷貝構造函數來初始化匿名對象。

4.2.3 拷貝構造函數調用時機

   C++中拷貝構造函數調用時機一般有三種狀況

  • 使用一個已經建立完畢的對象來初始化一個新對象。
  • 值傳遞的方式給函數參數傳值。
  • 以值方式返回局部對象。

4.2.4 構造函數調用規則

  默認狀況下,c++編譯器至少給一個類添加3個函數

  1. 默認構造函數(無參,函數體爲空)

  2. 默認析構函數(無參,函數體爲空)

  3. 默認拷貝構造函數,對屬性進行值拷貝

 

  構造函數調用規則以下:

  • 若是用戶定義有參構造函數,c++再也不提供默認無參構造,可是會提供默認拷貝構造
  • 若是用戶定義拷貝構造函數,c++不會再提供其餘構造函數

4.2.5 深拷貝和淺拷貝

  淺拷貝:簡單的賦值拷貝操做

  深拷貝:在堆區從新申請空間,進行拷貝操做

  注:淺拷貝帶來的問題就是堆區的內存重複釋放。淺拷貝的問題要利用深拷貝進行解決。(若是屬性有在堆區開闢的,要本身提供拷貝構造函數)

4.2.6 初始化列表

  做用:C++提供了初始化列表語法,用來初始化屬性。

  語法:構造函數():屬性1(值1),屬性2(值2)...{ }

  例:Person(int a, int b, int c): m_A(a), m_B(b), m_C(c) {}

4.2.7 類對象做爲類成員

  C++類中的成員能夠是另外一個類的對象,咱們稱該成員爲對象成員。

  例:

class A {}
class B
{
    A a;    
}

  B中有對象A做爲成員,A爲對象成員。

  那麼當建立B對象時,A與B的構造和析構的順序是誰先誰後?

  (當其餘類對象做爲本類成員,構造時候先構造類對象,再構造自身;析構的順序與構造相反)

4.2.8 靜態成員

  靜態成員就是在成員變量和成員函數前加上關鍵字static,稱爲靜態成員

  靜態成員分爲:

    靜態成員變量--- 全部對象共享同一份數據

           在編譯階段分配內存

                         類內聲明,類外初始化

    靜態成員函數--- 全部對象共享同一個函數

           靜態成員函數只能訪問靜態成員變量

4.3 C++對象模型和this指針

4.3.1 成員變量和成員函數分開存儲

  在C++中,類內的成員變量和成員函數分開存儲

  只有非靜態成員變量才屬於類的對象上

  空對象佔用內存空間爲1個字節,C++編譯器會給每一個空對象也分配一個字節空間,是爲了區分空對象佔內存的位置。 

  每一個空對象也應該有一個獨一無二的內存地址。

  非靜態成員變量屬於類的對象上,靜態成員變量不屬於類對象上,非靜態成員函數不屬於類對象上,靜態成員函數不屬於類的對象上。

4.3.2 this指針概念

  經過4.3.1咱們知道在C++中成員變量和成員函數是分開存儲的

  每個非靜態成員函數只會誕生一份函數實例,也就是說多個同類型的對象會功用一塊代碼

  那麼問題是:這一塊代碼是如何區分那個對象調用本身的呢?

  c++經過提供特殊的對象指針,this指針,解決上述問題,this指針指向被調用的成員函數所屬的對象。

  this指針是隱含每個非靜態成員函數內的一種指針。

  this指針不須要定義,直接使用便可。

  this指針的用途:

    當形參和成員變量同名時,可用this指針來區分。

    在類的非靜態成員函數中返回對象自己,可以使用return *this

4.3.3 空指針訪問成員函數

  C++中空指針也是能夠調用成員函數的,可是也要注意有沒有用到this指針。

  若是用到this指針,須要加以判斷保證代碼的健壯性。

4.3.4 const修飾成員函數

  常函數:

  • 成員函數後加const後咱們稱這個函數爲常函數。
  • 常函數內不能夠修改爲員屬性。
  • 成員屬性聲明時加關鍵字mutable後,在常函數中依然能夠修改。

  常對象:

  • 聲明對象前加const稱該對象爲常對象。
  • 常對象只能調用常函數。

  this指針的本質是指針常量,指針的指向是不能夠修改的。

  在成員函數後面加const,修飾的是this指針,讓指針指向的值也不能夠修改。

  特殊變量,即便在常函數中,也能夠修改這個值,加關鍵字mutable

4.4 友元

  在程序裏,有些私有屬性也想讓類外特殊的一些函數或者類進行訪問,就須要用到友元的技術。

  友元的目的就是讓一個函數或者類訪問另外一個類中私有成員。

  友元的關鍵字爲friend.

  友元的三種實現:全局函數作友元、類作友元、成員函數作友元

4.5 運算符重載

  運算符重載概念:對已有的運算符從新進行定義,賦予其另外一種功能,以適應不一樣的數據類型。

4.5.1 加號運算符重載

  做用:實現兩個自定義數據類型相加的運算。

  (1)成員函數重載+號

  (2)全局函數重載+號

  總結:對於內置的數據類型的表達式的運算符是不可能改變的;不要濫用運算符重載。

4.5.2 左移運算符重載

  做用:能夠輸出自定義數據類型。

  總結:重載左移運算符配合友元能夠實現輸出自定義數據類型。

4.5.3 遞增運算符重載

  做用:經過重載遞增運算符,實現本身的整型數據。

  總結:前置遞增返回引用,後置遞增返回值。

4.5.4 賦值運算符重載

  C++編譯器至少給一個類添加4個函數

  1. 默認構造函數(無參,函數體爲空)

  2. 默認析構函數(無參,函數體爲空)

  3. 默認拷貝構造函數,對屬性進行值拷貝

  4. 賦值運算符operator=,對屬性進行值拷貝

  若是類中有屬性指向堆區,作賦值操做時也會出現深淺拷貝問題。

4.5.5 關係運算符重載

  做用:重載關係運算符,可讓兩個自定義類型對象進行對比操做。

4.5.6 函數調用運算符重載

  函數調用運算符()也能夠重載

  因爲重載後使用的方式很是像函數的調用,所以成爲仿函數

  仿函數沒有固定寫法,很是靈活。

4.6 繼承

  繼承是面向對象三大特性之一

  有些類與類之間存在特殊的關係,下級別的成員除了擁有上一級的共性,還有本身的特性。

  繼承的好處:減小重複代碼。

  語法: class 子類:繼承方式 父類 例: class A: public B;

  子類也成爲派生類,父類也成爲基類。

  派生類中的成員,包含兩大部分:

  一類是從基類繼承過來的,一類是本身增長的成員。

  從基類繼承過來的表現其共性,而新增的成員體現了其個性。

4.6.2 繼承方式

  繼承方式一共有三種:公共繼承、保護繼承、私有繼承

  父類中的公共權限成員到子類中依然是公共權限;

  父類中的保護權限成員到子類中依然是保護權限;

  父類中的私有權限成員子類訪問不到。

4.6.3 繼承中的對象模型

  問題:從父類繼承過來的成員,哪些屬於子類對象中?

  父類中全部非靜態成員屬性都會被子類繼承下去。

  父類中私有成員屬性,是被編譯器給隱藏了,所以是訪問不到,可是確實被繼承下去了。

  利用開發人員命令提示工具查看對象模型:

  • 跳轉盤符 cd 具體路徑下
  • 查看命名
  • cl /d1 reportSingleClassLayout類名 文件名

  結論:父類中私有成員也是被子類繼承下去了,只是由編譯器給隱藏最後訪問不到。

4.6.4 繼承中構造和析構順序

  子類繼承父類後,當建立子類對象,也會調用父類的構造函數。

  問題:父類和子類的構造和析構順序是誰先誰後?

  先構造父類,再構造子類。析構的順序與構造的順序相反。

4.6.5 繼承同名成員處理方式

  問題:當子類與父類出現同名的成員,如何經過子類對象,訪問到子類或父類中同名的數據呢?

  • 訪問子類同名成員 直接訪問便可
  • 訪問父類同名成員 須要加做用域

  若是子類中出現和父類同名的成員函數,子類的同名成員會隱藏掉父類中全部同名成員函數。若是想訪問到父類中被隱藏的同名成員函數,須要加做用域。

  總結:

  1. 子類對象能夠直接訪問到子類中同名成員

  2. 子類對象加做用域能夠訪問到父類同名成員

  3. 當子類與父類擁有同名的成員函數,子類會隱藏父類中同名成員函數,加做用域能夠訪問到父類中同名函數。

4.6.6 繼承同名靜態成員處理方式

  問題:繼承中同名的靜態成員在子類對象上如何進行訪問?

  靜態成員和非靜態成員出現同名,處理方式一致。

  • 訪問子類同名成員 直接訪問便可
  • 訪問父類同名成員 須要加做用域

  例: Son::Base::m_A 第一個::表明經過類名方式訪問 第二個::表明訪問父類做用域下

  子類出現和父類同名靜態成員函數,也會隱藏父類中全部同名成員函數

  若是想訪問父類中被隱藏同名成員,須要加做用域。

  總結:同名靜態成員處理方式和非靜態處理方式同樣,只不過有兩種訪問的方式(經過對象和經過類名)

4.6.7 多繼承語法

  C++容許一個類繼承多個類

  語法:class 子類:繼承方式 父類1, 繼承方式 父類2

  多繼承可能會引起父類中有同名成員出現,須要加做用域區分

  C++實際開發中不建議用多繼承

4.6.8 菱形繼承

  菱形繼承概念:

    兩個派生類繼承同一個基類

    又有某個類同時繼承這兩個派生類

    這種繼承被稱爲菱形繼承,或者鑽石繼承

  利用虛繼承能夠解決菱形繼承的問題,繼承以前加上關鍵字 virtual變爲虛繼承

  vbptr(虛基類指針)

4.7 多態

 4.7.1 多態的基本概念

  多態是C++面向對象三大特性之一。

  多態分爲兩類:

  • 靜態多態:函數重載和運算符重載屬於靜態多態,複用函數名。
  • 動態多態:派生類和虛函數實現運行時多態。

  靜態多態和動態多態的區別:

  • 靜態多態的函數地址早綁定 - 編譯階段肯定函數地址
  • 動態多態的函數地址晚綁定 - 運行階段肯定函數地址

  動態多態知足條件:

  1. 有繼承關係;

  2. 子類重寫父類的虛函數。

  動態多態使用:

  父類的指針或者引用指向子類對象。

  vfptr (v - virtual; f - function; ptr - pointer)虛函數指針

  vftable (虛函數表)

  當子類重寫父類的虛函數,子類中的虛函數表內部會替換成子類的虛函數地址。

  當父類的指針或者引用指向子類對象時候,發生多態。

4.7.2 多態案例

  多態的優勢:

  代碼組織結構清晰

  可讀性強

  利於前期和後期的擴展以及維護

  總結:C++開發提倡利用多態設計程序架構,由於多態優勢不少。

4.7.3 純虛函數和抽象類

  在多態中,一般父類中虛函數的實現是毫無心義的,主要都是調用子類重寫的內容。所以能夠將虛函數改成純虛函數。

  純虛函數語法:virtual 返回值類型 函數名 (參數列表) = 0;

  當類中有了純虛函數,這個類也成爲抽象類。

  抽象類特色:

  • 沒法實例化對象
  • 子類必須重寫抽象類中的純虛函數,不然也屬於抽象類。

4.7.4 虛析構和純虛析構

  多態使用時,若是子類中有屬性開闢到堆區,那麼父類指針在釋放時沒法調用到子類的析構代碼。

  解決方式:將父類中的析構函數改成虛析構或者純虛析構。

  虛析構和純虛析構共性:

  • 能夠解決父類指針地方子類對象
  • 都須要有具體的函數實現

  虛析構和純虛析構區別:

  • 若是是純虛析構,該類屬於抽象類,沒法實例化對象。

  虛析構語法: virtual ~類名(){ }

  純虛析構語法:virtual ~類名()=0;

         類名::~類名(){}

  總結:

  1. 虛析構或純虛析構就是用來解決父類指針釋放子類對象;

  2. 若是子類中沒有堆區數據,能夠不寫爲虛析構或純虛析構;

  3. 擁有純虛析構函數的類也屬於抽象類。

5. 文件操做

  程序運行時產生的數據都屬於臨時數據,程序一旦運行結束都會被釋放。

  經過文件能夠將數據持久化

  C++中對文件操做須要包含頭文件<fstream>

  文件類型分爲兩種:

  1. 文本文件 - 文件以文本的ASCII碼形式存儲在計算機中。

  2. 二進制文件 - 文件以文本的二進制形式存儲在計算機中,用戶通常不能直接讀懂它們。

  操做文件的三大類:

  1. ofstream:寫操做

  2. ifstream:讀操做

  3. fstream:讀寫操做

5.1 文本文件

5.1.1 寫文件

  寫文件步驟以下:

  1. 包含頭文件 #include<fstream>

  2. 建立流對象 ofstream ofs;

  3. 打開文件 ofs.open("文件路徑",打開方式);

  4. 寫數據 ofs<<「寫入的數據」;

  5. 關閉文件 ofs.close();

  文件打開方式:

打開方式

解釋

ios::in

爲讀文件而打開文件

ios::out

爲寫文件而打開文件

ios::ate

初始位置:文件尾

ios::app

追加方式寫文件

ios::trunc

若是文件存在先刪除,再建立

ios::binary

二進制方式

  注:文件打開方式能夠配合使用,利用|操做符。例:用二進制方式寫文件 ios::binary | ios::out

  總結:

  • 文件操做必須包含頭文件fstream
  • 讀文件能夠利用ofstream,或者fstream類
  • 打開文件時候須要指定操做文件的路徑以及打開方式
  • 利用<<能夠向文件中寫數據
  • 操做完畢,要關閉文件。

5.1.2 讀文件

  讀文件與寫文件步驟類似,可是讀取方式相對比較多。

  讀文件步驟以下:

  1. 包含頭文件 #include<fstream>

  2. 建立流對象 ifstream ifs;

  3. 打開文件並判斷文件是否打開成功 ifs.open("文件路徑",打開方式);

    if(!ifs.is_open())

  4. 讀數據 四種方式讀取;

// 第一種
char buf[1024] = {0};
while (ifs >> buf)
{
    cout << buf << endl;
}

//第二種
char buf[1024] = {0};
while (ifs.getline(buf, sizeof(buf)))
{
    cout << buf << endl;   
}

//第三種
string buf;
while (getline(ifs, buf))
{
    cout << buf << endl;   
}

//第四種
char c;
while ( (c = ifs.get()) != EOF ) // EOF end of file
{
    cout << c;   
}

  5. 關閉文件 ifs.close();

  總結:

  • 讀文件能夠利用ifstream,或者fstream類
  • 利用is_open函數能夠判斷文件是否打開成功
  • close關閉文件

5.2 二進制文件

  以二進制的方式對文件進行讀寫操做

  打開方式要指定爲ios::binary

5.2.1 寫文件

  二進制方式寫文件主要利用流對象調用成員函數write

  函數原型: ostream& write(const char * buffer, int len);

  參數解釋:字符指針buffer指向內存中一段存儲空間。len是讀寫的字節數。

5.2.2 讀文件

  二進制方式讀文件主要利用劉對象調用成員函數read

  函數原型: istream& read(char *buffer, int len);

  參數解釋:字符指針buffer指向內存中一段存儲空間。len是讀寫的字節數。

相關文章
相關標籤/搜索