?內存模型
C++在執(zhí)行程序的時候,將內存方向劃分為4個區(qū)域:
-
代碼區(qū):存放二進制代碼,由操作系統(tǒng)進行管理
-
全局區(qū):存放全局變量、靜態(tài)變量、常量,程序結束后由操作系統(tǒng)釋放
-
棧區(qū):存放函數(shù)參數(shù)、局部變量,由編譯器自動分配和釋放
-
堆區(qū):由開發(fā)者申請分配和釋放,若程序員不釋放,程序結束由操作系統(tǒng)自動回收
意義:對于不同區(qū)域存放的數(shù)據,賦予不同的生命周期,給編程更大的靈活性。
代碼區(qū)
存放CPU執(zhí)行的二進制代碼(機器指令)
特點:
-
共享:對于頻繁被執(zhí)行的程序,只需要在內存中有一份就夠了
-
只讀:防止被意外修改
全局區(qū)
-
存放全局變量和靜態(tài)變量,還存放常量,包括字符串常量和其他常量
-
數(shù)據在程序結束后由操作系統(tǒng)進行釋放
棧區(qū)
-
存放函數(shù)參數(shù)、局部變量
-
不要返回局部變量的地址,因為函數(shù)一執(zhí)行完,棧區(qū)數(shù)據就被釋放了,雖然編譯器會做短暫的保留
堆區(qū)
-
這是由開發(fā)者分配和釋放的,如果程序結束開發(fā)者不釋放,也會操作系統(tǒng)回收
-
在C++中主要用new開辟堆區(qū)空間,用delete釋放
new 和 delete
new作用:用于讓開發(fā)者在堆區(qū)中開辟數(shù)據
delete作用:讓開發(fā)者手動釋放堆區(qū)數(shù)據
語法:
new 數(shù)據類型
delete 堆區(qū)地址
示例:
int *p = new int(8); //在堆區(qū)開辟一個int類型的內存,存放數(shù)據8
int *p = new int[10]; //在堆區(qū)開辟一個int類型的內存存放數(shù)組,數(shù)組中有10個元素
delete(p); //釋放地址a的數(shù)據
引用
作用:給變量起別名
語法:
數(shù)據類型 &別名 = 原名
注意事項:
-
引用必須初始化
-
初始化后,就不可以再發(fā)生改變了
-
引用必須引一塊合法的內存空間,可以是棧區(qū),可以是堆區(qū),但不可以是自變量(比如數(shù)字)
示例:
int a = 10;
int &b = a; //引用,且必須初始化
int &b = 10; //×,錯誤,引用必須引一塊合法的內存,10是自變量,既不是棧區(qū)也不是堆區(qū)
const int &b = 10; //√,正確,加上const后,編譯器會開辟出一塊臨時內存,int temp = 10,const int &b = temp;
引用做函數(shù)參數(shù)
作用:可以讓形參修飾實參,代替指針中形參修改實參的操作
示例:
void myswap(int &x, int &y) //引用就是取別名,所以參數(shù)就是實參,所以可以改變實參
{
int temp = x;
x = y;
y = temp;
}
int main
{
int a = 10;
int b = 20;
myswap(a , b); //引用傳遞,形參可以修改實參
system("pause");
}
引用做函數(shù)返回值
作用:可以作為函數(shù)返回值類型返回
注意事項:不要返回局部變量的引用,函數(shù)執(zhí)行完局部變量內存就被釋放了,返回個錘子
示例:
int& test() //函數(shù)返回值類型就是引用類型
{
static int a = 10; //加個關鍵字static,這樣變量a就不是關鍵字了
return a;
}
int main()
{
int &ref = test();
}
引用的本質
本質:引用的本質在C++內部實現(xiàn),它就是一個指針常量,由編譯器內部轉換
作用:也就說明為什么引用初始化之后就不可更改,因為指針指向不可改
& <—等于—> int* const
示例:
int& ref = a; <==> int* const ref = &a
ref = 20; <==> *ref = 20
常量引用
作用:主要用來修飾形參,防止誤操作
用法:在函數(shù)形參列表中,加const修飾形參,防止形參改變實參
示例:
void temp(const int& val)
{
}
函數(shù)進階用法
函數(shù)的默認參數(shù)
在C++中,函數(shù)的形參列表中形參是可以用默認參數(shù)的
語法:
返回值類型 函數(shù)名 (參數(shù) = 默認值)
{
}
注意事項:
-
如果函數(shù)某個參數(shù)有默認值,那么從這個位置之后的參數(shù)必須有默認值
-
如果調用的時候有實參,那就用實參,沒有實參,就用默認值
-
如果函數(shù)聲明有默認值,那么在函數(shù)定義的時候就不能有默認值
示例:
int func1(int a, int b=10, int c=20) //往后如果還有參數(shù),必須要有默認值
int func2(int a=10, int b=20) //函數(shù)聲明有默認值了
int func2(int a, int b) //函數(shù)實現(xiàn)就不能有默認值了
{
}
函數(shù)的占位參數(shù)
作用:用來給函數(shù)的參數(shù)列表中做占位,調用函數(shù)的時候填補該位置就行了
語法:
返回值類型 函數(shù)名(數(shù)據類型)
{
}
缺點:現(xiàn)階段函數(shù)的占位函數(shù)存在意義不大。
示例:
void func(int a, int) //int 就是占位參數(shù)了,只需要寫一個數(shù)據類型即可
{
}
int main
{
func(10,20); //調用的時候占位函數(shù)要補上
}
函數(shù)重載
作用:函數(shù)名相同,其他的可以不同,可以提高函數(shù)的復用性
滿足條件:
-
同一個作用域下
-
函數(shù)名稱相同
-
函數(shù)參數(shù)類型不同,或者個數(shù)不同,或者順序不同
注意事項:
-
函數(shù)的返回值不能作為函數(shù)重載的滿足條件
-
具體調用的是哪一個函數(shù),就看參數(shù),看實參是否對應形參,比如類型、個數(shù)、順序
示例:
void func() //func是函數(shù)重載,這是在全局作用域下
{
}
void func(int a) //參數(shù)類型不同,這是在全局作用域下
{
}
void func(double a,double b) //參數(shù)個數(shù)不同,這是在全局作用域下
{
}
void func(double a, int b) //參數(shù)順序不同,這是在全局作用域下
{
}
引用作為函數(shù)重載
當引用作為函數(shù)參數(shù)時:
-
實參必須是一塊合法的內存
-
如果實參不是內存,只是一個自變量,那么形參就必須加const來修飾
示例:
void func(int &a)
{
}
void func(const int &a) //這兩個func是函數(shù)重載
{
}
int main()
{
int a = 10;
func(a); //調用的是第一個func函數(shù),因為a是變量,是一塊合法內存
func(10); //調用的是第二個func函數(shù),因為10是自變量,加const修飾本質上是申請一塊臨時內存存放數(shù)據10
}
遇到默認參數(shù)
-
當函數(shù)重載遇到默認參數(shù)時,會出現(xiàn)二義性,也就是出錯
-
使用時盡量避免出現(xiàn)默認參數(shù)
示例:
void func(int a)
{
}
void func(int a, int b = 10)
{
}
int main()
{
func(10); //?,編譯器懵了,不知道該調用哪一個func
}
類與對象
-
C++本身就是面向對象的編程語言
-
面向對象三大特性:封裝、繼承、多態(tài)
-
C++中萬物皆可為對象,對象上有屬性和行為
-
具有相同性質的對象,稱之為類
示例:
人可以作為對象,屬性有姓名、年齡、身高···,行為有唱,跳、rap···
你和你的死黨,是同一性質,屬于人類;
車可以作為對象,屬性有輪胎、車燈、方向盤···,行為有載人、音樂、顯擺···
五菱與奧迪,是同一性質,屬于車類;
封裝
封裝的意義
封裝是C++面向對象三大特性之一
封裝的意義:
-
將屬性和行為作為一個整體,來表現(xiàn)生活中的事物
-
將屬性和行為用權限加以控制
封裝的術語:
-
類中的屬性和行為,統(tǒng)稱為成員
-
屬性也叫成員屬性或者成員變量
-
行為也叫成員函數(shù)或者成員方法
封裝意義一:將屬性和行為作為一個整體
語法:
class 類名{ 訪問權限:屬性/行為 }
示例:創(chuàng)建一個類為圓,那半徑就是它的屬性了。
class Circle
{
public: //設置訪問權限,公共權限
double m_r; //屬性——半徑,
double calculate() //行為——計算周長
{
return 2 * PI * m_r;
}
}
int main()
{
Circle C1; //通過一個類創(chuàng)建一個對象,對象就是圓,也就是實例化
C1.m_r = 10;
cout << "圓的周長:" << C1.calculate() << endl;
}
封裝意義二:將屬性和行為用訪問權限來加以管理
訪問權限有三種:
-
public:公共權限,成員類內和類外都可以訪問
-
protected:保護權限,成員類內可以訪問,,類外不可以訪問
-
private:私有權限,成員類內可以訪問,類外不可以訪問
protecred保護權限和private私有權限的區(qū)別在于后面要說到的繼承上,前者子類可以訪問父類,后者子類不可訪問父類,這是后面的內容了。
示例:
class person
{
public:
string m_name; //姓名,類內類外都可以訪問
protected:
string m_car; //汽車,類內可以訪問,類外不可以,家里的汽車只有家里人能用,外人不可以
private:
int m_password; //銀行卡密碼,類內極度私密,只有當事人能用,其他任何人甚至兒子也不能用
public:
void func()
{
m_name = "張三"; //類內可以訪問
m_car = "大眾"; //類內可以訪問
m_password = 123456; //類內可以訪問
}
}
int main()
{
person c1;
c1.m_name = "李四"; //√,類外可以訪問
c1.m_car = "吉利"; //×,類外不可以訪問
c1.m_password = 456789; //×,類外不可以訪問
c1.func(); //√,類外是可以訪問的
}
struct和class
struct和class都可以表示一個類,區(qū)別在于兩者默認的權限不同:
-
struct:默認權限為公共
-
class:默認權限為私有
成員屬性設置為私有
優(yōu)點:
-
所有成員設置成私有,自己可以控制讀寫權限
-
對于寫權限,可以檢查其數(shù)據的有效性
示例:所有成員設置成私有,自己可以控制讀寫權限
class person
{
private:
string m_name; //成員設置成私有權限,一般通過成員函數(shù)進行訪問
int m_age;
string m_lover;
public:
void SetName(string name) //這樣name就被設置成了可讀可寫的
{
m_name = name;
}
int getage() //age就被設置成了只讀
{
m_age = 18;
return m_age;
}
}
int main()
{
person c1;
c1.m_name = "張三"; //×,因為成員是私有權限,沒法訪問
c1.SetName("張三"); //√,設置姓名
cout << c1.getage() << endl; //√,獲取年齡
}
示例:對于寫權限,可以檢查其數(shù)據的有效性
class person
{
private:
string m_name;
int age;
public:
int GetAge(int age) //獲取年齡,設置成可讀可寫,如果想修改年齡,范圍必須是0—100
{
if( age < 0 || age >100 ) //加了判斷,可以判斷數(shù)據是否有效
{
cout << "年齡錯了"<< endl;
return ;
}
m_age = 0;
}
}
對象的初始化和清理
C++中,初始化和清理是非常重要的安全問題:
-
構造函數(shù):在創(chuàng)建對象時為對象成員屬性初始化,該函數(shù)由編譯器自動調用
-
析構函數(shù):在對象銷毀前系統(tǒng)自動調用,執(zhí)行一些清理工作
完成對象的初始化和清理工作是編譯器必須要我們做的事情,這兩個函數(shù)由編譯器自動調用,但是如果我們不寫構造函數(shù)和析構函數(shù),編譯器就會自己去實現(xiàn),只不過函數(shù)里面是空的,簡稱空實現(xiàn)
構造函數(shù)
語法:
類名(){}
特點:
-
不用寫void,沒有返回值
-
函數(shù)名與類名相同
-
可以有參數(shù),因為可以發(fā)生函數(shù)重載
-
在調用對象時編譯器自動調用,而且只會調用一次
析構函數(shù)
語法:
~類名(){}
特點:
-
不用寫void,沒有返回值
-
函數(shù)名與類名相同,且在前面加~
-
不可以有參數(shù),因此不可以發(fā)生函數(shù)重載
-
在對象銷毀前編譯器自動調用,而且只調用一次
示例:
class Person
{
public:
Person() //這就是構造函數(shù),如果不寫,編譯器會自己寫一個
{
}
~Person() //這就是析構函數(shù),如果不寫,編譯器會自己寫一個
{
}
}
構造函數(shù)的分類和調用
兩種分類方式:
-
按參數(shù)分:有參構造和無參構造(默認構造)
-
按類型分:普通構造和拷貝構造
三種調用方式:
-
括號法
-
顯示法
-
隱式轉換法
示例:
class Peoson()
{
public:
Person() //無參構造,也就是普通構造
{
}
Person(int a) //有參構造
{
}
Person(const Person &p) //拷貝構造
{
}
}
int main()
{
//1、括號法調用
Person P1; //普通調用,不用加(),不然編譯器會誤以為是函數(shù)聲明
Person P2(10); //調用的是對應的有參構造
Person P3(P1); //調用的是對用的拷貝構造
//2、顯示法調用
Person P1;
Person P2 = Person(10); //調用的是對應的有參構造
Person P3 = Person(P2); //調用的是對應的拷貝構造
//3、隱式轉換法
Person P1 = 10; //調用的是有參構造,相當于Person P1 = Person(10)
Person P2 = P1; //調用的是拷貝構造,相當于Person P2 = Person(P1)
}
說說拷貝構造函數(shù)
所謂拷貝構造函數(shù)就是將一個創(chuàng)建完畢對象的屬性默認賦值給新的對象,這個賦值過程由編譯器自動完成
通常以下三種情況會調用到拷貝構造:
-
使用一個已經創(chuàng)建完畢的對象來初始化一個新對象
-
值傳遞的方式給函數(shù)參數(shù)傳參
-
以值傳遞的方式返回局部對象
示例:
class Person()
{
Person()
{
}
Person(const Person &p)
{
}
}
void func1(Person p)
{
}
Person func2()
{
Person a;
return a;
}
int main()
{
Person P1;
Person P2(P1); //使用一個已經創(chuàng)建好的對象來初始化一個新對象
Person P3;
func1(P3); //以值傳遞的方式給函數(shù)傳參
Person P4 = func2(); //以值傳遞的方式返回對象,它會創(chuàng)建一個新的對象來接住返回對象
}
構造函數(shù)的調用規(guī)則
(1)默認情況下,創(chuàng)建一個類編譯器至少會添加3個函數(shù)
-
默認構造函數(shù)(無參,函數(shù)體為空)
-
默認析構函數(shù)(無參,函數(shù)體為空)
-
默認拷貝函數(shù),對屬性進行默認拷貝
(2)如果開發(fā)者寫了有參構造函數(shù),編譯器不再默認提供無參構造,但是會提供默認拷貝構造
(3)如果開發(fā)者寫了拷貝構造函數(shù),編譯器不再提供其他構造函數(shù),無參和有參都沒有
淺拷貝和深拷貝
這是經典面試題經常出現(xiàn)的案例,是一個常見的坑
淺拷貝:就是簡單的賦值拷貝
深拷貝:在堆區(qū)重新開辟一片內存,進行拷貝操作
注意事項:
-
如果涉及到空間開辟和釋放,淺拷貝容易發(fā)生內存重復釋放的非法操作
-
需要自己寫一個拷貝構造函數(shù),利用深拷貝去解決淺拷貝帶來的內存釋放問題
class person
{
public:
person(){}
person(int age,int height)
{
m_age = age; //這就是淺拷貝,就是簡單的賦值操作
m_height = new int(height); //這就是深拷貝,在堆區(qū)開辟一個新的內存
}
person(const person &p)
{
m_age = p.m_age;
//m_height = p.m_height //這是編譯器默認實現(xiàn)的,但是我們不要這樣的淺拷貝,會崩
m_height = new int(*p.m_height); //深拷貝,這樣不同的對象就有不同的堆區(qū)地址,釋放就不會發(fā)生重復了
}
~person()
{
if(m_height != NULL)
{
delete m_height; //在構造函數(shù)手動開辟了堆區(qū),就需要手動釋放
m_height NULL;
}
}
private:
int m_age;
int *height;
}
int main()
{
person P1(18,160);
person P2(P1); //將對象P1的屬性通過淺拷貝(拷貝構造函數(shù))copy了一份賦值給對象P2了
//同時淺拷貝的還包括有參構造中開辟出來的堆區(qū)地址
//這時候如果沒有自己寫一個拷貝構造函數(shù)的話,P1和P2就擁有一樣的堆地址
//結束的時候P1和P2釋放就釋放了相同的堆地址,重復釋放屬于非法操作,程序會崩
//所以需要在對象中自己寫一個拷貝函數(shù),創(chuàng)建一個堆區(qū),讓不同對象有不同的堆地址
//這就是利用深拷貝解決淺拷貝帶來的內存釋放問題
}
初始化列表
作用:用來初始化屬性,一般是在構造函數(shù)中初始化
語法:
構造函數(shù)():屬性(初值),屬性(初值),屬性(初值) { }
示例:
classs person
{
public:
/*
person(int a,int b,int c) //這是傳統(tǒng)賦值方式
{
m_A = a;
m_B = b;
m_C = c;
}
*/
/*
person():m_A(10),m_B(20),m_C(30) //初始化列表的方式賦初值
{ //不過這樣還是有點不夠靈活
}
*/
person(int a,int b,int c):m_A(a),m_B(b),m_C(c) //初始化列表的方式賦初值
{ //比上一個靈活一點
} //不過跟傳統(tǒng)方式相比,好處就是逼格高一點而已
private:
int m_A;
int m_B;
int m_C;
}
int main()
{
// person p1(10,20,30); //傳統(tǒng)賦初值的方式
// person P1; //利用初始化列表,創(chuàng)建的同時初始化完成
person P1(30,20,10); //初始化列表賦初值,比上一個靈活一點
//不過感覺跟傳統(tǒng)方式差不多吧,也就逼格高一點
}
類對象作為類成員
類中的成員可以是一個其他類的對象,該成員就是對象成員
注意事項:
-
創(chuàng)建此類對象的時候,先構造對象成員,再構造自身
-
先析構自身,再析構對象成員
示例:
class Phone //手機類
{
public:
Phone(string pName):m_phonename(pName)
{
}
private:
string m_phonename;
}
class Person //人類
{
public:
//第二個參數(shù)相當于:Phone m_phone = pName(隱式轉換法,編譯器隱藏轉換)
Person(string name,string pName):m_name(name),m_phone(pName)
{
}
private:
string m_name;
Phone m_phone; //先創(chuàng)建了Phone類,再有人類
}
int main()
{
Person p("張三","諾基亞"); //在賦值給了人類的同時,也賦值給了手機類
}
靜態(tài)成員
在成員變量和成員函數(shù)前面加一個關鍵字:static,就成了靜態(tài)成員
分類:
靜態(tài)成員變量:
-
所有對象共享一份同一份數(shù)據
-
在編譯階段分配內存
-
類內聲明,類外初始化
靜態(tài)成員函數(shù):
-
所有對象共享同一個函數(shù)
-
靜態(tài)成員函數(shù)只能訪問靜態(tài)成員變量
根據上面三個特點,靜態(tài)成員(變量或者函數(shù))不屬于某一個對象,所以:
-
public權限的既可以通過對象進行訪問,也可以通過類名進行訪問
-
private無論如何類外訪問不了
示例:
class Person
{
public:
int A;
static int m_A; //類內聲明
static void func1()
{
m_A = 50; //只能訪問靜態(tài)變量
//A = 50; //×,靜態(tài)成員函數(shù)只能訪問靜態(tài)成員變量
//因為靜態(tài)函數(shù)只能有一份,而且它在編譯的時候就已經有了
//而非靜態(tài)變量又只能通過類去訪問
//編譯的時候還沒有創(chuàng)建類呢,編譯器懵了,不知道這變量是哪一個類的
}
private:
static int m_B; //類內聲明
static void func2()
{
}
}
int Person::m_A = 100; //類外初始化
int Person::m_B = 200; //類外初始化
int main()
{
Person P1; //此時m_A就是初始化的100
Person P2;
P2.m_A = 200; //靜態(tài)m_A已經被改成了200,往后其他類使用m_A數(shù)值也是200
//===========================================================//
Person P3;
cout << P3.m_A << endl; //既可以通過對象進行訪問
P3.func1();
cout << Person::m_A << endl; //也可以通過類名進行訪問
Person::func1();
//cout << P3.m_B << endl; //×,別想了,private權限的靜態(tài)(變量或者函數(shù))類外訪問不了
//cout << Person::m_B << endl; //×
//cout << P3.func2() <<endl; //×
//cout <<Person::fun2()<<endl; //×
}
C++的對象模型
空對象
-
空對象占用1個字節(jié)的內存空間
-
每個空對象內存地址獨一無二
原因:C++編譯器為了區(qū)分空對象的所占內存的位置
成員變量和成員函數(shù)分開存儲
在C++中,類內的成員變量和成員函數(shù)是分開存儲的:
-
非靜態(tài)成員變量,屬于類的對象上的
-
靜態(tài)成員變量,不屬于類的對象上的,不占對象空間
-
成員函數(shù),不屬于類的對象上的,只產生一份函數(shù)實例,不占對象空間
示例:
class person
{
int m_name; //屬于類的對象上,占4字節(jié)
static int m_name; //靜態(tài)成員變量,不屬于對象上,不占空間
void func(); //成員函數(shù),不屬于對象上,不占空間
}
this指針概念
每一個成員函數(shù)只會誕生一份函數(shù)實例,說明會有多個對象同時調用同一份函數(shù)的情況存在。
用來區(qū)分究竟是哪一個對象調用的函數(shù),用的是this指針。
-
this指針是隱藏在每一個成員函數(shù)內部的一種特供的指針
-
不需要被定義,本來就是,直接使用即可
-
哪個對象調用了函數(shù),其this指針就指向哪個對象
作用:
-
當形參和成員變量同名時,可用this指針來區(qū)分(當然最好還是編程規(guī)范)
-
在返回對象本身時,可以用return *this
示例:
class Person
{
Person(int age)
{
//age = age; //×,因為形參和成員變量同名了,編譯器會誤以為這全是形參,實參就傳不進去了
this->age = age; //√,看主函數(shù)對象P調用了此構造函數(shù),所以this指針指向P對象
}
int age;
Person& Addage(Person &p) //返回的,就是其本身對象
{
this->age += p.age;
return *this;
}
}
int main()
{
Person P1(18);
Person P2(20);
P2.Addage(P1).Addage(P1).Addage(P1); //√,這就是鏈式編程思想
//因為P2.Addage(P1)返回的就是對象P2,自然是可以再調用.Addage(),并且可以無限調用下去
cout << "`···"<< P2.age << endl; //其實這個也是鏈式編程思想
}
空指針訪問成員函數(shù)
C++中空指針是可以訪問成員函數(shù)的,主要是要注意有沒有用到this指針
-
成員函數(shù)中沒有用到this指針,空指針可以調用成員函數(shù);
-
成員函數(shù)中用到了this指針,空指針就不可以調用成員函數(shù)
-
為了保持代碼的健壯性,一般會在函數(shù)中加入判斷 if(this == NULL){ return;}
示例:
Class Person
{
public:
void fun1()
{
cout << "hello" << endl;
}
void fun2()
{
cout << "=" << m_age << endl; //m_age 相當于 this->m_age
}
void fun3()
{
if(this == NULL)
return;
cout << "=" << m_age << endl; //m_age 相當于 this->m_age
}
int m_age;
}
int main()
{
Person *p = NULL; //創(chuàng)建一個空指針
p->fun1(); //√,調用成功,因為函數(shù)中沒有用到this指針
//p->fun2(); //×,調用失敗,因為函數(shù)中用到了this指針,就是 m_age 就相當于 this->m_age
p->func3(); //√,雖然里面用到了this指針,但是函數(shù)中加入了this指針為空的判斷
}
const修飾成員函數(shù)
常函數(shù):
-
成員函數(shù)后面加const修飾,稱之為常函數(shù)
-
不可以修改成員屬性
-
如果成員屬性在聲明時加了關鍵字mutable ,在常函數(shù)中就可以修改
常對象:
-
聲明對象加const修飾,稱之為常對象
-
不可以修改成員屬性
-
常對象只能調用常函數(shù),因為普通函數(shù)可以修改成員屬性
示例:
class Person
{
public:
void func1() const //常函數(shù)
{
//this->m_A ; //×,常函數(shù)不能修改成員屬性
this->m_B; //√,成員屬性加了mutable修飾,所以常函數(shù)可以修改成員屬性
}
void func2()
{
m_A = 100;
}
int m_A;
mutable int m_B;
}
int main()
{
const Person p; //常對象
p.func1(); //對,常對象只能調用常函數(shù)
//p.func2(); //×,常對象不可以調用普通函數(shù)
}
友元
作用:讓一個函數(shù)或者類訪問另一個類中的私有成員
關鍵字:friend
3種實現(xiàn)方式:
-
全局函數(shù)做友元
-
類做友元
-
成員函數(shù)做友元
示例:
class Building
{
friend void Enter(Building *building); //只需要在類最前面加friend聲明,全局函數(shù)就變成了友元函數(shù)
friend class Goodgay; //只需要在類最前面加friend聲明,其他的類就變成了本類的友元類
friend void Visit::visit1(); //只需要在類最前面加friend聲明,成員函數(shù)就變成了本類的友元類,成員函數(shù)前需要加上作用域
public:
Buding()
{
this->m_sittingroom = "客廳";
this->m_bedroom = "臥室";
}
public:
string m_sittingroom;
private:
string m_bedroom;
}
void Enter(Building *building)
{
cout << "=" <<building->m_bedroom << endl; //全局函數(shù)訪問私有成員
}
//========================================================================================================//
class Goodgay
{
Goodgay()
{
building = new Building;
}
void visit0()
{
cout << "=" << building->Bedroom << endl;
}
Building *building;
}
class Visit
{
public:
void visit()
{
building = new Building;
}
void visit1()
{
cout << "=" << building->Bedroom << endl;
}
}
//========================================================================================================//
int main()
{
Building building;
Enter(&building); //全局函數(shù)訪問私有成員
Goodgay goodgay; //類訪問私有成員
googgay.visit0();
Visit visit;
visit.visit1(); //成員函數(shù)訪問私有成員
}
運算符重載
作用:對已經有的運算符重新進行定義,賦予其另外一種功能,適應不同的自定義數(shù)據類型
關鍵字:operator
-
加號運算符重載
-
左移運算符重載
-
遞增運算符重載
-
賦值運算符重載
-
關系運算符重載
-
函數(shù)調用運算符重載
運算符重載的方式有2種:
-
成員函數(shù)重載
-
全局函數(shù)重載
溫馨提示:運算符重載也可發(fā)生函數(shù)重載(函數(shù)名相同,函數(shù)參數(shù)不同)
示例:
Class Person
{
friend ostream& operator<<(ostream &cout, Person &p) //友元函數(shù),不然無法訪問私有成員
public;
Person()
{
m_A = 10;
m_B = 20;
}
//成員函數(shù)重載:
Person operator+(Person &p) //加號運算符重載
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
void operator<<(cout) //左移運算符重載
{
//本質就是p.operator<<(cout),也就是 p << cout
//如果想要cout放在左邊,成員函數(shù)實現(xiàn)不了
//所以一般不用成員函數(shù)重載左移運算符,
}
//返回引用是為了一直對同一個數(shù)據進行遞增操作
Person& operator++() //(前置)遞增運算符重載
{
m_A ++;
return *this;
}
//返回值是因為temp只是一個臨時變量,函數(shù)內用完就被銷毀了
Person operator++(int) //(后置)遞增運算符重載
{
Person temp = *this;
m_A ++;
return temp;
}
Person& operator=(Person &p) //賦值運算符重載
{
if(m_C != NULL)
{
detele m_C;
m_C = NULL;
}
m_C = new int(* p.m_C);
return *this;
}
bool operator==(Person &p) //關系運算符重載之 == 號(其他的關系運算符寫法也是這樣的)
{
if(this->m_A== p.m_A && this->m_B == p.m_B)
return ture;
else
return flase;
}
void operator()(string temp) //函數(shù)調用運算符重載(寫法相當靈活,其對象就是匿名函數(shù)對象)
{
cout << temp << endl;
}
int operator()(int temp1, int temp2) //函數(shù)調用運算符重載(寫法相當靈活,其對象就是匿名函數(shù)對象)
{
return temp1+temp2;
}
private:
int m_A;
int m_B;
int *m_C;
}
//全局函數(shù)重載:
*/
Person operator+(Person &p1,Person &p2) //加號運算符重載
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
ostream& operator<<(ostream &cout, Person &p) //左移運算符重載
{
//本質就是operator<<(cout,p),也就是 cout << p
//這是訪問了私有成員了,該函數(shù)需要變成友元函數(shù)才行
cout << "m_A = " << p.m_A << "m_B = " << m_B;
return cout;
}
/*
int main()
{
Person p1;
Person p2;
Person p3;
p3 = p1 + p2 ; //加號運算符重載,其實本質就是 p3 = p1.operator+(p2)
cout << p3 ; //左移運算符重載,其實本質就是 operator<<(cout,p3)
cout << ++(++p3) << endl; //前置遞增運算符重載
cout << p3++ << endl; //后置遞增運算符重載
p1 = p2; //賦值運算符重載,其實本質就是p2.operator=(p2)
if(p1 == p2) //關系運算符之重載 == 號,其本質就是p1.operator==(p2)
cout << "p1 與 p2相等" << endl;
else
cout << "P1 與 p2不相等" << endl;
p1("hello world"); //函數(shù)調用運算符,其本質就是p1.operator()(參數(shù))
p1(100, 20); //函數(shù)調用運算符,其本質就是p1.operator()(參數(shù))
Person myadd;
Person()(100, 50); //類名()(參數(shù)) 這種形式,該對象就是匿名函數(shù)對象
//特點: 當前行執(zhí)行完了,立即被釋放
}
繼承
-
繼承是面向對象三大特性之一
-
有些類與類之前有些特殊的關系,用的就是繼承技術,減少重復代碼
-
下一級別類除了擁有上一級別類的共性外,還有自己的特性
語法:
class 子類 : 繼承方式 父類
子類,也叫派生類;
父類,也叫基類
示例:
class 類名 :public 類名
{
}
繼承方式
-
公共繼承
-
保護繼承
-
私有繼承
公共繼承:父類私有權限變量不可訪問,公共權限和保護權限變量照常繼承;
保護繼承:父類私有權限變量不可訪問,公共權限和保護權限變量變成自己的保護權限變量;
私有權限:父類私有權限變量不可訪問,公共權限和保護權限變量變成自己的私有權限變量;
示例:
?對象模型
?
-
父類中所有非靜態(tài)成員屬性都會被子類繼承下去
-
父類中的private也會被繼承,雖然子類訪問不到,那是因為編譯器隱藏起來了
-
利用”開發(fā)人員命令提示工具“可查看子類繼承后的對象模型
工具用法:
-
打開VS軟件下的”開發(fā)人員命令提示工具“
-
跳轉到當前子類所在的文件路徑下
-
查看命令:cl /dl reportSingleClassLayout類名 文件名
構造函數(shù)和析構函數(shù)
子類繼承父類中,構造函數(shù)和析構函數(shù)的順序:
-
父類 的 構造
-
子類 的 構造
-
子類 的 析構
-
父親 的 析構
同名成員/函數(shù)處理方式
-
訪問子類同名成員(變量和函數(shù)),直接訪問
-
訪問父類同名成員(變量和函數(shù)),需要加作用域
-
函數(shù)重載也一樣,就算參數(shù)不同也屬于同名函數(shù)
示例:
Class Base
{
pubcli:
func()
{
}
int m_A;
}
Class Son : public Base
{
public:
func()
{
}
func(int a)
{
}
int m_A;
}
int main()
{
Son son;
son.m_A = 100; //訪問的是子類的成員變量
son.Base::m_A = 50; //訪問的是父類中的成員變量
son.func(); //訪問的是子類中的成員函數(shù)
son.Base::func(); //訪問的是父類中的成員函數(shù)
son.Base::func(50); //函數(shù)重載也一樣,雖然參數(shù)不同,但是父類也需要加作用域
}
靜態(tài)同名成員
-
與非靜態(tài)成員處理方式一致(同上)
-
訪問子類同名成員,直接訪問
-
訪問父類同名成員,需要加作用域
多繼承
-
C++中允許一個類繼承多個父類
-
多繼承也會引發(fā)父類中同名成員出現(xiàn),也需要加作用域
-
實際開發(fā)中不建議用多繼承,容易出現(xiàn)太多二義性
語法:
Class 子類 : 繼承方式 父類1 , 繼承方式 父類2
菱形繼承
-
兩個派生類繼承同一個基類
-
同時又有某一個類同時繼承兩個派生類
-
這種繼承就被成為菱形繼承,或者鉆石繼承
-
當出現(xiàn)菱形繼承時,某一個類就擁有了兩份基類的相同數(shù)據,需要用作用域加以區(qū)分
-
但是我們其實只需要一份數(shù)據就夠了,多出來的數(shù)據純屬就是浪費,用虛繼承方式可以解決
-
因為不建議用多繼承方式,所以也不建議寫菱形繼承
-
可以用”開發(fā)人員命令提示工具“查看其對象模型
?
示例:
Class A //父類A
{
public:
int m_age;
}
Class B : public A {}; //派生類B
Class C : public A {}; //派生類C
Class D : public B , public C {}; //子類D,既繼承了B,又繼承了C,而 B 和 C 又繼承于A
int main()
{
Class a;
a.B::m_age = 20; //需要加作用域區(qū)分
a.C::m_age = 100;
}
虛繼承
關鍵字:virtual
-
利用虛繼承可以解決菱形繼承出現(xiàn)的二義性問題
-
在繼承之前加上關鍵詞virtual,就變成了虛繼承
-
虛繼承以最新修改的成員數(shù)據為準
-
多份相同數(shù)據的來源的那個基類,稱之為虛基類
-
因為不建議用多繼承方式,所以也不建議寫菱形繼承
示例:
Class A
{
public:
int m_age;
}
Class B : virtual public A {}; //虛繼承
Class C : virtual public A {}; //虛繼承
Class D : public B , public C {};
int main()
{
Class a;
a.B::m_age = 20; //有了虛繼承,可以加作用域區(qū)分,但沒必要了
a.C::m_age = 100; //這是最新修改的數(shù)據,所有最后數(shù)據 m_age 就是 100
cout << a.m_age << endl; //有了虛繼承,就不用加作用域區(qū)分了,直接訪問即可
多態(tài)
多態(tài)是C++面向對象三大特性之一
多態(tài)的分類:
-
靜態(tài)多態(tài):其函數(shù)的地址在編譯階段確定,比如 函數(shù)重載 和 運算符重載
-
動態(tài)多態(tài):其函數(shù)的地址在運行階段確定,比如 派生類 和 虛函數(shù)實現(xiàn)運行
動態(tài)多態(tài)的滿足條件:
-
有繼承關系
-
子類重寫父類的虛函數(shù)
-
用父類的指針或引用來執(zhí)行子類對象
純虛函數(shù)
-
在多態(tài)中,父類中的虛函數(shù)通常沒有任何意義,因為調用的都是子類重寫的函數(shù)
-
可以將父類中的虛函數(shù)改為 純虛函數(shù)
-
當一個類中有了純虛函數(shù),這個類被成為抽象類
語法:
virtual 返回值類型 函數(shù)名(參數(shù)列表)= 0 ;
特點:
-
無法實例化對象
-
子類必須重寫抽象類中的純虛函數(shù),否則也屬于抽象類
虛析構和純虛析構
問題:在使用多態(tài)時,如果子類中開辟了堆空間,父類指針在釋放時無法調用子類的析構函數(shù)
解決辦法:將父類中的析構函數(shù)改成 虛析構 或者 純虛析構
二者的共性:
-
可以幫助父類指針釋放子類對象
-
都需要有具體的函數(shù)實現(xiàn)
-
如果子類中沒有開辟堆區(qū),就可以不寫虛析構 或者 純虛析構
-
一個類中如果用于純虛析構,這個類也被成為抽象類
二者的區(qū)別:
-
純虛析構所在的類屬于抽象類,無法實例化對象
-
純虛析構除了需要聲明,也還需要實現(xiàn)
虛析構的語法:
virtual ~類名()
{
}
純虛析構的語法:
virtual ~類名() = 0 ; //聲明
類名::~類名() //實現(xiàn)
{
}
文件操作
-
程序運行時產生的數(shù)據都是臨時數(shù)據,程序一旦執(zhí)行完畢數(shù)據都會被釋放
-
C++提供一個文件流的操作,通過文件來讓數(shù)據持久化
-
文件操作需要的頭文件<fstream>
文件類型的分類:
-
文本文件:文件以ASCII的形式存儲
-
二進制文件:文件以二進制的形式存
文件操作的分類:
-
ofstream:寫操作
-
ifstream:讀操作
-
fstream:讀寫操作
文件打開方式:(多種打開方式用位或操作符 “ | ”隔開即可)
-
ios::in (為讀文件而打開)
-
ios::out (為寫文件而打開)
-
ios::ate (初始位置:文件尾部)
-
ios::app (以追加方式寫文件)
-
ios::trunc (如果文件已經存在,先刪除再創(chuàng)建)
-
ios::binary (以二進制方式打開文件)
文本文件
-
寫文件
-
包含頭文件:#include<fstream>
-
創(chuàng)建文件流對象:ofstream ofs
-
打開文件:ofs.open("文件路徑", "打開方式")
-
寫數(shù)據:ofs << "寫入的數(shù)據"
-
關閉文件:ofs.close()
示例:
#include<fstream> //1、包含頭文件
int main()
{
ofstream ofs; //2、創(chuàng)建流對象
ofs.open("test.txt", ios::out); //3、打開文件
ofs << "姓名:張三" << endl; //4、寫數(shù)據
ofs << "性別,男" << endl;
ofs << "年齡:18" << endl;
ofs.close(); //5、關閉文件
}
-
讀文件
-
包含頭文件:#include<fstream>
-
創(chuàng)建文件流對象:ifstream ifs
-
打開文件并判斷打開是否成功:ifs.open("文件路徑", "打開方式") if( !ofs.is_open() ) { }
-
讀數(shù)據:有四種讀取方式
-
關閉文件:ofs.close()
示例:
#include<fstream> //1、包含頭文件
int main()
{
ifstream ifs; //2、創(chuàng)建流對象
ifs.open("test.txt", ios::in); //3、打開文件并判斷是否打開成功
if( !ifs.is_open())
{
cout << "文件打開失敗" << endl;
return ;
}
//第一種 //4、讀數(shù)據
char buf[1024] = {0};
while( ifs >> buf) //將文件ifs的內容輸出到buf中
{
}
//第二種
char buf[1024] = {0};
while( ifs.getline(buf , seize(buf) ) ) //不斷的去獲取一行的數(shù)據
{
}
//第三種
string buf;
while( getline(ifs , buf) ) //直接將ifs文件的數(shù)據輸出給buf
{
}
//第四種,不建議用,因為一個個字符去讀,效率太慢了
char ch;
while( (c = ifs.get()) != EOF ) //一個字符一個字符去讀,直到讀取到文件尾部標志EOF為止
{
}
ifs.close(); 5、關閉文件
}
二進制文件
-
二進制文件比較強大,除了可以處理內置數(shù)據類型(int,char,double),還可以處理自定義數(shù)據類型
-
打開方式要額外指定:ios::binary
-
寫文件方式主要利用流對象調用成員函數(shù) write()
-
函數(shù)原型 : ostream& write ( const char * buffer, int len )
-
讀文件方式主要利用流對象調用成員函數(shù) read ()
-
函數(shù)原型:istream& read ( char * buffer, int len )
-
寫文件
-
包含頭文件:include<fstream>
-
創(chuàng)建流對象: ofstream ofs
-
打開文件并判斷是否打開成功:ofs.open("文件路徑", "打開方式")
-
讀文件:創(chuàng)建類對象p,ofs.write( (const char*)&p , sizeof(類) )
-
關閉文件:ofs.close()
示例:
class Person
{
public:
char Name[64];
int Age;
}
#include <fstream> //1、包含頭文件
int main()
{
ofstream ofs; //2、創(chuàng)建流對象
ofs.open("test.txt",ios::out | ios::binary); //3、打開文件
Person p("張三,18");
ofs.write( (const char *)&p, sizeof(Person) ); //4、寫數(shù)據
ofs.close(); //5、關閉文件
}
-
讀文件
-
包含頭文件:include<fstream>
-
創(chuàng)建流對象: ifstream ifs
-
打開文件并判斷是否打開成功:ifs.open("文件路徑", "打開方式") if( !ifs.is_open() )
-
讀文件:創(chuàng)建類對象p,ifs.read( (char*)&p , sizeof(類) )
-
關閉文件:ifs.close()
示例:
class Person
{
public:
char Name[64];
int Age;
}
#include <fstream> //1、包含頭文件
int main()
{
ifstream ifs; //2、創(chuàng)建流對象
ifs.open("test.txt",ios::in | ios::binary); //3、打開文件并判斷是否打開成功
if( !ifs.is_open() )
{
cout << "文件打開失敗 " << endl;
return ;
}
Person p;
ifs.read( (char *)&p, sizeof(Person) ); //4、讀數(shù)據
ifs.close(); //5、關閉文件
}
先更新到這兒吧,需要后面在補充。
希望以上內容可以幫助到大家。文章來源:http://www.zghlxwxcb.cn/news/detail-529030.html
祝各位生活愉快。文章來源地址http://www.zghlxwxcb.cn/news/detail-529030.html
?到了這里,關于學習C++這一篇就夠了(進階篇)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!