類和對象(下篇)(詳解)

【本節目標】

1. 再談構造函數

2. Static成員

3. 友元

4. 內部類

5. 再次理解封裝

1. 再談構造函數?

1.1 構造函數體賦值

在創建對象時,編譯器通過調用構造函數,給對象中各個成員變量一個合適的初始值。

#include <iostream>
using namespace std;
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};

雖然上述構造函數調用之后,對象中已經有了一個初始值,但是不能將其稱為對對象中成員變量的初始化, 構造函數體中的語句只能將其稱為賦初值,而不能稱作初始化。因為初始化只能初始化一次,而構造函數體 內可以多次賦值。

1.2 初始化列表

初始化列表:以一個冒號開始,接著是一個以逗號分隔的數據成員列表,每個"成員變量"后面跟一個放在括 號中的初始值或表達式。

class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day)
{}private:int _year;int _month;int _day;};

注意:?

1. 每個成員變量在初始化列表中最多只能出現一次(初始化只能初始化一次)

2. 類中包含以下成員,必須放在初始化列表位置進行初始化:

引用成員變量

const成員變量

自定義類型成員(且該類沒有默認構造函數時)

class A
{
public:A(int a):_a(a){}
private:int _a;
};
class B
{
public:
//初始化列表,對象成員定義的位置B(int a, int& ref):_aobj(a), _ref(ref), _n(10){}
private:A _aobj;// 沒有默認構造函數int& _ref;// 引用const int _n; // const 
};
int main()
{int x = 1;//對象整體定義B bb(10,x);return 0;
}

為什么const 類型與引用必須在列表中初始化,因為它們有一個共同特征,必須在定義的時候初始化。而我們知道類里寫成員變量的地方,寫的是成員變量的聲明并非定義。因此成員變量只能在列表處初始化,在函數體類的是賦值并非初始化。因為初始化只能初始化一次,而構造函數體 內可以多次賦值。

這里再回述一下默認構造函數,什么叫默認構造函數呢?不傳參的都叫默認構造函數。例如編譯器自己生成的,無參的,全缺省的。因此如果自定義類型沒有默認構造函數時我們需要像上面一樣,顯示的調用自定義類型的構造函數。(也就是手動傳參)

當然有默認構造函數時我們也能在初始化列表上面寫,只不過如果在初始化列表上面寫了,就不用缺省值了。例如下面這樣,這里用的是10并非1。(但不能多次寫,因為 每個成員變量在初始化列表中最多只能出現一次)

class A
{
public:A(int a=1):_a(a){}
private:int _a;
};
class B
{
public:
//初始化列表,對象成員定義的位置B(int a, int& ref):_aobj(a), _ref(ref), _n(10){}
private:A _aobj;// 沒有默認構造函數int& _ref;// 引用const int _n; // const 
};
int main()
{int x = 1;//對象整體定義B bb(10,x);return 0;
}

3. 盡量使用初始化列表初始化,因為不管你是否使用初始化列表,對于自定義類型成員變量,一定會先使 用初始化列表初始化。

class Time
{
public:Time(int hour = 0):_hour(hour){cout << "Time()" << endl;}
private:int _hour;
};class Date
{
public:Date(int day){}private:int _day;Time _t;
};int main()
{Date d(1);
}

注意:1.初始化列表和函數體賦值,一般是可以相互配合著使用的。

? ? ? ? ?2.就算初始化列表上什么都沒寫成員變量也會走初始化列表,因為這是它定義的地方。

4. 成員變量在類中聲明次序就是其在初始化列表中的初始化順序,與其在初始化列表中的先后次序無關,各位可以想一下下面代碼會有什么問題。

class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a2;int _a1;
};int main() {A aa(1);aa.Print();
}//A.輸出1  1
//B.程序崩潰
//C.編譯不通過
//D.輸出1  隨機值

答案為D,因為編譯器是按照聲明的順序初始化的,也就是這里先初始化_a2,再初始化_a1。因此在初始化列表里,用_a1來初始化_a2是錯誤的,_a1是隨機值初始化了_a2。

因此為了防止這種錯誤,我們寫代碼時,盡量聲明與定義相同順序。

1.3 explicit關鍵字

構造函數不僅可以構造與初始化對象,對于接收單個參數的構造函數,還具有類型轉換的作用。接收單個參 數的構造函數具體表現:

