C嘎嘎:類和對象(一)

目錄

面向過程和面向對象的初步認識

類的引入

類的定義

類的訪問限定符及封裝

訪問限定符

封裝

類的作用域

類的實例化

類對象模型

如何計算類對象大小

結構體內存對齊規則

this指針

this指針的引出

this指針的特性

類的6個默認成員函數

構造函數

概念

特性

析構函數

概念

特性

拷貝構造函數

概念

特性

賦值運算符重載

運算符重載

賦值運算符重載

前置++重載和后置++重載

const成員

取地址及const取地址操作符重載

友元

友元函數

友元類


面向過程和面向對象的初步認識

C語言是面向過程的,其核心在于過程,通常分析出問題的步驟然后進行調用函數進行解決,過程中的每一步都需要進行手動操作實現。
C++是面向對象的,也就是關注的是對象并不關注對象是怎么實現的對應操作的。
以洗衣服為例子,對于面向過程而言整個過程就是用盆子接水->放衣服->放洗衣粉->手搓->倒水->接水->放洗衣粉->...
而對于面向對象而言,相對就沒有這么繁瑣了,對象就是人、洗衣機、衣服、洗衣粉,我將衣服放入洗衣機中放入洗衣粉,然后打開洗衣機就可以了。至于洗衣機是怎么洗衣服的我并不需要關注。

類的引入

C 語言結構體中只能定義變量,在 C++ 中,結構體內不僅可以定義變量,也可以定義函數。 比如:
之前在數據結構初階中,用 C 語言方式實現的棧,結構體中只能定義變量 ;現在以 C++ 方式實現,
會發現 struct 中也可以定義函數
typedef int DataType;
struct Stack
{void Init(size_t capacity){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc申請空間失敗");return;}_capacity = capacity;_size = 0;}void Push(const DataType& data){// 擴容_array[_size] = data;++_size;}DataType Top(){return _array[_size - 1];}void Destroy(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}DataType* _array;size_t _capacity;size_t _size;
};
int main()
{Stack s;s.Init(10);s.Push(1);s.Push(2);s.Push(3);cout << s.Top() << endl;s.Destroy();return 0;
}

像這樣的我們通常在C++中以類class實現。

類的定義

class className
{
// 類體:由成員函數和成員變量組成
}; ? // 一定要注意后面的分號
class 定義類的 關鍵字, ClassName 為類的名字, {} 中為類的主體,注意 類定義結束時后面
號不能省略
類體中內容稱為 類的成員: 類中的 變量 稱為 類的屬性 成員變量 ; 類中的 函數 稱為 類的方法 或者

成員函數

類的兩種定義方式:
1. 聲明和定義全部放在類體中,需注意:成員函數如果 在類中定義 ,編譯器可能會將其當成
聯函數 處理。
class student
{
public:void print(){cout << name << " " << scores;     }
private:string name;double scores;   
};
2. 類聲明放在 .h 文件中,成員函數定義放在 .cpp 文件中,注意: 成員函數名前需要加類名 :
class student
{
public:void print();    
private:string name;double scores;   
};

?在.cpp文件中需要訪問類域:

void student::print()
{cout << name << " " << scores << endl ;
}

類的訪問限定符及封裝

訪問限定符

C++實現封裝的方式:用類將對象的屬性與方法結合在一塊,讓對象更加完善,通過訪問權限選

擇性的將其接口提供給外部的用戶使用.

【訪問限定符說明】
1. public 修飾的成員在類外可以直接被訪問
2. protected private 修飾的成員在類外不能直接被訪問 ( 此處 protected private 是類似的 )
3. 訪問權限 作用域從該訪問限定符出現的位置開始直到下一個訪問限定符出現時為止
4. 如果后面沒有訪問限定符,作用域就到 } 即類結束。
5. class 的默認訪問權限為 private struct public;

封裝

