目錄
- 一、類型與對象的概念
-
- 1.1什麼是對象
- 1.2類型
- 二、如何在計算機中描述類型
-
- 2.1使用結構體(struct)對類型進行描述
- 2.2使用類(class)對類型進行描述
- 三、構造函數
-
- 3.1構造函數概述
- 3.2一個知識點
- 四、一個對象建立的過程
- 五、構造函數的應用
-
- 5.1 要點
- 5.2 初始化清單
- 六、頭檔案和實作檔案的分離
- 七、this指針
-
- 7.1 this指針概述
- 7.2 this的使用
- 八、析構函數
-
- 8.1概述
- 8.2析構函數程式舉例:
一、類型與對象的概念
1.1什麼是對象
萬物皆對象
程式就是由一組對象組成的,對象和對象通過發消息,通知
做什麼。如對象——電池通知對象——電動機做動作
對象是由其他類型的對象組成的存儲區
每一個對象都屬于一個特定的類型
同類型的對象,都能接收相同的消息。
1.2類型
人類認識世界的過程就是面向對象的(貓、房子等事物)
人類認識世界的過程就是由具體到抽象(比如,小孩剛出生,見過真狼
之後才會對狼有一種概念;我們對煉鋼工廠真實考察過之後,才
能抽象出煉鋼的程式)
類型具有共同特征的對象,這些對象屬于同一共同類别;
類型是類别的抽象;
對象是類型建立的具體變量;
類型刻畫就是抽取對象的共同特征與行為
好,看一個例子你就明白了類型、對象、抽象這幾個概念了!
#include <iostream>
using namespace std;
struct Student{
/*特征*/
string name;
string sno;
int score;
/*行為*/
void learn(string par){
cout << "learn" << par << endl;//學生學什麼
}
void eat(string par){//學生吃什麼
cout << "eat" << par << endl;
}
};
int main(){
Student one;
one.name = "Zhangsan";
one.sno = "A07160289";
one.score = 188;
one.learn("English");
one.eat("米飯");
Student two;
two.name = "Lisi";
two.sno = "A07160300";
}
來了一個需求:要你寫出學生的特征和行為資訊。
我們根據對學生的實際考察,抽取學生的共同特征與行為(學生都有名
字、學号、分數的特征以及學什麼、吃什麼的行為),我們據此,
抽象出一個學生類型,即定義了一個struct Student{}結構體類型,
而我們用結構體定義的兩個學生(變量):Zhangsan、Lisi就是對象。
二、如何在計算機中描述類型
2.1使用結構體(struct)對類型進行描述
如1.2中的程式例子所示。
再寫一個例子:
(設定一個時鐘,并給其設定一個初始時間)
/*下例的struct Mytime{}是類,mt是對象*/
#include <iostream>
#include <cstdio>
#include <iomanip>
using namespace std;
struct Mytime{
int hour;
int min;
int sec;
void show(){
cout << setfill('0') << setw(2) <<
hour << ":" << setw(2) << min << ":"
<< setw(2) << sec << '\r' << flush;
}
void dida(){
sleep(1);
if(60 == ++sec){
sec = 0;
if(60 == ++min){
min = 0;
if(24 == ++hour){
hour = 0;
}
}
}
}
void run(){
while(1){
show();
dida();
}
}
};
int main(){
Mytime mt;
mt.hour = 9;
mt.min = 28;
mt.sec = 35;
mt.run();
}
2.2使用類(class)對類型進行描述
類内成員預設是私有的private
private隻能在類内通路,不讓外界通路
所謂類内是指在class Student{};大括号之内
public可以在類内和類外通路
private、public權限修飾範圍:
從一個權限開始 到 下一個權限的開始
在c++中,如果類的成員隻是變量而無函數,且變量公開,則我們一般使用struct,
否則,我們一般使用class。二者沒啥差別)
程式舉例:
(把上例的struct改為class,并添加private、public權限)
#include <iostream>
#include <cstdio>
#include <iomanip>
using namespace std;
class Mytime{
public:
int hour;
int min;
int sec;
private://設定show()、dida()為類内私有
void show(){
cout << setfill('0') << setw(2) <<
hour << ":" << setw(2) << min << ":"
<< setw(2) << sec << '\r' << flush;
}
void dida(){
sleep(1);
if(60 == ++sec){
sec = 0;
if(60 == ++min){
min = 0;
if(24 == ++hour){
hour = 0;
}
}
}
}
public:
void run(){
while(1){
show();
dida();
}
}
};
int main(){
Mytime mt;
mt.hour = 9;
mt.min = 28;
mt.sec = 35;
mt.run();
}
程式再改進:
(把時間的初始化挪到類内,并将hour、min、sec私有化)
#include <iostream>
#include <cstdio>
#include <iomanip>
using namespace std;
class Mytime{
private:
int hour;
int min;
int sec;
private://設定show()、dida()為類内私有
void show(){
cout << setfill('0') << setw(2) <<
hour << ":" << setw(2) << min << ":"
<< setw(2) << sec << '\r' << flush;
}
void dida(){
sleep(1);
if(60 == ++sec){
sec = 0;
if(60 == ++min){
min = 0;
if(24 == ++hour){
hour = 0;
}
}
}
}
public:
void run(){
while(1){
show();
dida();
}
}
void initMytime(){
mt.hour = 9;
mt.min = 28;
mt.sec = 35;
}
};
int main(){
Mytime mt;
mt.initMytime();
//mt.hour = 9;
//mt.min = 28;
//mt.sec = 35;
mt.run();
}
我們改進的這個程式,通過調用類型中的public函數mt.initMytime()
實作了對類内私有變量hour、min、sec的初始化。
但是,如果我們不小心重複初始化了呢?比如不小心重複調用了
mt.initMytime(),是以對于類内成員的初始化我們一般不這樣寫,
我們使用構造函數。見下節。
三、構造函數
3.1構造函數概述
概念:建立一個對象時,編譯器自動幫你調用一次,并且僅僅調用一次
建立 構造函數的條件:
和類名相同
沒有傳回值類型
目的是:初始化對象
如2.2第二個例子中的:
void initMytime(){
mt.hour = 9;
mt.min = 28;
mt.sec = 35;
}
改為:
class Mytime{
省略代碼......
Mytime(){
mt.hour = 9;
mt.min = 28;
mt.sec = 35;
}
};
int main(){
Mytime mt;
//mt.initMytime();
//mt.hour = 9;
//mt.min = 28;
//mt.sec = 35;
mt.run();
}
這就行了。注意,你不用再調用Mytime()進行初始了化。編譯時編譯器自動幫
你執行Mytime()進行初始化。并且僅僅調用一次。
3.2一個知識點
int main(){
Mytime mt;//在棧中申請mt
Mytime mt2();//這不是申請類mt,這是函數的聲明,是以不能随意加括号
Mytime *pt = new Mytime;//在堆中申請一塊記憶體
Mytime *pt2 = new Mytime();//在堆中申請一塊記憶體
mt.run();
}
四、一個對象建立的過程
第一步:根據類型的大小進行配置設定記憶體;
第二步:進行處理成員變量
①如果成員是基本類型(int、double等)的資料,編譯器就什麼都不做
②如果成員是類類型的成員,則建構這個成員。
也就是說,第一步不是已經配置設定好了一塊記憶體了嗎,現在在這片
記憶體裡面開辟出一片空間給這個類類型的成員。
第三步:調用這個類型的構造函數。
概念有點抽象,我們來寫個程式:
#include <iostream>
using namespace std;
class Data{
private:
int year;
int month;
int day;
public:
Data(){
cout << "Data's_構造函數" << endl;
year = 2020;
month = 8;
day = 20;
}
void show(){
cout << year << "-" << month << "-" << day
<< endl;
}
};
class Person{
double salary;
Data data;
public:
Person(){
cout << "Person's_構造函數" << endl;
}
void show(){
data.show();
cout << salary << endl;//salary沒有初始化,會輸出一個垃圾數
}
};
int main(){
/*
Data data;
data.show();
要點一:構造函數Data()對year、month、day進行了初始化。
如果你不對year、month、day進行初始化,那麼輸出的year、month、day會
是垃圾數*/
/*要點二:類Person中的一個成員是類Data data*/
Person p;
p.show();
/*運作結果:
Data's_構造函數
Person's_構造函數
2020-8-20
2.07368e-317
*/
}
五、構造函數的應用
5.1 要點
要點一:構造函數可以設計參數,可以有多個構造函數,能形成重載關系。
一旦提供構造函數,則系統提供的預設構造函數就會自動消失。
要點二:可以利用參數的預設值簡化構造函數的個數;
程式示例:
#include <iostream>
using namespace std;
class A({
public:
A(){
}
A(int x){cout << "A(int)" << endl;}
A(int x, double y){
}
};
int main(){
A a(111);//一:這個111就是傳參,傳遞給構造函數;
//二:A a(111);編譯器不會了解成函數聲明。函數聲明裡沒有帶111數值的是吧。
A b;
A c(1,1.5);
/*以上三個類的定義都能夠編譯通過,形成重載機會*/
/*看,上面例子咱們定義了三個構造函數,其實咱們之前學過函數參數的預設值吧,
可以利用函數參數預設值,進而把構造函數的個數縮減為一個。比如上面的
這個例子就可以隻要構造函數A(int x = 0, double y = 0.0){}*/
A *pa = new A(100,20.5);//100,20.5給構造函數傳參
}
5.2 初始化清單
如果類中有const類型的成員 或者 引用類型的成員,則使用初始化參數清單是一個
不二的選擇。
初始化參數清單的位置是在構造函數參數清單(即構造函數的()裡面的東東)之後,
實作體(即構造函數的{}裡面的東東)之前。
初始化參數清單的作用是:
保證參數初始化在構造函數執行之前被調用
程式舉例:
#include <iostream>
using namespace std;
int g_var = 188;
class A({
const int x;//const修飾,則必須初始化參數清單,否則編譯失敗
int y;
int& z;//引用類型變量,則必須初始化參數清單,否則編譯失敗
public:
A():x(100),y(199),z(g_var){
/*初始化參數清單隻能在構造函數的()和{}之間;const修飾的和引用
類型的成員都必須初始化參數清單。其他類型的成員無此限制*/
//這裡的:x(100)就是給x賦初值100。當然你要是給y初始化也沒啥問題
//對z初始化,z為g_var别名
}
void show(){
cout << x << ":" << y << endl;//x=100;y=199
}
};
int main(){
A a;
a.show();
}
六、頭檔案和實作檔案的分離
在實際開發中頭檔案和實作檔案要分離
背景知識:首先你要直到什麼是聲明、定義、extern,去看這篇文章:
https://blog.csdn.net/weixin_45519751/article/details/108142857
程式舉例:
mytime.h檔案
#ifndef MYTIME_H
#define MYTIME_H
/*在c++的頭檔案中隻能進行變量的聲明、函數的聲明,類的定義*/
extern int g_var;//變量聲明而非定義。必須要有extern
void show();//函數聲明
class Mytime{//類的定義
private:
int hour;
int min;
int sec;
public:
Mytime(int hour=0,int min=0,int sec=0);
void show();
void dida();
void run();
};
#endif
mytime.cpp檔案
#include "mytime.h"
#include <iostream>
#include <iomanip>
#include <unistd.h>
using namespace std;
void show(){
cout << "g_var" << g_var << endl;
}
/*來自于類中的函數,前要加上 類名:: */
Mytime::Mytime(int hour,int min,int sec):hour(hour),
min(min),sec(sec){
/*解釋一下hour(hour)
第一個hour是類的成員變量,第二個hour是構造函數的參數,
hour(hour)即把0指派給成員變量hour
咱們這裡隻不過讓類的成員變量hour與構造函數的參數hour重名了而已
*/
}
void Mytime::show(){
cout << setfill('0') << setw(2) << hour << ":" <<
setw(2) << ":" << min << ":" << setw(2) << sec << '\r' << flush;
}
void Mytime::dida(){
sleep(1);
if(60 == ++sec){
sec = 0;
if(60 == ++min){
min = 0;
if(24 == ++hour){
hour = 0;
}
}
}
}
void Mytime::run(){
::show();//這裡調的全局函數show()
while(1){
show();//這裡調的是類中的成員函數
dida();//這裡調的是類中的成員函數
}
}
testmytime.cpp檔案
#include "mytime.h"
int g_var = 100;//這裡是定義
int main(){
Mytime mt;
mt.run();
}
運作結果:
lioker:Desktop$ g++ ./*.cpp
lioker:Desktop$ ./a.out
g_var100
00:0:01
七、this指針
7.1 this指針概述
在構造函數中this代表正在被建構的對象的位址
在成員函數中,this指向調用這個函數的對象的位址。
程式舉例:
#include <iostream>
using namespace std;
class Mytime{
int hour;
int min;
int sec;
public:
Mytime(int hour=0,int min=0,int sec=0){
/*對hour賦上構造函數參數值0,可以用初始化參數清單
但我們這裡用this指針的方法實作*/
this->hour = hour;
this->min = min;
this->sec = sec;
}
void show(){
cout << "show()" << this << endl;//列印show()的this位址
cout << this->hour << ":" << this->min << ":" <<
this->sec << endl;
}
};
int main(){
Mytime mt;
cout << "main mt=" << &mt << endl;
mt.show();
}
運作結果:
lioker:Desktop$ g++ this.cpp
lioker:Desktop$ ./a.out
main mt=0x7ffe788792e0
show()0x7ffe788792e0
0:0:0
總結:this就是類的一個對象的位址;當this指向mt這個對
象時是一個位址,指向另一個對象就是另一個位址了
7.2 this的使用
①用在函數的參數上
②用于成員函數的傳回值
程式舉例:
#include <iostream>
using namespace std;
/*聲明類型Data 和 函數show()*/
class Data;
void show(Data *data);
/*寫一個日期類型*/
class Data{
public:
int year;
int month;
int day;
public:
Data(int year=0,int month=0,int day=0){
this->year = year;
this->month = month;
this->day = day;
}
void showSelf(){
show(this);//this作為參數。
}
/*寫一個成員函數,作用:調用一次day就加一*/
Data* addOneDay(){
day++;
return this;/*這裡的this就是對象data的位址,你把它
傳回,再->addOneDay(),就相當于調用
了兩次addOneDay()函數,即++day兩次.
即day=2*/
}
/*寫法二:
Data addOneDay(){
day++;
return *this;/*這裡的*this就相當于你把data複制了一份。
你把它這個複制的部分傳回了,
再addOneDay()(注:main函數中語句改為
data.addOneDay().addOneDay();),其實是對複制的data
中的day++而非data本身++。
也就是day隻加了一次,即day=1*/
}
*/
/*寫法三:
Data& addOneDay(){
day++;
return *this;/*這裡的*this就是對象data的值,(Data&引用
即傳回的還是data本身)。
再.addOneDay()(注:main函數中語句改為
data.addOneDay().addOneDay();),其實是對data中的
day++。也就是day加了2次,即day=2*/
}
*/
};
/*寫一個全局函數show()顯示一個日期*/
void show(Data *data){
cout << data->year << "-" << data->month <<
"-" << data->day << endl;
}
int main(){
Data data;
data.showSelf();
data.addOneDay()->addOneDay();//則day變為2(++2次)
data.showSelf();
}
八、析構函數
8.1概述
析構函數和類型名相同,但函數名前有一個~
在對象銷毀之前,系統(編譯器)自動調用一次析構函數
析構函數是無參的;
一個類型隻有一個析構函數
作用是:即在對象銷毀之前,做一些清理工作
8.2析構函數程式舉例:
(利用析構函數釋放掉在構造函數中建立的堆記憶體)
#include <iostream>
using namespace std;
class A{
int *parr;
public:
A(int size=0){
parr = new int[size];//配置設定堆記憶體;可以在析構函數裡釋放掉
cout << "A()" << endl;
}
~A(){
detele[] parr;//釋放掉parr堆記憶體
parr=NULL;
cout << "~A()" << endl;
}
};
int main(){
/*A *parr = new A[5];
delete[] parr;/*
運作結果:
5個A(),5個~A()
*/
A a(10);
}