C++之路:類基礎、構造析構、拷貝構造函數

目錄

  • 前言
  • 從結構體到類
  • 類的聲明與使用
    • 基礎聲明
    • 繼承聲明
    • 數據與函數聲明與調用
    • 聲明
      • 調用
  • 類的訪問修飾符
  • 類對象的內存分布
  • 類內數據相關
    • 靜態變量
    • 非靜態變量
  • 類成員函數相關
    • 普通成員函數
    • 友元函數
    • 構造與析構函數
      • 構造函數
      • 析構函數
    • 拷貝構造函數
      • 總結

前言

?面向對象編程有三大特性,分別是封裝繼承多態。這些特性在類的語法使用中都得到了充分的體現,我也預計寫幾篇文章來介紹一下C++的類語法。這是第一篇:類基礎。
?類基礎主要目的是介紹一下類的基礎使用,重點在于數據函數的封裝。

從結構體到類

?對于新手來說 這個概念可能比較陌生,但是提到結構體(struct)對于有C語言基礎的人應該比較熟悉。在C++中結構體和類的底層實現幾乎完全一致,結構體不僅可以封裝函數,甚至還可以繼承類。

#include <iostream>
#include <cstring>
using namespace std;    class Base_Class{
protected:int id; //占用大小為4字節的內存空間
public:Base_Class(int i=0){id = i;cout << "Base_Class constructor called." << endl;cout << "id = " << id << endl;}
};struct Derived_struct : public Base_Class
{Derived_struct(int i) : Base_Class(i) {cout << "Derived_Struct constructor called." << endl;cout << "id = " << id << endl;}
};int main() {Derived_struct obj(10);return 0;
}

輸出結果為:

Base_Class constructor called.
id = 10
Derived_Struct constructor called.
id = 10

?是不是對類的感覺親切了許多?當然在C語言中結構體里是不能封裝函數的,也沒有繼承這個說法。數據和函數不封裝到一起,那么當我處理數據的時候(比如結構體)就要去其它地方找函數(公共聲明函數的地方),而不是通過對象直接可以調用處理的函數。
?打個比方,就像用勺子吃西瓜:C語言買了西瓜后要到廚房去找勺子才能吃西瓜,而C++封裝的特性就是讓買西瓜時,西瓜和勺子綁定在一起出售。因而C語言是面向過程編程的,C++是面向對象編程
?當然了,結構體和類底層實現相同,這不意味著在C++中結構體和類能混用。從規范上來說,結構體用于數據聚合,基本上不封裝方法,就像C那樣(例如坐標點封裝,顏色封裝)而類則用于?對象進行封裝?,包括數據以及相關的方法

類的聲明與使用

基礎聲明

類的基礎語法聲明如下:
來源菜鳥教程
舉例說明:

class Box
{public:double length;   // 盒子的長度double width;  // 盒子的寬度double height;   // 盒子的高度int get_volume(){ //獲得盒子的體積return length*width*height;}
};

?類里面就兩樣東西:數據+方法

繼承聲明

繼承的語法如下:

class {derived_class} : {access_specifier} {base_class}

?其中,derived_class是派生類的名稱,base_class是基類名稱,二者之間通過: + 訪問修飾符連接。訪問修飾符 access-specifier 是 public、protected 或 private 其中的一個,如果未使用訪問修飾符 access-specifier,則默認為 private繼承。

// 基類
class Animal {
xxxx
};
//派生類
class Dog : public Animal {
xxxx
};

數據與函數聲明與調用

聲明

?類內變量與成員函數的聲明語法類外一致,值得注意的是成員函數的具體實現可以放在類外。具體方法如下:

  1. 首先在類內聲明這個函數:
class Dog {
public:void bark(); //類內聲明
};
  1. 然后在類外以范圍解析運算符(::) + 函數名的形式進行定義:
//類外實現
void Dog::bark(){cout << "汪汪汪" <<endl;
}

調用

?類的變量和函數的調用方法與結構體類似:

class Dog{
public: int age=10;//數據void bark() //方法{cout << "汪汪汪" <<endl;}
};
  1. 如果是類對象實例,使用.運算符訪問成員變量和函數方法:
Dog wangcai;
wangcai.age; //訪問變量
wangcai.bark(); //訪問函數
  1. 如果是類指針,使用->運算符訪問成員變量和函數方法:
Dog* wangcai;
wangcai->age; //訪問變量
wangcai->bark(); //訪問方法