面向對象的三大特性: 封裝、繼承、多態
在類和對象階段,主要是研究類的封裝特性,那什么是封裝呢?
封裝:將數據和操作數據的方法進行有機結合,隱藏對象的屬性和實現細節,僅對外公開接口來
和對象進行交互。
封裝本質上是一種管理,讓用戶更方便使用類 。比如:對于電腦這樣一個復雜的設備,提供給用
戶的就只有開關機鍵、通過鍵盤輸入,顯示器, USB 插孔等,讓用戶和計算機進行交互,完成日
常事務。但實際上電腦真正工作的卻是 CPU 、顯卡、內存等一些硬件元件。
對于計算機使用者而言,不用關心內部核心部件,比如主板上線路是如何布局的, CPU 內部是如
何設計的等,用戶只需要知道,怎么開機、怎么通過鍵盤和鼠標與計算機進行交互即可。因此
算機廠商在出廠時,在外部套上殼子,將內部實現細節隱藏起來,僅僅對外提供開關機、鼠標以
及鍵盤插孔等,讓用戶可以與計算機進行交互即可
C++ 語言中實現封裝,可以通過類將數據以及操作數據的方法進行有機結合,通過訪問權限來 隱藏對象內部實現細節,控制哪些方法可以在類外部直接被使用

類的作用域

類定義了一個新的作用域 ,類的所有成員都在類的作用域中 在類體外定義成員時,需要使用 ::
作用域操作符指明成員屬于哪個類域。
就像上面的類class一樣,不過多解釋。

類的實例化

用類類型創建對象的過程,稱為類的實例化
1. 類是對對象進行描述的 ,是一個 模型 一樣的東西,限定了類有哪些成員,定義出一個類 并沒
有分配實際的內存空間 來存儲它;比如:入學時填寫的學生信息表,表格就可以看成是一個
類,來描述具體學生信息。
2. 一個類可以實例化出多個對象, 實例化出的對象 占用實際的物理空間,存儲類成員變量
Person 類是沒有空間的,只有 Person 類實例化出的對象才有具體的年齡。
3. 做個比方。 類實例化出對象就像現實中使用建筑設計圖建造出房子,類就像是設計圖 ,只設
計出需要什么東西,但是并沒有實體的建筑存在,同樣類也只是一個設計,實例化出的對象才能實際存儲數據,占用物理空間。
總之,類跟struct一樣都看成是自定義類型即可;

類對象模型

如何計算類對象大小

先定義一個簡單的類;

class A
{
public:
void PrintA()
{cout<<_a<<endl;
}
private:
char _a;
};

類中既有成員函數又有成員變量,那么如何計算類的大小呢?
類的內存大小是指的類成員變量的所占的內存大小,成員函數并不計入在內;其計算方式與結構體的計算大小方式相同;不同的是如果一個類是空的那么他的大小不是0而是1;

結構體內存對齊規則

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

this指針

this指針的引出

先來定義一個日期類;

class Date
{ 
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout <<_year<< "-" <<_month << "-"<< _day <<endl;}
private:int _year; ? ? // 年int _month; ? ?// 月int _day; ? ? ?// 日
};
int main()
{Date d1, d2;d1.Init(2022,1,11);d2.Init(2022, 1, 12);d1.Print();d2.Print();return 0;
}
對于上述類,有這樣的一個問題:
Date 類中有 Init Print 兩個成員函數,函數體中沒有關于不同對象的區分,那當 d1 調用 Init
數時,該函數是如何知道應該設置 d1 對象,而不是設置 d2 對象呢?
C++ 中通過引入 this 指針解決該問題,即: C++ 編譯器給每個 非靜態的成員函數 增加了一個隱藏
的指針參數,讓該指針指向當前對象 ( 函數運行時調用該函數的對象 ) ,在函數體中所有 成員變量
的操作,都是通過該指針去訪問。只不過所有的操作對用戶是透明的,即用戶不需要來傳遞,編
譯器自動完成

this指針的特性

1. this 指針的類型:類類型 * const ,即成員函數中,不能給 this 指針賦值。
2. 只能在 成員函數 的內部使用
3. this 指針本質上是 成員函數 的形參 ,當對象調用成員函數時,將對象地址作為實參傳遞給
this 形參。所以 對象中不存儲 this 指針
4. this 指針是 成員函數 第一個隱含的指針形參,一般情況由編譯器通過 ecx 寄存器自動傳
遞,不需要用戶傳遞。
也就是每實例化一個類對象都會存在一個this指針,在訪問成員函數時,函數中存在一個隱藏的參數就是const? 類名?*this;在函數中可以直接訪問成員變量也可以使用this->成員變量;
class date 
{
public:void print(){cout << _year << "-" << _month << "-"<<_day;    }
private:    int _year;int _month;int _day;
};
class date 
{
public:void print(){cout << this->_year << "-" <<this-> _month << "-"<<this->_day;    }
private:    int _year;int _month;int _day;
};

