主頁 >  其他 > 在【守望先鋒】學習C++的類與物件

在【守望先鋒】學習C++的類與物件

2021-06-10 09:52:50 其他

C++是一門OOP(面向物件)的語言,
而C語言只是一門面向程序的語言,
里面有些思路需要重新改變,

前情提要:文章較長,請根據目錄自行選擇

目錄

  • 一、守望先鋒
  • 二、物件和類
    • 1、什么是類
      • C語言創建一個堆疊的”類“
      • C++創建一個堆疊的類
    • 2、類的定義
      • 宣告與定義的區別
      • 訪問限定符
      • 類大小的計算
    • 3、this指標
      • 簡單的日期類
      • 成員變數與成員函式的空間分布
      • this指標的分析
      • this指標的特性
      • this指標儲存在哪?
      • 看看這個,來理解
  • 三、類的六個默認成員函式
    • 1、 建構式
      • 建構式的概念
      • 什么?你不相信系統呼叫了建構式
      • 建構式的特性
      • 自定義型別與內置型別
      • 對于建構式的建議
    • 2、解構式
      • 解構式的定義
      • 解構式的特性
      • 多個物件,解構式呼叫的順序
    • 3、拷貝建構式
      • 拷貝建構式的特性
      • 為什么傳值傳參會引發無窮遞回呼叫
      • 拷貝構造與傳參考
      • 深拷貝與淺拷貝
    • 4、賦值運算子多載
      • 運算子多載的特性
      • 復制拷貝與拷貝構造
    • 5、對前4個默認成員函式的總結
    • 6、const成員函式
      • 特性
      • 幾個問題理解const
    • 7、取地址運算子多載
      • 保護物件不被讀取地址
  • 四、一個完整的日期類(選讀,可跳)
    • 日期類.cpp
    • Date.cpp
    • Date.h
  • 五、友元與>>、<<運算子多載
    • 為什么cin>>、cout<<可以自動識別內置型別
      • cout
      • cin
    • 友元函式
    • 友元類
  • 六、深入了解建構式
    • 1、函式體內初始化
    • 2、初始化串列
    • 3、為什么要多此一舉準備兩種初始化模式呢?
      • 初始化串列才是單個成員變數定義的階段
      • 初始化串列初始化的順序

一、守望先鋒

在這里插入圖片描述

天使姐姐不漂亮嗎?
在這里插入圖片描述

為一個場守望先鋒街機先鋒統計資料

如果是面向程序的程式員,可能會考慮:

記錄每個玩家所選的英雄,擊殺人數,對對面傷害量,治療量,承受傷害量,命中率等等,

同時還要記錄每個英雄的移動,釋放技能,技能是否命中,技能造成的傷害的計算,甚至還要考慮技能的運動等等,
甚至還要考慮對方的技能對自己的影響,以及隊友的技能對自己的影響,

用一個主函式 main 來獲取所有資料,
呼叫另外一些函式來分別計算英雄的移動,技能傷害,技能影響,收到的傷害等等,

再呼叫另外一部分函式來顯示結果,玩過守望先鋒的玩家都知道,對于這些資料的 計算,統計,顯示,都是瞬時性的,會隨著玩家的操作不斷變化,而且,一次組隊并不是只打一場,可能會由兩場左右,
在這里插入圖片描述

守望先鋒并不是傳統的FPS游戲,而是FPS與Moba游戲的結合

而且,對于不同的英雄都要根據英雄的技能去計算,統計不同的資料,
如:安娜要分別統計開鏡和不開鏡的命中率,以及睡針的命中人數,
法拉要統計火箭彈直接命中人數等等,

在這里插入圖片描述
在這里插入圖片描述

而不同模式,又會有不同的變化,對英雄的移動,技能的影響也是不同的,
如:戰斗狂歡,讓所有英雄的血量翻倍,技能CD減半,這就會造成很多不一樣的“化學反應”,
像我有一次戰斗狂歡打了 1.5h 才打完(路霸一直卡車旁邊,都在卡加時),對于普通快速10分鐘左右可是相當長的,對于比賽的各個資料上限又該怎么設定?
在這里插入圖片描述

而不一樣的地圖對角色的移動,技能的影響又是不一樣的,比如:地形殺

但對于資料怎么辦?又要重新統計,這相對于計算機而言都太復雜,(如果是這樣,我相信網易的服務器早就崩了),

總之,對于程序性編程,首先要考慮遵循的步驟,然后要考慮如何表示這些資料,

如果是一位OOP程式員,其不僅要考慮如何表示資料,還要考慮如何使用資料 ,

我要跟蹤什么呢?當然是每個玩家,因此要有一個物件來表示每個玩家的各個方面的資料,可以選擇為各個不同的英雄定義不同的類,在通過類來為玩家創建物件,讓計算機執行計算玩家之間的資料互動,可以自動計算,我們要做的僅僅是研究如何跟蹤或表示,每個玩家之間的資料互動,
對于OOP程式員,只需為每個不同英雄定義屬于他們的類,每次游戲開始,再為每個玩家根據其選擇創建物件,再利用演算法跟蹤玩家之間的資料互動即

這不比面向程序編程簡單?(不知道我理解的對不對)
在這里插入圖片描述
ps:Dv.A愛你呦~

二、物件和類