類的訪問修飾符

?訪問修飾符用來控制外界對類內變量以及成員函數(類成員)的訪問權限。關鍵字 public、private、protected 稱為訪問修飾符。

  1. public(公有)
    ?該成員變量/方法可以被外部的函數直接訪問。這是權限最低的一種,類內、派生類以及類外都能訪問往往用于對外接口上
  2. protected(保護)
    ?保護的類成員只能被類內以及派生類訪問,類外無法訪問。
  3. private(私有)
    ?如果沒有聲明訪問權限,私有是默認的訪問修飾符。是保護程度最高的一種,只能類內函數訪問,派生類和類外都是不能訪問的。往往只用于給內部成員函數數據交換

上面的內容總結來說就是:類內成員函數訪問類內的變量/方法時沒有任何限制派生類能訪問public和protected類外的普通函數就只能訪問public下的變量/方法(友元函數除外)

#include <iostream>
using namespace std;class Example {
public:    // 公有成員int publicVar;void publicMethod() {cout << "Public method" << endl;}private:   // 私有成員int privateVar;void privateMethod() {cout << "Private method" << endl;}protected: // 保護成員int protectedVar;void protectedMethod() {cout << "Protected method" << endl;}
};

類對象的內存分布

?首先要明確一下,類本身是一個抽象的概念,是不占用物理內存的。例如:

class Dog{
public: int age=10;//數據void bark() //方法{cout << "汪汪汪" <<endl;}
};

?如果不實例化這個類(Dog wangcai)是不會有內存占用的。提這個點是為了解答后面學習多態時,有的教材會說虛函數表存儲在類里面,這樣容易引起誤解,類就像int 、float那樣,如果不實例化是不會創造出內存空間的。

類對象實例的內存分布如下:

  1. 最開頭: 虛函數表指針。
  2. 非靜態成員變量。(按照聲明順序依次排布)

此外,

  • 對于靜態成員變量:存儲在全局數據區,不占用類實例內存空間。
  • 對于成員函數:代碼存儲在代碼段,所有對象共享同一份函數代碼。
  • 對于虛函數:虛函數實現也和成員函數一樣存儲在代碼段。
  • 對于虛函數表:虛函數表在可執行文件中位于.rodata段(Linux/Unix)或.rdata段(Windows),程序加載后存于內存的?常量區?,具有只讀屬性。

類內數據相關

靜態變量

?靜態變量是類內以static為修飾符定義的變量。要點如下:

  1. 對于類中的靜態變量來說,所有對象共享同一個靜態變量,修改會影響所有實例。(即存儲位置在全局數據區)
  2. 靜態變量的生命周期?在程序啟動時初始化,程序結束時銷毀。
  3. 靜態變量在類內聲明在類外定義
  4. 推薦使用類名訪問(推薦):ClassName::staticVar

示例代碼:

class Person {
public:static int count;  // 靜態變量聲明
};
int Person::count = 0;  // 必須在類外定義Person p1;
Person::count++;  // 推薦:通過類名訪問
p1.count++;       // 不推薦:通過對象訪問(合法但不清晰)

非靜態變量

?又被稱為實例變量,是指類內定義的普通成員變量。有以下特性:

  1. 每個對象獨立存儲?,不同對象的實例變量互不影響。(即存儲在實例的內存區域)
  2. 生命周期?:隨對象創建而分配,隨對象銷毀而釋放。
  3. 必須通過對象實例進行訪問。

舉例:

class Person {
public:std::string name;  // 實例變量int age;           // 實例變量
};
Person p1;
p1.name = "Alice";  // 訪問實例變量

類成員函數相關

普通成員函數

?要點:

  1. 普通成員函數暗含this指針,可以無需聲明使用成員變量
  2. 成員函數的調用要通過對象實例進行,不能單獨拎出來使用。

友元函數

?友元函數本質就是類外聲明定義的函數,不與類對象綁定,即在類內用friend聲明的外部函數,能突破封裝性直接訪問類的私有和保護成員,使其數據訪問權限等同于成員函數(也就是說可以訪問類的私有變量與函數)。
要點:

  1. 必須在類內使用friend修飾符進行聲明。
  2. 具體的實現放在類外。

示例:

class MyClass {
private:int secret;
public:friend void peek(MyClass& obj); // 聲明友元函數
};
void peek(MyClass& obj) { obj.secret = 42; } // 實現可訪問私有成員

構造與析構函數