1. 構造函數只有一個參數

2. 構造函數有多個參數,除第一個參數沒有默認值外,其余參數都有默認值

3. 全缺省構造函數

class Date
{
public:// 1. 單參構造函數,沒有使用explicit修飾,具有類型轉換作用// explicit修飾構造函數,禁止類型轉換---explicit去掉之后,代碼可以通過編譯Date(int year):_year(year){}/*// 2. 雖然有多個參數,但是創建對象時后兩個參數可以不傳遞,沒有使用explicit修飾,具有類型轉換作用// explicit修飾構造函數,禁止類型轉換explicit Date(int year, int month = 1, int day = 1): _year(year), _month(month), _day(day){}*/Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};
void Test()
{Date d1(2022);// 用一個整形變量給日期類型對象賦值// 實際編譯器背后會用2023構造一個無名對象,最后用無名對象給d1對象進行賦值d1 = 2023;//這是一個隱式類型轉換,整形轉換成自定義類型// 將1屏蔽掉,2放開時則編譯失敗,因為explicit修飾構造函數,禁止了單參構造函數類型轉換的作用
}
int main()
{Test();return 0;
}

上述代碼可讀性不是很好,用explicit修飾構造函數,將會禁止構造函數的隱式轉換。

這里的隱式類型轉換與不同類型的賦值十分相像,都是創建一個為左操作數的類型臨時變量(這里是用右操作數的值來創建的),再把臨時變量拷貝構造給左操作數。這里隱式類型轉換也是一樣的,2023這個值創建一個為Date類型的臨時變量,再把臨時變量拷貝構造給d1。

void Test()
{Date d1(2022);d1 = 2023;//這是一個隱式類型轉換,整形轉換成自定義類型}

現在的編譯器一般都會優化這個過程,例如用2023直接構造一個日期類對象。

class Date
{
public:Date(int year):_year(year){cout << "Date(int year)" << endl;}Date(const Date& yy):_year(yy._year){cout << "Date(const Date & yy)" << endl;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};
void Test()
{Date d1 = 2023;//這是一個隱式類型轉換,整形轉換成自定義類型}
int main()
{Test();return 0;
}

在同一個表達式上編譯器一般都會優化連續構造。?

2. static成員

2.1 概念

聲明為static的類成員稱為類的靜態成員,用static修飾的成員變量,稱之為靜態成員變量;用static修飾的 成員函數,稱之為靜態成員函數。靜態成員變量一定要在類外進行初始化

面試題:實現一個類,計算程序中創建出了多少個類對象。

class A{public:A() { ++_scount; }A(const A& t) { ++_scount; }~A() { --_scount; }static int GetACount() { return _scount; }private:static int _scount;};int A::_scount = 0;void TestA(){cout << A::GetACount() << endl;A a1, a2;A a3(a1);cout << A::GetACount() << endl;}int main(){TestA();return 0;}

2.2 特性

1. 靜態成員為所有類對象所共享,不屬于某個具體的對象,存放在靜態區

2. 靜態成員變量必須在類外定義,定義時不添加static關鍵字,類中只是聲明

3. 類靜態成員即可用 類名::靜態成員 或者 對象.靜態成員 來訪問

4. 靜態成員函數沒有隱藏的this指針,不能訪問任何非靜態成員

5. 靜態成員也是類的成員,受public、protected、private 訪問限定符的限制

【問題】

1. 靜態成員函數可以調用非靜態成員函數嗎?

2. 非靜態成員函數可以調用類的靜態成員函數嗎?

3. 友元

友元提供了一種突破封裝的方式,有時提供了便利。但是友元會增加耦合度,破壞了封裝,所以友元不宜多 用。

友元分為:友元函數和友元類

3.1 友元函數

問題:現在嘗試去重載operator<<,,然后發現沒辦法將operator<<重載成成員函數。因為cout的輸出流對象和隱含的this指針在搶占第一個參數的位置。this指針默認是第一個參數也就是左操作數了。但是實際使用中cout需要是第一個形參對象,才能正常使用。所以要將operator<<重載成全局函數。但又會導致類外沒辦法訪問成員,此時就需要友元來解決。operator>>同理。

#include <iostream>
using namespace std;
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){
}// d1 << cout; -> d1.operator<<(&d1, cout);  不符合常規調用// 因為成員函數第一個參數一定是隱藏的this,所以d1必須放在<<的左側ostream & operator<<(ostream & _cout){_cout << _year << "-" << _month << "-" << _day << endl;return _cout;}private:int _year;int _month;int _day;
};

友元函數可以直接訪問類的私有成員,它是定義在類外部的普通函數,不屬于任何類,但需要在類的內部聲 明,聲明時需要加friend關鍵字。

說明:

友元函數可訪問類的私有和保護成員,但不是類的成員函數

友元函數不能用const修飾

友元函數可以在類定義的任何地方聲明,不受類訪問限定符限制

一個函數可以是多個類的友元函數

友元函數的調用與普通函數的調用原理相同

3.2 友元類

友元類的所有成員函數都可以是另一個類的友元函數,都可以訪問另一個類中的非公有成員。

?友元關系是單向的,不具有交換性。

比如上述Time類和Date類,在Time類中聲明Date類為其友元類,那么可以在Date類中直接訪問Time 類的私有成員變量,但想在Time類中訪問Date類中私有的成員變量則不行。

友元關系不能傳遞

如果B是A的友元,C是B的友元,則不能說明C時A的友元。

友元關系不能繼承,在繼承位置再給大家詳細介紹。

class Time
{
friend class Date;public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){
}private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接訪問時間類私有的成員變量_t._hour = hour;_t._minute = minute;_t._second = second;}private:int _year;int _month;int _day;Time _t;
};