二者等同。

類的6個默認成員函數

如果一個類中什么成員都沒有,簡稱為空類。
空類中真的什么都沒有嗎?并不是,任何類在什么都不寫時,編譯器會自動生成以下 6 個默認成員
函數。
默認成員函數:用戶沒有顯式實現,編譯器會生成的成員函數稱為默認成員函數。

構造函數

概念

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Init(2022, 7, 5);d1.Print();Date d2;d2.Init(2022, 7, 6);d2.Print();return 0;
}
對于 Date 類,可以通過 Init 公有方法給對象設置日期,但如果每次創建對象時都調用該方法設置
信息,未免有點麻煩,那能否在對象創建時,就將信息設置進去呢?
構造函數 是一個 特殊的成員函數,名字與類名相同 , 創建類類型對象時由編譯器自動調用 ,以保證
每個數據成員都有 一個合適的初始值,并且 在對象整個生命周期內只調用一次

特性

構造函數 是特殊的成員函數,需要注意的是,構造函數雖然名稱叫構造,但是構造函數的主要任
并不是開空間創建對象,而是初始化對象
其特征如下:
1. 函數名與類名相同。
2. 無返回值。
3. 對象實例化時編譯器 自動調用 對應的構造函數。
4. 構造函數可以重載。
class Date
{
public:// 1.無參構造函數Date(){}// 2.帶參構造函數Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;};void TestDate(){Date d1; // 調用無參構造函數Date d2(2015, 1, 1); // 調用帶參的構造函數// 注意:如果通過無參構造函數創建對象時,對象后面不用跟括號,否則就成了函數聲明// 以下代碼的函數:聲明了d3函數,該函數無參,返回一個日期類型的對象// warning C4930: “Date d3(void)”: 未調用原型函數(是否是有意用變量定義的?)Date d3();}
5. 如果類中沒有顯式定義構造函數,則 C++ 編譯器會自動生成一個無參的默認構造函數,一旦
用戶顯式定義編譯器將不再生成。
class Date{public:/*// 如果用戶顯式定義了構造函數,編譯器將不再生成Date(int year, int month, int day){_year = year;_month = month;_day = day;}*/void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;};int main(){// 將Date類中構造函數屏蔽后,代碼可以通過編譯,因為編譯器生成了一個無參的默認構造函數// 將Date類中構造函數放開,代碼編譯失敗,因為一旦顯式定義任何構造函數,編譯器將不再生成// 無參構造函數,放開后報錯:error C2512: “Date”: 沒有合適的默認構造函數可用Date d1;return 0;}
. 關于編譯器生成的默認成員函數,很多童鞋會有疑惑:不實現構造函數的情況下,編譯器會
生成默認的構造函數。但是看起來默認構造函數又沒什么用? d 對象調用了編譯器生成的默
認構造函數,但是 d 對象 _year/_month/_day ,依舊是隨機值。也就說在這里 編譯器生成的
默認構造函數并沒有什么用??
解答: C++ 把類型分成內置類型 ( 基本類型 ) 和自定義類型。內置類型就是語言提供的數據類
型,如: int/char... ,自定義類型就是我們使用 class/struct/union 等自己定義的類型,看看
下面的程序,就會發現編譯器生成默認的構造函數會對自定類型成員 _t 調用的它的默認成員
函數。

class Time
{
public:Time()               {cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}private: int _hour;   int _minute; int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year;int _month;int _day;// 自定義類型Time _t;
};
int main()
{Date d;return 0;
}
注意: C++11 中針對內置類型成員不初始化的缺陷,又打了補丁,即: 內置類型成員變量在
類中聲明時可以給默認值

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year = 1970;int _month = 1;int _day = 1;// 自定義類型Time _t;
};
int main()
{Date d;return 0;
}
7. 無參的構造函數和全缺省的構造函數都稱為默認構造函數,并且默認構造函數只能有一個。
注意:無參構造函數、全缺省構造函數、我們沒寫編譯器默認生成的構造函數,都可以認為
是默認構造函數。
class Date
{
public:Date(){_year = 1900;_month = 1;_day = 1;}Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
// 以下測試函數能通過編譯嗎?
void Test()
{Date d1;    
}

析構函數

概念

通過前面構造函數的學習,我們知道一個對象是怎么來的,那一個對象又是怎么沒呢的?
析構函數:與構造函數功能相反,析構函數不是完成對對象本身的銷毀,局部對象銷毀工作是由
編譯器完成的。而 對象在銷毀時會自動調用析構函數,完成對象中資源的清理工作

特性

析構函數 是特殊的成員函數,其 特征 如下:
1. 析構函數名是在類名前加上字符 ~
2. 無參數無返回值類型。
3. 一個類只能有一個析構函數。若未顯式定義,系統會自動生成默認的析構函數。注意:析構
函數不能重載
4. 對象生命周期結束時, C++ 編譯系統系統自動調用析構函數
ypedef int DataType;
class Stack
{
public:Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申請空間失敗!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};
void TestStack()
{Stack s;s.Push(1);s.Push(2);
}
關于編譯器自動生成的析構函數,是否會完成一些事情呢?下面的程序我們會看到,編譯器
生成的默認析構函數,對自定類型成員調用它的析構函數

class Time
{
public:~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year = 1970;int _month = 1;int _day = 1;// 自定義類型Time _t;
};
int main()
{Date d;return 0;
}
// 程序運行結束后輸出:~Time()
// 在main方法中根本沒有直接創建Time類的對象,為什么最后會調用Time類的析構函數?
// 因為:main方法中創建了Date對象d,而d中包含4個成員變量,其中_year, _month, 
_day三個是
// 內置類型成員,銷毀時不需要資源清理,最后系統直接將其內存回收即可;而_t是Time類對
象,所以在d銷毀時,要將其內部包含的Time類的_t對象銷毀,所以要調用Time類的析構函數。但是:
main函數
// 中不能直接調用Time類的析構函數,實際要釋放的是Date類對象,所以編譯器會調用Date
類的析構函
// 數,而Date沒有顯式提供,則編譯器會給Date類生成一個默認的析構函數,目的是在其內部
調用Time
// 類的析構函數,即當Date對象銷毀時,要保證其內部每個自定義對象都可以正確銷毀
// main函數中并沒有直接調用Time類析構函數,而是顯式調用編譯器為Date類生成的默認析
構函數
// 注意:創建哪個類的對象則調用該類的析構函數,銷毀那個類的對象則調用該類的析構函數
如果類中沒有申請資源時,析構函數可以不寫,直接使用編譯器生成的默認析構函數,比如
Date 類;有資源申請時,一定要寫,否則會造成資源泄漏,比如 Stack 類。

拷貝構造函數

概念

拷貝構造函數 只有單個形參 ,該形參是對本 類類型對象的引用 ( 一般常用 const 修飾 ) ,在用 已存
在的類類型對象創建新對象時由編譯器自動調用

特性

拷貝構造函數也是特殊的成員函數,其 特征 如下:
1. 拷貝構造函數 是構造函數的一個重載形式
2. 拷貝構造函數的 參數只有一個 必須是類類型對象的引用 ,使用 傳值方式編譯器直接報錯
因為會引發無窮遞歸調用。
3. 若未顯式定義,編譯器會生成默認的拷貝構造函數。 默認的拷貝構造函數對象按內存存儲按
字節序完成拷貝,這種拷貝叫做淺拷貝,或者值拷貝。
class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year = 1970;int _month = 1;int _day = 1;// 自定義類型Time _t;
};
int main()
{Date d1;// 用已經存在的d1拷貝構造d2,此處會調用Date類的拷貝構造函數// 但Date類并沒有顯式定義拷貝構造函數,則編譯器會給Date類生成一個默認的拷貝構//造函數Date d2(d1);    return 0;
}
注意:在編譯器生成的默認拷貝構造函數中,內置類型是按照字節方式直接拷貝的,而自定
義類型是調用其拷貝構造函數完成拷貝的。
4. 編譯器生成的默認拷貝構造函數已經可以完成字節序的值拷貝了 ,還需要自己顯式實現嗎?
當然像日期類這樣的類是沒必要的。那么下面的類呢?驗證一下試試?
// 這里會發現下面的程序會崩潰掉?這里就需要我們以后講的深拷貝去解決。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申請空間失敗");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}

注意:類中如果沒有涉及資源申請時,拷貝構造函數是否寫都可以;一旦涉及到資源申請
時,則拷貝構造函數是一定要寫的,否則就是淺拷貝。
5. 拷貝構造函數典型調用場景:
a.使用已存在對象創建新對象
b.函數參數類型為類類型對象
c.函數返回值類型為類類型對象
class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d)
{Date temp(d);return temp;
}
int main()
{Date d1(2022, 1, 13);Test(d1);return 0;
}