#include <iostream>
using namespace std;class MyClass {
public:int value;// 構造函數MyClass(int v) {value = v;cout << "構造函數被調用,value=" << value << endl;}// 析構函數~MyClass() {cout << "析構函數被調用,value=" << value << endl;}
};int main() {MyClass obj1(10);  // 構造函數調用{MyClass obj2(20);  // 構造函數調用}  // obj2離開作用域,析構函數調用return 0;
}  // obj1離開作用域,析構函數調用

構造函數

?構造函數是一種特殊的成員函數,有以下要點:

  1. 無任何返回值(連void也沒有),可以傳入參數
  2. 函數名與類名相同。
  3. 在類對象創建時執行一次,主要用來初始化類對象
  4. 不被繼承,即子類的構造函數中必須要先手動調用父類的構造函數(無參構造時編譯器會自動調用)。

?構造函數還有一個初始化列表的語法,可以用來初始化字段。例如上面的構造函數可以改為:

MyClass(int v):value(v){cout << "構造函數被調用,value=" << value << endl;
}

如果MyClass繼承自BaseClass,初始化列表的語法也可以用來初始化構造函數,可以寫成這樣下面這樣,以符合第四點的要求:

MyClass(int v):BaseClass(Base_args),value(v){cout << "構造函數被調用,value=" << value << endl;
};

(初始化列表時,用逗號,隔開各個參數)

析構函數

?析構函數與構造函數相呼應,就是類對象在銷毀時自動調用的函數。(例如,程序結束時、局部對象離開作用域時)。要點如下:

  1. 無任何返回值(void也沒有)以及不傳入任何參數!
  2. 函數名為~+類名
  3. 對象銷毀時自動執行,用來釋放類對象手動創建的內存空間,避免內存泄漏
  4. 同樣不被繼承,但是自動調用(因為是無參的,編譯器會自動調用)。順序為先調用派生類的析構,再調用基類的析構

拷貝構造函數

?為什么需要額外的拷貝構造函數?其實想想就能清楚,一個類對象的創建只能有兩種途徑:

  1. 自定義。使用默認的構造函數進行初始化。
  2. 從其它對象中復制。這時就要調用拷貝構造函數而不是構造函數進行初始化了

why?拷貝構造函數必須要寫嗎?為什么之前寫的類沒有拷貝構造函數也能編譯通過?
回答這個問題首先要了解一下淺拷貝和深拷貝。
?其實C++中數據無非被分為兩種:普通變量和指針變量,對于淺拷貝來說,執行的就是簡單的賦值操作,不區分指針和值,這樣也就會造成一種情況:對象A里有個指針p,p指向某個地址。如果此時簡單的使用Class B = A這樣初始化對象B就是淺拷貝。那么對象B內的指針p的值和A當中指針p的值是一樣的(淺拷貝只是簡單賦值),這樣就會造成對象A和B的指針p指向同一塊內存空間,如果B中的p改動了指向的變量(*p),那么A中p指向的值也會隨之改變,在某些情況下這是很危險的。(即淺拷貝會造成多個指針指向同一個內存地址的情況)。
?對于深拷貝來說,就會區分普通變量和指針變量。對于普通變量,如int,float等就直接把值復制過去就行了。對于指針變量則會為指針成員分配新內存并復制內容,但是這一過程必須要手動實現,這就依賴我們的拷貝構造函數了!
?下面從使用場合、語法規則兩方面總結拷貝構造函數:

  1. 使用場合:需要以一個已經實例化的類對象為基礎,創建一個新的類對象(所謂構造),并且類的成員變量中有指針類型時,需要自定義拷貝構造函數為指針成員分配新內存,并將值賦值到新內存空間中(所謂深拷貝)。
    ?若未顯式定義拷貝構造函數,編譯器會生成默認拷貝構造函數執行?淺拷貝?(逐成員復制值)。拷貝構造二者缺一不可,如果僅僅是普通的賦值,例如B = A,那么默認觸發淺拷貝,此時如果需要深拷貝應該要在類內重載運算符=

  2. 語法規則:
    定義:

Class_name(const Class_name& obj)

?簡單來說就是構造函數的基礎上,固定傳入的參數必須為同類對象的?常量引用?(ClassName(const ClassName& obj)),若使用值傳遞(ClassName(const ClassName obj))會導致無限遞歸調用。

使用:

MyClass obj2 = obj1; // 隱式調用,此時=表示初始化而非賦值
MyClass obj3(obj1); // 顯式調用

