日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

【黑马程序员 C++教程从0到1入门编程】【笔记4】C++核心编程(类和对象——封装、权限、对象的初始化和清理、构造函数、析构函数、深拷贝、浅拷贝、初始化列表、友元friend、运算符重载)

發布時間:2025/3/20 c/c++ 26 豆豆

黑馬程序員C++教程

文章目錄

    • 4 類和對象(類屬性【成員屬性】,類函數【成員函數】)
      • 4.1 封裝
        • 4.1.1 封裝的意義(三種權限:public公共、protected保護、private私有)(將屬性和行為作為一個整體,表現生活中的事物;將屬性和行為加以權限控制)(`class 類名{ 訪問權限: 屬性 / 行為 };`)
          • 示例1:設計一個圓類,求圓的周長
          • 示例2:設計一個學生類,屬性有姓名和學號,可以給姓名和學號賦值,可以顯示學生的姓名和學號
          • 三種權限示例(雖然類外不可訪問類內的保護權限和私有權限元素,但是可以通過類內提供的公共方法進行設置【初始化】)(私有權限不能在子類中訪問,保護權限可以在子類中訪問)
        • 4.1.2 struct和class區別(struct 默認權限為公共,class 默認權限為私有【class不指定訪問權限時】)
        • 4.1.3 成員屬性設置為私有(成員私有屬性只有它自己的方法能修改和讀取)(然后在類內用方法實現對屬性的——讀、寫等功能)
      • 4.2 對象的初始化和清理
        • 4.2.1 構造函數(constructor `類名(){}`)和析構函數(destructor `~類名(){}`)(寫在類里面)(如果對象不是用new方法創建的,則函數塊結束后系統會自動調用(自己寫的或系統生成的)析構函數釋放內存;如果用new方法創建,則調用delete方法時會調用析構函數)(如果不寫析構函數,貌似也沒事,系統會自動生成構造函數和析構函數。。。這塊內存也不會被一直霸占著。。而且如果不寫析構函數,delete方法也能釋放用new方法創建的對象。。??偠灾?#xff0c;就是如果你不寫,系統會自動幫你寫)
        • 4.2.2 構造函數的分類及調用(有參構造和無參構造)(普通構造和拷貝構造)(三種調用方式:括號法、顯示法、隱式轉換法【不知道多參的情況怎么隱式轉換??】)
        • 4.2.3 拷貝構造函數調用時機(使用一個已經創建完畢的對象來初始化一個新對象、值傳遞的方式給函數參數傳值、以值方式返回局部對象)
        • 4.2.4 構造函數調用規則(如果用戶定義有參構造函數,c++不再提供默認無參構造,但是會提供默認拷貝構造;如果用戶定義拷貝構造函數,c++不會再提供其他構造函數)
        • 4.2.5 深拷貝與淺拷貝(淺拷貝:簡單的賦值拷貝操作、深拷貝:在堆區重新申請空間,進行拷貝操作)(delete()只能刪除在堆區開辟空間的指針)
        • 4.2.6 初始化列表(寫在構造函數的參數和大括號之間,用冒號:開始,格式為類型(初始化參數),不同項之間用逗號,隔開)(`構造函數():屬性1(值1),屬性2(值2)... {}`)
        • 4.2.7 類對象作為類成員(初始化列表可以告訴編譯器調用哪一個構造函數)
        • 4.2.8 靜態成員 static(包括靜態變量和靜態函數)(類內聲明,類外初始化)(靜態成員變量:所有對象共享同一份數據,在編譯階段分配內存,類內聲明,類外初始化)( 靜態成員函數:所有對象共享同一個函數,靜態成員函數只能訪問靜態成員變量)
          • 靜態成員變量能在類內初始化嗎?(靜態成員函數可以,變量不行)
      • 4.3 C++對象模型和this指針
        • 4.3.1 成員變量和成員函數分開存儲(只有非靜態成員變量占對象空間【成員函數,靜態變量和靜態函數都不占對象空間】)
        • 4.3.2 this指針概念(this確實是個指針!)
        • 4.3.3 空指針訪問成員函數(照理來說也是,類的成員函數跟它的實例對象沒關系)
        • 4.3.4 const修飾成員函數(常函數:在參數括號后和大括號之間加const)(什么意思,我越看越懵逼了!)(別蒙蔽,仔細看!)(常函數內不可以修改成員屬性;成員屬性聲明時加關鍵字mutable后,在常函數中依然可以修改)(常對象:聲明對象前加const稱該對象為常對象;常對象只能調用常函數,只能修改帶mutable關鍵字的成員屬性)
      • 4.4 友元 friend(讓一個函數或者類 訪問另一個類中私有成員)
        • 4.4.1 全局函數做友元
        • 4.4.2 類做友元(就是不知道干啥要這樣設計,繞來繞去的,有個啥意思?)(我又看了一遍還是懵逼,需要看視頻?)
        • 4.4.3 成員函數做友元
      • 4.5 運算符重載(對已有的運算符重新進行定義,賦予其另一種功能,以適應不同的數據類型)
        • 可重載運算符和不可重載運算符
        • 4.5.1 加號運算符重載(operator+)
        • 4.5.2 左移運算符重載(輸出自定義數據類型)(ostream& operator<<)
        • 下面未檢測-------------------------------上面需再次看視頻,因為確實很多知識文檔里沒講到而視頻里講到了
        • 4.5.3 遞增運算符重載
        • 4.5.4 賦值運算符重載(對屬性值進行拷貝)(這個用得多不多啊,搞得語法有點亂不是么?)
        • 4.5.5 關系運算符重載(靈活是靈活,但是用普通函數也可以實現呀!)
        • 4.5.6 函數調用運算符重載(仿函數)(如果要理解還得看視頻,沒看,先過了。。。)

4 類和對象(類屬性【成員屬性】,類函數【成員函數】)

C++面向對象的三大特性為:封裝、繼承、多態

C++認為萬事萬物都皆為對象,對象上有其屬性和行為

例如:

? 人可以作為對象,屬性有姓名、年齡、身高、體重…,行為有走、跑、跳、吃飯、唱歌…

? 車也可以作為對象,屬性有輪胎、方向盤、車燈…,行為有載人、放音樂、放空調…

? 具有相同性質的對象,我們可以抽象稱為類,人屬于人類,車屬于車類

4.1 封裝

4.1.1 封裝的意義(三種權限:public公共、protected保護、private私有)(將屬性和行為作為一個整體,表現生活中的事物;將屬性和行為加以權限控制)(class 類名{ 訪問權限: 屬性 / 行為 };)

封裝是C++面向對象三大特性之一

封裝的意義:

  • 將屬性和行為作為一個整體,表現生活中的事物
  • 將屬性和行為加以權限控制

封裝意義一:

? 在設計類的時候,屬性和行為寫在一起,表現事物

語法: class 類名{ 訪問權限: 屬性 / 行為 };

(如果權限相同,屬性和行為能寫在一起,也能分開寫)

示例1:設計一個圓類,求圓的周長

示例代碼:

#include<iostream> #include<string> using namespace std;//圓周率 const double PI = 3.14;//1、封裝的意義 //將屬性和行為作為一個整體,用來表現生活中的事物//封裝一個圓類,求圓的周長 //class代表設計一個類,后面跟著的是類名 class Circle { public: //訪問權限 公共的權限//屬性int m_r;//半徑//行為//獲取到圓的周長double calculateZC(){//2 * pi * r//獲取圓的周長return 2 * PI * m_r;} };int main() {//通過圓類,創建圓的對象// c1就是一個具體的圓Circle c1;c1.m_r = 10; //給圓對象的半徑 進行賦值操作//2 * pi * 10 = = 62.8cout << "圓的周長為: " << c1.calculateZC() << endl; //62.8system("pause");return 0; }
示例2:設計一個學生類,屬性有姓名和學號,可以給姓名和學號賦值,可以顯示學生的姓名和學號

示例2代碼:

#include<iostream> #include<string> using namespace std;//學生類 class Student { public:void setName(string name) {m_name = name;}void setID(int id) {m_id = id;}void showStudent() {cout << "name:" << m_name << " ID:" << m_id << endl;}public:string m_name;int m_id; };int main() {Student stu;stu.setName("德瑪西亞");stu.setID(250);stu.showStudent(); //name:德瑪西亞 ID:250system("pause");return 0; } 習慣上寫兩個public,一個寫屬性,一個寫方法

封裝意義二:

類在設計時,可以把屬性和行為放在不同的權限下,加以控制

訪問權限有三種:

  • public 公共權限
  • protected 保護權限
  • private 私有權限
  • 三種權限示例(雖然類外不可訪問類內的保護權限和私有權限元素,但是可以通過類內提供的公共方法進行設置【初始化】)(私有權限不能在子類中訪問,保護權限可以在子類中訪問)
    #include<iostream> #include<string> using namespace std;//三種權限 //公共權限 public 類內可以訪問 類外可以訪問 //保護權限 protected 類內可以訪問 類外不可以訪問 //私有權限 private 類內可以訪問 類外不可以訪問class Person {//姓名 公共權限 public:string m_Name;//汽車 保護權限 protected:string m_Car;//銀行卡密碼 私有權限 private:int m_Password;//相當于初始化 public:void func(){m_Name = "張三";m_Car = "拖拉機";m_Password = 123456;} };int main() {Person p;p.m_Name = "李四";//p.m_Car = "奔馳"; //保護權限類外訪問不到//p.m_Password = 123; //私有權限類外訪問不到system("pause");return 0; }

    4.1.2 struct和class區別(struct 默認權限為公共,class 默認權限為私有【class不指定訪問權限時】)

    在C++中 struct和class唯一的區別就在于 默認的訪問權限不同

    區別:

    • struct 默認權限為公共
    • class 默認權限為私有
    #include<iostream> #include<string> using namespace std;class C1 {int m_A; //默認是私有權限 };struct C2 {int m_A; //默認是公共權限 };int main() {C1 c1;//c1.m_A = 10; //錯誤,訪問權限是私有C2 c2;c2.m_A = 10; //正確,訪問權限是公共system("pause");return 0; }

    4.1.3 成員屬性設置為私有(成員私有屬性只有它自己的方法能修改和讀取)(然后在類內用方法實現對屬性的——讀、寫等功能)

    **優點1:**將所有成員屬性設置為私有,可以自己控制讀寫權限

    **優點2:**對于寫權限,我們可以檢測數據的有效性(通過類內方法實現)

    示例:

    #include<iostream> #include<string> using namespace std;class Person { public://姓名設置可讀可寫void setName(string name) {m_Name = name;}string getName(){return m_Name;}//設置年齡void setAge(int age) {if (age < 0 || age > 150) {cout << "你個老妖精!" << endl;return;}m_Age = age;}//獲取年齡 int getAge() {return m_Age;}//情人設置為只寫void setLover(string lover) {m_Lover = lover;}private:string m_Name; //可讀可寫 姓名int m_Age; //可讀可寫 年齡string m_Lover; //只寫 情人 };int main() {Person p;//姓名設置p.setName("張三");cout << "姓名: " << p.getName() << endl;//年齡設置p.setAge(50);cout << "年齡: " << p.getAge() << endl;//情人設置p.setLover("蒼井");//cout << "情人: " << p.m_Lover << endl; //只寫屬性,不可以讀取system("pause");return 0; }

    4.2 對象的初始化和清理

    • 生活中我們買的電子產品都基本會有出廠設置,在某一天我們不用時候也會刪除一些自己信息數據保證安全
    • C++中的面向對象來源于生活,每個對象也都會有初始設置以及 對象銷毀前的清理數據的設置。

    4.2.1 構造函數(constructor 類名(){})和析構函數(destructor ~類名(){})(寫在類里面)(如果對象不是用new方法創建的,則函數塊結束后系統會自動調用(自己寫的或系統生成的)析構函數釋放內存;如果用new方法創建,則調用delete方法時會調用析構函數)(如果不寫析構函數,貌似也沒事,系統會自動生成構造函數和析構函數。。。這塊內存也不會被一直霸占著。。而且如果不寫析構函數,delete方法也能釋放用new方法創建的對象。。??偠灾?#xff0c;就是如果你不寫,系統會自動幫你寫)

    (疑問:什么情況下才寫析構函數?)

    析構函數(destructor)
    與構造函數相反,當對象結束其生命周期,如對象所在的函數已調用完畢時,系統自動執行析構函數。析構函數往往用來做“清理善后”
    的工作(例如在建立對象時用new開辟了一片內存空間,delete會自動調用析構函數后釋放內存)。

    對象的初始化和清理也是兩個非常重要的安全問題

    ? 一個對象或者變量沒有初始狀態,對其使用后果是未知的

    ? 同樣的使用完一個對象或變量,沒有及時清理,也會造成一定的安全問題

    c++利用了構造函數析構函數解決上述問題,這兩個函數將會被編譯器自動調用,完成對象初始化和清理工作。

    對象的初始化和清理工作是編譯器強制要我們做的事情,因此如果我們不提供構造和析構,編譯器會提供

    編譯器提供的構造函數和析構函數是空實現(?)。

    • 構造函數:主要作用在于創建對象時為對象的成員屬性賦值,構造函數由編譯器自動調用,無須手動調用。
    • 析構函數:主要作用在于對象銷毀前系統自動調用,執行一些清理工作(清理啥?。。。以后你會知道的)。

    構造函數語法:類名(){}

  • 構造函數,沒有返回值也不寫void
  • 函數名稱與類名相同
  • 構造函數可以有參數,因此可以發生重載
  • 程序在調用對象時候會自動調用構造,無須手動調用,而且只會調用一次
  • 析構函數語法: ~類名(){}

  • 析構函數,沒有返回值也不寫void
  • 函數名稱與類名相同,在名稱前加上符號 ~
  • 析構函數不可以有參數,因此不可以發生重載
  • 程序在對象銷毀前會自動調用析構,無須手動調用,而且只會調用一次
  • #include<iostream> #include<string> using namespace std;class Person { public://構造函數Person(){cout << "Person的構造函數調用" << endl;}//析構函數~Person(){cout << "Person的析構函數調用" << endl;}};void test01() {Person p; }int main() {test01();system("pause");return 0; }

    運行結果:

    Person的構造函數調用 Person的析構函數調用

    4.2.2 構造函數的分類及調用(有參構造和無參構造)(普通構造和拷貝構造)(三種調用方式:括號法、顯示法、隱式轉換法【不知道多參的情況怎么隱式轉換??】)

    兩種分類方式:

    ? 按參數分為: 有參構造和無參構造

    ? 按類型分為: 普通構造和拷貝構造

    三種調用方式:

    ? 括號法

    ? 顯示法

    ? 隱式轉換法

    示例:

    #include<iostream> #include<string> using namespace std;//1、構造函數分類 // 按照參數分類分為 有參和無參構造 無參又稱為默認構造函數 // 按照類型分類分為 普通構造和拷貝構造class Person { public://無參(默認)構造函數Person() {cout << "無參構造函數!" << endl;}//有參構造函數1Person(int a) {age = a;cout << "有參構造函數1!" << endl;}//有參構造函數2Person(int a, int b) {age = a + b;cout << "有參構造函數2!" << endl;}//拷貝構造函數(不加const還不行!)//(當然你也可以通過傳入Person*的方式,這里只是傳入引用防止對被引用Person對象更改)Person(const Person& p) {age = p.age;cout << "拷貝構造函數!" << endl;}//析構函數~Person() {cout << this << endl;cout << "析構函數!" << endl;} public:int age; };//2、構造函數的調用 //調用無參構造函數 void test01() {Person p; //調用無參構造函數 }//調用有參的構造函數 void test02() {//2.1 括號法,常用Person p1(10);Person p1_(10, 20);//注意1:調用無參構造函數不能加括號,如果加了編譯器認為這是一個函數聲明//Person p2();//2.2 顯式法Person p2 = Person(10);Person p3 = Person(p2);//Person(10)單獨寫就是匿名對象 當前行結束之后,馬上析構//2.3 隱式轉換法Person p4 = 10; // Person p4 = Person(10); //不能用這種方法進行多參數隱式轉換啊?!//Person p4_ = (10, 20);Person p5 = p4; // Person p5 = Person(p4); //注意2:不能利用 拷貝構造函數 初始化匿名對象 編譯器認為是對象聲明//Person p5(p4); }//函數塊結束時會調用析構函數,析構順序是創建順序的逆序int main() {//test01();test02();system("pause");return 0; }

    調試發現,每個類對象銷毀時都會調用一次析構函數

    運行結果:

    有參構造函數1! 有參構造函數2! 有參構造函數1! 拷貝構造函數! 有參構造函數1! 拷貝構造函數! 0099F9AC 析構函數! 0099F9B8 析構函數! 0099F9C4 析構函數! 0099F9D0 析構函數! 0099F9DC 析構函數! 0099F9E8 析構函數! 請按任意鍵繼續. . .

    4.2.3 拷貝構造函數調用時機(使用一個已經創建完畢的對象來初始化一個新對象、值傳遞的方式給函數參數傳值、以值方式返回局部對象)

    C++中拷貝構造函數調用時機通常有三種情況

    • 使用一個已經創建完畢的對象來初始化一個新對象
    • 值傳遞的方式給函數參數傳值
    • 以值方式返回局部對象

    示例:

    #include<iostream> #include<string> using namespace std;class Person { public:Person() {cout << "無參構造函數!" << endl;mAge = 0;}Person(int age) {cout << "有參構造函數!" << endl;mAge = age;}Person(const Person& p) {cout << "拷貝構造函數!" << endl;mAge = p.mAge;}//析構函數在釋放內存之前調用~Person() {cout << "析構函數!" << endl;} public:int mAge; };//1. 使用一個已經創建完畢的對象來初始化一個新對象 void test01() {Person man(100); //p對象已經創建完畢Person newman(man); //調用拷貝構造函數Person newman2 = man; //拷貝構造Person newman3;newman3 = man; //不是調用拷貝構造函數,賦值操作(Ar:一模一樣復制過去) }//2. 值傳遞的方式給函數參數傳值 //相當于Person p1 = p;(Ar:實參復制到形參??) void doWork(Person p1) {} void test02() {Person p; //無參構造函數doWork(p); }//3. 以值方式返回局部對象 Person doWork2() {Person p1;cout << (int*)&p1 << endl;//cout << &p1 << endl; //跟上面一樣的return p1; }void test03() {Person p = doWork2();cout << (int*)&p << endl;//cout << &p << endl; //跟上面一樣的 }int main() {test01();test02();test03();system("pause");return 0; }

    運行結果:

    有參構造函數! 拷貝構造函數! 拷貝構造函數! 無參構造函數! 析構函數! 析構函數! 析構函數! 析構函數! 無參構造函數! 拷貝構造函數! 析構函數! 析構函數! 無參構造函數! 00AFF6FC 拷貝構造函數! 析構函數! 00AFF7F4 析構函數!

    4.2.4 構造函數調用規則(如果用戶定義有參構造函數,c++不再提供默認無參構造,但是會提供默認拷貝構造;如果用戶定義拷貝構造函數,c++不會再提供其他構造函數)

    默認情況下,c++編譯器至少給一個類添加3個函數

    1.默認構造函數(無參,函數體為空)

    2.默認析構函數(無參,函數體為空)

    3.默認拷貝構造函數,對屬性進行值拷貝

    構造函數調用規則如下:

    • 如果用戶定義有參構造函數,c++不再提供默認無參構造,但是會提供默認拷貝構造

    • 如果用戶定義拷貝構造函數,c++不會再提供其他構造函數

    示例:

    #include<iostream> #include<string> using namespace std;class Person { public://無參(默認)構造函數Person() {cout << "無參構造函數!" << endl;}//有參構造函數Person(int a) {age = a;cout << "有參構造函數!" << endl;}//拷貝構造函數Person(const Person& p) {age = p.age;cout << "拷貝構造函數!" << endl;}//析構函數~Person() {cout << "析構函數!" << endl;} public:int age; };void test01() {Person p1(18);//如果不寫拷貝構造,編譯器會自動添加拷貝構造,并且做淺拷貝操作(Ar:我調試也沒發現是淺拷貝呀??【引用】。。。是淺拷貝)Person p2(p1);//p1.age = 19;//p2.age = 21;cout << "p2的年齡為: " << p2.age << endl; //18 }void test02() {//如果用戶提供有參構造,編譯器不會提供默認構造,會提供拷貝構造Person p1; //此時如果用戶自己沒有提供默認構造,會出錯(Ar:VS下編譯都編譯不過)Person p2(10); //用戶提供的有參Person p3(p2); //此時如果用戶沒有提供拷貝構造,編譯器會提供(Ar:編譯器提供的貌似是全部復制)//如果用戶提供拷貝構造,編譯器不會提供其他構造函數Person p4; //此時如果用戶自己沒有提供默認構造,會出錯Person p5(10); //此時如果用戶自己沒有提供有參,會出錯Person p6(p5); //用戶自己提供拷貝構造 }int main() {test01();test02();system("pause");return 0; }

    運行結果:

    有參構造函數! 拷貝構造函數! p2的年齡為: 18 析構函數! 析構函數! 無參構造函數! 有參構造函數! 拷貝構造函數! 無參構造函數! 有參構造函數! 拷貝構造函數! 析構函數! 析構函數! 析構函數! 析構函數! 析構函數! 析構函數!

    4.2.5 深拷貝與淺拷貝(淺拷貝:簡單的賦值拷貝操作、深拷貝:在堆區重新申請空間,進行拷貝操作)(delete()只能刪除在堆區開辟空間的指針)

    深淺拷貝是面試經典問題,也是常見的一個坑

    淺拷貝:簡單的賦值拷貝操作

    深拷貝:在堆區重新申請空間,進行拷貝操作

    示例:

    #include<iostream> #include<string> using namespace std;class Person { public://無參(默認)構造函數Person() {cout << "無參構造函數!" << endl;}//有參構造函數Person(int age, int height) {cout << "有參構造函數!" << endl;m_age = age;m_height = new int(height);}//拷貝構造函數 Person(const Person& p) {cout << "拷貝構造函數!" << endl;//如果不利用深拷貝在堆區創建新內存,會導致淺拷貝帶來的重復釋放堆區問題//(Ar:就是說兩個不同Person對象的m_height指針指向的是同一塊內存?)m_age = p.m_age;m_height = new int(*p.m_height);//m_height = p.m_height; //(這是錯的!不該這么做!)}//析構函數~Person() {cout << "析構函數!" << endl;if (m_height != NULL){delete m_height;}} public:int m_age;int* m_height; };void test01() {Person p1(18, 180);Person p2(p1);cout << "p1的年齡: " << p1.m_age << " 身高: " << *p1.m_height << endl;cout << "p2的年齡: " << p2.m_age << " 身高: " << *p2.m_height << endl; }int main() {test01();system("pause");return 0; }

    運行結果:

    有參構造函數! 拷貝構造函數! p1的年齡: 18 身高: 180 p2的年齡: 18 身高: 180 析構函數! 析構函數!

    4.2.6 初始化列表(寫在構造函數的參數和大括號之間,用冒號:開始,格式為類型(初始化參數),不同項之間用逗號,隔開)(構造函數():屬性1(值1),屬性2(值2)... {})

    作用:

    C++提供了初始化列表語法,用來初始化屬性

    語法:構造函數():屬性1(值1),屬性2(值2)... {}

    示例:

    #include<iostream> #include<string> using namespace std;class Person { public:傳統方式初始化//Person(int a, int b, int c) {// m_A = a;// m_B = b;// m_C = c;//}//初始化列表方式初始化Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}void PrintPerson() {cout << "mA:" << m_A << endl;cout << "mB:" << m_B << endl;cout << "mC:" << m_C << endl;} private:int m_A;int m_B;int m_C; };int main() {Person p(1, 2, 3);p.PrintPerson();system("pause");return 0; } mA:1 mB:2 mC:3

    4.2.7 類對象作為類成員(初始化列表可以告訴編譯器調用哪一個構造函數)

    C++類中的成員可以是另一個類的對象,我們稱該成員為 對象成員

    例如:

    class A {} class B {A a; //B類的成員,A類的對象 }

    B類中有對象A作為成員,A為對象成員

    那么當創建B對象時,A與B的構造和析構的順序是誰先誰后?

    構造的順序是 :先調用對象成員的構造,再調用本類構造
    析構順序是:先析構本類構造,再析構對象成員構造

    示例:

    #include<iostream> #include<string> using namespace std;class Phone { public:Phone(string name){m_PhoneName = name;cout << "Phone構造" << endl;}~Phone(){cout << "Phone析構" << endl;}string m_PhoneName;};class Person { public://初始化列表可以告訴編譯器調用哪一個構造函數【m_Phone(pName)】Person(string name, string pName) :m_Name(name), m_Phone(pName){cout << "Person構造" << endl;}~Person(){cout << "Person析構" << endl;}void playGame(){cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手機! " << endl;}string m_Name;Phone m_Phone;};void test01() {//當類中成員是其他類對象時,我們稱該成員為 對象成員//構造的順序是 :先調用對象成員的構造,再調用本類構造//析構順序與構造相反Person p("張三", "蘋果X");p.playGame();}int main() {test01();system("pause");return 0; }

    運行結果:

    Phone構造 Person構造 張三 使用蘋果X 牌手機! Person析構 Phone析構

    4.2.8 靜態成員 static(包括靜態變量和靜態函數)(類內聲明,類外初始化)(靜態成員變量:所有對象共享同一份數據,在編譯階段分配內存,類內聲明,類外初始化)( 靜態成員函數:所有對象共享同一個函數,靜態成員函數只能訪問靜態成員變量)

    靜態成員就是在成員變量和成員函數前加上關鍵字static,稱為靜態成員

    靜態成員分為:

    • 靜態成員變量
      • 所有對象共享同一份數據
      • 在編譯階段分配內存
      • 類內聲明,類外初始化
    • 靜態成員函數
      • 所有對象共享同一個函數
      • 靜態成員函數只能訪問靜態成員變量

    示例1:靜態成員變量

    #include<iostream> #include<string> using namespace std;class Person {public:static int m_A; //靜態成員變量//靜態成員變量特點://1 在編譯階段分配內存//2 類內聲明,類外初始化//3 所有對象共享同一份數據//我就想試試如果m_B不初始化,用類內函數調用會怎么樣(結果當然是編譯不過啦by Ar)//void PPP() {// printf("%d\n", m_B);//}private:static int m_B; //靜態成員變量也是有訪問權限的 };//Ar 竟然不加類型修飾符還不行(去掉int報錯),它難道不知道m_A是int嗎?? int Person::m_A = 10; //類內聲明,類外初始化(必須初始化,否則有引用時編譯不過)(Arnold) //int Person::m_B = 10;void test01() {//靜態成員變量兩種訪問方式//1、通過對象Person p1;//p1.PPP();p1.m_A = 100;cout << "p1.m_A = " << p1.m_A << endl;Person p2;p2.m_A = 200;cout << "p1.m_A = " << p1.m_A << endl; //共享同一份數據cout << "p2.m_A = " << p2.m_A << endl;//2、通過類名cout << "m_A = " << Person::m_A << endl;//cout << "m_B = " << Person::m_B << endl; //私有權限訪問不到 }int main() {test01();system("pause");return 0; }

    運行結果:

    p1.m_A = 100 p1.m_A = 200 p2.m_A = 200 m_A = 200
    靜態成員變量能在類內初始化嗎?(靜態成員函數可以,變量不行)

    不能

    static成員變量和普通static變量一樣,都在內存分區的全局數據區分配內存,到程序結束后釋放。這就意味著,static 成員變量不隨對象的創建而分配內存,也不隨對象的銷毀而釋放內存。而普通成員變量在對象創建時分配內存,在對象銷毀時釋放內存

    參考文章:C++中靜態成員變量的可以在類內初始化嗎?

    示例2:靜態成員函數(靜態成員函數只能訪問靜態成員變量)

    #include<iostream> #include<string> using namespace std;class Person {public://靜態成員函數特點://1 程序共享一個函數//2 靜態成員函數只能訪問靜態成員變量static void func(){cout << "func調用" << endl;m_A = 100;//m_B = 100; //錯誤,不可以訪問非靜態成員變量}static int m_A; //靜態成員變量int m_B; // private://靜態成員函數也是有訪問權限的static void func2(){cout << "func2調用" << endl;} }; int Person::m_A = 10;void test01() {//靜態成員變量兩種訪問方式//1、通過對象Person p1;p1.func();//2、通過類名Person::func();//Person::func2(); //私有權限訪問不到 }int main() {test01();system("pause");return 0; } func調用 func調用

    4.3 C++對象模型和this指針

    4.3.1 成員變量和成員函數分開存儲(只有非靜態成員變量占對象空間【成員函數,靜態變量和靜態函數都不占對象空間】)

    在C++中,類內的成員變量和成員函數分開存儲

    只有非靜態成員變量才屬于類的對象上

    #include<iostream> #include<string> using namespace std;class Person { public:Person() {mA = 0;}//非靜態成員變量占對象空間int mA;//靜態成員變量不占對象空間static int mB;//函數也不占對象空間,所有函數共享一個函數實例void func() {cout << "mA:" << this->mA << endl;}//靜態成員函數也不占對象空間static void sfunc() {} };int main() {cout << sizeof(Person) << endl; //4system("pause");return 0; }

    靜態成員函數和普通成員函數的區別

    靜態函數不傳遞this指針,不識別對象個體

    4.3.2 this指針概念(this確實是個指針!)

    通過4.3.1我們知道在C++中成員變量和成員函數是分開存儲的

    每一個非靜態成員函數只會誕生一份函數實例,也就是說多個同類型的對象會共用一塊代碼

    那么問題是:這一塊代碼是如何區分那個對象調用自己的呢?

    c++通過提供特殊的對象指針,this指針,解決上述問題。this指針指向被調用的成員函數所屬的對象

    this指針是隱含每一個非靜態成員函數內的一種指針

    this指針不需要定義,直接使用即可

    this指針的用途:

    • 當形參和成員變量同名時,可用this指針來區分
    • 在類的非靜態成員函數中返回對象本身,可使用return *this
    #include<iostream> #include<string> using namespace std;class Person { public:Person(int age){//1、當形參和成員變量同名時,可用this指針來區分this->age = age;}Person& PersonAddPerson(Person p){this->age += p.age;cout << this << endl;//返回對象本身(this是個指針!)return *this;}int age; };void test01() {Person p1(10);cout << "p1.age = " << p1.age << endl;Person p2(10);p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);cout << "p2.age = " << p2.age << endl; }int main() {test01();system("pause");return 0; }

    運行結果:

    p1.age = 10 0133F730 0133F730 0133F730 p2.age = 40

    4.3.3 空指針訪問成員函數(照理來說也是,類的成員函數跟它的實例對象沒關系)

    C++中空指針也是可以調用成員函數的,但是也要注意有沒有用到this指針

    如果用到this指針,需要加以判斷保證代碼的健壯性

    示例:

    #include<iostream> #include<string> using namespace std;//空指針訪問成員函數 class Person { public:void ShowClassName() {cout << "我是Person類!" << endl;}void ShowPerson() {if (this == NULL) {return;}cout << mAge << endl;}public:int mAge; };void test01() {Person* p = NULL;p->ShowClassName(); //空指針,可以調用成員函數p->ShowPerson(); //但是如果成員函數中用到了this指針,就不可以了 }int main() {test01();system("pause");return 0; } 我是Person類!

    4.3.4 const修飾成員函數(常函數:在參數括號后和大括號之間加const)(什么意思,我越看越懵逼了!)(別蒙蔽,仔細看!)(常函數內不可以修改成員屬性;成員屬性聲明時加關鍵字mutable后,在常函數中依然可以修改)(常對象:聲明對象前加const稱該對象為常對象;常對象只能調用常函數,只能修改帶mutable關鍵字的成員屬性)

    常函數:

    • 成員函數后加const后我們稱為這個函數為常函數
    • 常函數內不可以修改成員屬性
    • 成員屬性聲明時加關鍵字mutable后,在常函數中依然可以修改

    常對象:

    • 聲明對象前加const稱該對象為常對象
    • 常對象只能調用常函數

    示例:

    #include<iostream> #include<string> using namespace std;class Person { public:Person() {m_A = 0;m_B = 0;}//this指針的本質是一個指針常量,指針的指向不可修改//如果想讓指針指向的值也不可以修改,需要聲明常函數void ShowPerson() const {//const Type* const pointer;//this = NULL; //不能修改指針的指向 Person* const this;//this->m_A = 100; //沒加mutable不能修改//const修飾成員函數,表示指針指向的內存空間的數據不能修改,除了mutable修飾的變量this->m_B = 100;}void MyFunc() const {//m_A = 10000; //報錯,無法修改cout << m_A << endl; //但是可以訪問m_B = 11234;}void MyFunc2(){}public:int m_A;mutable int m_B; //可修改 可變的 };//const修飾對象 常對象 void test01() {const Person person; //常量對象 cout << person.m_A << endl;//person.mA = 100; //常對象不能修改成員變量的值,但是可以訪問person.m_B = 100; //但是常對象可以修改mutable修飾成員變量//常對象訪問成員函數person.MyFunc(); //常對象只能調用const的函數//person.MyFunc2(); //報錯:不能訪問(常對象只能調用常函數)//person.m_A = 1111; //報錯:表達式必須是可修改的左值(常對象只能修改帶mutable關鍵字的成員屬性)}int main() {test01();system("pause");return 0; }

    運行結果:

    0 0

    4.4 友元 friend(讓一個函數或者類 訪問另一個類中私有成員)

    生活中你的家有客廳(Public),有你的臥室(Private)

    客廳所有來的客人都可以進去,但是你的臥室是私有的,也就是說只有你能進去

    但是呢,你也可以允許你的好閨蜜好基友進去。

    在程序里,有些私有屬性 也想讓類外特殊的一些函數或者類進行訪問,就需要用到友元的技術

    友元的目的就是讓一個函數或者類 訪問另一個類中私有成員

    友元的關鍵字為 friend

    友元的三種實現

    • 全局函數做友元
    • 類做友元
    • 成員函數做友元

    4.4.1 全局函數做友元

    #include<iostream> #include<string> using namespace std;class Building {//告訴編譯器 goodGay全局函數 是 Building類的好朋友,可以訪問類中的私有內容(相當于函數聲明)friend void goodGay(Building* building);public:Building(){this->m_SittingRoom = "客廳";this->m_BedRoom = "臥室";}public:string m_SittingRoom; //客廳private:string m_BedRoom; //臥室 };void goodGay(Building* building) {cout << "好基友正在訪問: " << building->m_SittingRoom << endl;cout << "好基友正在訪問: " << building->m_BedRoom << endl; }void test01() {Building b;goodGay(&b); }int main() {test01();system("pause");return 0; }

    運行結果:

    好基友正在訪問: 客廳 好基友正在訪問: 臥室

    4.4.2 類做友元(就是不知道干啥要這樣設計,繞來繞去的,有個啥意思?)(我又看了一遍還是懵逼,需要看視頻?)

    C++有的地方為什么要類內定義,類外實現(類內聲明,類外初始化)?

    #include<iostream> #include<string> using namespace std;class Building; //前面的調用后面的,要把后面的弄到前面來聲明一下 class goodGay { public:goodGay();void visit();private:Building* building; };class Building {//告訴編譯器 goodGay類是Building類的好朋友,可以訪問到Building類中私有內容friend class goodGay;public:Building();public:string m_SittingRoom; //客廳 private:string m_BedRoom;//臥室 };Building::Building() {this->m_SittingRoom = "客廳";this->m_BedRoom = "臥室"; }goodGay::goodGay() //類內聲明,類外初始化 {building = new Building; }void goodGay::visit() {cout << "好基友正在訪問" << building->m_SittingRoom << endl;cout << "好基友正在訪問" << building->m_BedRoom << endl; }void test01() {goodGay gg;gg.visit();}int main() {test01();system("pause");return 0; } 好基友正在訪問: 客廳 好基友正在訪問: 臥室

    4.4.3 成員函數做友元

    #include<iostream> #include<string> using namespace std;class Building; class goodGay { public:goodGay();void visit(); //只讓visit函數作為Building的好朋友,可以發訪問Building中私有內容void visit2();private:Building* building; };class Building {//告訴編譯器 goodGay類中的visit成員函數 是Building好朋友,可以訪問私有內容friend void goodGay::visit();public:Building();public:string m_SittingRoom; //客廳 private:string m_BedRoom;//臥室 };Building::Building() {this->m_SittingRoom = "客廳";this->m_BedRoom = "臥室"; }goodGay::goodGay() {building = new Building; }void goodGay::visit() {cout << "好基友正在訪問" << building->m_SittingRoom << endl;cout << "好基友正在訪問" << building->m_BedRoom << endl; }void goodGay::visit2() {cout << "好基友正在訪問" << building->m_SittingRoom << endl;//cout << "好基友正在訪問" << building->m_BedRoom << endl; //visit2不是好朋友,不能訪問私有內容(臥室) }void test01() {goodGay gg;gg.visit();gg.visit2(); }int main() {test01();system("pause");return 0; } 好基友正在訪問客廳 好基友正在訪問臥室 好基友正在訪問客廳

    4.5 運算符重載(對已有的運算符重新進行定義,賦予其另一種功能,以適應不同的數據類型)

    可重載運算符和不可重載運算符

    運算符重載概念:對已有的運算符重新進行定義,賦予其另一種功能,以適應不同的數據類型

    4.5.1 加號運算符重載(operator+)

    作用:實現兩個自定義數據類型相加的運算

    #include<iostream> #include<string> using namespace std;class Person { public:Person() {};Person(int a, int b){this->m_A = a;this->m_B = b;}//成員函數實現 + 號運算符重載(調用它的對象(this)+(p)?)Person operator+(const Person& p) {Person temp;temp.m_A = this->m_A + p.m_A;temp.m_B = this->m_B + p.m_B;return temp;}public:int m_A;int m_B; };//全局函數實現 + 號運算符重載 //Person operator+(const Person& p1, const Person& p2) { // Person temp(0, 0); // temp.m_A = p1.m_A + p2.m_A; // temp.m_B = p1.m_B + p2.m_B; // return temp; //}//運算符重載 可以發生函數重載 Person operator+(const Person& p2, int val) {Person temp;temp.m_A = p2.m_A + val;temp.m_B = p2.m_B + val;return temp; }void test() {Person p1(10, 10);Person p2(20, 20);//成員函數方式Person p3 = p2 + p1; //相當于 p2.operaor+(p1)cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;Person p4 = p3 + 10; //相當于 operator+(p3,10)cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;}int main() {test();system("pause");return 0; } mA:30 mB:30 mA:40 mB:40

    總結1:對于內置的數據類型的表達式的的運算符是不可能改變的

    總結2:不要濫用運算符重載

    4.5.2 左移運算符重載(輸出自定義數據類型)(ostream& operator<<)

    https://www.bilibili.com/video/BV1et411b73Z?p=122
    看了視頻教程才看明白,

    作用:可以輸出自定義數據類型

    #include<iostream> #include<string> using namespace std;class Person {friend ostream& operator<<(ostream& out, Person& p);public:Person(int a, int b){this->m_A = a;this->m_B = b;}//成員函數 實現不了 p << cout 不是我們想要的效果//void operator<<(Person& p){//}private:int m_A;int m_B; };//全局函數實現左移重載 //ostream對象只能有一個 ostream& operator<<(ostream& out, Person& p) {out << "a:" << p.m_A << " b:" << p.m_B;return out; }void test() {Person p1(10, 20);cout << p1 << "hello world" << endl; //鏈式編程 }int main() {test();system("pause");return 0; } a:10 b:20hello world

    總結:重載左移運算符配合友元可以實現輸出自定義數據類型

    下面未檢測-------------------------------上面需再次看視頻,因為確實很多知識文檔里沒講到而視頻里講到了

    4.5.3 遞增運算符重載

    作用: 通過重載遞增運算符,實現自己的整型數據

    class MyInteger {friend ostream& operator<<(ostream& out, MyInteger myint);public:MyInteger() {m_Num = 0;}//前置++MyInteger& operator++() {//先++m_Num++;//再返回return *this;}//后置++MyInteger operator++(int) {//先返回MyInteger temp = *this; //記錄當前本身的值,然后讓本身的值加1,但是返回的是以前的值,達到先返回后++;m_Num++;return temp;}private:int m_Num; };ostream& operator<<(ostream& out, MyInteger myint) {out << myint.m_Num;return out; }//前置++ 先++ 再返回 void test01() {MyInteger myInt;cout << ++myInt << endl;cout << myInt << endl; }//后置++ 先返回 再++ void test02() {MyInteger myInt;cout << myInt++ << endl;cout << myInt << endl; }int main() {test01();//test02();system("pause");return 0; }

    總結: 前置遞增返回引用,后置遞增返回值

    4.5.4 賦值運算符重載(對屬性值進行拷貝)(這個用得多不多啊,搞得語法有點亂不是么?)

    c++編譯器至少給一個類添加4個函數

  • 默認構造函數(無參,函數體為空)
  • 默認析構函數(無參,函數體為空)
  • 默認拷貝構造函數,對屬性進行值拷貝
  • 賦值運算符 operator=, 對屬性進行值拷貝
  • 如果類中有屬性指向堆區,做賦值操作時也會出現深淺拷貝問題

    示例:

    #include<iostream> #include<string> using namespace std;class Person { public:Person(int age){//將年齡數據開辟到堆區m_Age = new int(age);}//重載賦值運算符 Person& operator=(Person& p){//if(this->m_Age != NULL)if (m_Age != NULL){delete m_Age;m_Age = NULL;}//編譯器提供的代碼是淺拷貝//m_Age = p.m_Age;//提供深拷貝 解決淺拷貝的問題m_Age = new int(*p.m_Age);//返回自身return *this;}~Person(){if (m_Age != NULL){delete m_Age;m_Age = NULL;}}//年齡的指針int* m_Age;};void test01() {Person p1(18);Person p2(20);Person p3(30);p3 = p2 = p1; //賦值操作cout << "p1的年齡為:" << *p1.m_Age << endl;cout << "p2的年齡為:" << *p2.m_Age << endl;cout << "p3的年齡為:" << *p3.m_Age << endl; }int main() {test01();//int a = 10;//int b = 20;//int c = 30;//c = b = a;//cout << "a = " << a << endl;//cout << "b = " << b << endl;//cout << "c = " << c << endl;system("pause");return 0; }

    4.5.5 關系運算符重載(靈活是靈活,但是用普通函數也可以實現呀!)

    作用:重載關系運算符,可以讓兩個自定義類型對象進行對比操作

    示例:

    #include<iostream> #include<string> using namespace std;class Person { public:Person(string name, int age){this->m_Name = name;this->m_Age = age;};bool operator==(Person& p){if (this->m_Name == p.m_Name && this->m_Age == p.m_Age){return true;}else{return false;}}bool operator!=(Person& p) //為啥不直接寫個equal函數?{if (this->m_Name == p.m_Name && this->m_Age == p.m_Age){return false;}else{return true;}}string m_Name;int m_Age; };void test01() {//int a = 0;//int b = 0;Person a("孫悟空", 18);Person b("孫悟空", 18);if (a == b){cout << "a和b相等" << endl;}else{cout << "a和b不相等" << endl;}if (a != b){cout << "a和b不相等" << endl;}else{cout << "a和b相等" << endl;} }int main() {test01();system("pause");return 0; } a和b相等 a和b相等

    4.5.6 函數調用運算符重載(仿函數)(如果要理解還得看視頻,沒看,先過了。。。)

    • 函數調用運算符 () 也可以重載
    • 由于重載后使用的方式非常像函數的調用,因此稱為仿函數
    • 仿函數沒有固定寫法,非常靈活

    示例:

    #include<iostream> #include<string> using namespace std;class MyPrint { public:void operator()(string text){cout << text << endl;}}; void test01() {//重載的()操作符 也稱為仿函數MyPrint myFunc;myFunc("hello world"); }class MyAdd { public:int operator()(int v1, int v2){return v1 + v2;} };void test02() {MyAdd add;int ret = add(10, 10);cout << "ret = " << ret << endl;//匿名對象調用 cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl; }int main() {test01();test02();system("pause");return 0; } hello world ret = 20 MyAdd()(100,100) = 200

    總結

    以上是生活随笔為你收集整理的【黑马程序员 C++教程从0到1入门编程】【笔记4】C++核心编程(类和对象——封装、权限、对象的初始化和清理、构造函数、析构函数、深拷贝、浅拷贝、初始化列表、友元friend、运算符重载)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 天堂在线免费观看 | 在线国产网站 | 91成人高清 | 欧洲成人午夜精品无码区久久 | www.婷婷色| 国产主播一区二区三区 | 99热最新在线 | 在线国产视频 | 99久久久 | 欧美日韩国产精品一区二区三区 | 免费在线观看毛片视频 | 狠狠做| 黄色av网页 | 男人疯狂高潮呻吟视频 | 免费吃奶摸下激烈视频 | 久久精品国产亚洲av高清色欲 | 自拍偷自拍亚洲精品播放 | 影音先锋亚洲一区 | 北条麻妃久久精品 | 人与性动交zzzzbbbb | 红桃视频91 | 欧美日韩高清一区 | 无码人妻精品一区二区三区66 | 91视频国产精品 | 韩国伦理大片 | 欧美 中文字幕 | 性色影院| 美人被强行糟蹋np各种play | 操操操网 | 欧美黄色片免费看 | 日韩乱码在线观看 | 91国产精品一区 | 96视频在线观看 | 黄色小说在线视频 | 国产精品一级二级三级 | 天堂中文在线最新 | 射黄视频 | 日本一区二区在线不卡 | 东北老女人av | 老司机久久精品视频 | 亚洲国产成人一区二区精品区 | 91精品啪在线观看国产线免费 | 四虎视频 | 色哟哟在线 | 佐山爱在线视频 | 国产日日夜夜 | 九九久久视频 | 人体毛片 | 亚洲 激情| 狠狠干成人 | 日本 在线| 日本免费一二三区 | 成人黄色一区二区 | 精品成人av一区二区在线播放 | 亚洲一区二区免费 | 176精品免费 | 日韩亚洲欧美一区二区三区 | 丰满少妇大力进入 | 九七在线视频 | 亚洲爆乳无码一区二区三区 | 久热精品视频在线观看 | 热热热av | 台湾a级艳片潘金莲 | 国产精品视频一区二区三区, | 抖音视频在线观看 | 亚洲人成电影在线播放 | www.欧美com | 黄色在线视频网站 | 91久久久久久 | 蘑菇av | 日本激情在线 | 美女啪啪免费视频 | 激情视频免费观看 | 黑鬼大战白妞高潮喷白浆 | 香蕉视频黄色片 | 校园春色欧美 | 成人黄色大片在线观看 | 国产日韩欧美在线观看 | 久久一区| 污污污www精品国产网站 | 一级做a爰片毛片 | 国产天堂在线 | 美女三级网站 | 欧美激情亚洲综合 | 啪网址 | www.色网 | 免费黄色大片 | 国产欧美日韩精品一区 | 欧美精品色哟哟 | 久久久久97国产 | 青青草www | 在线一二区 | 国产精品99久久久久久一二区 | 黄色在线视频播放 | 免费一二三区 | 男生把女生困困的视频 | 亚洲区小说区图片区qvod | 久久入口| 色综合五月天 |