這個世界太復雜,處理復雜性的方法之一是簡化抽象
守望先鋒游戲中,通過為每個英雄定義一個類,為每個玩家創建一個物件來統籌資料,
在C++中,用戶定義型別指點是實作抽象介面的類的設計,
(說人話就是按照需求,定義類,

1、什么是類

我們都知道建一棟房子首先需要什么?

需要圖紙,房子的結構圖紙,

在這里插入圖片描述

有了圖紙,我們就可以根據圖紙見很多大同小異的房子(一張圖紙建出來的么),
在這里插入圖片描述

在這里插入圖片描述

房子可以建很多,而圖紙始終是那一張,
這樣可以幫助我們更好理解,
在這里插入圖片描述

對像是一個物體,而類只是一個自定義的型別,
就像int a;
a 是一個物體變數,在記憶體中有空間,而 int 只是一個型別,表明 a 的身份,

這樣應該理解了吧,
基本型別(內置型別)定義變數完成了三項操作

  1. 決定資料物件需要的記憶體數量
  2. 決定如何解釋記憶體在的位(long和float在記憶體中占用的位數相同,但是他們轉換成數值的方法不同);
  3. 決定可使用資料物件執行的操作或方法,

對內置型別而言,有關資訊全部被內置到編譯器中,但是C++在自定義型別是,必須要自己提供所有資訊,

類的實體化:就是根據類創建一個物件

在這里插入圖片描述

C語言創建一個堆疊的”類“

C語言也是存在”類“,我們常用的結構體,
比如我們用C語言去是實作一個堆疊

#define CAP 4
typedef int STData;
typedef struct Stack//結構體用于維護堆疊
{
	int top;//堆疊頂標記
	STData* arr;//堆疊的指標
	int capacity;//堆疊的容量
}STstack;

這是對于堆疊的資料的定義,堆疊就是我們用結構體定義的一個”自定義型別“–”堆疊型別“,(因為它并不具有自定義型別的全部資訊

void InitStack(STstack* st);//堆疊的初始化
void StackPush(STstack* st, STData n);//元素入堆疊
STData StackPop(STstack* st);//元素退堆疊
void StackExpansion(STstack* st);//擴容
int StackEmpty(STstack* st);//判斷堆疊是否為空
void StackDestory(STstack* st);//銷毀堆疊,防止記憶體泄漏
void StackPrint(STstack* st);//列印堆疊的元素,但前提是要退堆疊才能得到元素

這些函式是我們能夠對堆疊執行的操作

但是資料和執行方法都是分離的,

這就導致我們重點關注的是對堆疊操作的整個程序
就是,我們這一步選擇入堆疊,下一步選擇彈堆疊,

C++創建一個堆疊的類

我們會將堆疊看為一個類,也就是一個型別,一個自定義型別,
而一個型別,我們需要兩個部分

1,類的成員變數(類的屬性)
2,類的成員函式(類的行為/能夠執行的操作)

這個也滿足上面關于型別的三項操作,

對于C++而言,就不再用結構體這個概念了,該叫
C++將structC語言的結構體 - 升級到 - 類

目前可以認為C++中的類就是C語言的結構體除了定義結構體成員變數還有結構體成員函式
比如:

#define CAP 4
typedef int STData;
struct Stack//結構體用于維護堆疊
{
	//結構體成員變數
	int top;//堆疊頂標記
	STData* arr;//堆疊的指標
	int capacity;//堆疊的容量
	
	//結構體成員函式
	void InitStack(STData capacity=4);//堆疊的初始化//預設
	void StackPush(STData n);//元素入堆疊
	STData StackPop();//元素退堆疊
	void StackExpansion();//擴容
	int StackEmpty();//判斷堆疊是否為空
	void StackDestory();//銷毀堆疊,防止記憶體泄漏
	void StackPrint();//列印堆疊的元素,但前提是要退堆疊才能得到元素
};

你目前可以理解成長這樣,

而且,其類名就可以是其自定義型別的類名,
直接Stack a1;,這就定義了一個物件,
而且也不用傳上面指標過去了,可以a1.Init(),就可以呼叫那些成員函式,
由于C++兼容C語言,這可以是一個類,但C++會使用class(其中文有類的意思),

2、類的定義

class ClassName//class是定義類的關鍵字 后面接一個類的名字
{
//類體:由成員變數和成員函陣列成
};

與定義結構體很像,
對于類而言,其中的成員函式,你可以直接在類中定義,也可以在.h檔案類中宣告,去.c檔案中定義,但是,在類中的成員變數,那只是宣告,并不是定義,

宣告與定義的區別

宣告:告訴編譯器,我有一個這樣的東西在這,但并未實作,
定義:根據宣告,去實作這個物件的需求,

形象來說就是:

游戲公司建了一個新游戲的檔案夾,并向外發布公告,說未來會發布一款新游戲,也可能檔案夾都還沒建好,
這就是宣告,只是告訴你有,但你不知道游戲劇情、內容、玩法是什么,只知道有這個游戲,

當游戲發售后,你買了,你就可以知道游戲的所有內容,
這就是定義,

宣告是對的是一個屬性,而定義對的是一個物體,

訪問限定符

C++對于類中的成員提供了訪問限定符
private(私有),public(公有),protected(保護)
他們描述了對類成員的訪問控制

根據類創建的物件,都可以訪問到物件類的公有部分,并且只能通過公有函式來訪問物件類的私有成員

訪問控制,也是對物件類的資料的保護,

OOP編程的主要目標之一是隱藏資料,因此這類資料通常放在私有部分,
而組成類介面的成員函式放在公有部分,否則就無法呼叫這些函式,

由于C++兼容C語言,且C++對C語言的結構進行了拓展,如

struct A
{};
class A
{};

在C++中,都是類,

兩者區別

對于struct創建的類,里面的成員默認公有
對于class 創建的類,里面成員默認私有

例如:創建一個OW英雄-安娜的類
在這里插入圖片描述

class Ana
{
public:
	void ShowData();//展示資料
	void Teletherapy(bool input);//遠程治療
	void SleepyNeedle(bool input);//睡針
private:
	char Name[20];//名字
	int Weight;//體重
	int Height;//身高
	double Speed;//移動速度
	double Blood;//血量
	//睡針類 睡針
};

私有資料,我們是不能修改或訪問的,
只能通過公有函式去操作,或通過公有函式來了解私有資料的狀態,

如果我們能直接訪問到私有資料,那么我們也能對私有資料進行修改,但那樣不就成開掛了嗎?(正常人誰打競技游戲開掛),

所以,一般情況下,成員變數都是私有的,想給你用的成員函式是公有的,不希望被呼叫的函式是私有的,

類大小的計算

根據規則:計算類的大小,是不考慮類中成員函式的,只計算類中成員變數,同時還要考慮結構體記憶體對齊規則,也就是計算結構體大小的規則,

結構體記憶體對齊規則

  1. 第一個成員在與結構體偏移量為0的地址處,
  2. 其他成員變數要對齊到某個數字(對齊數)的整數倍的地址處,
    對齊數 = 編譯器默認的一個對齊數 與 該成員大小的較小值,
    VS中默認的對齊數為8
  3. 結構體總大小為:最大對齊數(所有變數型別最大者與默認對齊引數取最小)的整數倍,
  4. 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是
    所有最大對齊數(含嵌套結構體的對齊數)的整數倍,

那就計算一下

#include <iostream> 
class Ana
{
public:
	void ShowData();//展示資料
	void Teletherapy(bool input);//遠程治療
	void SleepyNeedle(bool input);//睡針
private:
	char Name[20];//名字
	int Weight;//體重
	int Height;//身高
	double Speed;//移動速度
	double Blood;//血量
};
int main(void)
{
	Ana a1;
	Ana a2;
	std::cout << "a1 = "<<sizeof(a1)<<std::endl;
	std::cout << "a2 = "<<sizeof(a2)<< std::endl;
	return 0;
}

創建了兩個物件,
在這里插入圖片描述
編譯器輸出結果,

來看看其記憶體的概念圖

在這里插入圖片描述

對于類中的成員函式是不會進行計算大小的,

3、this指標

在這里插入圖片描述

這也是類中一個比較重要的知識點

通過一個日期類來分析(因為日期類比較簡單)

簡單的日期類

#include <iostream>
class Date
{
public:
	void Init(int year = 1, int month = 1, int day = 1)//預設引數
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	void print(void)
	{
		std::cout << _year << "年" << _month << "月" << _day<<"日"<<std::endl;
	}
	
private:
	int _year;
	int _month;
	int _day;
};
int main(void)
{
	Date d1;//類的實體化
	Date d2;
	
	d1.Init(2002,2,5);//物件d1初始化
	d2.Init(2019,4,1);//物件d2初始化
	
	d1.print();//列印
	d2.print();//列印
}

輸出結果
在這里插入圖片描述

在呼叫物件類的成員函式初始化物件 d1,d2 ,
在成員函式中可以訪問到物件封裝的成員變數,

再介紹一下
對于類的實體化,創建物件,

	Date d1;//類的實體化
	Date d2;

系統會為物件d1 d2堆疊中分配空間,
但是只是物件中的成員變數分配在該物件的空間內,

成員變數與成員函式的空間分布

但是對于成員函式,其是在一個代碼公共區段,而不是在每個物件空間的內部,不會每呼叫一次就分配一塊空間,
每個物件都可以訪問那個區段
在這里插入圖片描述

this指標的分析

我們就該想,當呼叫函式時,進入公共區段,如何確定呼叫的是那個物件的成員變數呢?
其實,傳遞過去的引數,不僅僅有顯示宣告定義的引數,還有一個隱藏的this指標,
即,C++編譯器會為每一個非靜態成員函式配備一個隱藏的指標引數,

該指標指向當前物件(函式運行時呼叫該函式的物件),

在這里插入圖片描述

在這里插入圖片描述

當然,你也可以這樣寫

void Init(int year = 1, int month = 1, int day = 1)//預設引數
{
	this->_year = year;
	this->_month = month;
	this->_day = day;
}
void print(void)
{
	std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}

但是我們不能自己在引數串列中加一個this指標,因為編譯器自己會傳遞,會處理,要是自己加了,就會造成引數缺失,

這樣也是允許的,不容易搞混,

成員函式呼叫的真實樣子

d1.Init(2002, 2, 5);//->Init(&d1,2002, 2, 5);
d2.Init(2019, 4, 1);//->Init(&d2,2019, 4, 1);

d1.print();//->print(&d1)
d2.print();//->print(&d2)

圖片更好看一些,
在這里插入圖片描述

注意

而且,對于類的成員變數,最好命名獨特一點,不然,如果和預設引數名字一樣,編譯器會無法識別,this指標指向不明,從而報錯

this指標的特性

  1. this指標的型別:型別const
  2. 只能在成員函式的內部使用
  3. this指標本質上其實是一個成員函式的形參,是物件呼叫成員函式時,將物件地址作為實參傳遞給this形參,所以物件中不存盤this指標,
  4. this指標是成員函式第一個隱含的指標形參,一般情況由編譯器通過ecx暫存器自動傳遞,不需要用戶傳遞

this指標儲存在哪?

就我目前微薄的知識,this指標是儲存在堆疊中的,
因為,this指標畢竟是一個形參,而形參和區域變數都是儲存在堆疊中的,
可以隨著成員函式的呼叫而創建函式結束就銷毀,但是不同編譯器是按照不同的規則的,比如VS就是通過ecx暫存器自動傳遞,

看看這個,來理解

class A
{ 
public:
 	void PrintA() 
 	{
 		cout<<_a<<endl;
 	}
 
	 void Show()
	 {
 		cout<<"Show()"<<endl;
	 }
private:
 	int _a;
};
int main()
{
 	A* p = nullptr;
	p->PrintA(); 
	p->Show();
}

創建一個物件指標,并把它初始化為空,

p->PrintA();如果this->_a是行不通的,本身就是空指標,指向空,根本就不會有指向有權限的空間,算是非法訪問

但是p->Show();,并未訪問物件內,而是訪問到類的公共區段,不會造成非法訪問,

三、類的六個默認成員函式

類的默認成員函式即使我們

不自己宣告定義,編譯器也會自動創建定義

對于一個什么成員函式都沒有的類,是一個空類,但是,當編譯器處理時,會自動生成6個默認成員函式來防止出錯,
在這里插入圖片描述
事實上,真正用處大的是前4個,后面兩個,基本上沒多大用處,

1、 建構式

該函式可以完成對物件的初始化,
C++提供這個默認成員函式,是為了解決,沒有初始化就使用物件的問題,

對于日期類

class Date
{
public:
	//void Init(int year = 1, int month = 1, int day = 1)//預設引數
	//{
	//	this->_year = year;
	//	this->_month = month;
	//	this->_day = day;
	//}

	void print(void)
	{
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

如果沒有Init()函式,如果是C語言的話,就會報錯,因為沒有初始化,
就比如這樣

int main(void)
{
	
	Date d1;
	Date d2;
	d1.print();//->print(&d1)
	d2.print();//->print(&d2)
}

我們沒有初始化,卻直接列印,事實上,編譯器輸出
在這里插入圖片描述
雖然沒有報錯,但是卻輸出了隨機值,這樣也是防止了程式直接崩潰的問題,

建構式的概念

建構式是一個特殊的成員函式,名字與類名相同,創建型別別物件時由編譯器自動呼叫,保證每個資料成員
都有 一個合適的初始值,并且在物件的生命周期內只呼叫一次

這是一個默認成員函式,意思就是如果我們自己不定義,系統就會自動生成,
加上建構式

Date(int year = 1, int month = 1, int day = 1)
{
	_year = year;
	_month = month;
	_day = day;
}

輸出結果
在這里插入圖片描述

如果我們自己不定義,系統會自動生成,我們定義,系統就呼叫我們定義的函式,

什么?你不相信系統呼叫了建構式

那我就加一個列印,來看看結果

Date(int year = 1, int month = 1, int day = 1)
{
	std::cout << "Date()" << std::endl;
	_year = year;
	_month = month;
	_day = day;
}

看看輸出結果:
在這里插入圖片描述
現在相信了吧,
在這里插入圖片描述

建構式的特性

建構式雖然名字感覺像是負責為物件創建分配空間,
但實際上,起作用只是完成對物件的成員變數的初始化,防止未初始化就使用而造成的程式崩潰,

特性

  1. 名字與類名一樣
  2. 函式無回傳值(注意:是無回傳值,void是回傳一個空,實際上還是有回傳值,而建構式其根本就沒有回傳型別)
  3. 在物件實體化時,編譯器會自動呼叫對應的建構式(我們沒定義就呼叫默認的)
  4. 建構式可以多載

雖然可以多載
但是

Date()
{
	std::cout << "Date()" << std::endl;
	_year = 10;
	_month = 1;
	_day = 1;
}
Date(int year=1, int month = 1, int day = 1)
{
	std::cout << "Date()" << std::endl;
	_year = year;
	_month = month;
	_day = day;
}
	

卻不受歡迎的,因為編譯器也不知道如何處理,會報錯,

但可以這樣

Date(int year,int month = 1, int day = 1)
{
	std::cout << "Date()" << std::endl;
	_year = year;
	_month = month;
	_day = day;
}

在初始化的時候,自己無論如何都要提供一個值,

或者

Date()
{
	std::cout << "Date()" << std::endl;
	_year = 10;
	_month = 1;
	_day = 1;
}
Date(int year, int month, int day)
{
	std::cout << "Date()" << std::endl;
	_year = year;
	_month = month;
	_day = day;
}

可以運行,但還不如全預設的建構式,

自定義型別與內置型別

內置型別:內置型別就是語法已經定義好的型別:如int/char…,
自定義型別:是我們使用class/struct/union自己定義的型別

對于普通的成員變數而言,其默認的建構式,感覺效果并不怎樣,但是,如果有一個成員變數也是類?(自定義型別),

#include <iostream>
class A
{
public:
	A()
	{
		std::cout << "A()" << std::endl;
	}
private:
	int a;
};
class Date
{
public:
	
	void print(void)
	{
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
	A a1;
};
int main(void)
{
	
	Date d1;
	Date d2;
	d1.print();//->print(&d1)
	d2.print();//->print(&d2)
}

其輸出結果為
在這里插入圖片描述
像上面那段代碼,對于類Date,我們自己沒有定義建構式,而編譯器會生成默認建構式,取對成員變數初始化,

而對于內置型別,直接初始化一個隨機值,防止程式崩潰,

但是對于自定義型別,編譯去會取呼叫它的建構式對自定義型別的成員變數進行初始化

我定義了A的建構式是為了可以更好的看到自定義型別的建構式被呼叫,

對于內置型別,編譯器基本不會處理,
對于自定義型別,編譯器會去呼叫自定義型別的建構式

大多數情況下,默認建構式都不太頂用,最好還是自己寫一個滿足要求的,

對于建構式的建議

一般而言,建構式有三種(不需要傳遞引數就可以呼叫的函式

  1. 編譯器默認生成的(直接賦隨機值的那種)
  2. 我們自己寫的無引數的
Date()
{
	_year = 10;
	_month = 1;
	_day = 1;
}

3.我們自己寫的全預設的

Date(int year=1, int month=1, int day=1)
{
	_year = year;
	_month = month;
	_day = day;
}

不過,就我而言,還是推薦使用全預設的建構式
因為,這個完全可以兼容前面兩種,

既可以帶參,
也可以不帶參,
還可以帶部分參

豈不美哉!!!!!
在這里插入圖片描述
猩猩看了都說好

2、解構式

這個和名字其實不太一樣,解構式并不是負責銷毀物件的空間,只是做一些,資源清理的作業,

就跟內置型別的物件一樣,類物件的銷毀都是由編譯器完成的,其本身不具備這種操作,

解構式的定義

解構式:與建構式功能相反,解構式不是完成物件的銷毀,區域物件銷毀作業是由編譯器完成的,
而物件在銷毀時會自動呼叫解構式,完成類的一些資源清理作業,

解構式的特性

特征

  1. 解構式名是在類名前加上字符 ~,
  2. 無引數無回傳值,不能多載,
  3. 一個類有且只有一個解構式,若未顯式定義,系統會自動生成默認的解構式,
  4. 物件生命周期結束時,C++編譯系統系統自動呼叫解構式,

對于日期類,額,這個解構式基本沒什么作用,但是對于堆疊、佇列等資料結構的類還是相當有意義的,

清理資源可以防止記憶體泄漏等等,

比如陣列堆疊呼叫結束后,解構式就會自動呼叫,去銷毀其陣列,就不會因為我們忘記呼叫free()而造成記憶體泄漏

對于內置型別,解構式不會起什么作用
對于自定義型別,解構式會清理物件的資源,進行析構,

多個物件,解構式呼叫的順序

對于日期類

class Date
{
public:
	
	Date(int year=1, int month=1, int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print(void)
	{
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main(void)
{
	
	Date d1(2002);
	Date d2;
	Date d3;
	Date d4;
	d1.print();//->print(&d1)
	d2.print();//->print(&d2)
}

物件d1 d2 d3 d4都是通過函式調用在堆疊為物件分配空間,當其生命周期結束后,編譯器就會銷毀物件在堆疊上的空間,

但是,物件銷毀的順序是什么呢?資料結構-堆疊

要知道,這幾個物件都是存放在堆疊上的,

對于作業系統中的堆疊與資料結構中的堆疊,基本沒啥關系,但是兩者都符合后進先出的條件
在這里插入圖片描述

我們知道資料結構堆疊對于資料元素的處理規則是后進先出,則,對于在堆疊上的物件而言,也會符合這個要求,

在生命周期結束后,
先創建的物件,后呼叫解構式;
后創建的物件,先呼叫解構式,


解構式呼叫順序

  1. d4
  2. d3
  3. d2
  4. d1

要注意,當生命周期到的時候,

3、拷貝建構式

在這里插入圖片描述
回聲開大:人格復制
就是在一定時間內(好像最近時間又削短了),回聲可以復制敵方任意一個英雄,并擁有其所有技能(充能時間也大大縮短),

就相當于,在這一定時間內,你所操作的英雄換了一個類物件,

或者說,
對面一個被你選中的英雄的所有物件資料被你Ctrl C + Ctrl V,完全被你拷貝過來了,

換句話說,在這一定時間內,你根據他的類,創建了一個一模一樣的物件,

拷貝建構式的特性

特性如下:

  1. 拷貝建構式是建構式的一個多載形式,即函式名也是類名,不過其引數串列是與物件有關,
  2. 拷貝建構式的引數只有一個且必須使用參考傳參,使用傳值方式會引發無窮遞回呼叫

為什么傳值傳參會引發無窮遞回呼叫

正常的拷貝構造-傳參考呼叫
對于參考的介紹請參考C++入門語法

Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
Date d1;
Date d2(d1);

如果是傳值呼叫

Date(Date d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

因為,
對于形參要開辟一個物件空間,再將實參物件傳遞過來,將實參拷貝到這個形參空間,但,又需要呼叫拷貝建構式,又需要開辟一個形參空間,,,,就這樣不斷拷貝,不斷開辟空間,無限遞回,

概念圖
在這里插入圖片描述

而對于傳參考呼叫
傳遞的是要拷貝的物件的別名,只是拿到了目標物件的別名,可以對其進行訪問,并且,這個程序并沒有再開辟空間

其實,也可以傳址呼叫

Date(Date* d)
{
	_year = d->_year;
	_month = d->_month;
	_day = d->_day;
}

Date d1;
Date d2(&d1);

雖然也可以達到拷貝的效果,但是,其語法上并不是拷貝建構式,而且比較麻煩,畢竟指標這容易出錯,

拷貝構造與傳參考

如果是對于一個物件而言,

void test1(Date d)
{

}
void test2(Date& d)
{

}
test1(d1);
test2(d1);

對于test1()而言,是傳值呼叫,要先開辟一個空間,將物件d1拷貝過去,就需要呼叫拷貝建構式,
但是test2(),是傳參考,直接就可以訪問到源空間,根本不需要呼叫拷貝建構式,這樣對于效率也要快好多,

而且,拷貝構造畢竟是傳參考,如果不小心會改變源空間的值,因為拷貝構造也是在類的公共區段,是在類內,不會被訪問限定,
那么最好加一個const去防止源物件的成員變數被修改,

Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

這樣,哪怕寫反了復制順序,編譯器自己也會報錯,而不會影響程式,

深拷貝與淺拷貝

拷貝構造也是一個默認成員函式
如果我們不自己定義,編譯器也會自動生成,

如果是一個日期類,類的成員變數全部都是內置型別的那種,編譯器會自動生成一個拷貝建構式,一個個直接拷貝過去,簡稱:值拷貝淺拷貝

#include <iostream>

class A
{
public:
	A()
	{
		std::cout << "A()" << std::endl;
	}
private:
	int a;
};
class Date
{
public:
	
	Date(int year=1, int month=1, int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print(void)
	{
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;

};

int main(void)
{
	
	Date d1(2002);
	Date d2(d1);
	d1.print();//->print(&d1)
	d2.print();//->print(&d2)
}

在這里插入圖片描述
這就是輸出結果,依舊完成了拷貝構造,

但是對于一些自定義型別而言會出大問題,
比如:陣列堆疊類

class Stack
{
private:
	int* arr;
	int size;
	int capacoty;
};

當其對于堆疊物件進行拷貝構造,對這三個內置型別進行淺拷貝
在這里插入圖片描述
這樣淺拷貝完全不符合我們的要求,我們是希望通過拷貝,可以得到另一個與原物件一模一樣的物件,
而這樣的淺拷貝,當對物件 s1 進行改變時,物件 s2 也會受到改變,

就像

int a=10;
int b=a;

這是我們希望達到的效果,

而對,像堆疊、佇列這一類的類,淺拷貝都無法達到正確效果,只有依靠深拷貝去完成,(嘿嘿,這個下次再完成)
在這里插入圖片描述
而如果日期類中有自定義型別的成員變數,其會去呼叫自定義型別的拷貝建構式,來完成對自定義型別的拷貝,

4、賦值運算子多載

這也是類的默認成員函式

  • 函式多載:支持定義同名函式
  • 運算子多載:為了讓自定義型別可以像內置型別一樣去使用運算子

C++為了增強代碼的可讀性引入了運算子多載,運算子多載是具有特殊函式名的函式,也具有其回傳值類
型,函式名字以及引數串列,其回傳值型別與引數串列與普通的函式類似

C++為了讓自定義型別的物件也能像內置型別的物件一樣進行例如+、-、*、/、=、==的一系列操作,提供了運算子多載這種特性,

運算子多載的特性

特性如下:

  1. 函式名字為:關鍵字operator后面接需要多載的運算子符號,
  2. 函式原型:回傳值型別 operator運算子(引數串列)
  3. 不能通過連接其他符號來創建新的運算子:比如operator@
  4. 多載運算子必須有一個型別別或者列舉型別的運算元
  5. 用于內置型別的運算子,其含義不能改變,例如:內置的整型+,不 能改變其含義
  6. 作為類成員的多載函式時,其形參看起來比運算元數目少1成員函式的
    運算子有一個默認的形參this,限定為第一個形參

注意.* 、:: 、sizeof 、?: 、. 注意以上5個運算子不能多載,

像這類函式,都是定義在類中,不然,訪問不到成員變數,

繼續拿日期類舉例

#include <iostream>


class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	bool operator==(const Date& d)
	{
		return (_year == d._year) && (_month == d._month) && (_day = d._day);
	}
	void print(void)
	{
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main(void)
{
	
	Date d1(2020,1,3);
	Date d2(d1);
	std::cout << (d1 == d2) << std::endl;
	d1.print();//->print(&d1)
	d2.print();//->print(&d2)
}

在類中定義運算子多載函式,使用時,就像內置型別一樣使用運算子即可,
同時,

要注意,在自定義型別使用運算子時,第一個引數會是this指向的物件,

第一個引數this指標是左運算元,第二個引數是右運算元

再來看一個陣列類,有點神奇,

#include <iostream> 
class Array
{
public:
	Array()
	{
		for (int i = 0; i < 10; i++)
		{
			_arr[i] = i;
		}
		_size = 10;
	}
	int& operator[](int pos)
	{
		return _arr[pos];
	}
	int GetSize(void)
	{
		return _size;
	}
private:
	int _arr[10];
	int _size;
};
int main(void)
{
	Array arr;
	for (int i = 0; i < arr.GetSize(); i++)
	{
		std::cout << arr[i] << " ";
	}
	return 0;
}

在這里插入圖片描述

這個[]運算子多載,讓物件可以像陣列一樣訪問,如果是回傳參考的話
甚至可以對陣列的值進行修改,就像普通陣列那樣操作

復制拷貝與拷貝構造

內置型別

int a=10;
int b=a;//拷貝構造
//
int a=10;
int b;
b=a;//復制拷貝

自定義型別

Date d1(2021,6,5);
Date d2(d1);//拷貝構造
//
Date d1(2021,6,5);
Date d2;
d2=d1;//復制拷貝

復制拷貝:
物件已經初始化好后,再把一個物件拷貝給另一個物件
拷貝構造:
在物件還在創建時,拿另一個同類物件去初始化這一個物件

對于自定義型別,
拷貝構造呼叫拷貝建構式,
復制拷貝呼叫=運算子多載函式

Date& operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

為了讓自定義型別能夠像內置型別一樣,連續賦值,與防止自己給自己賦值

a=b=c;

畢竟,賦值運算子多載函式是默認成員函式,即使我們不寫,編譯器也會自動生成,

#include <iostream>


class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	bool operator==(const Date& d)
	{
		return (_year == d._year) && (_month == d._month) && (_day = d._day);
	}
	void print(void)
	{
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main(void)
{
	
	Date d1(2020,1,3);
	Date d2;
	d2=d1;
	d1.print();//->print(&d1)
	d2.print();//->print(&d2)
}

輸出結果
在這里插入圖片描述

對于內置型別的復制拷貝,編譯器會通過默認的賦值運算子多載完成淺拷貝,

對于自定義型別的復制拷貝,編譯器會呼叫自定義型別的賦值運算子多載完成拷貝,

5、對前4個默認成員函式的總結

  1. 建構式:完成物件的初始化,但大部分情況下,還是需要我們自己去定義建構式
  2. 解構式:在對像生命周期結束時,對物件的資源進行清理
  3. 拷貝建構式:建構式的多載,其中一個物件還未初始化,完成對物件之間的拷貝(深/淺拷貝)
  4. 賦值運算子多載:也是拷貝行為,但是是基于兩個物件已經被初始化的情況下,

對于構造和析構的特性是類似的,
對于內置型別,我們不寫,編譯器基本不會處理,
而自定義型別,編譯器會呼叫自定義型別的析構和構造

對于拷貝構造和賦值運算子多載的特性是類似的,
對于內置型別,編譯器會進行淺拷貝(直接賦值),
對于自定義型別,編譯器要呼叫自定義型別的拷貝構造和賦值運算子多載,進行深拷貝,
在這里插入圖片描述

6、const成員函式

特性

將const修飾的類成員函式稱之為const成員函式,const修飾類成員函式,
實際修飾該成員函式隱含的this指標,表明在該成員函式中不能對類的任何成員進行修改

注意:只有成員函式才能加const,如構造,析構等等都不能加

就是當我們確幸該成員函式中不會,也不會造成物件的任何成員變數被修改
就使用const去修飾成員函式,這樣也是為了保險,防止錯誤操作,
比如:

bool operator==(const Date& d)const
	{
		return (_year == d._year) && (_month == d._month) && (_day = d._day);
	}

幾個問題理解const

  1. const物件可以呼叫非const成員函式嗎?
  2. 非const物件可以呼叫const成員函式嗎?
  3. const成員函式內可以呼叫其它的非const成員函式嗎?
  4. 非const成員函式內可以呼叫其它的const成員函式嗎?

const修飾后,對成員函式或者物件所能操作的權限就被縮小,而且,權限不能放大,且不能有放大權限的可能
對于題目

  1. 不可以,因為非const成員函式中其this指標沒有const修飾,也就存在放大權限的可能,可能會對this指標指向的成員變數進行修改,
  2. 可以,這屬于權限縮小,而縮小權限,是不會報錯的,
  3. 不可以,本身this指標就是const,當呼叫非const成員函式后,其隱含的this指標未被const修飾,就存在權限放大的可能,
  4. 可以,this指標在const成員函式中被const修飾,屬于權限縮小,

7、取地址運算子多載

class Date
{ 
public :
 	Date* operator&()
 	{
 		return this ;
 	}
 
 	const Date* operator&()const
 	{
 		return this ;
 	}
private :
 	int _year ; // 年
 	int _month ; // 月
 	int _day ; // 日
 }

這個運算子基本沒什么那啥價值,根本不需要我們去實作它,默認的成員函式就完全夠用了,

保護物件不被讀取地址

但是,如果你不希望物件的地址被讀出來,要對其進行保護

class Date
{ 
public :
 	Date* operator&()
 	{
 		return nullptr ;
 	}
 
 	const Date* operator&()const
 	{
 		return nullptr ;
 	}
private :
 	int _year ; // 年
 	int _month ; // 月
 	int _day ; // 日
 }

這樣,當別人想讀物件的地址時,就只能讀出00000000,可以對你的物件進行保護,

四、一個完整的日期類(選讀,可跳)

直接看代碼,這里面是學會對運算子多載的應用,本身沒什么演算法知識,

日期類.cpp

#include "date.h"
void test(void)
{
	Date d1(-11,5,26);
	Date d2(2025,1,1);
	d1.print();
	//date d3= d1 + 4;//->operator(&d2,4) 
	//d3 = d1 + 3;//->operator(&d2,4)
	d3.print();
	//date d3 = d1 - 10;
	//d1.print();
	//d2.print();
	d3.print();
	//int days = d1 - d2;
	//std::cout << days;
	Date d3=d2++;
	d3.print();
	d2.print();
	if (d3 >= d2)
	{
		std::cout << "d3<=d2" << std::endl;
	}
	//d1 += 4;//->operator+=(&d1,4)
	//d1 += 1;
	/*d1.print();
	d2.print();
	d3.print();*/
}
int main(void)
{
	test();
	
}

Date.cpp

#include "Date.h"
inline int MonthDays(int year, int month)
{
	static int DayArrary[] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = DayArrary[month - 1];
	if (month == 2 && (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0))
	{
		day = 29;
	}
	return day;
}
Date::Date(int year, int month, int day)
{
	if (year != 0 && month <= 12 && month > 0 && day <= MonthDays(year, month) && day > 0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		std::cout << "date illegal" << std::endl;
		assert(false);
	}
}
void Date::print(void)
{
	if (_year >= 1)
		std::cout << "公元 ";
	else
		std::cout << "公元前 ";
	std::cout << _year << "年" << _month << "月" << _day<<"日"<<std::endl;
}
Date::Date(const Date& d)//拷貝構造
{
	_year =d._year;
	_month = d._month;
	_day = d._day;
}
Date& Date::operator+=(int day)//+=運算賦多載
{
	_day += day;
	while (_day > MonthDays(_year, _month))
	{
		_day -= MonthDays(_year, _month);
		_month++;
		if (_month > 12)
		{
			_year++;
			if (_year == 0)
				_year = 1;
			_month = 1;
		}
	}
	return *this;
}
Date Date::operator+(int day)//+運算子多載
{
	//建立一個臨時物件
	Date tmp(_year,_month,_day);//
	tmp += day;//->operator+=(&tmp,day);//對這個臨時物件的操作
	return tmp;
}
Date& Date::operator-=(int day)//-運算子多載
{
	_day -= day;
	while (_day<=0)//終止條件->>_day > 0 && _day <= MonthDays(_year, _month)
	{
		_month--;
		if (_month < 1)
		{
			_year--;
			if (_year == 0)
				_year = -1;
			_month = 12;
		}
		_day += MonthDays(_year, _month);
	}
	return *this;
}
Date Date::operator-(int day)//-運算子多載
{
	Date tmp = (*this);
	tmp -= day;
	return tmp;
}



//日期之間的天數


int YearDays(int year)
{
	if (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0)
		return 366;
	return 365;
}
int Date::operator-(Date& d)//日期減日期
{
	int Days = 0;
	Date bigDate;
	Date smallDate;
	if (_year > d._year)
	{
		bigDate = *this;
		smallDate = d;
	}
	else if (_year < d._year)
	{
		bigDate = d;
		smallDate = *this;
	}
	
	else//同一年
	{
		//小月的剩余天數加上中間月份的天數,再加上大月的天數
		if (_month > d._month)
		{
			bigDate = *this;
			smallDate = d;
		}
		else if (_month < d._month)
		{
			bigDate = d;
			smallDate = *this;
		}
		else//同一個月
		{
			int tmp = abs(_day - d._day);
			return tmp;
		}
	}
	int yearTmp = bigDate._year - smallDate._year - 1;
	int monthTmp = bigDate._month - smallDate._month - 1;
	
	for (int i = 1; i <= yearTmp; ++i)
	{
		Days += YearDays(smallDate._year + i);
	}

	Days += MonthDays(smallDate._year, smallDate._month) - smallDate._day;
	for (int i = smallDate._month + 1; i <= 12; i++)
		Days += MonthDays(smallDate._year, i);
	for(int i=1;i<bigDate._month;i++)
		Days+= MonthDays(smallDate._year, i);
	Days += bigDate._day;
	return Days;
}


Date& Date::operator++()//前置++
{
	*this += 1;
	return *this;
}
Date Date::operator++(int) 后置++
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
Date Date::operator--(int)// 后置--
{
	Date tmp(*this);
	--* this;
	return tmp;
}
Date& Date::operator--()// 前置--
{
	*this -= 1;
	return *this;
}
// >=運算子多載
bool Date::operator>=(const Date& d)
{
	if (_year > d._year)
		return 1;
	else if(_year<d._year)
		return 0;
	else
	{
		if (_month > d._month)
			return 1;
		else if (_month < d._month)
			return 0;
		else
		{
			if (_day > d._day)
				return 1;
			else if (_day < d._day)
				return 0;
			else
				return 1;
		}
	}
}

// <運算子多載
bool Date::operator<(const Date& d)
{
	if (_year < d._year)
		return 1;
	else if (_year > d._year)
		return 0;
	else
	{
		if (_month < d._month)
			return 1;
		else if (_month > d._month)
			return 0;
		else
		{
			if (_day < d._day)
				return 1;
			else if (_day > d._day)
				return 0;
			else
				return 0;
		}
	}
}
// <=運算子多載
bool Date::operator<=(const Date& d)
{
	if (_year < d._year)
		return 1;
	else if (_year > d._year)
		return 0;
	else
	{
		if (_month < d._month)
			return 1;
		else if (_month > d._month)
			return 0;
		else
		{
			if (_day < d._day)
				return 1;
			else if (_day > d._day)
				return 0;
			else
				return 1;
		}
	}
	
}
// !=運算子多載
bool Date::operator!=(const Date& d)
{
	if (_year == d._year && _month == d._month && _day == d._day)
		return 0;
	return 1;
}

這么長?源氏看來都要拔刀了
在這里插入圖片描述

Date.h

#pragma once
#include <iostream>
#include <cassert>
class Date
{
public:
	//建構式
	Date(int year = 1, int month = 1, int day = 1);
	Date(const Date& d);//拷貝構造
	Date& operator+=(int day);//+=運算賦多載
	Date operator+(int day);//+運算子多載
	Date& operator-=(int day);//-=運算子多載
	Date operator-(int day);//-運算子多載
	int operator-(Date& d);//日期減日期

	Date& operator++();//前置++
	Date operator++(int); 后置++
	Date operator--(int);// 后置--
	Date& operator--();// 前置--

	 // >運算子多載

	bool operator>(const Date& d);



	// ==運算子多載

	bool operator==(const Date& d);



	// >=運算子多載
	bool operator>=(const Date& d);
	// <運算子多載
	bool operator<(const Date& d);
	// <=運算子多載
	bool operator<=(const Date& d);
	// !=運算子多載
	bool operator!=(const Date& d);

	void print(void);
private:
	int _year;
	int _month;
	int _day;
};

實作>,<之類的運算子多載過于瑣碎,大家小心觀看,因為我當時的順序不一樣,寫多了,

五、友元與>>、<<運算子多載

在這里插入圖片描述

為什么cin>>、cout<<可以自動識別內置型別

因為,在庫中早已經多載好了,并且可以自動識別型別,各個函式之間構成多載,

cout

在這里插入圖片描述
我以我LZ的英文水平翻譯看看,cout是一個ostream類的物件,可以通過運算子<<將格式化或未格式化的資料通過成員函式寫入到,,,,我就不知道咋翻了,

cin

在這里插入圖片描述
很明顯,cin是一個istream的類的物件,通過<<與成員函式完成對輸入流的寫入,
這些都不重要,大概了解是啥就行,

cout是根據ostream創建的一個物件
cin是根據istream創建的一個物件

這樣,我們可以完成對自定義型別簡化為內置型別.

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 >> d._month >> d._day;
	return _cin;
}

按照這樣的形式,我們可以做到像內置型別一樣直接輸入,

如果,要定義在類中,我們的coutcin都是運算子的左邊,導致,this指標就無法指向類中的成員變數,
所以定義在類中并不合適,只能定義在類外,

但是,在類外,我們就無法訪問到類中的成員變數,
所以,引入一個友元

友元函式

提供友元函式,

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

class Date
{
public:
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);

	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	bool operator==(const Date& d)
	{
		return (_year == d._year) && (_month == d._month) && (_day = d._day);
	}
	void print(void)
	{
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
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 >> d._month >> d._day;
	return _cin;
}
int main()
{
	 Date d;
	 cin>>d;
 	cout<<d<<endl;
 	return 0;
 }

這樣,友元可以突破封裝,相當于開了一個后門,

注意

  1. 友元函式可訪問類的私有和保護成員,但不是類的成員函式
  2. 友元函式不能用const修飾
  3. 友元函式可以在類定義的任何地方宣告,不受類訪問限定符限制
  4. 一個函式可以是多個類的友元函式
  5. 友元函式的呼叫與普通函式的呼叫和原理相同

友元類

友元類的所有成員函式都可以是另一個類的友元函式,都可以訪問另一個類中的非公有成員,
例如:

class Time;//時間類前置宣告
class Date
{
public:
	friend class Time;//宣告時間類是日期類的友元
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	bool operator==(const Date& d)
	{
		return (_year == d._year) && (_month == d._month) && (_day = d._day);
	}
	void print(void)
	{
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
class Time
{
public:
	Time(int second = 0, int minute = 0, int hour = 1)
	{
		_second = second;
		_minute = minute;
		_hour = hour;
	}
private:
	int _second;
	int _minute;
	int _hour;
};

時間類是日期類的友元,則,時間類中的所有成員函式都可以說日期類的友元函式,
則,在時間類中可以訪問到日期類在的私有成員變數
但是,日期類依舊不能訪問到時間類中的私有成員變數,

總結:

  1. 友元關系是單向的不具有交換性,(如上)
  2. 友元關系不能傳遞
    如果B是A的友元,C是B的友元,則不能說明C時A的友元,

在這里插入圖片描述

六、深入了解建構式

在創建物件的時候,物件通過呼叫建構式來初始化物件,
C++在建構式上提供了兩種方案來初始化成員變數,

1、函式體內初始化

就是我們常見的初始化的模式

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

2、初始化串列

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

此模式的初始化規則:

初始化串列:以一個冒號開始,接著是一個以逗號分隔的資料成員串列,每個"成員變數"后面跟一個放在括號中的初始值或運算式

3、為什么要多此一舉準備兩種初始化模式呢?

事實上,雖然函式體內呼叫建構式后,物件中已經有了一個初始值,但是不能將其稱作為類物件成員的初始化,建構式體中的陳述句只能將其稱作為賦初值,而不能稱作初始化,因為初始化只能初始化一次,而建構式體內可以多次賦值,
例如:
在這里插入圖片描述
初始化串列兩次初始化,編譯器直接報錯,因為在串列中初始化只能初始化一次,但是,
在這里插入圖片描述
編譯器卻可以允許,
來看看結果
在這里插入圖片描述
這已經不叫初始化了,而是叫賦值,初始化畢竟只能初始化一次,
這也再一次證明了,函式體內“初始化”,準確來叫應該是賦值,而非初始化,
初始化串列才是真正的初始化成員變數
從而可以推出,創建一個物件,對于物件而言,其中的成員變數是在初始化串列的時候定義的

初始化串列才是單個成員變數定義的階段

而且,對于有些成員變數,只有在初始化的時候才能賦初值,且之后就不能修改了,
例如

  1. const修飾的成員變數
  2. 參考成員變數
  3. 自定義型別的成員變數(沒有默認建構式)

對于沒有要求一定要在定義的時候初始化的成員變數,可以在函式體內”初始化“,

例如這樣

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
		,i(10)
		,ret(_year)
	{
		_year = 100;
		_year = 200;
	}
private:
	int _year;
	int _month;
	int _day;
	const int i;
	int& ret;
};

對于第三種,沒有默認建構式的自定義成員變數
也就是說,自定義類中沒有不需要傳參就能初始化的建構式,也就是說,忘記傳參就會導致報錯,
這樣將自定義成員變數,顯示定義到初始化串列中,可以我i自定義成員變數初始化,還不會報錯,

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)

		:_year(year)
		, _month(month)
		, _day(day)
		,i(10)
		,ret(_year)
		,t(0)
	{
		_year = 100;
		_year = 200;
	}
private:
	int _year;
	int _month;
	int _day;
	const int i;
	int& ret;
	Time t;
};
class Time
{
public:
	Time(int t)
	{
		_t = t;
	}
private:
	int _t;
};

如果不顯示初始化串列,編譯器就會報錯沒有默認的建構式

還有就是注意,初始化串列是成員函式定義的階段,無論是哪種“初始化”模式,最終都會經歷初始化串列這個階段
所以還是推薦使用初始化串列,保險一點

初始化串列初始化的順序

成員變數在類中宣告次序就是其在初始化串列中的初始化順序與其在初始化串列中的先后次序無關

這個知識點有點繞,但還蠻重要的,

學會了嗎?不會?死神手把手教你,他很會繞的
在這里插入圖片描述

文章關于類與物件的知識比較零碎,又比較多,還比較重要,其實更多是對我自己的總結,

看到這也挺不容易的,
在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286495.html

標籤:其他

上一篇:Unity3D UGUI實作翻書效果

下一篇:2021藍橋杯國賽B組C/C++個人記錄

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more