思維導圖
面向對象
1.面向對象思想
概念:面向對象編程(OOP)是一種以對象為基礎的編程范式,強調將數據和操作數據的方法封裝在一起。這就是上篇文章講過的。面向過程是以“怎么解決問題”為核心,而面向對象思想在于“誰來解決問題”為核心。
特點:
1. 將操作的事物看成對象
2. 不需要自己親自去做事,而是直接調用對象的行為完成需要的操作
3. 簡化復雜的分步操作,提高編程效率
2.類和對象
類:類是一個概念,用于描述具體對象的特點,包括屬性(數據)和行為(函數)。自定義類的命名必須使用帕斯卡命名法:所有單詞的首字母大寫。
????????屬性:也就是成員變量,具體的數據。
????????行為:也就是成員函數,具體的函數,可以實現一定的方法。
對象:對象是依照類創建的實體,可以有多個,但是多個對象之間的數據是相互隔離的
一.類和對象的創建和使用
想象現在你就是上帝,你創造一只貓,它有年齡身高名字等基礎屬性,這些就是這個貓的“數據”,它還會奔跑,喵喵叫等,這些就是這個“數據”的動作,也就是“方法”,在C++中就可以把數據和方法封裝在一起,這就有了面向對象的第一個屬性----封裝。這樣一來,這個對象就不只是一個數據,還包含它的方法,就可以讓這個對象完成一些特定行為,而不是我創造貓,我讓貓叫,讓貓跑,而是我創造了貓,讓貓自己叫,自己跑。
1.創建類
#include <iostream>using namespace std;class Cat{
public: //公有成員,可以在外部訪問string name; //屬性string color;int age;double weight;void run(){ //行為:跑cout << "run for you" << endl;}void meow(int a){ //叫if(a == 8)cout << "miao miao" << endl;}void name1(){ //顯示名字name = "mike";cout << name << endl;}
};int main()
{return 0;
}
2.創建對象
(1)棧內存對象:
只存活在最近的{}內,{}執行結束后,自動被銷毀,適合臨時使用。
Cat cat1;
(2)堆內存對象:
使用new關鍵字創建,使用delete關鍵字銷毀,如果不銷毀則持續存在,會導致內存泄漏。堆內存對象使用指針存儲首地址,使用->調用成員。
Cat *cat2 = new Cat;
3.使用對象
當使用的pubulic修飾的類,可以直接在外部賦值,使用在對象后面加“.”就可以使用內部函數。
#include <iostream>using namespace std;class Cat{
public: //公有成員,可以在外部訪問string name; //屬性string color;int age;double weight;void run(){ //行為:跑cout << "run for you" << endl;}void meow(int a){ //叫if(a == 8)cout << "miao miao" << endl;}void name1(){ //顯示名字name = "mike";cout << name << endl;}
};int main()
{//棧內存對象Cat cat1;cat1.name = "Mike";cat1.color = "yellow";cat1.age = 8;cat1.weight = 35.7;/*if(1){ //錯誤,創建的棧內存對象只能存活在最近的兩個{}內,if一結束就不存在Cat cat1;}*/cout << cat1.name <<endl;cout << cat1.color <<endl;cout << cat1.age <<endl;cout << cat1.weight <<endl;cout << "#################" <<endl;cat1.run();cat1.meow(8);cat1.name1();cout << "#################" <<endl;//堆內存對象Cat *cat2 = new Cat;cat2->name = "Join";cat2->color = "red";cat2->age = 7;cat2->weight = 44.2;cout << cat2->name <<endl;cout << cat2->color <<endl;cout << cat2->age <<endl;cout << cat2->weight <<endl;return 0;
}
?
?二.封裝
概念:上面的代碼與結構體非常相似,因為一個完全開放的類就是結構體。實際上類需要封裝,封裝需要先把類中的一些屬性和細節隱藏,根據實際的需求對外部開放調用的讀寫等接口。
外部讀取數據的接口稱為getter,外部寫入數據的結構稱為setter。
核心思想:隱藏內部實現細節,通過公共接口控制訪問
封裝優勢:
????????(1)數據保護(防止非法修改)
????????(2)實現與接口分離
????????(3)代碼可維護性增強
#include <iostream>using namespace std;class Cat{
private: // 私有成員(封裝的核心)string name;string color;int age;double weight;
public: // 公共接口string get_name(){ //得到(返回)名字return name;}void set_name(string a){ //設置名字name = a;}string get_color(){ //得到(返回)顏色return color;}void set_color(string a){ //設置顏色color = a;}//嘗試自己寫一下其他操作
};int main()
{//棧內存對象Cat cat1;cat1.set_name("Mike"); //設置名字cout << cat1.get_name() << endl;cat1.set_color("yellow"); //設置顏色cout << cat1.get_color() << endl;cout << "#################" <<endl;//堆內存對象Cat *cat2 = new Cat;cat2->set_name("Lisa"); //設置名字cout << cat2->get_name() << endl;cat2->set_color("red"); //設置顏色cout << cat2->get_color() << endl;delete cat2;return 0;
}
?三.構造函數
構造函數(constructor)是一種特殊的成員函數:
????????(1)用于創建對象,創建對象必須調用構造函數
????????(2)如果一個類中程序員不寫構造函數,編譯器會自動添加一個無參的構造函數
????????(3)不寫返回值類型,返回一個創建的對象
????????(4)函數名稱必須是類名
構造函數的最主要功能是在創建對象時,完成對象數據的初始化。
可以和函數重載聯合使用。
1.初始化構造函數:
#include <iostream>using namespace std;class Cat{
private:string name;string color;int age;double weight;
public:Cat(){ name = "mike";color = "red";age = 4;weight = 33.5;}//函數重載Cat(string a,string b,int c,double d){name = a;color = b;age = c;weight = d;}void printf(){cout << name << endl;cout << color << endl;cout << age << endl;cout << weight << endl;}
};int main()
{//棧內存對象Cat cat1; //不使用參數默認調用無參的構造函數cout << "#################" <<endl;cat1.printf();cout << "#################" <<endl;Cat cat2("Join","blue",8,66.2); //調用有參的構造函數cat2.printf();cout << "#################" <<endl;//堆內存對象Cat *cat3 = new Cat("Lisa","red",1,12.5); //調用有參的構造函數cat3->printf();delete cat3;return 0;
}
2.構造初始化列表:
#include <iostream>using namespace std;class Cat{
private:string name;string color;int age;double weight;
public:Cat(){name = "mike";color = "red";age = 4;weight = 33.5;}//函數重載,構造初始化列表Cat(string a,string b,int c,double d):name(a),color(b),age(c),weight(d){/*name = a;color = b;age = c;weight = d;*/}void printf(){cout << name << endl;cout << color << endl;cout << age << endl;cout << weight << endl;}
};int main()
{//棧內存對象Cat cat1; //不使用參數默認調用無參的構造函數cout << "#################" <<endl;cat1.printf();cout << "#################" <<endl;Cat cat2("Join","blue",8,66.2); //調用有參的構造函數cat2.printf();cout << "#################" <<endl;//堆內存對象Cat *cat3 = new Cat("Lisa","red",1,12.5); //調用有參的構造函數cat3->printf();delete cat3;return 0;
}
3.調用方式:
(1)顯式調用
明確指定使用構造函數,下面代碼中cat1~3都是顯式調用。
(2)隱式調用
沒有明確指定使用構造函數,當隱式調用是一個字符串類型(Cat cat6 = "Lisa";),或報錯,編譯器沒辦法優化復雜這樣復雜的情況。下面代碼中cat4和cat5都是隱式調用。?
#include <iostream>using namespace std;class Cat{
private:int age;double weight;string name;string color;
public:Cat(){age = 4;weight = 33.5;name = "mike";color = "red";}//函數重載,構造初始化列表Cat(int a,double b = 25.3,string c = "mike", string d = "red"):age(a),weight(b),name(c),color(d){cout << a << endl;}void printf(){cout << "年齡:" << age << endl;cout << "體重:" << weight <<endl;cout << "名字:" << name << endl;cout << "顏色:" << color << endl;}
};int main()
{//棧內存對象Cat cat1; //不使用參數默認調用無參的構造函數cout << "#################" <<endl;cat1.printf();cout << "#################" <<endl;Cat cat2(8); //調用有參的構造函數cat2.printf();cout << "#################" <<endl;//堆內存對象Cat *cat3 = new Cat(1); //調用有參的構造函數cat3->printf();delete cat3;cout << "#################" <<endl;Cat cat4 = 8; //編譯器優化,可以把8作為參數傳入(隱式調用)cat4.printf();cout << "#################" <<endl;Cat cat5 = {4,44.3,"Lisa","red"}; //隱式調用cat5.printf();//Cat cat6 = "Lisa"; //當第一個參數是字符串類型,就不會優化,對于編譯器而言太復雜return 0;
}
補:可以使用explicit屏蔽隱式調用
隱式調用一般用不到,也不好用,使用不安全,容易出現錯誤
#include <iostream>using namespace std;class Cat{
private:int age;double weight;string name;string color;
public:explicit Cat(){age = 4;weight = 33.5;name = "mike";color = "red";}//函數重載,構造初始化列表explicit Cat(int a,double b = 25.3,string c = "mike", string d = "red"):age(a),weight(b),name(c),color(d){cout << a << endl;}void printf(){cout << "年齡:" << age << endl;cout << "體重:" << weight <<endl;cout << "名字:" << name << endl;cout << "顏色:" << color << endl;}
};int main()
{//棧內存對象Cat cat1; //不使用參數默認調用無參的構造函數cout << "#################" <<endl;cat1.printf();cout << "#################" <<endl;Cat cat2(6); //調用有參的構造函數cat2.printf();cout << "#################" <<endl;//堆內存對象Cat *cat3 = new Cat(4); //調用有參的構造函數cat3->printf();delete cat3;cout << "#################" <<endl;/* //隱式調用失效Cat cat4 = 8; cat4.printf();cout << "#################" <<endl;Cat cat5 = {4,44.3,"Lisa","red"}; //隱式調用cat5.printf();*/return 0;
}
4.拷貝構造函數
拷貝就是復制,創建一個和原對象一樣的新對象,被拷貝的對象和拷貝到對象的存儲地址是不一樣的,不是引用。
在每個類創建的時候,如果程序員不寫,都自己會帶一個默認的拷貝構造函數(淺拷貝函數)。
//拷貝構造函數(不寫也可以,默認存在)Cat(const Cat &a){age = a.age;name = a.name;}Cat cat1(); //創建第一對象 Cat cat2(cat1); //拷貝一個對象
1.淺拷貝
如果成員變量出現指針類型,默認的拷貝構造函數會直接拷貝指針變量,多個變量保存在同一個地址,這些對象的成員變量都指向同一塊內存,這樣的成員變量不符合面向對象的要求,會導致成員變量隨著這塊空間的內容的變化而變化,還記得我們封裝的時候的核心思想是什么?隱藏內部實現細節,成員變量為什么要用private修飾?就是為了防止外部改變,但是拷貝存在指針類型的對象就會出現成員變量隨著外部變化而變化的情況。
重寫一下代碼(寫簡單點):
#include <iostream>
#include <string.h>
using namespace std;class Cat{
private:int age;char *name;
public://默認的拷貝構造函數(不寫也可以)//構造初始化列表explicit Cat(int a,char *b):age(a),name(b){cout << a << endl;}void printf(){cout << "年齡:" << age << endl;cout << "名字:" << name << endl;}
};int main()
{char name[5] = "Lisa";Cat cat1(6,name); //調用有參的構造函數cat1.printf();cout << "#################" <<endl;Cat cat2(cat1); //拷貝構造函數cat2.printf();cout << "#################" <<endl;strcpy(name,"Join"); //此時我們改變name內的值cat2.printf(); //因為構造類我們用的是指針,所以里面的值發生了改變return 0;
}
2.深拷貝
出現上述情況,我們需要修改拷貝構造函數和初始化構造函數,在初始化構造函數的時候,開辟空間,再把內容復制到新開的空間里,這樣就避免了成員變量指向外部空間。在C++中使用new開辟空間。
開辟堆空間:
char *a = new char[10];
優化代碼:
#include <iostream>
#include <string.h>
using namespace std;class Cat{
private:int age;char *name;
public:Cat(const Cat &d){age = d.age;name = new char[5];strcpy(name,d.name);}explicit Cat(){age = 4;name = new char[5]; //開辟空間strcpy(name,"mike"); //復制內容到空間}//函數重載,構造初始化列表explicit Cat(int a,char *b){age = a;name = new char[5]; //開辟空間strcpy(name,b); //復制內容到空間cout << b << endl;}void printf(){cout << "年齡:" << age << endl;cout << "名字:" << name << endl;}
};int main()
{char name[5] = "Lisa";Cat cat1(6,name); //不使用參數默認調用無參的構造函數cat1.printf();cout << "################" <<endl;Cat cat2(cat1);cat2.printf();cout << "################" <<endl;strcpy(name,"Join");cat2.printf();return 0;
}
四.析構函數
學習上面內容后,還有一個問題,我們使用New開辟的堆空間存在一個問題,這個空間是由程序員開辟和釋放的,程序結束才會釋放。就像上面代碼,我們創造一個棧空間對象,但是內部的name是開的堆空間存放,當對象所在的{}結束,對象cat1就會釋放,當時name沒有釋放,會出現內存泄露。
1.什么是內存泄露?
內存泄露指程序中已動態分配的堆內存由于某種原因未釋放或無法釋放,造成系統內存浪費,導致程序運行速度減慢或崩潰。這類問題在長時間運行的應用中尤為嚴重。若果不解決,運行過程總會堆積這樣的垃圾空間。
2.析構函數
這個時候就可以使用析構函數來解決;析構函數(destructor)是與構造函數對立的函數,也是一種特殊的成員函數,如果程序員不手寫,編譯器會自動添加一個空的析構函數;析構函數是用來對對象資源的回收,關閉和釋放。下面就是析構函數初始版本(不寫的默認):
~Cat(){}
這也沒參數啊,怎么做到的?別管,問就是C++特性,感興趣可以去看源碼。現在我們在析構函數中加上delete name;專門去釋放存在堆區域的name變量,就解決了內存泄露的問題。
3.優化代碼:
#include <iostream>
#include <string.h>
using namespace std;class Cat{
private:int age;char *name;
public:explicit Cat(){age = 4;name = new char[5];strcpy(name,"mike");}//函數重載,構造初始化列表explicit Cat(int a,char *b){age = a;name = new char[5];strcpy(name,b);cout << b << endl;}//拷貝構造函數Cat(const Cat &a){age = a.age;name = a.name;}void printf(){cout << "年齡:" << age << endl;cout << "名字:" << name << endl;}~Cat(){ //析構函數,為防止內存泄露cout << "析構函數" << endl;delete name;}
};int main()
{char name[5] = "Lisa";Cat cat1(6,name); //不使用參數默認調用無參的構造函數cat1.printf();cout << "################" <<endl;Cat *cat2 = new Cat;cat2->printf();delete cat2;cout << "################" <<endl;cout << "結束" << endl;return 0;
}
4.析構函數和構造函數比較:
析構函數 | 構造函數 |
沒有參數 | 可以有參數,支持默認值和重載 |
函數名稱是 ~類名 | 函數名稱是 類名 |
對象銷毀時被調用 | 創建對象時調用 |
各種的資源的回收、關閉和釋放 | 數據初始化,各種資源的開辟 |
五.作用域限定符 ::
概念:
作用域限定符?::
(也稱為范圍解析運算符)在C++中,是一個關鍵運算符,用于明確指定標識符(變量、函數、類等)所屬的作用域。它的主要功能是消除命名沖突,并提供對特定作用域成員的精確訪問。
1.名字空間
名字空間(namespace)是C++用于解決重名問題設計的。在上一篇文章中提到過。
#include <iostream>//using namespace std;//std是C++源碼的名字空間
int a = 1;
// 自定義名字空間
namespace my {int a = 3;int b = 4;
}
using namespace my;int main()
{int a = 2;std::cout << a << std::endl; // 2// 匿名名字空間std::cout << ::a << std::endl; // 1std::cout << my::a << std::endl; // 3std::string s = "123";std::cout << b << std::endl; // 4return 0;
}
2.類內聲明,類外定義
類內的成員(尤指成員函數)可以聲明與定義分離,聲明要在類內,定義可以寫在類外,這個以后會用的比較頻繁。
#include <iostream>using namespace std;class Teacher
{
private:string name;
public:// 只聲明Teacher(string n);string get_name();void set_name(string n);
};
// 類外定義
Teacher::Teacher(string n)
{name = n;
}
string Teacher::get_name()
{return name;
}
void Teacher::set_name(string n)
{name = n;
}int main()
{Teacher t1("t1");cout << t1.get_name() << endl;t1.set_name("t2");cout << t1.get_name() << endl;return 0;
}
3.與static關鍵字配合
在 C++ 中,作用域限定符?::
?與?static
?關鍵字?配合使用主要涉及類的靜態成員管理,這是實現類級別數據共享的核心機制。(后面再講)