摘要:當(dāng)你用該日期類創(chuàng)建一個(gè)對象時(shí),編譯器會(huì)自動(dòng)調(diào)用該構(gòu)造函數(shù)對新創(chuàng)建的變量進(jìn)行初始化。注意構(gòu)造函數(shù)的主要任務(wù)并不是開空間創(chuàng)建對象,而是初始化對象。編譯器對內(nèi)置類型使用默認(rèn)構(gòu)造函數(shù)時(shí),對其成員賦的是隨機(jī)值。
C語言是面向過程的,關(guān)注的是過程,分析出求解問題的步驟,通過函數(shù)調(diào)用逐步解決問題。
C++是基于面向?qū)ο蟮?,關(guān)注的是對象,將一件事情拆分成不同的對象,靠對象之間的交互完成。
C語言中,結(jié)構(gòu)體中只能定義變量,在C++中,結(jié)構(gòu)體內(nèi)不僅可以定義變量,也可以定義函數(shù)。
struct Student{ void SetStudentInfo(const char* name, const char* gender, int age) { strcpy(_name, name); strcpy(_gender, gender); _age = age; } void PrintStudentInfo() { cout << _name << " " << _gender << " " << _age << endl; } char _name[20]; char _gender[3]; int _age;};
上面結(jié)構(gòu)體的定義,在C++中更喜歡用class來代替
class className{ // 類體:由成員函數(shù)和成員變量組成 }; // 一定要注意后面的分號
class為定義類的關(guān)鍵字,ClassName為類的名字,{}中為類的主體,注意類定義結(jié)束時(shí)后面分號。
類中的元素稱為類的成員:類中的數(shù)據(jù)稱為類的屬性或者成員變量; 類中的函數(shù)稱為類的方法或者成員函數(shù)。
聲明和定義全部放在類體中,需要注意:成員函數(shù)如果在類中定義,編譯器可能會(huì)將其當(dāng)成內(nèi)聯(lián)函數(shù)處
理
class Student{ void SetStudentInfo(const char* name, const char* gender, int age) { strcpy(_name, name); strcpy(_gender, gender); _age = age; } void PrintStudentInfo() { cout << _name << " " << _gender << " " << _age << endl; } char _name[20]; char _gender[3]; int _age;};
//person.hclass Person{public: //顯示信息 void show();public: char* _name; char* _sex; int _age;}//person.cpp#include"person.h>void Person::show(){ cout<<_name<<" "<<_sex<<" "<<_age<<endl;}
注意:一般情況下我們采用第二種方式
C++實(shí)現(xiàn)封裝的方式:用類將對象的屬性與方法結(jié)合在一塊,讓對象更加完善,通過訪問權(quán)限選擇性的將其
接口提供給外部的用戶使用。
【訪問限定符說明】
在類和對象階段,我們只研究類的封裝特性,那什么是封裝呢?
封裝本質(zhì)上是一種管理:我們使用類將數(shù)據(jù)和方法都封裝起來。不想對外開放的就用 protected/private 封裝起來,用 public 封裝的成員允許外界對其進(jìn)行合理的訪問。所以封裝本質(zhì)上是一種管理。
類定義了一個(gè)新的作用域,類的所有成員都在類的作用域中。在類體外定義成員,需要使用 :: 作用域解析符
指明成員屬于哪個(gè)類域。
class Person{public: void PrintPersonInfo();private: char _name[20]; char _gender[3]; int _age;};// 這里需要指定PrintPersonInfo是屬于Person這個(gè)類域void Person::PrintPersonInfo(){ cout<<_name<<" "_gender<<" "<<_age<<endl; }
用類類型創(chuàng)建對象的過程,稱為類的實(shí)例化
class Person{public: void PrintPersonInfo();private: char _name[20]; char _gender[3]; int _age;};void test(){ Person man; //類的實(shí)例化 man._name="hehe"; man._age="66"; man._sex="男"; man._PrintPersonInfo();}
class A {public: void PrintA() { cout<<_a<<endl; }private: char _a;};
那么問題來了?類中既可以有成員變量,又可以有成員函數(shù),那么一個(gè)類的對象中包含了什么?如何計(jì)算一個(gè)類的大
??? 想要知道這個(gè),首先我們要弄明白類在內(nèi)存中的存儲(chǔ)方式。
那為什么內(nèi)存要這樣存儲(chǔ)類了?
原因:每個(gè)對象中成員變量是不同的,但是調(diào)用同一份函數(shù),如果按照此種方式存儲(chǔ),當(dāng)一個(gè)類創(chuàng)建多
個(gè)對象時(shí),每個(gè)對象中都會(huì)保存一份代碼,相同代碼保存多次,浪費(fèi)空間。
結(jié)論:一個(gè)類的大小,實(shí)際就是該類中”成員變量”之和,當(dāng)然也要進(jìn)行內(nèi)存對齊,注意空類的大小,空類比
較特殊,編譯器給了空類一個(gè)字節(jié)來唯一標(biāo)識這個(gè)類。 如果有小伙伴不怎么明白內(nèi)存對齊:可以看看這篇文章:自定義類型的知識點(diǎn)
我們先來定義一個(gè)日期類Date
class Date{ public : void Display () { cout <<_year<< "-" <<_month << "-"<< _day <<endl; } void SetDate(int year , int month , int day) { _year = year; _month = month; _day = day; }private : int _year ; // 年 int _month ; // 月 int _day ; // 日};int main(){ Date d1, d2; d1.SetDate(2018,5,1); d2.SetDate(2018,7,1); d1.Display(); d2.Display(); return 0; }
對于上述類,有這樣的一個(gè)問題:
Date類中有SetDate與Display兩個(gè)成員函數(shù),函數(shù)體中沒有關(guān)于不同對象的區(qū)分,那當(dāng)d1調(diào)用SetDate函數(shù)
時(shí),該函數(shù)是如何知道應(yīng)該設(shè)置d1對象,而不是設(shè)置d2對象呢?
C++中通過引入this指針解決該問題,即:C++編譯器給每個(gè)“非靜態(tài)的成員函數(shù)“增加了一個(gè)隱藏的指針參
數(shù),讓該指針指向當(dāng)前對象(函數(shù)運(yùn)行時(shí)調(diào)用該函數(shù)的對象),在函數(shù)體中所有成員變量的操作,都是通過該
指針去訪問。只不過所有的操作對用戶是透明的,即用戶不需要來傳遞,編譯器自動(dòng)完成
注意:this指針不能為空
下面來看一個(gè)例子
這里為什么會(huì)報(bào)錯(cuò)了?首先這個(gè)p是一個(gè)空指針,但是并不是對象是空指針就一定報(bào)錯(cuò),這里其實(shí)更重要的一個(gè)原因是PrintA里面為this->_a你對p進(jìn)行了訪問,而空指針是不能訪問的。下面我們再來來p->Show()會(huì)不會(huì)報(bào)錯(cuò)?
如果一個(gè)類中什么成員都沒有,我們簡稱其為空類。但是空類中真的什么都沒有嗎?其實(shí)不然,任何一個(gè)類,即使我們什么都不寫,類中也會(huì)自動(dòng)生成6個(gè)默認(rèn)成員函數(shù)。
class Date {}; //空類
class Date{public: Date(int year = 0, int month = 1, int day = 1)// 構(gòu)造函數(shù) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "年" << _month << "月" << _day << "日" << endl; }private: int _year; int _month; int _day;};
例如,上述日期類中的成員函數(shù)Date就是一個(gè)構(gòu)造函數(shù)。當(dāng)你用該日期類創(chuàng)建一個(gè)對象時(shí),編譯器會(huì)自動(dòng)調(diào)用該構(gòu)造函數(shù)對新創(chuàng)建的變量進(jìn)行初始化。
注意:構(gòu)造函數(shù)的主要任務(wù)并不是開空間創(chuàng)建對象,而是初始化對象。(這兒可以先暫時(shí)這么理解)
class Date{public: // 1.無參構(gòu)造函數(shù) Date () {} Date(int year = 0, int month = 1, int day = 1)// 構(gòu)造函數(shù) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "年" << _month << "月" << _day << "日" << endl; }private: int _year; int _month; int _day;};void TestDate(){ Date d1; // 調(diào)用無參構(gòu)造函數(shù) Date d2 (2015, 1, 1); // 調(diào)用帶參的構(gòu)造函數(shù) // 注意:如果通過無參構(gòu)造函數(shù)創(chuàng)建對象時(shí),對象后面不用跟括號,否則就成了函數(shù)聲明 // 以下代碼的函數(shù):聲明了d3函數(shù),該函數(shù)無參,返回一個(gè)日期類型的對象 Date d3(); }
class Date{public: /* // 如果用戶顯式定義了構(gòu)造函數(shù),編譯器將不再生成 Date (int year, int month, int day) { _year = year; _month = month; _day = day; } */private: int _year; int _month; int _day;};void Test(){ // 沒有定義構(gòu)造函數(shù),對象也可以創(chuàng)建成功,因此此處調(diào)用的是編譯器生成的默認(rèn)構(gòu)造函數(shù) Date d; }
// 默認(rèn)構(gòu)造函數(shù)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 ;};// 以下測試函數(shù)能通過編譯嗎?void Test(){ Date d1; }
顯然這兒是過不了的,因?yàn)轭愔杏卸鄠€(gè)默認(rèn)函數(shù)。
7.編譯器對內(nèi)置類型使用默認(rèn)構(gòu)造函數(shù)時(shí),對其成員賦的是隨機(jī)值。但對自定義類型,會(huì)調(diào)用它的默認(rèn)函數(shù)。
這兒并沒有我們自己寫的構(gòu)造函數(shù),所以編譯時(shí)會(huì)調(diào)用默認(rèn)的構(gòu)造函數(shù),又由于類成員都是內(nèi)置類型,因此賦的都是隨機(jī)值。下面我們再來看看自定義類型。
注意:如果你Time類中沒有自己寫構(gòu)造函數(shù),用編譯器默認(rèn)的構(gòu)造函數(shù),它也是一樣會(huì)輸入隨機(jī)值的。
前面通過構(gòu)造函數(shù)的學(xué)習(xí),我們知道一個(gè)對象時(shí)怎么來的,那一個(gè)對象又是怎么沒呢的?
析構(gòu)函數(shù):與構(gòu)造函數(shù)功能相反,析構(gòu)函數(shù)不是完成對象的銷毀,局部對象銷毀工作是由編譯器完成的。而
對象在銷毀時(shí)會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),完成類的一些資源清理工作。
構(gòu)造函數(shù):只有單個(gè)形參,該形參是對本類類型對象的引用(一般常用const修飾),在用已存在的類類型對象 創(chuàng)建新對象時(shí)由編譯器自動(dòng)調(diào)用
#include using namespace std;class Date{public: Date(int year = 0, int month = 1, int day = 1)// 構(gòu)造函數(shù) { _year = year; _month = month; _day = day; } Date(const Date& d)// 拷貝構(gòu)造函數(shù) ,與構(gòu)造函數(shù)形成函數(shù)重載 { _year = d._year; _month = d._month; _day = d._day; }private: int _year; int _month; int _day;};int main(){ Date d1(2021, 9, 27); Date d2(d1); // 用已存在的對象d1創(chuàng)建對象d2 return 0;}
因此通過形參不寫成引用的形式,會(huì)形成無限遞歸。
一般涉及到堆區(qū)的問題,淺拷貝是無法解決問題的。
下面我們來舉個(gè)例子:
class Stack{public: Stack(int capacity = 4) { _ps = (int*)malloc(sizeof(int)* capacity); _size = 0; _capacity = capacity; } void Print() { cout << _ps << endl;// 打印棧空間地址 }private: int* _ps; int _size; int _capacity;};int main(){ Stack s1; s1.Print();// 打印s1??臻g的地址 Stack s2(s1);// 用已存在的對象s1創(chuàng)建對象s2 s2.Print();// 打印s2??臻g的地址 return 0;}
我們可以看到,類中沒有自己定義拷貝構(gòu)造函數(shù),那么當(dāng)我們用已存在的對象來創(chuàng)建另一個(gè)對象時(shí),將調(diào)用編譯器自動(dòng)生成的拷貝構(gòu)造函數(shù)。這段代碼中,我們的本意是用已存在的對象s1創(chuàng)建對象s2,但編譯器自動(dòng)生成的拷貝構(gòu)造函數(shù),完成的是淺拷貝,拷貝出來的對象s2將不能滿足我們的要求。
結(jié)果打印s1棧和s2??臻g的地址相同,這就意味著,就算在創(chuàng)建完s2棧后,我們對s1棧做的任何操作都會(huì)直接影響到s2棧。
?
這個(gè)時(shí)候問題就很嚴(yán)重了。首先我們對s1的修改都會(huì)直接影響s2,而且更重要的一個(gè)是:我們對它們共同指向的那塊空間進(jìn)行了兩次的析構(gòu),會(huì)造成空間多次釋放的問題。
C++為了增強(qiáng)代碼的可讀性引入了運(yùn)算符重載,運(yùn)算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回值類
型,函數(shù)名字以及參數(shù)列表,其返回值類型與參數(shù)列表與普通的函數(shù)類似。
函數(shù)名字為:關(guān)鍵字operator后面接需要重載的運(yùn)算符符號。
函數(shù)原型:返回值類型 operator操作符(參數(shù)列表)
注意:
1.不能通過連接其他符號來創(chuàng)建新的操作符:比如operator@
2.重載操作符必須有一個(gè)類類型或者枚舉類型的操作數(shù)
3.用于內(nèi)置類型的操作符,其含義不能改變,例如:內(nèi)置的整型+,不 能改變其含義
4.作為類成員的重載函數(shù)時(shí),其形參看起來比操作數(shù)數(shù)目少1成員函數(shù)的
操作符有一個(gè)默認(rèn)的形參this,限定為第一個(gè)形參
5.* 、:: 、sizeof 、?: 、. 注意以上5個(gè)運(yùn)算符不能重載。這個(gè)經(jīng)常在筆試選擇題中出現(xiàn)。
bool operator==(const Date& d1, const Date& d2) { return d1._year == d2._year; && d1._month == d2._month && d1._day == d2._day; }
對于這個(gè)重載的函數(shù),你可以定義再類里面,這樣就少一個(gè)參數(shù),因?yàn)橛衪his指針的存在。你也可以定義在外面,但是定義在外面時(shí),可能你的類成員時(shí)private封裝的,無法訪問到,這時(shí)有兩個(gè)解決辦法:一是把類成員用public封裝,二是用友元函數(shù)(之后會(huì)講到)。
Date& operator=(const Date& d)// 賦值運(yùn)算符重載函數(shù) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; }
這里為什么要返回引用了?如果你去測試發(fā)現(xiàn)D1=D2,如果你的返回值是Date的話,似乎也能過,但是如果你的測試用例是D1=D2=D3的話,那就一定過不了了,因?yàn)槟悴皇欠祷氐膶ο蟊旧?,無法形成鏈?zhǔn)骄幊蹋@也是為什么這兒返回*this的原因,因?yàn)閠his是指向左操作符的。
其他一些運(yùn)算符的重載這兒就不多說了,有興趣的小伙伴可以自己去嘗試嘗試。下面來說幾個(gè)重載運(yùn)算符時(shí)的注意點(diǎn)。
重載賦值運(yùn)算符需要注意以下幾點(diǎn):
一、參數(shù)類型設(shè)置為引用,并用const進(jìn)行修飾
賦值運(yùn)算符重載函數(shù)的第一個(gè)形參默認(rèn)是this指針,第二個(gè)形參是我們賦值運(yùn)算符的右操作數(shù)。
由于是自定義類型傳參,我們?nèi)羰鞘褂脗髦祩鲄?,?huì)額外調(diào)用一次拷貝構(gòu)造函數(shù),所以函數(shù)的第二個(gè)參數(shù)最好使用引用傳參(第一個(gè)參數(shù)是默認(rèn)的this指針,我們管不了)。
其次,第二個(gè)參數(shù),即賦值運(yùn)算符的右操作數(shù),我們在函數(shù)體內(nèi)不會(huì)對其進(jìn)行修改,所以最好加上const進(jìn)行修飾。
二、返回值使用引用返回
原因在=運(yùn)算符重載中說過了,為了返回對象自身,形成鏈?zhǔn)骄幊?。(return *this才是返回自身,不要忘記解引用哦)
三、一個(gè)類如果沒有顯示定義賦值運(yùn)算符重載,編譯器也會(huì)自動(dòng)生成一個(gè),完成對象按字節(jié)序的值拷貝
沒錯(cuò),賦值運(yùn)算符重載編譯器也可以自動(dòng)生成,并且也是支持連續(xù)賦值的。但是編譯器自動(dòng)生成的賦值運(yùn)算符重載完成的是對象按字節(jié)序的值拷貝,例如d2 = d1,編譯器會(huì)將d1所占內(nèi)存空間的值完完全全地拷貝到d2的內(nèi)存空間中去,類似于memcpy。
但是有些類就不行了,所以有些類還是要我們自己寫賦值運(yùn)算符重載的。
注意區(qū)分拷貝和賦值:
Date d1(2021, 6, 1); Date d2(d1); Date d3 = d1;
這里一個(gè)三句代碼,我們現(xiàn)在都知道第二句代碼調(diào)用的是拷貝構(gòu)造函數(shù),那么第三句代碼呢?調(diào)用的是哪一個(gè)函數(shù)?是賦值運(yùn)算符重載函數(shù)嗎?
其實(shí)第三句代碼調(diào)用的也是拷貝構(gòu)造函數(shù),注意區(qū)分拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載函數(shù)的使用場景:
拷貝構(gòu)造函數(shù):用一個(gè)已經(jīng)存在的對象去構(gòu)造初始化另一個(gè)即將創(chuàng)建的對象。
賦值運(yùn)算符重載函數(shù):在兩個(gè)對象都已經(jīng)存在的情況下,將一個(gè)對象賦值給另一個(gè)對象。
我們將const修飾的類成員函數(shù)稱之為const成員函數(shù),const修飾類成員函數(shù),實(shí)際修飾的是類成員函數(shù)隱含的this指針,表明在該成員函數(shù)中不能對this指針指向的對象進(jìn)行修改。
例如,我們可以對類成員函數(shù)中的打印函數(shù)進(jìn)行const修飾,避免在函數(shù)體內(nèi)不小心修改了對象:
void Print()const// cosnt修飾的打印函數(shù) { cout << _year << "年" << _month << "月" << _day << "日" << endl; }
注意:
在使用const時(shí)要注意,權(quán)限不能放大,但是可以縮小。
在創(chuàng)建對象時(shí),編譯器會(huì)通過調(diào)用構(gòu)造函數(shù),給對象中的各個(gè)成員變量一個(gè)合適的初始值:
class Date{public: // 構(gòu)造函數(shù)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/121415.html
文章目錄 強(qiáng)烈推薦系列教程,建議學(xué)起來!! 一.pycharm下載安裝二.python下載安裝三.pycharm上配置python四.配置鏡像源讓你下載嗖嗖的快4.1pycharm內(nèi)部配置 4.2手動(dòng)添加鏡像源4.3永久配置鏡像源 五.插件安裝(比如漢化?)5.1自動(dòng)補(bǔ)碼神器第一款5.2漢化pycharm5.3其它插件 六.美女背景七.自定義腳本開頭八、這個(gè)前言一定要看九、pyt...
摘要:于是乎,冰河寫了一個(gè)腳本完美去除了桌面圖標(biāo)煩人的小箭頭。今天,給大家分享一個(gè)如何完美去除桌面快捷圖標(biāo)小箭頭的技巧,希望能夠給大家?guī)韼椭_@種方法不會(huì)導(dǎo)致任何問題可放心使用,冰河已經(jīng)親自測試過了。 ...
人生苦短,我用Python 開發(fā)環(huán)境搭建安裝 Python驗(yàn)證是否安裝成功安裝Pycharm配置pycharm 編碼規(guī)范基本語法規(guī)則保留字單行注釋多行注釋行與縮進(jìn)多行語句數(shù)據(jù)類型空行等待用戶輸入print輸出 運(yùn)算符算術(shù)運(yùn)算符邏輯運(yùn)算符成員運(yùn)算符身份運(yùn)算符運(yùn)算符優(yōu)先級 字符串訪問字符串中的值字符串更新合并連接字符串刪除空白startswith()方法endswith()方法字符串格式化...
閱讀 2973·2021-10-28 09:32
閱讀 3017·2021-10-11 10:57
閱讀 3183·2021-10-08 10:05
閱讀 2667·2021-09-28 09:36
閱讀 2259·2019-08-30 15:55
閱讀 2298·2019-08-30 15:44
閱讀 2423·2019-08-30 14:02
閱讀 3102·2019-08-29 17:16