C++類和對象進階:初始化列表和static成員深度詳解

C++類和對象:初始化列表和static成員深度詳解

  • 1. 前言
  • 2. 構造函數初始化成員變量的方式
    • 2.1 構造函數體內賦值
    • 2.2 初始化列表
      • 2.2.1 初始化列表的注意事項
    • 2.3 初始化列表的初始化順序
  • 3. 類的靜態成員
    • 3.1 引入
    • 3.2 靜態成員變量
    • 3.3 靜態成員函數
    • 3.4 靜態成員的注意事項
    • 3.5 靜態成員的另一個實踐場景
  • 4. 常見問題解答
  • 4. 總結對比

1. 前言

在C++面向對象編程中,構造函數初始化列表和靜態成員是提升代碼質量與安全性的重要特性。本文深度詳解關于C++構造函數的初始化列表C++類中的static成員,其中static成員包含static成員變量static成員函數,以及介紹static的相關實踐場景

2. 構造函數初始化成員變量的方式

2.1 構造函數體內賦值

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

class Date{
public://賦值方式初始化Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

我們可以利用以上構造函數,在創建一個對象的時候對該對象進行初始化。

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

2.2 初始化列表

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

例如:

class Date{
public://初始化列表初始化Date(int year, int month, int day): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
int main(){Date d1(2025, 2, 18);//傳入的參數會通過初始化列表的方式進行初始化。return 0;
}

2.2.1 初始化列表的注意事項

首先回顧一下往期文章提出的對默認構造函數的理解:
C++類和對象進階:構造函數和析構函數詳解

默認構造函數,以下三種函數都可以被稱作是默認構造函數。

  1. 無參構造函數。
  2. 全缺省構造函數。
  3. 我們沒寫編譯器默認生成的構造函數
  • 總結來說就是,不需要傳參的構造函數,都屬于是默認構造函數
  1. 無參構造函數。沒有參數,因此無需傳參。
  2. 全缺省構造函數。參數全缺省,不需要傳參。
  3. 我們沒寫編譯器默認生成的構造函數。編譯器生成的,我們無法顯示調用。自動調用時無需傳參。

注意

  1. 要把初始化列表理解成非靜態成員變量創建(占內存)的地方。類中只是成員變量的聲明
  2. 無論是否顯式指定初始化列表,都會走一遍初始化列表來對成員變量進行初始化。
    (靜態成員變量在類外進行初始化)
  1. 每個成員變量在初始化列表中只能出現一次(初始化只能初始化一次)
  2. 類中包含以下成員,必須放在初始化列表位置進行初始化:
    • 引用成員變量
    • const成員變量
    • 自定義類型成員(且該類沒有默認構造函數時)

分析這三類變量的特征:

  • 引用成員變量:引用成員變量的初始化是“綁定”過程,而非賦值操作。構造函數體內無法完成初始化(此時成員已定義,只能賦值),因此必須在初始化列表(引用變量創建時)初始化
  • const成員變量const成員變量的初始化是“定義時賦值”,構造函數體內無法修改其值。const變量在定義后其值不可修改,因此必須在創建時初始化,也就是必須在初始化列表初始化
  • 自定義類型成員(該類沒有默認構造函數時):編譯器只會調用默認構造函數,若類沒有默認構造函數(即無參構造函數或所有參數均有默認值的構造函數或編譯器自己生成的構造函數),也就說程序員自己寫了構造函數且調用時需要顯式傳參,則必須顯式調用其某個構造函數,該工作在初始化列表中進行。

對比以上三類特殊情形:

變量類型核心特征初始化方式未正確初始化的后果
引用成員變量必須綁定對象,不可重新綁定初始化列表中綁定編譯錯誤(未初始化引用)
const成員變量不可修改,需定義時賦值初始化列表中賦值編譯錯誤(未初始化常量)
自定義類型成員(無默認構造函數)必須顯式調用構造函數初始化列表中調用帶參構造函數編譯錯誤(找不到默認構造函數)

2.3 初始化列表的初始化順序

成員變量在類中聲明次序就是其在初始化列表中的初始化順序與其在初始化列表中的先后次序無關

我們來看如下代碼:

class MyClass {
public:MyClass(int init_value): _value1(init_value)   // 類中的聲明順序決定初始化順序, _value2(_value1)  // _value2先聲明,會先用 _value1 初始化 _value2// 此時 _value1 還未完成初始化,是隨機值{}void Print() const {cout << "_value1: " << _value1<< "  _value2: " << _value2 << endl;}
private:int _value2;     int _value1;
};
int main() {MyClass obj(5);obj.Print();  // 輸出:_value1: 5  _value2: 隨機值return 0;
}

在這里插入圖片描述

結論:

