C++基礎精講-05

文章目錄

  • 1.構造函數初始化列表
    • 1.1 初始化列表的使用
    • 1.2 有參構造函數的默認值
  • 2.對象所占空間大小
    • 2.1 大小的計算
    • 2.2 內存對齊機制
  • 3. 析構函數
    • 3.1 基本概念
    • 3.2 總結
  • 4.valgrind工具集
    • 4.1 介紹
    • 4.2 memcheck的使用
  • 5. 拷貝構造函數
    • 5.1 拷貝構造函數定義
    • 5.2 淺拷貝/深拷貝
    • 5.3 拷貝構造函數的調用時機
    • 5.4 總結


1.構造函數初始化列表

1.1 初始化列表的使用

在構造函數體里對數據成員賦值,實際上是先調用數據成員的默認構造函數來創建對象,接著再對其進行賦值操作;而在初始化列表中初始化數據成員,是在對象創建的時候就直接完成初始化。

#include <iostream>
#include <string>
using std::endl;
using std::cout;
using std::string;
using std::cin;class Person
{
public:Person():_age(0),_name("張三"){cout << "我是無參構造函數" << endl;}Person(int age,string name):_age(age),_name(name){cout << "我是有參構造函數" << endl;}void print();
private:int _age;string _name;
};void Person::print()
{cout << "name=" << _name << ";age=" << _age << endl;
}int main(void)
{Person p1;p1.print();Person p2(10, "張萬三");p2.print();return 0;
}

在這里插入圖片描述

1.2 有參構造函數的默認值

構造函數的參數也可以按從右向左規則賦默認值,同樣的,如果構造函數的聲明和定義分開寫,只用在聲明或定義中的一處設置參數默認值,一般建議在聲明中設置默認值。

class Person
{
public://有參構造函數參數默認值Person(int age=10 ,string name="張萬一"):_age(age) ,_name(name){}void print();
private:int _age;string _name;
};void Person::print()
{cout << "name=" << _name << ";age=" << _age << endl;
}int main(void)
{Person p1(20);p1.print();return 0;
}

在這里插入圖片描述

2.對象所占空間大小

2.1 大小的計算

成員函數并不影響對象的大小,對象的大小與數據成員有關

#include <iostream>
#include <string>
using std::endl;
using std::cout;
using std::string;
using std::cin;class Person
{
private:int _age;double _price;
};int main(void)
{Person p1;size_t  s1 = sizeof(Person);size_t s2 = sizeof(p1);cout << s1 << ";" << s2 << endl;return 0;
}

在這里插入圖片描述

2.2 內存對齊機制

有時一個類所占空間大小就是其數據成員類型所占大小之和,有時則不是,這就是因為有內存對齊的機制。
1.什么是內存對齊:
存對齊指的是將數據存儲在特定的內存地址上,使得數據的起始地址是其大小的整數倍。例如,一個int類型(通常為 4 字節)的變量,其起始地址通常是 4 的倍數。
2.原因
(1)提高訪問效率:計算機的硬件設計使得在訪問對齊的數據時更加高效。因為 CPU 通常按字長(如 4 字節或 8 字節)來訪問內存,如果數據是對齊的,CPU 可以一次讀取完整的數據;如果數據未對齊,可能需要多次訪問內存才能獲取完整的數據。
(2)硬件限制:某些硬件平臺要求數據必須按特定的對齊方式存儲,否則會拋出異常或導致性能下降。
對齊規則
(1)基本數據類型的對齊值:每種基本數據類型都有其默認的對齊值,通常等于該類型的大小。例如,char 類型的對齊值為 1 字節,int 類型的對齊值為 4 字節,double 類型的對齊值為 8 字節。
(2)結構體或類的對齊值:結構體或類的對齊值是其成員中最大對齊值。
(3)成員的存儲位置:每個成員的起始地址必須是其對齊值的整數倍。如果前一個成員的結束地址不滿足下一個成員的對齊要求,會在它們之間插入填充字節。
(4)結構體或類的總大小:結構體或類的總大小必須是其對齊值的整數倍。如果不滿足,會在結構體或類的末尾插入填充字節。

