天天看點

【C++】day03 - 【類型與對象的概念】【類型】【構造函數】【一個對象建立的過程】【構造函數的應用】【頭檔案和實作檔案的分離】【this指針】【析構函數】一、類型與對象的概念二、如何在計算機中描述類型三、構造函數四、一個對象建立的過程五、構造函數的應用六、頭檔案和實作檔案的分離七、this指針八、析構函數

目錄

  • 一、類型與對象的概念
    • 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);
}
           

繼續閱讀