  • 初始化列表中,變量初始化的順序應該和變量在類中聲明的次序保持一致
  • 盡量使用初始化列表對成員變量進行初始化,因為不管程序員是否使用初始化列表,對于自定義類型成員變量,一定會先試用初始化列表初始化。
  • 不能在初始化列表中完成的,在函數體內用語句來完成(如開辟空間后對指針的檢查等)

初始化列表總結:

  • ?論是否顯式寫初始化列表,每個構造函數都有初始化列表
  • ?論是否在初始化列表顯式初始化成員變量,每個成員變量都要?初始化列表初始化
    在這里插入圖片描述

3. 類的靜態成員

3.1 引入

設計一個類,計算程序中創建了多少個該類的類對象

#include <iostream>
//設計一個程序,統計當前正在使用的某個對象有多少個
int _scount = 0;	//我們可以利用全局變量class A {
public:A() { ++_scount; }	//構造函數A(const A& t) { ++_scount; }	//拷貝構造函數~A() { --_scount; }		//析構函數
};
int main() {cout << __LINE__ << ": " << _scount << endl;	// 是 1 ,此處還沒進入Func函數,static 對象還沒創建A aa1;Func();	//3Func(); //3return 0;
}

在這里插入圖片描述
以上程序確實可以實現統計,但全局變量有極大的缺陷:

void Func() {static A aa2;	//局部靜態對象,只會創建一次,不在函數棧幀內,在靜態區cout << __LINE__ << ": " << _scount << endl;	//3//全局變量的劣勢:任何地方都可以隨意改變,不安全//_scount++;
}

因此,我們想到了利用類來對計數器進行封裝,并將計數器設置成靜態成員變量。

什么是靜態成員?

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

利用靜態成員實現的類:

class A {
public:A(){ ++_scount;}A(const A& t) { ++_scount; }~A() { --_scount; }
private:static int _scount;		//此處只是該成員變量的聲明//類內的靜態成員,相當于用類去封裝全局變量
};
//全局位置,類外定義   類的 static 成員,類內聲明,類外初始化  static 成員定義時不受訪問限定符的限制
int A::_scount = 0;
  • 需要尤其注意,靜態成員變量,在類內是聲明,需要在類外進行初始化:int A::_scount = 0;這是規定的寫法,通過類作用域限定符來訪問

3.2 靜態成員變量

private:// 非靜態成員變量 ----- 屬于每一個類對象, 存儲在對象里面int _a1 = 1;	//成員變量給缺省值,會自動進入初始化列表int _a2 = 2;// 靜態成員變量 ----- 屬于類,類的每個對象共享,存儲在靜態區, 生命周期是全局的,不能用初始化列表初始化static int _scount;
};

需要注意:靜態成員變量和非靜態成員變量存儲的位置不同。

  • 靜態成員變量:屬于類內,類的每個對象共享,存儲在靜態區, 生命周期是全局的,程序運行期間持續存在,不能用初始化列表初始化。
  • 非靜態成員變量: 屬于每一個類對象, 存儲在對象里面

3.3 靜態成員函數

靜態成員函數一般是和靜態成員變量成對出現的。
我們在類中添加以下函數方便我們獲取_scount的值

public:
static int GetACount() {return _scount;
}