為了提高程序效率,一般對象傳參時,盡量使用引用類型,返回時根據實際場景,能用引用
盡量使用引用。

賦值運算符重載

運算符重載

C++ 為了增強代碼的可讀性引入了運算符重載 運算符重載是具有特殊函數名的函數 ,也具有其
返回值類型,函數名字以及參數列表,其返回值類型與參數列表與普通的函數類似。
函數名字為:關鍵字 operator 后面接需要重載的運算符符號
函數原型: 返回值類型 ?operator 操作符 ( 參數列表 )
注意:
不能通過連接其他符號來創建新的操作符:比如 operator@
重載操作符必須有一個類類型參數
用于內置類型的運算符,其含義不能改變,例如:內置的整型 + ,不 能改變其含義
作為類成員函數重載時,其形參看起來比操作數數目少 1 ,因為成員函數的第一個參數為隱
藏的 this
.* :: sizeof ?: . 注意以上 5 個運算符不能重載。這個經常在筆試選擇題中出現。
// 全局的operator==
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};
// 這里會發現運算符重載成全局的就需要成員變量是公有的,那么問題來了,封裝性如何保證?
// 這里其實可以用我們后面學習的友元解決,或者干脆重載成成員函數。
bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
void Test()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);cout << (d1 == d2) << endl;
}

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* this, const Date& d2)// 這里需要注意的是,左操作數是this,指向調用函數的對象bool operator==(const Date& d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};