3. 析構函數

3.1 基本概念

作用:析構函數是一種特殊的成員函數,在對象的生命周期結束時自動調用,用于釋放對象所占用的資源,比如動態分配的內存、打開的文件、網絡連接等。

析構函數語法
析構函數函數名是在類名前面加”~”組成,沒有返回值,不能有void,不能有參數,不能重載。 ~ClassName(){}

#include <iostream>
#include <cstring>// 使用命名空間 std
using namespace std;class Str {
private:char* str;
public:// 構造函數Str(const char* s) {//在堆上分配空間str = new char[strlen(s) + 1];strcpy(str, s);}// 析構函數~Str() {//堆內存的銷毀if(nullptr!=str){ delete[] str;str = nullptr;cout << "析構函數" << endl;}}void printString() {cout << str << endl;}
};void test(void)
{Str sw("Hello, World!");sw.printString();// 函數結束,對象 sw 被銷毀,析構函數自動調用
}int main(void) 
{test();return 0;
}

3.2 總結

  1. 對于全局對象整個程序結束時,自動調用全局對象的析構函數。
  2. 對于局部對象,在程序離開局部對象的作用域時調用對象的析構函數。
  3. 對于靜態對象,在整個程序結束時調用析構函數。
  4. 對于 堆對象在使用 delete 刪除該對象時,調用析構函數。

4.valgrind工具集

4.1 介紹

1.概念
Valgrind 是一款用于內存調試、性能分析和代碼剖析的工具集,在 Linux 系統中廣泛使用。
2.工具集中的主要工具
(1).Memcheck:這是 Valgrind 最常用的工具之一,主要用于檢測內存錯誤。它可以檢測出諸如內存泄漏、非法內存訪問(如越界訪問、使用未初始化的內存等)等問題。在開發過程中,這些內存錯誤可能會導致程序出現難以調試的崩潰或錯誤行為,Memcheck 能夠幫助開發者快速定位和解決這些問題。
(2)Callgrind用于性能分析,它可以收集程序中函數調用的信息,包括函數的執行時間、調用次數等。通過分析這些數據,開發者可以找出程序中的性能瓶頸所在,從而有針對性地進行優化。
(3)Cachegrind:主要關注程序的緩存行為。它可以模擬 CPU 緩存,分析程序對緩存的使用情況,幫助開發者了解緩存命中率、緩存缺失等信息,以便優化程序的內存訪問模式,提高緩存利用率,從而提升程序性能。
(4)Helgrind:用于檢測多線程程序中的數據競爭問題。在多線程環境下,當多個線程同時訪問共享數據且沒有適當的同步機制時,就可能發生數據競爭,導致程序出現不確定的行為。Helgrind 能夠檢測出這些潛在的數據競爭情況,幫助開發者確保多線程程序的正確性和穩定性。
3.特點
(1)強大的檢測能力:能夠深入檢測各種內存相關的問題和性能瓶頸,為開發者提供詳細的錯誤信息和性能數據。
(2)易于使用:Valgrind 的命令行界面相對簡單,只需指定要運行的程序以及相應的工具選項,就可以開始對程序進行分析。
(3)跨平臺性:支持多種 Linux 平臺,具有較好的移植性,方便在不同的系統環境中使用。
4.應用場景
(1)軟件開發與調試:在開發過程中,及時發現和解決內存錯誤可以提高軟件的穩定性和可靠性,減少后期維護成本。
(2)性能優化:通過分析性能數據和緩存行為,開發者可以采取針對性的優化措施,如優化算法、調整數據結構、改進內存訪問模式等,以提高程序的運行效率。
(3)多線程程序開發:確保多線程程序的正確性和穩定性,避免數據競爭等問題導致的程序錯誤和異常。