4. 內部類

概念:如果一個類定義在另一個類的內部,這個內部類就叫做內部類。內部類是一個獨立的類,它不屬于外 部類,更不能通過外部類的對象去訪問內部類的成員。外部類對內部類沒有任何優越的訪問權限。

注意:內部類就是外部類的友元類,參見友元類的定義,內部類可以通過外部類的對象參數來訪問外部類中 的所有成員。但是外部類不是內部類的友元。

特性:

1. 內部類可以定義在外部類的public、protected、private都是可以的。

2. 注意內部類可以直接訪問外部類中的static成員,不需要外部類的對象/類名。

3. sizeof(外部類)=外部類,和內部類沒有任何關系。

class A
{
private:static int k;int h;
public:class B // B天生就是A的友元{public:void foo(const A& a){cout << k << endl;//OKcout << a.h << endl;//OK}};};
int A::k = 1;int main()
{A::B b;b.foo(A());return 0;
}

如果sizeof(A)就可以發現A類只占四個字節,究其原因是k是在靜態區不算在對象類,由于B類沒有定義對象,因此雖然看起來在A類里,其實并不占用A類空間。因此只算h的大小。可以回顧之前所學的B類在沒創建對象的時候只是起到一個圖紙的效果。但是要訪問B類就需要突破A類的限制,因此要用域作用限定符。如果還用了訪問限定符,設為了私有那么還要突破訪問限定符才能訪問。

5. 再次理解類和對象

現實生活中的實體計算機并不認識,計算機只認識二進制格式的數據。如果想要讓計算機認識現實生活中的 實體,用戶必須通過某種面向對象的語言,對實體進行描述,然后通過編寫程序,創建對象后計算機才可以 認識。比如想要讓計算機認識洗衣機,就需要:

1. 用戶先要對現實中洗衣機實體進行抽象---即在人為思想層面對洗衣機進行認識,洗衣機有什么屬性,有 那些功能,即對洗衣機進行抽象認知的一個過程

2. 經過1之后,在人的頭腦中已經對洗衣機有了一個清醒的認識,只不過此時計算機還不清楚,想要讓計 算機識別人想象中的洗衣機,就需要人通過某種面相對象的語言(比如:C++、Java、Python等)將洗衣 機用類來進行描述,并輸入到計算機中

3. 經過2之后,在計算機中就有了一個洗衣機類,但是洗衣機類只是站在計算機的角度對洗衣機對象進行 描述的,通過洗衣機類,可以實例化出一個個具體的洗衣機對象,此時計算機才能洗衣機是什么東西。

4. 用戶就可以借助計算機中洗衣機對象,來模擬現實中的洗衣機實體了。 在類和對象階段,大家一定要體會到,類是對某一類實體(對象)來進行描述的,描述該對象具有那些屬性, 那些方法,描述完成后就形成了一種新的自定義類型,才用該自定義類型就可以實例化具體的對象。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/900515.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/900515.shtml
英文地址,請注明出處:http://en.pswp.cn/news/900515.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

高精度算法

高精度加法 輸入兩個數&#xff0c;輸出他們的和&#xff08;高精度&#xff09; 輸入樣例 111111111111111111111111111111 222222222222222222222222222222 輸出樣例 333333333333333333333333333333 #include <bits/stdc.h> using namespace std;string a,b; in…

Linux開發中注意哪些操作系統安全

在 Linux 開發中&#xff0c;確保操作系統的安全至關重要。以下是一些需要注意的方面&#xff1a; 用戶管理與權限控制 合理設置用戶權限&#xff1a;為不同的用戶和用戶組分配適當的權限&#xff0c;遵循最小權限原則。避免給普通用戶過多的權限&#xff0c;以免他們誤操作或…

x64dbg調試python解釋器

可以先寫個input()這會讓dbg中斷在ntdll模塊中&#xff0c;查看調用堆棧在系統調用結束后的打斷點 然后直接斷到PyObject_Vectorcall函數

? Ultralytics YOLO驗證(Val)時自動輸出COCO指標(AP):2025最新配置與代碼詳解 (小白友好 + B站視頻)

? YOLO獲取COCO指標(3)&#xff1a;驗證(Val) 啟用 COCO API 評估&#xff08;自動輸出AP指標&#xff09;| 發論文必看&#xff01; | Ultralytics | 小白友好 文章目錄 一、問題定位二、原理分析三、解決方案與實踐案例步驟 1: 觸發 COCO JSON 保存步驟 2: 確保 self.is_coc…

【嵌入式學習3】基于python的tcp客戶端、服務器

目錄 1、tcp客戶端 2、tcp服務器 3、服務器多次連接客戶端、多次接收信息 1、tcp客戶端 """ tcp:客戶端 1. 導入socket模塊 2. 創建socket套接字 3. 建立tcp連接(和服務端建立連接) 4. 開始發送數據(到服務端) 5. 關閉套接字 """ import soc…

Linux: network: 兩臺直連的主機業務不通

前提環境,有一個產品的設定是兩個主機之間必須是拿網線直連。但是設備管理者可能誤將設置配錯,不是直連。 最近遇到一個問題,說一個主機發的包,沒有到對端,一開始懷疑設定的bond設備的問題,檢查了bond的設置狀態,發現沒有問題,就感覺非常的奇怪。后來就開始懷疑兩個主機…

COMSOL固體力學接觸

目錄 一、接觸非線性及接觸面積計算 一、接觸非線性及接觸面積計算 COMSOL接觸非線性及接觸面積計算_嗶哩嗶哩_bilibili 形成聯合體&#xff0c;在定義處右鍵選擇“建立接觸對” 位移dz使用參數化掃描。 接觸選擇定義中設置的接觸對&#xff0c;選擇罰函數&#xff0c;摩擦設置…

22.OpenCV輪廓匹配原理介紹與使用

OpenCV輪廓匹配原理介紹與使用 1. 輪廓匹配的基本概念 輪廓匹配&#xff08;Contour Matching&#xff09;是計算機視覺中的一種重要方法&#xff0c;主要用于比較兩個輪廓的相似性。它廣泛應用于目標識別、形狀分析、手勢識別等領域。 在 OpenCV 中&#xff0c;輪廓匹配主要…

oracle 快速創建表結構

在 Oracle 中快速創建表結構&#xff08;僅復制表結構&#xff0c;不復制數據&#xff09;可以通過以下方法實現&#xff0c;適用于需要快速復制表定義或生成空表的場景 1. 使用 CREATE TABLE AS SELECT (CTAS) 方法 -- 復制源表的全部列和數據類型&#xff0c;但不復制數據 C…

若依原理筆記

代碼生成器 源碼分析 查詢數據庫列表 導入表結構 生成代碼 修改generator.yml配置文件 代碼生成器增強 Velocity模版引擎 基礎語法-變量 Lombok集成 E:\javaProject\dkd-parent\dkd-generator\src\main\resources\vm\java\domain.java.vm package ${packageName}.domain;#fo…

Ansible的使用

##### Ansible使用環境 - 控制節點 - 安裝Ansible軟件 - Python環境支持&#xff1a;Python>2.6 - 必要的模塊&#xff1a;如PyYAML等 - 被控節點 - 啟用SSH服務 - 允許控制節點登錄&#xff0c;通常設置免密登錄 - Python環境支持 http://www.ansible.com/ …

C++ 提高編程:模板與 STL 深度剖析

摘要&#xff1a;本文深入探討 C 提高編程中的模板編程與標準模板庫&#xff08;STL&#xff09;相關內容。詳細闡述模板編程中函數模板和類模板的概念、語法、特性及應用案例&#xff1b;對 STL 的誕生背景、基本概念、六大組件進行剖析&#xff0c;并對常用容器、函數對象、常…

C++(類模板的運用)

使用vector實現一個簡單的本地注冊登錄系統 注冊&#xff1a;將賬號密碼存入vector里面&#xff0c;注意防重復判斷 登錄&#xff1a;判斷登錄的賬號密碼是否正確 #include <iostream> #include <vector> #include <fstream> #include <sstream> usi…

【大模型】DeepSeek+藍耕MaaS平臺+海螺AI生成高質量視頻實戰詳解

目錄 一、前言 二、藍耘智能云MaaS平臺介紹 2.1 藍耘智算平臺是什么 2.2 平臺優勢 2.3 平臺核心能力 三、海螺AI視頻介紹 3.1 海螺AI視頻是什么 3.2 海螺AI視頻主要功能 3.3 海螺AI視頻應用場景 3.4 海螺AI視頻核心優勢 3.5 項目git地址 四、藍耘MaaS平臺DeepSeek海…

接口自動化學習二:session自動管理cookie

session自動管理cookie&#xff1a; cookie中的數據&#xff0c;都是session提供的 實現步驟&#xff1a; 1.創建session對象&#xff1b;my_sessionrequests.Session() 2.使用session實例&#xff0c;調用get方法&#xff0c;發送獲取驗證碼請求&#xff08;不需要提取cookie&…

C++類型轉換詳解

目錄 一、內置 轉 內置 二、內置 轉 自定義 三、自定義 轉 內置 四、自定義 轉 自定義 五、類型轉換規范化 1.static_case 2.reinterpret_cast 3.const_cast 4.dynamic_cast 六、RTTI 一、內置 轉 內置 C兼容C語言&#xff0c;在內置類型之間轉換規則和C語言一樣的&am…

QEMU源碼全解析 —— 塊設備虛擬化(17)

接前一篇文章:QEMU源碼全解析 —— 塊設備虛擬化(16) 本文內容參考: 《趣談Linux操作系統》 —— 劉超,極客時間 《QEMU/KVM源碼解析與應用》 —— 李強,機械工業出版社 《KVM實戰 —— 原理、進階與性能調優》—— 任永杰 程舟,機械工業出版社

mac 卸載流氓軟件安全助手

之前個人電腦在公司使用過一段時間&#xff0c;為了使用網線聯網安裝了公司指定的 聯軟上網助手&#xff0c;誰知安裝容易卸載難&#xff0c;后來找運維來卸載&#xff0c;輸入管理員密碼后&#xff0c;也無反應&#xff0c;最后不了了之了&#xff0c;這個毒瘤軟件長期在后臺駐…

Java 大視界 -- Java 大數據機器學習模型在智能客服多輪對話系統中的優化策略(179)

&#x1f496;親愛的朋友們&#xff0c;熱烈歡迎來到 青云交的博客&#xff01;能與諸位在此相逢&#xff0c;我倍感榮幸。在這飛速更迭的時代&#xff0c;我們都渴望一方心靈凈土&#xff0c;而 我的博客 正是這樣溫暖的所在。這里為你呈上趣味與實用兼具的知識&#xff0c;也…

極氪汽車云原生架構落地實踐

云原生架構落地實踐的背景 隨著極氪數字業務的飛速發展&#xff0c;背后的 IT 技術也在不斷更新迭代。極氪極為重視客戶對服務的體驗&#xff0c;并將系統穩定性、業務功能的迭代效率、問題的快速定位和解決視為構建核心競爭力的基石。 為快速響應用戶的需求&#xff0c;例如…