賦值運算符重載

1. 賦值運算符重載格式
參數類型 const T& ,傳遞引用可以提高傳參效率
返回值類型 T& ,返回引用可以提高返回的效率,有返回值目的是為了支持連續賦值
檢測是否自己給自己賦值
返回 *this :要復合連續賦值的含義
class Date
{ 
public :Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date (const Date& d){_year = d._year;_month = d._month;_day = d._day;}Date& operator=(const Date& d){if(this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:
int _year ;int _month ;int _day ;
};
2. 賦值運算符只能重載成類的成員函數不能重載成全局函數
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
// 賦值運算符重載成全局函數,注意重載成全局函數時沒有this指針了,需要給兩個參數
Date& operator=(Date& left, const Date& right)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
// 編譯失敗:
// error C2801: “operator =”必須是非靜態成員
原因:賦值運算符如果不顯式實現,編譯器會生成一個默認的。此時用戶再在類外自己實現
一個全局的賦值運算符重載,就和編譯器在類中生成的默認賦值運算符重載沖突了,故賦值
運算符重載只能是類的成員函數。
3. 用戶沒有顯式實現時,編譯器會生成一個默認賦值運算符重載,以值的方式逐字節拷貝 。注
意:內置類型成員變量是直接賦值的,而自定義類型成員變量需要調用對應類的賦值運算符
重載完成賦值。

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year = 1970;int _month = 1;int _day = 1;// 自定義類型Time _t;
};
int main()
{Date d1;Date d2;d1 = d2;return 0;
}
既然 編譯器生成的默認賦值運算符重載函數已經可以完成字節序的值拷貝了 ,還需要自己實
現嗎?當然像日期類這樣的類是沒必要的。那么下面的類呢?驗證一下試試?
// 這里會發現下面的程序會崩潰掉?這里就需要我們以后講的深拷貝去解決。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申請空間失敗");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;
}
注意:如果類中未涉及到資源管理,賦值運算符是否實現都可以;一旦涉及到資源管理則必
須要實現。

