面向對象
- 1.面向對象編程(難點)
- 2.類和對象
- demo1:地主類的實現版本1
- demo2:地主類的實現版本2
- 3.訪問修飾符
- demo3:外部修改成員變量不安全(版本3)
- demo4: 使用封裝防止直接修改成員變量(版本3)
- demo5:進一步封裝:設置/獲取名字,修改積分(版本4)
- 4.構造函數與析構函數(重點)
- 4.1默認構造函數
- demo6: 使用構造函數進行成員變量的初始化
- 4.2 帶參構造函數
- demo7:學生類構建--堆內存對象/棧內存對象
- 4.3 析構函數
- demo8:學生類析構函數演示
- 5.this指針(重點)
- demo9:學霸返回引用的使用
《老九學堂C++課程》《C++ primer》學習筆記。《老九學堂C++課程》詳情請到B站搜索《老九零基礎學編程C++入門》
-------------簡單的事情重復做,重復的事情用心做,用心的事情堅持做(老九君)---------------
1.面向對象編程(難點)
oop: object oriented programming
何為面向對象:基于對象的概念,以對象為中心,以類和繼承為構造機制,來認識、理解、刻畫客觀世界;涉及構建相應的軟件系統(模擬現實)
1.對象–有數據和容許的操作組成的封裝體,與客觀實體有直接的對應關系(屬性和方法的集合)
面向對象不是某一種語言的特性,而是一種編程思想。原來面向過程代碼超過10W行就會難管理,原來有因為飛機控制程序中一個,寫成.號造成的空難。
舉個粒子:斗地主游戲的開發
面向過程:一步一步來,很多很多的過程函數:開始游戲-洗牌-發牌-顯示手牌…-輸出結果
面向對象:
1.游戲參與者,行為模式是相同的–玩家對象,相同的屬性和行為;
2.進行游戲的場景–牌桌對象,負責現實游戲的界面及內容;
3.游戲規則系統–裁判對象,負責判定牌面、輸贏;
小結:
1.面向過程編程,首先考慮要遵循的步驟,然后考慮如何表示這些數據
2.oop編程,首先會考慮數據,包括數據的表示和數據的使用
2.類和對象
面向對象的編程流程:
1.抽象:從具體食物抽取共同的本質特征,【處理復雜問題的技巧–簡化、抽象】
地主對象:外表特征–胖,留兩撇胡子,兩顆大金牙;行為特點–先出牌,多摸三張牌
2.用類封裝:將抽象轉換為用戶定義類型的工具,將數據表示和操作數據的方法組合成一個整體。類的實例成為對象(對象的集合就是類,還可以這么理解:類就是對象模版),類中的變量和函數稱為成員。
地主類:
成員變量:名稱、積分、手牌
成員函數:摸牌、出牌、產看積分
類的聲明:使用class/struct 關鍵字聲明,
兩者的區別:使用class聲明的類默認成員是私有的(private),struct聲明的類默認成員是共有的(public)。
推薦使用class聲明類;struct聲明結構,只包含數據POD,老式數據
class 類名{};
struct 類名{};
頭文件中聲明類(.h/.cpp),專門有一個類名.cpp文件實現類。
demo1:地主類的實現版本1
–LandOwnerV1.cpp 文件中既聲明又實現,包含main 函數的main.cpp文件中調用
// mian.cpp 文件
#include "LandOwnerV1.cpp"
int main(){// 類,對象實驗LandOwnerV1 landOwner1; // 聲明了一個LandOwner1類型的變量landowner1// 調用對象的成員方法, 不能直接使用對象的私有成員// landOwner1.cards[0] = 0; 'cards' is a private member of 'LandOwnerV1' ,直接報錯landOwner1.TouchCard(100);return 0;
}
// LandOwnerV1.cpp 文件既聲明又實現
#include <iostream>
using namespace std;
// .hpp 一般包含實現的內聯函數,通常用于模版類這種聲明與實現共存的情況
// 建議:只要不是純模版,一律使用.h 作為頭文件后綴, .cpp 作為函數的實現文件
// 地主類的聲明、實現
class LandOwnerV1 {private:string name; // 名稱long score; // 積分int cards[20]; // 手牌數組public :LandOwnerV1() {}; // 默認構造函數~LandOwnerV1() {}; // 默認析構函數void TouchCard(int CardCount){// 暫時省略函數實現cout << name << "摸了" << CardCount << "張牌" << endl;}void ShowScore(){cout << name << "當前的積分為:" << score << endl;}};
輸出
摸了100張牌
demo2:地主類的實現版本2
–LandOwnerV2.h中聲明,LandOwnerV2.cpp中實現,main.cpp文件中調用
成員函數沒有調用成功
// mian.cpp 文件
#include "LandOwnerV2.h" // 關注.h 文件
using namespace std;
int main(){LandOwnerV2 landowner2;landowner2.name = "小明";//landowner2.TouchCard(20); // 這個方法實現不了cout << landowner2.name << endl;return 0;
}
// LandOwnerV2.h
#include <iostream>
using namespace std;
// .hpp 一般包含實現的內聯函數,通常用于模版類這種聲明與實現共存的情況
// 建議:只要不是純模版,一律使用.h 作為頭文件后綴, .cpp 作為函數的實現文件
// 地主類的聲明class LandOwnerV2 {private:long score; // 積分int cards[20]; // 手牌數組public :string name; // 名稱LandOwnerV2(); // 構造函數聲明~LandOwnerV2(); // 析構函數聲明 void TouchCard(int); // 聲明摸牌函數void PlayCard(int); // 聲明出牌函數void ShowScore(); // 聲明產看積分函數
};
// LandOwnerV2.cpp
#include <iostream>
#include "LandOwnerV2.h"
using namespace std;LandOwnerV2::LandOwnerV2()
{//ctor
}void LandOwnerV2::TouchCard(int CardCount){cout << name << "摸了" << CardCount << "張牌" << endl;
}
void LandOwnerV2::ShowScore(){cout << name << "當前的積分為:" << score << endl;
}LandOwnerV2::~LandOwnerV2()
{//dtor
}
(xcode 太不友好了,寫到類分文件時就無法編譯,棄坑!轉向CLION,友好很多。)
3.訪問修飾符
public: 修飾的成員在任意地方都可以訪問
private:修飾的成員只能在類中或者友元函數中訪問, 私有屬性可以習慣性在名字前面加一個_
protected:修飾的成員可以在類中函數、子類函數、友元函數中訪問
(數據隱藏:不希望別人隨意操作,對應的操作叫做封裝)
在修飾關鍵字放在類定義的關鍵字中,加冒號,無修飾關鍵字默認為private
class 類名{
修飾符:成員類標;
};
demo3:外部修改成員變量不安全(版本3)
非私有成員,會被直接修改–修改地主積分
// mian.cpp 文件
#include <iostream>
#include "LandOwnerv3.h"
using namespace std;
int main(){// 訪問修飾符的實驗LandOwnerv3 landOwner3;landOwner3.name = "巴依老爺";// 修改地主積分landOwner3.score = 100;landOwner3.ShowScore(landOwner3.score);return 0;
}
// LandOwnerv3.h 文件
// Created by 陳瑩瑩 on 2021/1/28.
#include <iostream>
#ifndef HELLOWORLD_LANDOWNERV3_H
#define HELLOWORLD_LANDOWNERV3_H
using namespace std;
class LandOwnerv3 {int cards[20]; // 手牌數組
public :string name;long score; // 積分LandOwnerv3(); // 構造函數聲明, 沒有返回值~LandOwnerv3(); // 析構函數聲明void TouchCard(int); // 聲明摸牌函數void PlayCard(int); // 聲明出牌函數void ShowScore(int); // 聲明產看積分函數
};
#endif //HELLOWORLD_LANDOWNERV3_H
// LandOwnerv3.cpp 文件
// Created by 陳瑩瑩 on 2021/1/28.
// 用來演示封裝的基本概念
#include <iostream>
#include "LandOwnerv3.h"
using namespace std;
LandOwnerv3::LandOwnerv3()
{//ctor
}
void LandOwnerv3::ShowScore(int score){cout << name << "當前的積分為:" << score << endl;
}LandOwnerv3::~LandOwnerv3()
{//dtor
}
輸出:
巴依老爺當前的積分為:100
demo4: 使用封裝防止直接修改成員變量(版本3)
但是如此操作使得大家可以隨意修改這個積分,有一些不合理的積分就會出現。為了解決積分被賦值為不合理的情況,需要將成員變量score進行封裝(使用方法來實現對成員的封裝)
demo4:類封裝概念,get/set方法
// mian.cpp 文件
#include <iostream>
#include "LandOwnerv3.h"
using namespace std;
int main(){// 訪問修飾符的實驗LandOwnerv3 landOwner3;landOwner3.name = "巴依老爺";// 修改地主積分landOwner3.SetScore(-100);landOwner3.ShowScore();return 0;
}
// LandOwnerv3.h 文件
// Created by 陳瑩瑩 on 2021/1/28.
#include <iostream>
#ifndef HELLOWORLD_LANDOWNERV3_H
#define HELLOWORLD_LANDOWNERV3_H
using namespace std;
class LandOwnerv3 {long score; // 積分int cards[20]; // 手牌數組
public :string name;LandOwnerv3(); // 構造函數聲明, 沒有返回值~LandOwnerv3(); // 析構函數聲明void TouchCard(int); // 聲明摸牌函數void PlayCard(int); // 聲明出牌函數void ShowScore(); // 聲明產看積分函數// 定義成內聯函數即可void SetScore(long lScore){// 通過條件判斷封裝了score的賦值過程if(lScore < 0){score = 0;}else{score = lScore;}}
};
#endif //HELLOWORLD_LANDOWNERV3_H
// LandOwnerv3.cpp 文件
// Created by 陳瑩瑩 on 2021/1/28.
// 用來演示封裝的基本概念
#include <iostream>
#include "LandOwnerv3.h"
using namespace std;
LandOwnerv3::LandOwnerv3()
{//ctor
}
void LandOwnerv3::ShowScore(){cout << name << "當前的積分為:" << score << endl;
}LandOwnerv3::~LandOwnerv3()
{//dtor
}
輸出:
巴依老爺當前的積分為:0
demo5:進一步封裝:設置/獲取名字,修改積分(版本4)
// mian.cpp 文件
#include <iostream>
#include "LandOwnerv41.h"
using namespace std;
int main(){// 訪問修飾符的實驗LandOwnerv41 landOwner4;landOwner4.SetName("巴依老爺");cout << landOwner4.GetName() << endl;// 修改地主積分landOwner4.SetScore(-100);landOwner4.ShowScore();return 0;
}
// LandOwnerv41.h 文件
// Created by 陳瑩瑩 on 2021/1/29.
#ifndef HELLOWORLD_LANDOWNERV41_H
#define HELLOWORLD_LANDOWNERV41_H#include <iostream>
using namespace std;
class LandOwnerv41 {long score; // 積分int cards[20]; // 手牌數組string name;
public :LandOwnerv41(); // 構造函數聲明, 沒有返回值~LandOwnerv41(); // 析構函數聲明void TouchCard(int); // 聲明摸牌函數void PlayCard(int); // 聲明出牌函數void ShowScore(); // 聲明產看積分函數// 定義成內聯函數即可void SetScore(long lScore){// 通過條件判斷封裝了score的賦值過程if(lScore < 0){score = 0;}else{score = lScore;}}string GetName(){return name;}void SetName(string lName){name = lName;}};
#endif //HELLOWORLD_LANDOWNERV41_H
// LandOwnerv41.cpp 文件
// Created by 陳瑩瑩 on 2021/1/29.
#include "LandOwnerv41.h"
#include <iostream>
using namespace std;
LandOwnerv41::LandOwnerv41()
{//ctor
}
void LandOwnerv41::ShowScore(){cout << name << "當前的積分為:" << score << endl;
}LandOwnerv41::~LandOwnerv41()
{//dtor
}
4.構造函數與析構函數(重點)
構造函數:與類同名,沒有返回值
構造函數的作用:給編譯器看的,編譯器在對象被創建時,為對象分配內存空間,并自動調用構造函數以完成成員的初始化
構造函數的種類:無參數構造,一般構造(重載構造),拷貝構造。(本節主要介紹:無參構造,一般構造)
4.1默認構造函數
demo6: 使用構造函數進行成員變量的初始化
// mian.cpp 文件
//演示構造函數
#include "LandOwnerv41.h"
using namespace std;
int main(){LandOwnerv41 landOwnerv4; // 省略了默認構造LandOwnerv41 landOwnerv4(); // 標準寫法,但是不調用默認構造函數了?--不輸出內容了return 0;
}
// LandOwnerv41.h 文件
// 與demo5一致
// LandOwnerv41.cpp 文件
// Created by 陳瑩瑩 on 2021/1/29.
//
#include "LandOwnerv41.h"
#include <iostream>
#include <memory.h>
using namespace std;
LandOwnerv41::LandOwnerv41()
{cout << "LandOwnerV41的無參數構造函數(默認構造)被調用!" << endl;name = "默認地主";score = 0;//將用戶的手牌數組初始化為0memset(cards, 0, sizeof(cards)/sizeof(cards[0]));cout << "初始化的結果如下:" << endl;cout << "名字:" << name << endl;cout << "積分:" << score << endl;cout << "手牌數組:" ;for(int i=0; i < sizeof(cards)/sizeof(cards[0]); i++){cout << cards[i] << "\t";}cout << endl;
}
void LandOwnerv41::ShowScore(){cout << name << "當前的積分為:" << score << endl;
}LandOwnerv41::~LandOwnerv41()
{//dtor
}
為啥手牌數組的初始化不是全為0?
LandOwnerV41的無參數構造函數(默認構造)被調用!
初始化的結果如下:
名字:默認地主
積分:0
手牌數組:0 0 0 0 0 0 0 0 -375207560 32766 -375207584 32766 0 1 0 0 0 0 0 0
默認構造顯式寫法:
LandOwnerV4() = default;
注意:
1.如果創建的類中未書寫任何構造函數,系統會自動生成默認的無參構造函數(函數為空,什么都不做)
2.如果書寫了構造函數,系統不會自動生成默認構造函數,如果希望有一個這樣的無參數構造函數,需要自己顯示書寫LandOwnerV4() = default;
4.2 帶參構造函數
語法:
類名::構造(類型1 參數1, 類型2 參數2, ....){// 相關初始化方法
}
demo7:學生類構建–堆內存對象/棧內存對象
1.棧內存中的對象(類似于函數聲明),
具體對象由系統創建并釋放,不用擔心內存泄露。
聲明周期只在聲明區域的大括號內。
棧內存的優勢存取速度快(僅次于寄存器),缺點棧內存中的數據大小生存期是確定的,缺乏靈活性。
自定義類型名 對象名;
Student stu();
Student stu;
2.堆內存中的對象(需要new關鍵字)
p_stu1是指針,必須使用delete釋放
使用靈活可以賦給全局變量,可以把對象作為函數的返回值
用好了
Student *p_stu1= new Student();
Student *p_stu2= new Student();
auto *p_stu3= new Student(); // auto 自動類型判斷,不推薦使用,sizeof 會報錯
類對象推薦放在堆內存中,即使用new來創建對象。
// mian.cpp 文件
#include <iostream>
#include "Student.h"
using namespace std;
int main(){// 演示構造函數,實質就是函數重載Student stu1;Student stu2("馬化騰","普通家庭"); // 在棧內存中直接分配空間,速度塊// 如果構造函數中只有一個參數,且單參數構造函數只有一個,可以直接使用賦值操作進行初始化// Student stu4 = 45stu2.showInfo();// 學生指針類型, 使用new 分配內存空間Student * stu5 = new Student("杰克馬", "毀創阿里"); // 在堆內存中分配空間// 類指針訪問方法要使用 ->stu5 ->showInfo();return 0;
}
輸出結果:
默認構造函數
設置帶參構造
普通家庭馬化騰
設置帶參構造
毀創阿里杰克馬
// Student.h 文件
//
// Created by 陳瑩瑩 on 2021/2/2.
//
#ifndef HELLOWORLD_STUDENT_H
#define HELLOWORLD_STUDENT_H
#include <iostream>
using namespace std;class Student {
private:string m_name;string m_desc;int m_age;
public:Student(); // 默認構造函數Student(string, string);~Student();void showInfo();};
#endif //HELLOWORLD_STUDENT_H
// Student.cpp 文件
//
// Created by 陳瑩瑩 on 2021/2/2.
//
#include <iostream>
#include "Student.h"
using namespace std;Student::Student() {cout << "默認構造函數" << endl;
}
//Student::Student(string name, string desc){
// // 參數列表不同,函數重載
// m_name = name;
// m_desc = desc;
// cout << "設置帶參構造" << endl;
//}
//初始化參數列表的寫法,和上上面的帶參數構造方法完全相同
Student::Student(string name, string desc):m_name(name), m_desc(desc){cout << "設置帶參構造" << endl;
};void Student::showInfo() {cout << m_desc << m_name << endl;
}
Student::~Student() {}
4.3 析構函數
對象銷毀時自動調用的特殊成員函數, 析構函數一般用來完成清理工作。
析構函數名稱在類名前加一個~,析構函數沒有參數,只能有一個
在棧內存中構造的對象,在棧區(mian函數)銷毀時,類對象會被自動銷毀。在堆內存中構建的類對象,需要手動釋放。
在析構函數內部需要釋放(delete)掉在構造函數中手動分配(new)的空間(對象內部的成員變量)。
1.析構函數用來釋放對象使用的資源,并銷毀對象的非static數據成員.
2.無論何時一個對象被銷毀,都會自動調用析構函數(隱式析構)
demo8:學生類析構函數演示
類對象推薦放在堆內存中,即使用new來創建對象。
// mian.cpp 文件
#include <iostream>
#include "Student.h"
using namespace std;
int main(){// 演示構造函數,實質就是函數重載Student stu1;Student stu2("馬化騰","普通家庭"); // 在棧內存中直接分配空間,速度塊// 如果構造函數中只有一個參數,且單參數構造函數只有一個,可以直接使用賦值操作進行初始化// Student stu4 = 45stu2.showInfo();// 學生指針類型, 使用new 分配內存空間Student * stu5 = new Student("杰克馬", "毀創阿里"); // 在堆內存中分配空間// 類指針訪問方法要使用 ->stu5 ->showInfo();return 0;
}
輸出結果:
默認構造函數
設置帶參構造
普通家庭馬化騰
設置帶參構造
毀創阿里杰克馬
杰克馬被釋放 # 杰克馬為啥被釋放了?
馬化騰被釋放
被釋放
// Student.h 文件
// 與demo7一樣
// Student.cpp 文件
//
// Created by 陳瑩瑩 on 2021/2/2.
//
#include <iostream>
#include "Student.h"
using namespace std;Student::Student() {cout << "默認構造函數" << endl;
}
//Student::Student(string name, string desc){
// // 參數列表不同,函數重載
// m_name = name;
// m_desc = desc;
// cout << "設置帶參構造" << endl;
//}
//初始化參數列表的寫法,和上上面的帶參數構造方法完全相同
Student::Student(string name, string desc):m_name(name), m_desc(desc){cout << "設置帶參構造" << endl;
};void Student::showInfo() {cout << m_desc << m_name << endl;
}
Student::~Student() {cout << m_name << "被釋放" << endl;
}
5.this指針(重點)
(類似于于python中的self,在python中顯式傳遞self參數)
每個成員函數(包括構造函數和析構函數)都有一個this指針。this指針指向調用對象,可以通過this關鍵字訪問當前對象的成員。
- 訪問成員變量:this -> 成員名;
- 訪問成員函數:this -> 函數名();
this 在成員函數執行前就已經創建,在析構函數之后被銷毀。
->this 指針的類型為*const,為右值;(可以賦值)
->this指針本身不占用大小,并不是對象的一部分,用sizeof 測不出來大小
->this指針作用域在成員函數內部;
->this指針是類成員函數的第一個默認隱含參數,編譯器自動維護傳遞,編寫者不能顯式傳遞。
->在類非靜態成員函數中才可以使用this指針,其他任何函數都不可以(static是先于類實例存在的,this還沒有創建)
this 返回當前對象的引用,太難用了-- Student & GetSuperScholars(Student &);
demo9:學霸返回引用的使用
//mian.cpp
#include <iostream>
#include "Student.h"
using namespace std;
int main(){Student *ptr_stu1 = new Student("迪麗熱巴","微胖女孩");ptr_stu1->AddScore(78.9);ptr_stu1->AddScore(77.9);ptr_stu1->AddScore(72.9);ptr_stu1->AddScore(79.9);ptr_stu1->AddScore(800);ptr_stu1->showInfo();Student stu2("劉強東","不愛美人");stu2.AddScore(78.9);stu2.AddScore(77.9);stu2.AddScore(72.9);stu2.AddScore(79.9);stu2.AddScore(90);stu2.showInfo();// Student scholar1 = stu2.GetSuperScholars(*ptr_stu1); 返回new出來的結果,接受的確實棧類型,矛盾,空間釋放的時候會出問題
// Student scholar2 = ptr_stu1->GetSuperScholars(stu2);Student &scholar1 = stu2.GetSuperScholars(*ptr_stu1); //返回迪麗熱巴的引用,Student &scholar2 = ptr_stu1->GetSuperScholars(stu2); //返回迪麗熱巴的引用cout << "學霸是" << scholar1.GetName() << "\t" << scholar2.GetName() << endl;delete ptr_stu1;return 0;//scholar1 棧內存中定義的,main 結束后會自動釋放//scholar2 棧內存中定義的,main 結束后會自動釋放
}
//Student.h
//
// Created by 陳瑩瑩 on 2021/2/2.
//
#ifndef HELLOWORLD_STUDENT_H
#define HELLOWORLD_STUDENT_H
#include <iostream>
using namespace std;class Student {
private:string m_name;string m_desc;int m_age;float *scores; // 學生的分數數組,在某次構造的時候進行初始化int scoreCount; // 學生成績的個數public:Student(); // 默認構造函數Student(string, string);~Student();void showInfo();void InitScores(); // 初始化學生的成績數組,默認分配一個元素空間void AddScore(float score);// 返回值是引用時,是非常危險的。this可以直接修改屬性,建議在函數聲明處加一個const,限制在該函數內部不能通過this指針修改屬性,太麻煩了,要該的地方太多了Student & GetSuperScholars(Student &); //返回學霸對象// 參數處加const 保證不對該參數進行修改。float GetTotal();string GetName() {return m_name;}};#endif //HELLOWORLD_STUDENT_H
//Student.cpp
//
// Created by 陳瑩瑩 on 2021/2/2.
//
#include <iostream>
#include "Student.h"
using namespace std;Student::Student() {cout << "默認構造函數" << endl;InitScores();
}
//Student::Student(string name, string desc){
// // 參數列表不同,函數重載
// m_name = name;
// m_desc = desc;
// cout << "設置帶參構造" << endl;
//}
//初始化參數列表的寫法,和上上面的帶參數構造方法完全相同
Student::Student(string name, string desc):m_name(name), m_desc(desc){cout << "設置帶參構造" << endl;InitScores();
};void Student::showInfo() {cout << m_desc << m_name << endl;for(int i = 0; i < scoreCount-1; i++){cout << this->scores[i] << "\t";}cout << endl;
}
// C的類方法寫(我這么寫編譯不過)
//void MyShow(const Student* this){
// this->
//}
void Student::InitScores() {this -> scores = new float [1];this -> scoreCount = 1;
}
void Student::AddScore(float score) {this -> scores[this->scoreCount -1] = score;// 1. 創建一個新數組,分配scoreCount+1 個空間// 2. 復制愿數組中的內容到新數組中// 3. scoreCount++// 4. scores指向新數組float *newScores = new float [scoreCount + 1];float *oldScores = scores;memcpy(newScores, scores, sizeof(float) * scoreCount);scoreCount++;scores = newScores;delete oldScores; // 刪除原來的指針
}
Student & Student::GetSuperScholars(Student &otherstu){// otherstu 要對比的另一學生對象// 返回總分比較大的那個學生對象// 分別計算兩個學生的總分if(this->GetTotal() > otherstu.GetTotal()){return *this;}else{return otherstu;}}
float Student::GetTotal(){float sum = 0;for(int i = 0; i < scoreCount; i++){sum += scores[i];}return sum;}
Student::~Student() {cout << m_name << "被釋放" << endl;delete this -> scores;}
輸出
設置帶參構造
微胖女孩迪麗熱巴
78.9 77.9 72.9 79.9 800
設置帶參構造
不愛美人劉強東
78.9 77.9 72.9 79.9 90
學霸是迪麗熱巴 迪麗熱巴
迪麗熱巴被釋放
劉強東被釋放
小結:類就是自己定義的數據類型,對象就是變量