靜態成員函數的特點:

  • 沒有this指針
  • 指定類域和訪問限定符就可以訪問
  • 可以直接訪問類內的靜態成員變量

通過指定類域和訪問限定符訪問靜態成員函數。

int main(){//由于靜態成員變量是私有的//可以通過靜態成員函數來訪問靜態成員變量cout << A::GetACount() << endl;return 0;
}

因此我們可以得出:

  1. 靜態成員函數,不能訪問類內的非靜態成員變量,因為沒有this指針(沒有傳入調用對象的地址)
  2. 靜態成員函數不能調用非靜態成員函數,非靜態成員函數的調用需要傳遞this指針,但static成員函數沒有this指針

3.4 靜態成員的注意事項

  1. 靜態成員為所有類對象所共享,不屬于某個具體的對象,存放在靜態區
  2. 靜態成員變量必須在類外定義,定義時不添加static關鍵字,類中只是聲明。類內聲明,類外初始化
  3. 類靜態成員(成員變量和成員函數)可用 類名::靜態成員 或者 對象.靜態成員 來訪問
  4. 靜態成員函數沒有隱藏的this指針,不能用const修飾不能訪問任何非靜態成員
  5. 靜態成員也是類的成員,受public、protected、private 訪問限定符的限制
  6. 靜態成員變量:不能是auto推導類型。

在這里插入圖片描述

  • 核心特點
    • 靜態成員函數無this指針,無法訪問非靜態成員
    • 靜態成員函數可直接調用無需實例化

3.5 靜態成員的另一個實踐場景

靜態成員函數的妙用
設計一個類,在類外只能在 棧 上創建對象
設計一個類,在類外只能在 堆 上創建對象

我們可以這樣做

class Obj {
public://通過類作用域限定符來調用函數獲取對象static Obj GetStackObj() {Obj obj;return obj;}static Obj* GetHeapObj() {Obj* obj = new Obj;return obj;}//構造函數私有化,防止直接調用構造函數在棧或堆上創建對象。
private:Obj(){}
private:int _a1 = 1;int _a2 = 2;
};
int main() {//這三種方式都會調用構造函數,我們將構造函數私有化后,就無法再類外創建 堆/棧 上的對象了/*static OBj o1;OBj o2;Obj* o3 = new Obj;*///提供對外的接口//無需創建對象,通過類作用域限定符來調用靜態成員函數。Obj obj_1 = Obj::GetStackObj();Obj* p_obj = Obj::GetHeapObj();return 0;
}
  • 實現原理
    1. 私有化構造函數
    2. 通過靜態工廠方法控制對象創建

4. 常見問題解答

Q1:為什么靜態成員變量必須類外初始化?

  • 靜態成員不屬于單個對象,類內聲明僅表示存在性,需在程序全局空間進行內存分配

Q2:靜態成員函數能否調用非靜態成員函數?

  • 不能。非靜態成員函數隱含this指針參數,而靜態函數無this指針

Q3:如何選擇初始化列表與構造函數體?

  • 優先使用初始化列表,特別是對于const/引用成員/無默認構造函數的自定義類型等必須初始化的場景

4. 總結對比

特性初始化列表靜態成員
作用對象對象成員初始化類級別共享數據/操作
關鍵優勢處理特殊類型成員初始化減少全局變量使用
典型應用場景const/引用成員初始化計數器、工具類函數
內存管理對象內存空間靜態存儲區

以上就是本文的所有內容了,碼字整理不易,歡迎各位大佬在評論區留言交流

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

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

相關文章

ubuntu ffmpeg 安裝踩坑

ffmpeg 安裝踩坑 安裝命令: sudo apt update sudo apt install ffmpeg如果以上命令沒有報錯&#xff0c;那么恭喜你很幸運&#xff0c;可以關閉這篇文章了&#xff01; 如果跟我一樣&#xff0c;遇到如下報錯&#xff0c;可以接著往下看&#xff1a; 報錯信息&#xff1a; …

第13章 int指令