前置++重載和后置++重載


class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 前置++:返回+1之后的結果// 注意:this指向的對象函數結束后不會銷毀,故以引用方式返回提高效率Date& operator++(){_day += 1;return *this;}// 后置++:// 前置++和后置++都是一元運算符,為了讓前置++與后置++形成能正確重載// C++規定:后置++重載時多增加一個int類型的參數,但調用函數時該參數不用傳遞,編譯器//自動傳遞// 注意:后置++是先使用后+1,因此需要返回+1之前的舊值,故需在實現時需要先將this保存//一份,然后給this + 1// ? ? ? 而temp是臨時對象,因此只能以值的方式返回,不能返回引用Date operator++(int){Date temp(*this);_day += 1;return temp;}
private:int _year;int _month;int _day;
};int main()
{Date d;Date d1(2022, 1, 13);d = d1++; ? ?// d: 2022,1,13 ? d1:2022,1,14d = ++d1; ? ?// d: 2022,1,15 ? d1:2022,1,15return 0;
}

const成員

const 修飾的 成員函數 稱之為 const 成員函數 const 修飾類成員函數,實際修飾該成員函數
隱含的 this 指針 ,表明在該成員函數中 不能對類的任何成員進行修改。
我們先來看看下面的代碼
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022, 1, 13);  d1.Print(); const Date d2(2022, 1, 13); d2.Print();    
}

取地址及const取地址操作符重載

這兩個默認成員函數一般不用重新定義 ,編譯器默認會生成。
class Date{public:Date* operator&(){return this;}const Date* operator&()const{return this;}private:int _year; // 年int _month; // 月int _day; // 日};
這兩個運算符一般不需要重載,使用編譯器生成的默認取地址的重載即可,只有特殊情況,才需
要重載,比如 想讓別人獲取到指定的內容!

友元

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

友元函數

問題:現在嘗試去重載 operator<< ,然后發現沒辦法將 operator<< 重載成成員函數。 因為 cout
輸出流對象和隱含的 this 指針在搶占第一個參數的位置 this 指針默認是第一個參數也就是左操作
數了。但是實際使用中 cout 需要是第一個形參對象,才能正常使用。所以要將 operator<< 重載成
全局函數。但又會導致類外沒辦法訪問成員,此時就需要友元來解決。 operator>> 同理。
	class Date{public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常規調用
// 因為成員函數第一個參數一定是隱藏的this,所以d1必須放在<<的左側ostream& operator<<(ostream& _cout){_cout << _year << "-" << _month << "-" << _day << endl;return _cout;}private:int _year;int _month;int _day;};
友元函數 可以 直接訪問 類的 私有 成員,它是 定義在類外部 普通函數 ,不屬于任何類,但需要在
類的內部聲明,聲明時需要加 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;
};
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;
}

說明:
友元函數可訪問類的私有和保護成員,但不是類的成員函數
友元函數不能用
const修飾
友元函數可以在類定義的任何地方聲明,不受類訪問限定符限制
一個函數可以是多個類的友元函數
友元函數的調用與普通函數的調用原理相同

友元類

