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