4.2 memcheck的使用

sudo apt-get install valgrind       //安裝//使用
valgrind --tool=memcheck ./a.out                 (簡略)
valgrind --tool=memcheck --leak-check=full ./a.out	(詳細)//在home目錄下編輯.bashrc文件,改別名;方便使用vim ~/.bashrc    #打開并添加一下內存
alias memcheck='valgrind --tool=memcheck --leak-check=full --show-reachable=yes'
#保存退出  :wq
#生效
source ~/.bashrc

在這里插入圖片描述

5. 拷貝構造函數

5.1 拷貝構造函數定義

1.語法格式

class ClassName {
public:// 拷貝構造函數ClassName(const ClassName& other) {// 初始化新對象的成員變量}
};

2.定義
該函數用一個已經存在的同類型的對象,來初始化新對象,即對對象本身進行復制 。在沒有顯式定義拷貝構造函數時,編譯器自動提供了默認的拷貝構造函數。

#include <iostream>
#include <cstring>namespace myspace1
{using std::endl;using std::cout;using std::cin;using std::string;
}using namespace myspace1;class Person
{
public://構造函數Person():_x(0),_y(0) {cout << "我是無參構造函數" << endl;}Person(int x, int y) :_x(x), _y(y){cout << "我是有參構造函數" << endl;}//析構函數~Person(){cout << "我是析構函數" << endl;}//拷貝構造函數Person(const Person& r):_x(r._x),_y(r._y){cout << "我是拷貝構造函數" << endl;}void print(){cout << "x=" << _x << ";y=" << _y << endl;}private:int _x;int _y;
};void test()
{Person p1(3, 4);//這里調用拷貝構造函數Person p2 = p1;         //==Person p2(p1)p1.print();p2.print();
}int main(void)
{test();
}

在這里插入圖片描述

5.2 淺拷貝/深拷貝

淺拷貝默認拷貝構造函數執行的是淺拷貝,只復制對象的成員變量的值。對于包含指針成員的對象,淺拷貝會導致多個對象的指針成員指向同一塊內存區域,當其中一個對象被銷毀時,釋放該內存會導致其他對象的指針成為懸空指針,引發未定義行為。
深拷貝:為了避免淺拷貝帶來的問題,需要顯式定義拷貝構造函數,實現深拷貝。深拷貝會為新對象的指針成員分配新的內存空間,并將原對象指針所指向的內容復制到新的內存中。

錯誤案例

#include <iostream>
#include <cstring>namespace myspace1
{using std::endl;using std::cout;using std::cin;using std::string;
}using namespace myspace1;class Person
{
public://構造函數Person():_x(0),_name(new char[strlen("張三")+1]()){strcpy(_name, "張三");cout << "我是無參構造函數" << endl;}Person(int x, const char *name) :_x(x), _name(new char[strlen(name) + 1]()){strcpy(_name, name);cout << "我是有參構造函數" << endl;}//析構函數~Person(){delete[] _name;cout << "我是析構函數" << endl;}void print(){cout << "x=" << _x << ";name=" << _name << endl;}private:int _x;char* _name;
};void test()
{Person p1(20, "張萬三");//這里調用拷貝構造函數Person p2 = p1;         //==Person p2(p1)p1.print();p2.print();
}int main(void)
{test();
}

在這里插入圖片描述
如果是默認的拷貝構造函數,p2會對p1的_name進行淺拷貝指向同一片內存;p2被銷毀時,會調用析構函數,將這片堆空間進行回收;p1再銷毀時,析構函數中又會試圖回收這片空間,出現double free問題

所以需要我們顯示定義拷貝構造函數