友元類的所有成員函數都可以是另一個類的友元函數,都可以訪問另一個類中的非公有成員。
友元關系是單向的,不具有交換性。
比如上述 Time 類和 Date 類,在 Time 類中聲明 Date 類為其友元類,那么可以在 Date 類中直接
訪問 Time 類的私有成員變量,但想在 Time 類中訪問 Date 類中私有的成員變量則不行。
友元關系不能傳遞
如果 C B 的友元, B A 的友元,則不能說明 C A 的友元。
友元關系不能繼承,在繼承位置再給大家詳細介紹。
	class Time{friend class Date; ? // 聲明日期類為時間類的友元類,則在日期類中就直接訪問Time類//中的私有成員變量public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;};class Date{public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接訪問時間類私有的成員變量_t._hour = hour;_t._minute = minute;_t._second = second;}private:int _year;int _month;int _day;Time _t;   };

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

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

相關文章

喜訊丨美格智能通過國際EcoVadis平臺認證企業社會責任并榮獲承諾獎章,彰顯可持續發展實力

作為全球領先的無線通信模組及解決方案提供商&#xff0c;美格智能在社會責任領域再創新高。近日&#xff0c;美格智能憑借在企業社會責任和可持續性采購發展方面的卓越表現&#xff0c;通過國際在線權威評價機構EcoVadis對公司環境、勞工與人權、商業道德、可持續采購等方面審…

根據空格、制表符、回車符等分割字符串re.split

【小白從小學Python、C、Java】 【考研初試復試畢業設計】 【Python基礎AI數據分析】 根據空格、制表符、 回車符等分割字符串 re.split [太陽]選擇題 根據給定的Python代碼&#xff0c;哪個選項是正確的&#xff1f; import re pattern r\s print(f"【顯示】pattern{…

高清圖片壓縮無水印小程序源碼系統 前后端分離 帶完整的安裝代碼包以及搭建教程

系統概述 在當今的數字化時代&#xff0c;圖片作為信息傳播的重要載體&#xff0c;其質量和傳輸效率直接影響到用戶體驗。然而&#xff0c;高清圖片往往伴隨著較大的文件體積&#xff0c;這不僅會占用大量存儲空間&#xff0c;還會拖慢網頁或應用的加載速度。因此&#xff0c;…

熱烈祝賀!全視通多家客戶上榜全球自然指數TOP100!

2024年6月18日&#xff0c;全球醫療機構自然指數TOP100榜單發布&#xff0c;中國醫療機構在其中的表現尤為引人注目。 根據《自然》雜志網站發布的數據&#xff0c;此次公布的排名是基于&#xff08;2023年3月1日至2024年2月29日&#xff09;的統計數據&#xff0c;全球醫療機構…

Python在網絡爬蟲和數據抓取中的應用

Python在網絡爬蟲和數據抓取中的應用 引言 在數字化時代&#xff0c;數據的價值日益凸顯。無論是市場趨勢分析&#xff0c;還是個人偏好預測&#xff0c;數據都扮演著至關重要的角色。Python&#xff0c;作為一種功能強大、語法簡潔的編程語言&#xff0c;為數據的獲取、處理…

旗晟機器人AI智能算法有哪些?

在當今迅猛發展的工業4.0時代&#xff0c;智能制造和自動化運維已然成為工業發展至關重要的核心驅動力。伴隨技術的持續進步&#xff0c;工業場景中的運維巡檢已不再單純地依賴于傳統的人工運維方式&#xff0c;而是愈發多地融入了智能化的元素&#xff0c;其中智能巡檢運維系統…

前端Din字體和造字工房力黑字體文件

Din 字體是一種經典的、簡潔的無襯線字體&#xff0c;它源自1930年代的德國交通標志設計。 造字工房力黑字體適用于數字&#xff0c;駕駛艙標題等統計界面 DIN-Medium.otf 案例 造字工房力黑.TTF 案例

記錄一次MySql鎖等待 (Lock wait timeout exceeded)異常

[TOC](記錄一次MySql鎖等待 (Lock wait timeout exceeded)異常) Java執行一個SQL查詢未提交&#xff0c;遇到1205錯誤。 java.lang.Exception: ### Error updating database. Cause: java.sql.SQLException: Lock wait timeout exceeded; try restarting transactionCluster…

動手學深度學習6.2 圖像卷積-筆記練習(PyTorch)

以下內容為結合李沐老師的課程和教材補充的學習筆記&#xff0c;以及對課后練習的一些思考&#xff0c;自留回顧&#xff0c;也供同學之人交流參考。 本節課程地址&#xff1a;卷積層_嗶哩嗶哩_bilibili 代碼_嗶哩嗶哩_bilibili 本節教材地址&#xff1a;6.2. 圖像卷積 — 動…

Python使用watchdog庫實現監控文件系統的更改

1. 先下載對應庫&#xff1a; pip install watchdog import time from watchdog.observers import Observer from watchdog.events import FileSystemEventHandlerclass FileChangeHandler(FileSystemEventHandler):def on_modified(self, event):# 當文件被修改時觸發此方法…

淺析Nginx技術:開源高性能Web服務器與反向代理

什么是Nginx&#xff1f; Nginx是一款輕量級、高性能的HTTP和反向代理服務器&#xff0c;也可以用作郵件代理服務器。它最初由俄羅斯的程序員Igor Sysoev在2004年開發&#xff0c;并于2004年首次公開發布。Nginx的主要優勢在于其非阻塞的事件驅動架構&#xff0c;能夠處理大量并…

Vue3使用ref綁定組件獲取valueRef.value為null的解決

問題&#xff1a; onMounted(() > {nextTick(()>{console.log(treeselectRef, treeselectRef.value);console.log(treeselectRef.value, treeselectRef.value);}); });輸出&#xff1a; 查看綁定和定義都沒有問題&#xff0c;還是獲取不到 解決&#xff1a;使用getCur…

數據結構第17節 最小堆

最小堆&#xff08;Min Heap&#xff09;是一種特殊的完全二叉樹數據結構&#xff0c;在這種結構中&#xff0c;對于任意節點&#xff0c;其值都小于或等于它的子節點的值。根節點是堆中的最小元素。最小堆常用于實現優先隊列&#xff0c;以及堆排序算法。 在Java中&#xff0…

14-55 劍和詩人29 - RoSA:一種新的 PEFT 方法

介紹 參數高效微調 (PEFT) 方法已成為 NLP 領域研究的熱門領域。隨著語言模型不斷擴展到前所未有的規模&#xff0c;在下游任務中微調所有參數的成本變得非常高昂。PEFT 方法通過將微調限制在一小部分參數上來提供解決方案&#xff0c;從而以極低的計算成本在自然語言理解任務上…

深度學習(筆記內容)

1.國內鏡像網站 pip使用清華源鏡像源 pip install <庫> -i https://pypi.tuna.tsinghua.edu.cn/simple/ pip使用豆瓣的鏡像源 pip install <庫> -i https://pypi.douban.com/simple/ pip使用中國科技大學的鏡像源 pip install <庫> -i https://pypi.mirro…

vite工程化開發配置---持續更新

vite支持tsx開發 根據之前寫的文章vue3vitetseslintprettierstylelinthuskylint-stagedcommitlintcommitizencz-git里面tsconfig配置了jsx相關選項&#xff0c;但是想要vite能夠識別我們還需要配置一下 安裝vitejs/plugin-vue-jsx pnpm i -D vitejs/plugin-vue-jsxvite.confi…

Scapy庫實現SYN洪水攻擊的Python腳本

Scapy庫實現SYN洪水攻擊的Python腳本 代碼用于學習熟悉Scapy庫及其在網絡安全研究和測試中提供的各種選項和功能 腳本旨在執行SYN洪水攻擊,這是一種分布式拒絕服務(DDoS)攻擊的類型。未經授權參與此類攻擊通常是違法的,可能會產生嚴重后果 代碼 SynFlood.py from scapy.all…

Google 搜索引擎:便捷高效、精準查詢,帶來無與倫比的搜索體驗

Google搜索引擎不僅具備檢索功能&#xff0c;實則是引領探索萬千世界的神秘鑰匙。試想&#xff0c;無論何時何地&#xff0c;只需輕觸屏幕&#xff0c;所需信息即可唾手可得。便捷與高效&#xff0c;令人嘆為觀止。其界面設計簡約直觀&#xff0c;操控體驗猶如與未來對話&#…

如何壓縮pdf文件大小,怎么壓縮pdf文件大小

在數字化時代&#xff0c;pdf文件因其穩定的格式和跨平臺兼容性&#xff0c;成為了工作與學習中不可或缺的一部分。然而&#xff0c;隨著pdf文件內容的豐富&#xff0c;pdf文件的體積也隨之增大&#xff0c;給傳輸和存儲帶來了不少挑戰。本文將深入探討如何高效壓縮pdf文件大小…

小米手機短信怎么恢復?不用求人,3個技巧一網打盡

當你突然發現安卓手機里的重要短信不見了&#xff0c;是不是感到一陣心慌意亂&#xff1f;別急&#xff0c;不用求人&#xff0c;更不用焦慮。作為基本的社交功能&#xff0c;短信是我們與外界溝通的重要橋梁&#xff0c;當刪除后&#xff0c;短信怎么恢復呢&#xff1f;今天&a…