C++核心編程(面向對象編程)ios
一、內存分區模型c++
C++程序在執行時,將內存大方向劃分爲4個區域:程序員
1.1 程序運行前編程
在程序編譯後,生成了exe可執行程序,未執行該程序前分爲兩個區域數組
代碼區:安全
存放CPU執行的機器指令。架構
代碼區是共享的,共享的目的是對於頻繁被執行的程序,只須要在內存中有一份代碼便可。app
代碼區是只讀的,使其只讀的緣由是防止程序意外地修改了它的指令。函數
全局區:工具
全局變量和靜態變量(static關鍵字)存放在此。
全局區還包含了常量區,字符串常量和其餘常量(const修飾的全局變量:全局常量)也存放在此。
該區域的數據在程序結束後由操做系統釋放。
注:局部變量、const修改的局部變量(局部常量)不在全局區中,
全局變量:函數體外的變量。
局部變量:函數體內的變量。
靜態變量:在普通變量前面加static,屬於靜態變量。
常量:字符串常量((int)&"hello world")、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. 默認拷貝構造函數,對屬性進行值拷貝
構造函數調用規則以下:
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修飾成員函數
常函數:
常對象:
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 繼承中的對象模型
問題:從父類繼承過來的成員,哪些屬於子類對象中?
父類中全部非靜態成員屬性都會被子類繼承下去。
父類中私有成員屬性,是被編譯器給隱藏了,所以是訪問不到,可是確實被繼承下去了。
利用開發人員命令提示工具查看對象模型:
結論:父類中私有成員也是被子類繼承下去了,只是由編譯器給隱藏最後訪問不到。
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
總結:
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();
總結:
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是讀寫的字節數。