#include <iostream>
#include <cstring>namespace myspace1
{using std::endl;using std::cout;using std::cin;using std::string;
}using namespace myspace1;class Person
{
public://構造函數Person():_x(0),_name(new char[strlen("張三")+1]()){strcpy(_name, "張三");cout << "我是無參構造函數" << endl;}Person(int x, const char *name) :_x(x), _name(new char[strlen(name) + 1]()){strcpy(_name, name);cout << "我是有參構造函數" << endl;}//析構函數~Person(){delete[] _name;cout << "我是析構函數" << endl;}//拷貝構造函數Person(const Person& r):_x(r._x), _name(new char[strlen(r._name) + 1]){strcpy(_name, r._name);cout << "我是拷貝構造函數" << endl;}void print(){cout << "x=" << _x << ";name=" << _name << endl;}private:int _x;char* _name;
};void test()
{Person p1(20, "張萬三");//這里調用拷貝構造函數Person p2 = p1;         //==Person p2(p1)p1.print();p2.print();
}int main(void)
{test();
}

在這里插入圖片描述
所以,如果拷貝構造函數需要顯式寫出時(該類有指針成員申請堆空間),在自定義的拷貝構造函數中要換成深拷貝的方式,先申請空間,再復制內容

5.3 拷貝構造函數的調用時機

1.當使用一個已經存在的對象初始化另一個同類型的新對象時

void test()
{Person p1(20, "張萬三");Person p2 = p1;         //==Person p2(p1)p1.print();p2.print();
}

2.當函數參數(實參和形參的類型都是對象),形參與實參結合時(實參初始化形參);

void fun(Person p)     //避免多余的復制,節省空間:fun(Person &p)  使用引用
{p.print();
}void test()
{Person p1(20, "張三");fun(p1);
}

3.當函數的返回值是對象,執行return語句時

//避免多余的復制,節省空間
//Person &fun1()
//{
//    //返回的是引用,要注意返回對象的聲明周期(返回全局變量/變量前加  static)
//    return 
//}
Person fun2()
{//Person(20, "李四"); 匿名對象;在這行代碼執行完畢后,該臨時對象就會立即調用析構函數銷毀。Person p3(30, "王強");return p3;          //發生復制
}

5.4 總結

1.默認情況下,c++編譯器至少為我們寫的類增加3個函數
(1).默認構造函數(無參,函數體為空)
(2).默認析構函數(無參,函數體為空)
(3).默認拷貝構造函數,對類中非靜態成員屬性簡單值拷貝
如果用戶定義拷貝構造函數,c++不會再提供任何默認構造函數
如果用戶定義了普通構造(非拷貝),c++不在提供默認無參構造,但是會提供默認拷貝構造
2.構造/析構
(1)構造函數主要作用在于創建對象時為對象的成員屬性賦值,構造函數由編譯器自動調用,無須手動調用。
(2)析構函數主要用于對象銷毀前系統自動調用,執行一些清理工作。
3.淺拷貝/深拷貝
(1)一般情況下,淺拷貝沒有任何副作用,但是當類中有指針,并且指針指向動態分配的內存空間,析構函數做了動態內存釋放的處理,會導致內存問題。
(2)當類中有指針,并且此指針有動態分配空間,析構函數做了釋放處理,往往需要自定義拷貝構造函數,自行給指針動態分配空間,深拷貝。

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

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

相關文章

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

一&#xff0c;單詞釋義 shirt /???t/ n. 襯衫&#xff1b;襯衣commonly /?k?m?nli/ adv. 通常地&#xff1b;一般地&#xff1b;普遍地pick /p?k/ v. 挑選&#xff1b;采摘&#xff1b;撿起&#xff1b;選擇&#xff1b;n. 選擇&#xff1b;鶴嘴鋤&#xff1b;精華com…

安裝低版本Pytorch GPU

網上很多教程都是自動安裝&#xff0c;不指定版本&#xff0c;其實有大問題。而且torch、torchvision、torchaudio的版本必須是對應&#xff0c;所以一旦版本不對&#xff0c;就可能會出現各種問題。 其實Pytorch官網就已經給出了安裝低版本的教程 登入Pytorch官網 點擊previo…