目錄 13.1 int 指令13.2 編寫供應用程序調用的中斷例程13.3 對int、iret和棧的深入理解13.4 BIOS和DOS所提供的中斷例程13.5 BIOS和DOS中斷例程的安裝過程13.6 BIOS中斷例程應用13.7 DOS中斷例程應用實驗13 編寫、應用中斷例程 中斷信息可以來自CPU的內部和外部&#xff0c;當C…

最新扣子(Coze)案例教程:全自動DeepSeek 寫影評+批量生成 + 發布飛書,提效10 倍!手把手教學,完全免費教程

&#x1f468;?&#x1f4bb;群里有同學是做影視賽道的博主&#xff0c;聽說最近DeepSeek這么火&#xff0c;咨詢能不能用DeepSeek寫影評&#xff0c;并整理電影數據資料&#xff0c;自動發布到飛書文檔&#xff0c;把每天的工作做成一個自動化的流程。 那今天斜杠君就為大家…

DeepSeek 提示詞:定義、作用、分類與設計原則

&#x1f9d1; 博主簡介&#xff1a;CSDN博客專家&#xff0c;歷代文學網&#xff08;PC端可以訪問&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移動端可微信小程序搜索“歷代文學”&#xff09;總架構師&#xff0c;15年工作經驗&#xff0c;精通Java編…

鳥語林-論壇系統自動化測試

文章目錄 一、自動化實施步驟1.1編寫Web測試用例1.2 編寫自動化代碼1.2.1 LoginPageTest1) 能否正確打開登錄頁面2) 點擊去注冊能否跳轉注冊頁面3) 模擬用戶登錄&#xff0c;輸入多組登錄測試用例 1.2.2 RegisterPageTest1) 能否成功打開注冊頁面2) 注冊測試用例3) 點擊去登錄按…

DeepSeek模型量化

技術背景 大語言模型&#xff08;Large Language Model&#xff0c;LLM&#xff09;&#xff0c;可以通過量化&#xff08;Quantization&#xff09;操作來節約內存/顯存的使用&#xff0c;并且降低了通訊開銷&#xff0c;進而達到加速模型推理的效果。常見的就是把Float16的浮…

本周行情——250222

本周A股行情展望與策略 結合近期盤面特征及市場主線演化&#xff0c;本周A股預計延續結構性分化行情&#xff0c;科技成長與政策催化板塊仍是資金主戰場&#xff0c;但需警惕高標股分歧帶來的波動。以下是具體分析與策略建議&#xff1a; 1. 行情核心驅動因素 主線延續性&…

【JT/T 808協議】808 協議開發筆記 ② ( 終端注冊 | 終端注冊應答 | 字符編碼轉換網站 )

文章目錄 一、消息頭 數據1、消息頭拼接2、消息 ID 字段3、消息體屬性 字段4、終端手機號 字段5、終端流水號 字段 二、消息體 數據三、校驗碼計算四、最終計算結果五、終端注冊應答1、分解終端應答數據2、終端應答 消息體 數據 六、字符編碼轉換網站 一、消息頭 數據 1、消息頭…

使用ezuikit-js封裝一個對接攝像頭的組件

ezuikit-js 是一個基于 JavaScript 的視頻播放庫&#xff0c;主要用于在網頁中嵌入實時視頻流播放功能。它通常用于與支持 RTSP、RTMP、HLS 等協議的攝像頭或視頻流服務器進行交互&#xff0c;提供流暢的視頻播放體驗。 主要功能 多協議支持&#xff1a;支持 RTSP、RTMP、HLS …

一周學會Flask3 Python Web開發-flask3模塊化blueprint配置

鋒哥原創的Flask3 Python Web開發 Flask3視頻教程&#xff1a; 2025版 Flask3 Python web開發 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili 我們在項目開發的時候&#xff0c;多多少少會劃分幾個或者幾十個業務模塊&#xff0c;如果把這些模塊的視圖方法都寫在app.py…

DSC數字選擇性呼叫