示例:

class DeepString {
public:char* data;DeepString(const DeepString& other) {data = new char[strlen(other.data) + 1]; //重新分配地址strcpy(data, other.data);  // 復制內容而非指針}
};

總結

?拷貝和構造缺一不可:

  1. 少了拷貝就會導致多個指針指向同一個地址。
  2. 少了構造就是普通的賦值操作,是在已存在對象間進行賦值(a = b)不屬于根據一個已有對對象初始化一個新對象。

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

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

相關文章

黑神話悟空游戲輿情分析

完整項目包點擊文末名片 黑神話悟空上線初期輿情分析 背景 《黑神話&#xff1a;悟空》在上線首日便創下了全球游戲行業的多項新紀錄&#xff0c;包括Steam同時在線人數超過222萬&#xff0c;全渠道總銷量超過450萬份&#xff0c;總銷售額超過15億元。本項目旨在對 3A 游戲《黑…

python的or-tools算法踩坑

debug模式代碼好的,然后正常運行不行(用的PyCharm) 不知道為什么debug模式這個可以的,但是正常模式不行 用or-tools算路徑的時候 因為要多次到達同一個點,但是or-tools不支持,所以弄了虛擬點和真實點的距離是0,但是實際上如果虛擬點到真實點為0的話or-tools結果秒出,但是實…

docker-compose一鍵部署全棧項目。springboot后端,react前端

部署總覽前端打包: 我們將配置 package.json&#xff0c;使用 npm run build (內部調用 vite build) 來打包。這個過程將完全在 Docker 構建鏡像的過程中自動完成&#xff0c;你的主機上甚至不需要安裝 Node.js。后端打包: 我們將配置 pom.xml&#xff0c;使用 mvn clean packa…

MCMC:高維概率采樣的“隨機游走”藝術

MCMC&#xff08;馬爾可夫鏈蒙特卡洛&#xff09; 是一種從復雜概率分布中高效采樣的核心算法&#xff0c;它解決了傳統采樣方法在高維空間中的“維度災難”問題。以下是其技術本質、關鍵算法及實踐的深度解析&#xff1a; 本文由「大千AI助手」原創發布&#xff0c;專注用真話…

HarmonyOS免密認證方案 助力應用登錄安全升級

6月21日&#xff0c;2025年華為開發者大會"安全與隱私分論壇"在松山湖順利舉辦。本論壇聚焦App治理與監管、星盾安全2.0的核心能力等進行深度分享與探討。其中&#xff0c;HarmonyOS Passkey免密認證方案作為安全技術創新成果備受矚目。該方案基于FIDO協議實現&#…

flutter flutter_vlc_player播放視頻設置循環播放失效、初始化后獲取不到視頻寬高

插件&#xff1a;flutter_vlc_player: ^7.4.3 問題1&#xff1a;設置循環播放_controller.setLooping(true);無用。 解決方法&#xff1a; //vlcPlayer設置循環播放失效&#xff0c;以這種方式失效循環播放 _setLoopListener() async {if (_videoController!.value.hasError…

React與Vue的主要區別

React和Vue都是當今最流行、最強大的前端Javascript框架&#xff0c;它們都能構建出色的單頁面應用。 以下是React和Vue的主要區別&#xff1a; React&#xff1a; React官方自稱是一個用于構建用戶界面的JavaScript庫&#xff08;尤其是UI組件&#xff09;。它專注于視圖層。…

瀏覽器原生控件上傳PDF導致hash值不同

用戶要求對上傳的pdf計算hash排重&#xff0c;上線后發現排重失敗 1、postman直接調用接口沒有發現問題&#xff0c;每次獲取的hash值是一樣的 2、apifox網頁版&#xff0c;調用接口發現問題&#xff0c;清除緩存后&#xff08;將選擇的文件刪除重新選擇&#xff09;&#xf…

.net 的依賴注入

依賴注入(Dependency Injection,簡稱 DI)是一種軟件設計模式,旨在將對象之間的依賴關系從代碼內部解耦出來,通過外部提供的方式來建立依賴關系,從而提高軟件的可維護性、可測試性和可擴展性。以下從概念、工作原理、常見類型、在不同框架中的應用等方面進行詳細解釋: 概…

【unitrix】 4.17 類型級別右移運算實現解析(shr.rs)

一、源碼 這段代碼實現了類型級別的右移運算&#xff08;>>&#xff09;&#xff0c;包括對整數和定點數的支持。 /*類型級別的右移運算實現* 編制人: $ource* 修改版次:0版完成版* 本版次創建時間: 2025年6月27日* 最后修改時間: 2025年7月2日 Sub1修改* 待完善問題&a…

Java 并發核心:AQS(AbstractQueuedSynchronizer) 詳解

一、什么是 AQS&#xff1f; AbstractQueuedSynchronizer&#xff08;簡稱 AQS&#xff09;是 Java 并發包 java.util.concurrent.locks 中的一個核心同步框架&#xff0c;用于構建鎖和同步器&#xff0c;如&#xff1a; ReentrantLockReentrantReadWriteLockCountDownLatchS…

LoRA 實戰指南:NLP 與 CV 場景的高效微調方法全解析

大模型已成 AI 應用的“標配”&#xff0c;但高昂的訓練和部署成本讓很多企業望而卻步。LoRA&#xff08;Low-Rank Adaptation&#xff09;作為一種輕量級微調方案&#xff0c;正成為 NLP 與 CV 場景中低成本定制的利器。本文詳細通過詳細介紹 LoRA 的核心原理、在文本與圖像任…

centos指令

一、python 1、查詢運行中腳本 ps aux | grep python2、關閉腳本進程 kill -15 <PID> # 優雅終止&#xff08;SIGTERM&#xff09; kill -9 <PID> # 強制終止&#xff08;SIGKILL&#xff09;

用c語言寫一個找兩張圖片不同處的游戲

回答 思考完成 我們計劃開發一個用C語言實現的查找兩張圖片不同之處的游戲。這個程序需要加載兩張圖片&#xff0c;然后允許用戶通過點擊等方式標記出差異的位置。我們將使用一個簡單的圖形庫&#xff08;例如SDL&#xff09;來處理圖像和用戶交互。步驟&#xff1a;1.加載兩…

【深度學習:進階篇】--4.3.seq2seq與Attention機制

學習目標 目標 掌握seq2seq模型特點掌握集束搜索方式掌握BLEU評估方法掌握Attention機制 應用 應用Keras實現seq2seq對日期格式的翻譯 目錄 學習目標 1.seq2seq 1.1.定義 1.2.條件語言模型理解 1.3.應用場景 2.注意力機制 2.1.長句子問題 2.2.定義 2.3.公式 3.機器…

MYSQL與PostgreSQL的差異

一、架構設計的根本差異 進程模型 vs 線程模型 ?PostgreSQL?&#xff1a;采用多進程架構&#xff08;每個連接獨立進程&#xff09;&#xff0c;通過共享內存通信。優勢在于進程隔離性強&#xff0c;單連接崩潰不影響整體服務&#xff0c;但資源消耗較高。 ?MySQL?&…

Wpf布局之StackPanel!

文章目錄 前言一、引言二、使用步驟 前言 Wpf布局之StackPanel&#xff01; 一、引言 StackPanel面板在水平或垂直的堆棧中放置元素。這個布局容器通常用于更大、更復雜窗口中的一些區域。 二、使用步驟 StackPanel默認是垂直堆疊 <Grid><StackPanel><Butt…

【MySQL】 內置函數

目錄 1.時間函數2.字符串函數3.數學函數4.其他函數 1.時間函數 函數名稱描述current_date()當前日期current_time()當前時間current_timestamp()當前時間戳date(datetime)返回datetime參數的日期部分date_add(date,interval d_value_type)在date中添加日期/時間&#xff0c;in…

【RK3568+PG2L50H開發板實驗例程】Linux部分/FAN 檢測案例

本原創文章由深圳市小眼睛科技有限公司創作&#xff0c;版權歸本公司所有&#xff0c;如需轉載&#xff0c;需授權并注明出處&#xff08;www.meyesemi.com) 1.案例簡介 本案例旨在介紹如何測試開發板上風扇接口控制風扇啟停與調速功能 2. FAN接口介紹 開發板上 FAN接口是一個…

Spring AI ETL Pipeline使用指南

前言&#xff08;Introduction&#xff09; 版本聲明&#xff1a;本文基于 Spring AI 1.0.0 版本編寫。由于 Spring AI 目前仍處于活躍開發階段&#xff0c;API 和組件可能在后續版本中發生變化&#xff0c;請注意及時關注官方文檔更新以保持兼容性。 在當今大數據和人工智能快…