2025認證杯挑戰賽B題【 謠言在社交網絡上的傳播 】原創論文講解(含完整python代碼)

大家好呀&#xff0c;從發布賽題一直到現在&#xff0c;總算完成了認證杯數學中國數學建模網絡挑戰賽第一階段B題目謠言在社交網絡上的傳播完整的成品論文。 本論文可以保證原創&#xff0c;保證高質量。絕不是隨便引用一大堆模型和代碼復制粘貼進來完全沒有應用糊弄人的垃圾半…

并發編程--互斥鎖與讀寫鎖

并發編程–互斥鎖與讀寫鎖 文章目錄 并發編程--互斥鎖與讀寫鎖1. 基本概念2. 互斥鎖2.1 基本邏輯2.2 函數接口2.3示例代碼12.4示例代碼2 3. 讀寫鎖3.1 基本邏輯3.2示例代碼 1. 基本概念 互斥與同步是最基本的邏輯概念&#xff1a; 互斥指的是控制兩個進度使之互相排斥&#x…

親手打造可視化故事線管理工具:開發全流程、難點突破與開發過程經驗總結

親手打造可視化故事線管理工具&#xff1a;開發全流程、難點突破與開發過程經驗總結 作為還沒入門的業余編程愛好者&#xff0c;奮戰了2天&#xff0c;借助AI開發一款FLASK小工具&#xff0c;功能還在完善中&#xff08;時間軸可以跟隨關聯圖縮放&#xff0c;加了一個用C鍵控制…

網絡攻防技術-虛擬機安裝和nmap端口掃描

文章是博主上實驗課做的實驗和心得體會&#xff0c;有些高深的地方我可能也比較一知半解&#xff0c;歡迎來交流。全文參考課程所習得&#xff0c;純粹梳理知識點和分享&#xff0c;如有不妥請聯系修改。 文章側重實驗部分&#xff0c;也會講述實驗相關的理論知識。理論后期如果…

中斷的硬件框架

今天呢&#xff0c;我們來講講中斷的硬件框架&#xff0c;這里會去舉3個開發板&#xff0c;去了解中斷的硬件框架&#xff1a; 中斷路徑上的3個部件&#xff1a; 中斷源 中斷源多種多樣&#xff0c;比如GPIO、定時器、UART、DMA等等。 它們都有自己的寄存器&#xff0c;可以…

動手學深度學習:手語視頻在VGG模型中的測試

前言 其他所有部分同上一篇AlexNet一樣&#xff0c;所以就不再贅訴&#xff0c;直接看VGG搭建部分。 模型 VGG是第一個采取塊進行模塊化搭建的模型。 def vgg_block(num_convs,in_channels,out_channels):layers[]for _ in range(num_convs):layers.append(nn.Conv2d(in_ch…

信息學奧賽一本通 1498:Roadblocks | 洛谷 P2865 [USACO06NOV] Roadblocks G

【題目鏈接】 ybt 1498&#xff1a;Roadblocks 洛谷 P2865 [USACO06NOV] Roadblocks G 【題目考點】 1. 圖論&#xff1a;嚴格次短路徑 嚴格次短路的路徑長度必須大于最短路的路徑長度。 非嚴格次短路的路徑長度大于等于最短路的路徑長度。 【解題思路】 每個交叉路口是一…

Arm CPU安全通告:基于TrustZone的Cortex-M系統面臨多重故障注入攻擊

安全之安全(security)博客目錄導讀 目錄 一、概述 二、致謝 三、參考文獻??????Black Hat USA 2022 | Briefings Schedule 四、版本歷史 一、概述 Arm注意到BlackHat 2022大會官網發布的演講摘要《糟糕..&#xff01;我又一次故障注入成功了&#xff01;——如何突…

【頻域分析】包絡分析

