C++入門?關于類的一些特殊知識點

涉及的關于類中的默認成員函數的知識點可以看我的這篇博客哦~

C++入門必須知道的知識?類的默認成員函數,一文講透+運用

目錄

初始化列表

類型轉換

static成員?

友元

內部類

匿名對象

?對象拷貝時的一些編譯器的優化


初始化列表

我們知道類中的構造函數的任務是完成對象的初始化,使用構造函數完成初始化的方式除了在函數體內對成員變量進行賦值的方式,如下:

class Date
{
public:// 構造函數Date(int year, int month, int day){// 函數體內賦值初始化_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

還有一種方式可以進行初始化——初始化列表

初始化列表的格式:

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

比如上面的構造函數就可以寫成下面的樣子:?

class Date
{
public:// 初始化列表的形式Date(int year,int month,int day):_year(year),_month(month),_day(day){}
private:int _yeaer;int _month;int _day;
};

兩種初始化的區別:

使用函數體內賦值的方式時,成員變量會先進行默認初始化,然后再在構造函數體內被賦值。也就是在函數體內被賦值時不能稱為"初始化",只能稱為"賦值",初始化只能進行一次,而構造函數體內可以多次賦值;

如果用初始化列表,則直接跳過默認初始化步驟,一步到位完成初始化

默認初始化:指當對象被創建時,如果沒有顯式指定初始值,編譯器會自動進行的初始化行為。具體行為取決于成員變量的類型。具體行為如下:

  1. 內置類型(如?int,?double, 指針等)

    • 如果變量在全局作用域(或靜態存儲區),默認初始化為?0/nullptr

    • 如果變量在局部作用域(如函數內、類構造函數體內),不初始化,值是未定義的(垃圾值)。

  2. 類類型(如?std::string, 自定義類)

    • 調用該類的默認構造函數(如果沒有默認構造函數,會編譯報錯)。

兩種方式在面對自定義類型的初始化時,還會有效率的差異,初始化列表的效率更高:

使用函數體內賦值的方式,會先去調用自定義類型的構造函數,然后再調用賦值重載將構造的值賦值給成員變量

使用初始化列表的方式,對于自定義類型只會調用一次拷貝構造函數一次性完成初始化

初始化列表的注意事項:

1、每個成員變量在初始化列表中只能出現一次(即初始化只能初始化一次)?

2、類中包含以下成員時,它們的初始化必須放在初始化列表位置進行(否則編譯報錯)

  • 引用成員變量(因為引用在定義初始化時必須賦值,且不能更改引用的對象)
  • const修飾的成員變量(const的原因與引用類似)
  • 自定義類型的成員變量,且該成員變量的類沒有默認構造函數時(因為使用函數體內的方式時,對于自定義類型,會先去調用其構造函數)

3、成員變量在類中的聲明順序就是在初始化列表的初始化順序,與其在初始化列表中的位置順序無關(★),所以建議初始化列表的順序和聲明的順序一致

關于第三條:如下述代碼,會得到錯誤的結果,因為雖然初始化列表中的順序是先初始化_a,再用_a的值初始化_b,看著是沒錯,但是因為成員變量聲明時,是先聲明的_b,再是_a,所以初始化時會先初始化_b,而不是_a,也就是初始化順序只和聲明的順序相關,和初始化列表中的順序無關,但是因為_a還未初始化,所以_b的值就會是隨機數

class A
{
public:// 初始化列表A(int a):_a(a),_b(_a+1){}
private:int _b;int _a;
};

另外,C++11還支持在成員變量聲明的位置給缺省值,這個缺省值主要是給沒有顯示在初始化列表初始化的成員使用的,如:

class A
{
public:A(int a):_a1(a){}
private:int _a2 = 2;int _a1;
};

此時,_a2初始化后的值就是2,相當于_a2也走了初始化列表,只不過用的聲明時給的缺省值進行初始化,_a1初始化后的值就是傳給參數a的值?

總結:

盡量使用初始化列表初始化,因為無論是否顯示寫初始化列表,每個構造函數都有初始化列表;?論是否在初始化列表顯示初始化,每個成員變量都要走初始化列表初始化,如果這個成員在聲明位置給了缺省值,初始化列表就會用這個值進行初始化。如果你沒有給缺省值,那么編譯器對其的初始化是不確定的,且對于自定義類型,使用初始化列表的方式會更加高效一點

?成員變量初始化思維導圖:

類型轉換

C++支持內置類型隱式類型轉換為類類型的對象,需要有相關內置類型為參數的構造函數

如下:使用一個int類型的值構造了一個A類型的對象,其中發生了隱式類型轉換,將int類型轉為自定義類型A

class A
{
public:// 以int類型為參數的相關構造函數A(int a):_a(a){}
private:int _a;
};
int main()
{// 使用int類型的1構造了一個A類型的對象A b(1);return 0;
}

explicit關鍵字?

但是上述的隱式類型轉換,如果你不想讓它發生可以使用explicit關鍵字對構造函數進行修飾,那么這種隱式類型就會被禁止

// 此時再想用一個數字去構造A類型的對象就會編譯報錯
class A
{
public:// 以int類型為參數的相關構造函數explicit A(int a):_a(a){}
private:int _a;
};

對于內置類型隱式類型轉換構造自定義類型時,相關內置類型的構造函數必須要有,且需要保證傳入該內置類型參數時,可以構造出一個對象

除了內置類型類類型的隱式類型轉換,類類型的對象之間也可以隱式轉換,需要相應的構造函數的支持

如下方的將A類型的對象隱式類型轉換為B類型的對象進行構造,構造出一個臨時對象用來拷貝構造B類型的對象b,但是編譯器會優化,所以就變成了直接用A類型的對象構造出了一個B類型的對象,但這個過程是因為有相關類型參數的構造函數支持才完成的

class A
{ 
public:A(int a1, int a2):_a1(a1), _a2(a2){}int Get() const{return _a1 + _a2;}
private:int _a1 = 1;int _a2 = 2;
};
class B
{ 
public:// 臨時對象具有常性,所以需要用const接收B(const A& a):_b(a.Get()){}
private:int _b = 0;
};
int main()
{// { 2,2 }構造?個A的臨時對象,再?這個臨時對象拷?構造a// 編譯器遇到連續構造+拷?構造->優化為直接構造A a = { 2,2 };   // C++11之后才?持的多參數轉化// a 隱式類型轉換為b對象// 原理跟上?類似B b = a;return 0;
}

static成員

?定義:

被static修飾的成員稱為static成員,也叫類的靜態成員