GMDSS Digital Selective Calling WAVECOM Decoder Online Help 12.0.0 VHF Marine GMDSS/DSC Decode & Scicos Simulation Black Cat Systems &#xff08;一&#xff09;DSC調制方式 DSC&#xff08;Digital Selective Calling&#xff0c;數字選擇性呼叫&#xff0…

科普:你的筆記本電腦中有三個IP:127.0.0.1、無線網 IP 和局域網 IP;兩個域名:localhost和host.docker.internal

三個IP 你的筆記本電腦中有三個IP&#xff1a;127.0.0.1、無線網 IP 和局域網 IP。 在不同的場景下&#xff0c;需要選用不同的 IP 地址&#xff0c;如下為各自的特點及適用場景&#xff1a; 127.0.0.1&#xff08;回環地址&#xff09; 特點 127.0.0.1 是一個特殊的 IP 地…

《AI與NLP:開啟元宇宙社交互動新紀元》

在科技飛速發展的當下&#xff0c;元宇宙正從概念逐步走向現實&#xff0c;成為人們關注的焦點。而在元宇宙諸多令人矚目的特性中&#xff0c;社交互動體驗是其核心魅力之一。人工智能&#xff08;AI&#xff09;與自然語言處理&#xff08;NLP&#xff09;技術的迅猛發展&…

量化方法bitsandbytes hqq eetq區別

量化方法bitsandbytes、HQQ&#xff08;Half-Quadratic Quantization&#xff09;和EETQ&#xff08;Efficient and Effective Ternary Quantization&#xff09;在深度學習模型壓縮和加速中各有特點&#xff0c;以下是它們的區別&#xff1a; 1. bitsandbytes 概述: bitsand…

Hutool - Log:自動識別日志實現的日志門面

一、簡介 在 Java 開發中&#xff0c;日志記錄是一項非常重要的功能&#xff0c;它可以幫助開發者在開發和生產環境中監控程序的運行狀態、排查問題。然而&#xff0c;Java 生態系統中有多種日志實現框架&#xff0c;如 Log4j、Logback、JDK 自帶的日志框架等。為了在不同的項…

偽404兼容huawei生效顯示404

根據上述思考&#xff0c;以下是詳細的中文分步說明&#xff1a; --- **步驟 1&#xff1a;獲取目標設備的User-Agent信息** 首先&#xff0c;我們需要收集目標設備的User-Agent字符串&#xff0c;包括&#xff1a; 1. **iPhone設備的User-Agent**&#xff1a; Mozi…

github配置sshkey

使用命令生成sshkey ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 依此會要求輸入以下信息&#xff0c;可以使用默認值 設置保存密鑰的路徑 設置SSH密鑰密碼&#xff08;備注&#xff1a;空內容表示不設置SSH密鑰密碼&#xff09; 再次確認SSH密鑰密…

深入理解WebSocket接口:如何使用C++實現行情接口

在現代網絡應用中&#xff0c;實時數據傳輸變得越來越重要。通過WebSocket&#xff0c;我們可以建立一個持久連接&#xff0c;讓服務器和客戶端之間進行雙向通信。這種技術不僅可以提供更快的響應速度&#xff0c;還可以減少不必要的網絡流量。本文將詳細介紹如何使用C來實現We…

FFMPEG編碼容錯處理解決辦法之途徑----升級庫文件

在qt開發環境下接收網絡數據&#xff0c;調用ffmpeg解碼播放視頻&#xff0c;出現閃屏現象&#xff0c;具體現象可以使用操作系統自帶的ffplay播放器播放原始視頻流可復現&#xff1b;而使用操作系統自帶的mpv播放器播放視頻則不會出現閃屏&#xff1b;閃屏時會報Could not fin…

什么是超越編程(逾編程)(元編程?)

超越編程(逾編程)(元編程&#xff1f;)(meta-programming) 目錄 1. meta- 的詞源 2. 逾編程(meta-programming) 的直實含義 2.1 定義 2.2 說明 3. 翻譯成“元編程”應該是一種錯誤 1. meta- 的詞源 這是一個源自希臘語的構詞元素&#xff0c;其有三種含義&#xff…