【頻域分析】包絡分析 算法配置頁面 可以一鍵導出結果數據 報表自定義繪制 獲取和下載【PHM學習軟件PHM源碼】的方式 獲取方式&#xff1a;Docshttps://jcn362s9p4t8.feishu.cn/wiki/A0NXwPxY3ie1cGkOy08cru6vnvc

ElMessage

以下是關于 ElMessage 的詳細說明和使用方法&#xff1a; 什么是 ElMessage ElMessage 是 Element Plus 提供的一個全局消息提示組件&#xff0c;用于在頁面上顯示短暫的消息提示。它可以用于顯示成功、警告、錯誤等不同類型的消息。 基本用法 1. 引入 ElMessage 在使用 E…

全面解析 KaiwuDB 數據庫的數據類型

在現代數據庫管理系統中&#xff0c;數據類型的選擇至關重要。它不僅決定了數據存儲的效率&#xff0c;還影響到查詢的速度和數據的一致性。KaiwuDB&#xff0c;作為一款開源的分布式數據庫&#xff0c;提供了多種數據類型&#xff0c;以適應不同的業務需求和存儲要求。本文將全…

【計網】網絡交換技術之分組交換(復習自用,重要1)

復習自用的&#xff0c;處理得比較草率&#xff0c;復習的同學或者想看基礎的同學可以看看&#xff0c;大佬的話可以不用浪費時間在我的水文上了 另外兩種交換技術可以直接點擊鏈接訪問相關筆記&#xff1a; 電路交換 報文交換 一、分組交換的定義 1.定義 分組交換&#x…

C++ STL及Python中等效實現

一. STL 概述 STL 包含以下核心組件&#xff1a; 容器&#xff08;Containers&#xff09;&#xff1a;存儲數據的結構&#xff0c;如數組、鏈表、集合等。迭代器&#xff08;Iterators&#xff09;&#xff1a;用于遍歷容器的接口&#xff0c;類似指針。算法&#xff08;Alg…

python-63-前后端分離之圖書管理系統的Flask后端

文章目錄 1 flask后端1.1 數據庫實例extension.py1.2 數據模型models.py1.3 .flaskenv1.4 app.py1.5 運行1.6 測試鏈接2 關鍵函數和文件2.1 請求視圖類MethodView2.2 .flaskenv文件3 參考附錄基于flask形成了圖書管理系統的后端,同時對其中使用到的關鍵文件.flaskenv和函數類M…

藍橋杯真題——好數、R格式

目錄 藍橋杯2024年第十五屆省賽真題-好數 【模擬題】 題目描述 輸入格式 輸出格式 樣例輸入 樣例輸出 提示 代碼1&#xff1a;有兩個案例過不了&#xff0c;超時 藍橋杯2024年第十五屆省賽真題-R 格式 【vector容器的使用】 題目描述 輸入格式 輸出格式 樣例輸入…

Python中NumPy的索引和切片

在數據科學和科學計算領域&#xff0c;NumPy是一個功能強大且廣泛使用的Python庫。它提供了高效的多維數組對象以及豐富的數組操作函數&#xff0c;其中索引和切片是NumPy的核心功能之一。通過靈活運用索引和切片操作&#xff0c;我們可以輕松訪問和操作數組中的元素&#xff0…

設計模式:策略模式 - 消除復雜條件判斷的利器

一、什么是策略模式&#xff1f; 策略模式&#xff08;Strategy Pattern&#xff09;是一種行為型設計模式&#xff0c;它將一組算法或業務邏輯封裝為獨立的策略類&#xff0c;使這些策略可以互換使用&#xff0c;并通過上下文類動態選擇合適的策略。 核心思想 ? 將不同的行…

LeetCode hot 100—不同路徑

題目 一個機器人位于一個 m x n 網格的左上角 &#xff08;起始點在下圖中標記為 “Start” &#xff09;。 機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角&#xff08;在下圖中標記為 “Finish” &#xff09;。 問總共有多少條不同的路徑&#xff1f; …