  • 靜態成員也是類的成員,受public、protected、private訪問限定符的限制
  • 突破類域可以訪問靜態成員,可以通過類名::靜態成員 或者 對象.靜態成員?來訪問靜態成員變量和靜態成員函數

由static修飾的成員變量稱為靜態成員變量,由static修飾的成員函數稱為靜態成員函數。

靜態成員變量:?

靜態成員變量一定要類外進行初始化,即不在構造函數初始化

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

靜態成員變量不能在聲明位置給缺省值初始化,因為缺省值是給構造函數初始化列表初始化的,靜態成員變量不屬于某個對象,不走構造函數的初始化列表

靜態成員函數:?

靜態成員函數沒有this指針

靜態成員函數中可以訪問其他的靜態成員,但是不能訪問非靜態的,因為沒有this指針

非靜態成員函數可以訪問任意的靜態成員變量和靜態成員函數

場景:統計一個類實例化了多少個對象——使用靜態成員變量

#include<iostream>
using namespace std;
class A
{
public:A(){count++;}static int getCount(){return count;}
private:static int count;
};
int A::count = 0;
int main()
{A a1;A a2;A a3;cout << "創建了"<< A::getCount() << "個A類型對象" << endl;return 0;
}

在A類型的構造函數中使用靜態成員變量count++進行計數,則每初始化一個對象就會對其進行一次++,最終就會得到實例化出的對象個數,同時注意count變量是在類外進行初始化的,只是在類內聲明,且在訪問靜態成員時都使用了類域::靜態成員變量的方式(A::count、A::getCount)

上方代碼,如果是非靜態的成員函數getCount()的話,就通過對象.靜態成員函數的方式獲得靜態成員變量count的值

// 非靜態成員函數
int getCount()
{return count;// 返回靜態成員變量
}
// 其余代碼一致....
int main()
{A a1;A a2;A a3;cout << "創建了"<< a3.getCount() << "個A類型對象" << endl;return 0;
}

友元

定義:

友元分為友元函數和友元類,在一個類里,給函數聲明或者類的聲明前面加上friend的修飾,則稱其為類的友元函數或者友元類,成為類的友元后可以訪問類中的私有成員和保護成員

友元類:
class A
{// 友元聲明,B這個類變成A的友元類friend class B;
private:int _a1 = 1;
};class B
{
public:void func(const A& aa){// 友元類B就可以訪問A類的私有成員cout << aa._a1 << endl;cout << _b1 << endl;}
private:int _b1 = 3;
};
int main()
{A aa;B bb;bb.func(aa);return 0;
}

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

  • 友元關系是單向的,不具有交換性。(比如上面的B類可以訪問A類的私有成員,但是A類就不可以反過來訪問B類的非公有成員,因為A類不是B類的友元類)
  • 友元關系不能傳遞(如果C是B的友元, B是A的友元,則不能說明C時A的友元)
  • 友元關系不能繼承(涉及繼承知識)
友元函數:

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

class Date
{// 友元函數聲明friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
// 自定義Date類的輸入輸出
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year;_cin >> d._month;_cin >> d._day;return _cin;
}
int main()
{
Date d;
cin >> d;
cout << d << endl;
return 0;
}

1、友元函數可以在類定義的任意地方聲明(注意:是在類定義的地方聲明,類的定義有聲明和定義都在一個文件中實現的方式,也有聲明定義分離的方式,如果是分離的方式,要在定義的地方聲明友元函數),且它不受任何訪問限定符的限制

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

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


內部類

定義:

如果一個類定義另一個類的內部,這個內部的類就叫做內部類。內部類是一個獨立的類,跟定義在全局相比,他只是受外部類類域限制和訪問限定符限制,所以它不屬于外部類,更不能通過外部類的對象去訪問內部類的成員。外部類對內部類沒有任何優越的訪問權限。

注意:

  • 內部類默認就是外部類的友元類,內部類可以通過外部類的對象參數來訪問外部類中的所有成員但是外部類不是內部類的友元
  • 內部類可以訪問的外部的所有成員中包括外部類的靜態成員,不需要外部類的對象/類名來突破類域訪問
  • sizeof(外部類)=外部類,和內部類沒有任何關系。

內部類本質也是一種封裝,當A類跟B類緊密關聯,A類實現出來主要是給B類使用時,可以考慮將其設為B類的內部類,?如果放到private/protected位置,那么A類就是B類的專屬內部類,其他地方都用不了。


匿名對象

定義:

類型(實參) 定義出來的對象叫做匿名對象,前面我們使用?類型 對象名(實參) 格式定義出來的叫有名對象

// 假設有一個名為A的類,且可以用int類型隱式轉換初始化
int main()
{A aa1(1);// 有名對象A(1);// 匿名對象
}

如果A類的構造是可以無參的,那么A類的匿名對象的創建也不可以省略掉括號,否則語法出錯

A();// 無參的A類型匿名對象

注意:匿名對象的生命周期只在當前一行,一般臨時定義一個對象當前用一下即可,就可以使用匿名對象

?匿名對象調用場景:

?①成員函數調用:

class Solution {
public:int Sum_Solution(int n) {//...return n;}
};
int main()
{// 沒有匿名對象時候,需要寫兩行調用Solution s1;cout << s1.Sum_Solution(10) << endl;// 有了匿名對象直接寫成一行調用cout << Solution().Sum_Solution(10) << endl;
}

?②函數參數自定義類型,需要給參數缺省值:給匿名對象

void func(A aa = A(1))
{}

?延長匿名對象生命周期:

?引用匿名對象,延長到和引用的生命周期一致,

?但是匿名對象和臨時對象一樣具有常性,要加const

const A& r = A();

匿名對象就像一個一次性的事物,用完就可丟~


?對象拷貝時的一些編譯器的優化

現代編譯器會為了盡可能提?程序的效率,在不影響正確性的情況下會盡可能減少?些傳參和傳返回值的過程中可以省略的拷貝

假設一個類的定義如下:

class A
{
public:// 構造函數A(int a = 0):_a1(a){cout << "A(int a)" << endl;}// 拷貝構造A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}// 賦值重載A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a1 = aa._a1;}return *this;}// 析構函數~A(){cout << "~A()" << endl;}private:int _a1 = 1;
};
傳參時的編譯器優化:

連續構造+拷貝構造優化——>直接構造,省略了一次拷貝

void f1(A aa)
{}
int main()
{// 優化// 按理這里會先構造臨時對象,再把臨時對象拷貝構造給aa0// 但是這里優化成直接用1進行構造了A aa0 = 1;// 隱式類型轉換cout << endl;// 優化// 按理是用1構造一個匿名對象,再把匿名對象拷貝構造給aa// 隱式類型轉換,連續構造+拷貝構造->優化為直接構造f1(1);// 優化// 一個表達式中,連續構造+拷貝構造->優化為一個構造f1(A(2));cout << endl;return 0;
}
傳值返回時的編譯器優化:

兩次連續的拷貝構造優化成一次拷貝構造

A f2()
{A aa;return aa;// 不會返回aa,會用aa創建一個臨時對象,返回臨時對象
}
// 傳值返回的優化
int main()
{// 按理需要先用返回值aa拷貝構造一個臨時對象,再用臨時對象拷貝構造aa2// 優化:兩個拷貝構造合二為一為一個拷貝構造// 更新的編譯器優化:直接優化成一次構造,直接用aa的值構造aa2A aa2 = f2();cout << endl;// 按理是返回值aa拷貝構造一個臨時對象,再用臨時對象賦值拷貝給aa1// 優化:直接用aa的值賦值拷貝給aa1A aa1 = 1;aa1 = f2();cout << endl;return 0;
}

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

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

相關文章

只用Prettier進行格式化項目

1.下載Prettier插件&#xff0c;禁用ESlint 2.在項目根目錄新建.prettierrc文件 {"singleQuote": true,"jsxSingleQuote": true,"printWidth": 100,"trailingComma": "none","tabWidth": 2,"semi": f…

XXL-TOOL v1.4.0 發布 | Java工具類庫

Release Notes 1、【新增】JsonRpc模塊&#xff1a;一個輕量級、跨語言遠程過程調用實現&#xff0c;基于json、http實現&#xff08;從XXL-JOB底層通訊組件提煉抽象&#xff09;。2、【新增】Concurrent模塊&#xff1a;一系列并發編程工具&#xff0c;具備良好的線程安全、高…

基于LVGL的登錄界面設計

目錄 一、演示 二、前言 三、部件知識 3.1 圖片按鈕部件 3.1.1 圖片按鈕部件的組成 3.1.2 圖片的來源 3.1.3 添加/清除的狀態 3.1.4 圖片按鈕部件 API 函數 3.2 鍵盤部件(lv_keyboard) 3.2.1 鍵盤部件的組成 3.2.2 鍵盤部件的相關知識 3.2.2.1 鍵盤部件模式 3.…

S3 跨賬戶復制:增強云中的災難恢復計劃

您準備好提升您的云和 DevOps 技能了嗎&#xff1f; &#x1f425;《云原生devops》專門為您打造&#xff0c;我們精心打造的 30 篇文章庫&#xff0c;這些文章涵蓋了 Azure、AWS 和 DevOps 方法論的眾多重要主題。無論您是希望精進專業知識的資深專業人士&#xff0c;還是渴望…

線程與進程深度解析:從fork行為到生產者-消費者模型

線程與進程深度解析&#xff1a;從fork行為到生產者-消費者模型 一、多線程環境下的fork行為與線程安全 1. 多線程程序中fork的特殊性 核心問題&#xff1a;fork后子進程的線程模型 當多線程程序中的某個線程調用fork時&#xff1a; 子進程僅包含調用fork的線程&#xff1…

Circular Plot系列(五): circle plot展示單細胞互作

這是我們circle系列的最后一節&#xff0c;我想常見的弦圖是繞不開的&#xff0c;所以最后從前面介紹的circle plot思路&#xff0c;做一遍弦圖。其實前面的內容如果消化了&#xff0c;plot互作弦圖也就不成什么問題了。 效果如下&#xff1a; #cellchat提取互作結果&#xff…

(11)Vue-Router路由的詳細使用

本系列教程目錄&#xff1a;Vue3Element Plus全套學習筆記-目錄大綱 文章目錄 第2章 路由 Vue-Router2.1 Vue路由快速入門2.1.1 創建項目2.1.2 路由運行流程 2.2 傳遞參數-useRoute2.2.1 路徑參數-params1&#xff09;普通傳參2&#xff09;傳遞多個參數3&#xff09;對象方式傳…

react + antd 實現后臺管理系統

文章目錄 完整路由搭建Layout 和 Aside組件引入 AntdAside組件實現 項目效果圖 項目完整代碼地址 https://gitee.com/lyh1999/react-back-management 項目完整代碼地址 react依賴安裝 最好采用yarn 安裝 react-router 安裝依賴 配置路由 history模式 / // src/router/…

基于AWS Marketplace的快速解決方案:從選型到部署實戰

1. 引言&#xff1a;為什么選擇AWS Marketplace&#xff1f; 在數字化轉型的背景下&#xff0c;企業需要快速獲取成熟的軟件工具和服務以降低開發成本。AWS Marketplace 作為亞馬遜云科技的官方應用商店&#xff0c;提供超過萬款預配置的第三方和AWS原生解決方案&#xff0c;涵…

2021年第十二屆藍橋杯省賽B組C++題解

2021年第十二屆藍橋杯省賽B組C題解 關鍵詞&#xff1a;藍橋杯、省賽、題解、C、算法 一、個人見解 第十二屆藍橋杯省賽B組共有10道題目&#xff0c;包含5道填空題&#xff08;T1-T5&#xff09;和5道編程題&#xff08;T6-T10&#xff09;&#xff0c;總分150分。比賽時長4小…

日語學習-日語知識點小記-進階-JLPT-N1階段(1):語法單詞

日語學習-日語知識點小記-進階-JLPT-N1階段&#xff08;1&#xff09;&#xff1a;語法單詞 1、前言&#xff08;1&#xff09;情況說明&#xff08;2&#xff09;工程師的信仰&#xff08;3&#xff09;高級語法N1語法和難點一、N1語法學習內容&#xff08;高級語法&#xff…

Python|Pyppeteer實現自動登錄小紅書(32)

前言 本文是該專欄的第32篇,結合優質項目案例持續分享Pyppeteer的干貨知識,記得關注。 本文中,筆者以小紅書為例,基于Pyppeteer實現自動登錄“小紅書”。 需要注意的是,對Pyppeteer不太熟悉的同學,可往前翻閱本專欄前面介紹的Pyppeteer知識點,本專欄將帶你了解并熟練使…

【翻譯、轉載】【轉載】LLM 的函數調用與 MCP

來源&#xff1a; https://www.dailydoseofds.com/p/function-calling-mcp-for-llms/ 【代碼以圖像顯示的是原文內容&#xff0c;以代碼形式顯示的是大模型給出的參考】 LLM 的函數調用與 MCP 在 MCP 變得像現在這樣主流&#xff08;或流行&#xff09;之前&#xff0c;大多…

【QT】QT中http協議和json數據的解析-http獲取天氣預報

QT中http協議和json數據的解析 1.http協議的原理2.QT中http協議的通信流程2.1 方法步驟 3.使用http協議&#xff08;通過http下載圖片和獲取天氣預報信息&#xff09;3.1 http下載網絡上的圖片(下載小文件)3.1.1 示例代碼3.1.2 現象 3.2 獲取網絡上天氣預報3.2.1 免費的天氣預報…

hot100:鏈表倒數k個節點- 力扣(LeetCode)

題目&#xff1a; 實現一種算法&#xff0c;找出單向鏈表中倒數第 k 個節點。返回該鏈表中倒數第k個節點。 示例一&#xff1a; 輸入&#xff1a;{1,2,3,4,5},2 返回值&#xff1a;{4,5} 說明&#xff1a;返回倒數第2個節點4&#xff0c;系統會打印后面所有的節點來比較。 …

Spring AI 實戰:第十一章、Spring AI Agent之知行合一

引言:智能體的知行辯證法 “知為行之始,行為知之成”,王陽明的哲學智慧在AI時代煥發光彩。智能體(LLM Agent)的進化之路,正是"認知-決策-執行"這一閉環的完美詮釋: 知明理:融合大語言模型的推理能力與知識圖譜的結構化認知行致用:基于ReAct模式的動態工具調…

365打卡第R6周: LSTM實現糖尿病探索與預測

&#x1f368; 本文為&#x1f517;365天深度學習訓練營中的學習記錄博客 &#x1f356; 原作者&#xff1a;K同學啊 &#x1f3e1; 我的環境&#xff1a; 語言環境&#xff1a;Python3.10 編譯器&#xff1a;Jupyter Lab 深度學習環境&#xff1a;torch2.5.1 torchvision0…

W-TinyLFU緩存驅逐算法解析

文章目錄 1. 背景與概述1.1 什么是緩存驅逐算法1.2 W-TinyLFU 的定義與價值 2. 核心思想與設計理念2.1 時間局部性與頻率局部性的結合2.2 高效的頻率統計2.3 窗口機制的引入 3. 架構設計與組件3.1 整體架構3.2 窗口緩存&#xff08;Window Cache&#xff09;3.3 主緩存&#xf…

[特殊字符] 人工智能大模型之開源大語言模型匯總(國內外開源項目模型匯總) [特殊字符]

Large Language Model (LLM) 即大規模語言模型&#xff0c;是一種基于深度學習的自然語言處理模型&#xff0c;它能夠學習到自然語言的語法和語義&#xff0c;從而可以生成人類可讀的文本。 所謂 "語言模型"&#xff0c;就是只用來處理語言文字&#xff08;或者符號…

文章記單詞 | 第60篇(六級)

一&#xff0c;單詞釋義 liar&#xff1a;英 [?la??(r)]&#xff1b;美 [?la??r]&#xff1b;n. 說謊者verbal&#xff1a;英 [?v??bl]&#xff1b;美 [?v??rbl]&#xff1b;adj. 言語的&#xff1b;文字的&#xff1b;口頭的&#xff1b;動詞的comprehension&…