類與對(duì)象
- 類與對(duì)象的區(qū)別
類是對(duì)某一類事物的描述,是抽象的漓滔;
而對(duì)象是一個(gè)實(shí)實(shí)在在的個(gè)體,是類的一個(gè)實(shí)例乖篷。
比如:“人”是一個(gè)類响驴,而“教師”則是“人”的一個(gè)實(shí)例。對(duì)象是函數(shù)撕蔼、變量的集合體豁鲤;
而類是一組函數(shù)和變量的集合體,
即類是一組具有相同屬性的對(duì)象集合體罕邀。UML的類圖和對(duì)象圖之間的區(qū)別是:
類圖中類名首字母大寫畅形,對(duì)象圖中的對(duì)象名首字母小寫。
對(duì)象名下有一條下劃線诉探,而類名沒有日熬。類的數(shù)據(jù)值是共享的,一個(gè)實(shí)例能訪問它所屬類的類數(shù)據(jù)值肾胯;
而實(shí)例數(shù)據(jù)屬于單個(gè)對(duì)象竖席,除共享了所在類中的數(shù)據(jù)外,
不同對(duì)象還會(huì)有不同的數(shù)據(jù)值敬肚。先有類毕荐,才有類的實(shí)例——對(duì)象。
應(yīng)用在:當(dāng)你在創(chuàng)建某個(gè)類的實(shí)例(對(duì)象)之前艳馒,這個(gè)類必須被定義憎亚。實(shí)例方法和類方法的區(qū)別在于:
實(shí)例方法屬于單個(gè)對(duì)象,類方法屬于類弄慰。
-
定義對(duì)象
屬于不同類的對(duì)象在不同的時(shí)刻第美、不同的地方分別被建立。全局對(duì)象在主函數(shù)開始執(zhí)行前首先被建立陆爽,局部對(duì)象在程序執(zhí)行遇到它們的對(duì)象定義時(shí)才被建立什往。與定義變量類似,定義對(duì)象時(shí)慌闭,c會(huì)為分配空間别威。例如躯舔,下面的代碼定義了兩個(gè)類,創(chuàng)建了類的全局對(duì)象省古、局部對(duì)象粥庄、靜態(tài)對(duì)象和堆對(duì)象:
class Desk //Desk類
{
public:
int weight;
int height;
int width;
int length;
};
class Stool //另一個(gè)類: Stool類
{
public:
int weight;
int height;
int width;
int length;
};
Desk da; //全局對(duì)象
Stool sa;
void fu()
{
static Stool ss; //靜態(tài)局部對(duì)象
Desk da; //局部對(duì)象
//...
}
void main()
{
Stool bs; //局部對(duì)象
Desk *pd=new Desk; //堆對(duì)象
Desk nd[50]; //局部對(duì)象數(shù)組
//...
delete pd; //釋放對(duì)象
}
-
對(duì)象的初始化
當(dāng)對(duì)象在創(chuàng)建時(shí)獲得了一個(gè)特定的值衫樊,我們說這個(gè)對(duì)象被初始化飒赃。初始化不是賦值,初始化的含義是創(chuàng)建變量賦予其一個(gè)初始值科侈,而賦值的含義是把當(dāng)前值擦除载佳,而以一個(gè)新值來替代。對(duì)象初始化可以分為默認(rèn)初始化臀栈、直接初始化蔫慧、拷貝初始化以及值初始化。- 默認(rèn)初始化:如果定義變量時(shí)沒有指定初值权薯,則變量被默認(rèn)初始化姑躲。默認(rèn)值到底是什么由變量類型決定,同時(shí)定義變量的位置也會(huì)對(duì)此有影響盟蚣。如果是內(nèi)置類型的變量未被顯示初始化黍析,它的值由定義的位置決定,定義在任何函數(shù)體之外的變量被初始化為0屎开。但是有一種例外阐枣,定義在函數(shù)體內(nèi)部的內(nèi)置類型變量將不被初始化。一個(gè)未被初始化的內(nèi)置類型變量時(shí)未定義的奄抽,如果試圖拷貝或以其他形式訪問此變量將引發(fā)錯(cuò)誤蔼两。
int i1;//默認(rèn)初始化,在函數(shù)體之外(初始化為0)
int f(void)
{
int i2;//不被初始化逞度,如果使用此對(duì)象則報(bào)錯(cuò)
}
每個(gè)類各自決定了其初始化對(duì)象的方式额划。絕大數(shù)類支持無須顯示的初始化而定義對(duì)象。默認(rèn)調(diào)用該類的默認(rèn)構(gòu)造方法档泽。
string empty;//empty非顯示的初始化為一個(gè)空串俊戳,調(diào)用的是默認(rèn)構(gòu)造函數(shù)
拷貝初始化:使用等號(hào)(=)初始化一個(gè)變量,實(shí)際上執(zhí)行的是拷貝初始化馆匿,編譯器把等號(hào)右側(cè)的初始值拷貝到新創(chuàng)建的對(duì)象中去抑胎,拷貝初始化通常使用拷貝構(gòu)造函數(shù)來完成√鹑郏拷貝初始化不僅在我們使用=定義變量時(shí)會(huì)發(fā)生,在下列情況也會(huì)發(fā)生
(1)將一個(gè)對(duì)象作為實(shí)參傳遞給一個(gè)非引用類型的形參突倍。
(2)從一個(gè)返回類型為非引用類型的函數(shù)返回一個(gè)對(duì)象腔稀。直接初始化:當(dāng)使用直接初始化時(shí)盆昙,我們實(shí)際上是要求編譯器使用普通的函數(shù)來選擇與我們提供的參數(shù)最匹配的構(gòu)造函數(shù)。
string str1(10,'9');//直接初始化
string str2(str1);//直接初始化
string str3 = str1;//拷貝初始化
- 值初始化:我們只提供vector對(duì)象容納的元素?cái)?shù)量而去忽略元素的初始值焊虏,此時(shí)庫會(huì)創(chuàng)建一個(gè)值初始化的元素初值淡喜,并把它賦予容器中的所有元素。這個(gè)初值由vector對(duì)象中元素的類型決定诵闭。如果vector對(duì)象的元素是內(nèi)置類型炼团,比如int,則元素初始值自動(dòng)設(shè)置為0.如果元素是某種類類型疏尿,比如string瘟芝,則元素由類默認(rèn)初始化。
vector<int> v1(10);//10個(gè)元素褥琐,每個(gè)元素的初始化為0
vector<string> v2(10);//10個(gè)元素锌俱,每個(gè)元素都為空
使用new動(dòng)態(tài)分配和初始化對(duì)象
在自由空間分配的內(nèi)存是無名的,因此new無法為其分配的對(duì)象命名敌呈,而是返回一個(gè)指向該對(duì)象的指針:
int *pi = new int;//pi指向一個(gè)動(dòng)態(tài)分配的贸宏,未初始化的無名對(duì)象
默認(rèn)情況下,動(dòng)態(tài)分配的對(duì)象是默認(rèn)初始化的磕洪,這意味著內(nèi)置類型或組合類型的對(duì)象的值將是未定義的吭练,而類類型對(duì)象將使用默認(rèn)構(gòu)造函數(shù)進(jìn)行初始化:
string *ps = new string;//初始化為空string
int *pi = new int;//pi指向一個(gè)未初始化的int
我們可以使用直接初始化方式來初始化一個(gè)動(dòng)態(tài)分配的對(duì)象:
int *pi = new int(1024);//pi指向的對(duì)象的值為1024
string *ps = new string(10,'9');//*ps為"9999999999"
也可以對(duì)動(dòng)態(tài)分配的對(duì)象進(jìn)行值初始化,只需要在類型名后跟一對(duì)空括號(hào)即可:
string *ps1 = new string;//默認(rèn)初始化為空string
string *ps2 = new string();//值初始化為空string
int *pi1 = new int;//默認(rèn)初始化
int *pi2 = new int();//值初始化為0
構(gòu)造函數(shù)&析構(gòu)函數(shù)
- 構(gòu)造函數(shù)
類的構(gòu)造函數(shù)是類的一種特殊的成員函數(shù)析显,它會(huì)在每次創(chuàng)建類的新對(duì)象時(shí)執(zhí)行鲫咽。
構(gòu)造函數(shù)的名稱與類的名稱是完全相同的,并且不會(huì)返回任何類型叫榕,也不會(huì)返回 void浑侥。構(gòu)造函數(shù)可用于為某些成員變量設(shè)置初始值。
下面的實(shí)例有助于更好地理解構(gòu)造函數(shù)的概念:
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 這是構(gòu)造函數(shù)
private:
double length;
};
// 成員函數(shù)定義晰绎,包括構(gòu)造函數(shù)
Line::Line(void)
{
cout << "Object is being created" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函數(shù)
int main( )
{
Line line;
// 設(shè)置長(zhǎng)度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
當(dāng)上面的代碼被編譯和執(zhí)行時(shí)寓落,它會(huì)產(chǎn)生下列結(jié)果:
Object is being created
Length of line : 6
注意:
- 構(gòu)造函數(shù)定義既可以放在類的內(nèi)部定義,也可放在類的外部定義荞下;放在外部定義的構(gòu)造函數(shù)伶选,其函數(shù)名前要加上"類名::"。因?yàn)樵陬惗x外部尖昏,有各種函數(shù)的定義仰税,采用此方法是為了區(qū)別成員和非成員函數(shù)。
- 構(gòu)造函數(shù)有一特殊之處就是沒有返回類型抽诉,函數(shù)體中也不允許返回值陨簇,但可以有無值返回語句"return"。因?yàn)闃?gòu)造函數(shù)專門用于創(chuàng)建對(duì)象和其初始化迹淌,所以它不能被隨意調(diào)用河绽。
- 一個(gè)類定義中己单,類的數(shù)據(jù)成員可能為另一個(gè)類的對(duì)象。如果一個(gè)類的對(duì)象是另一個(gè)類的數(shù)據(jù)成員耙饰,則在那個(gè)類的對(duì)象創(chuàng)建所調(diào)用的構(gòu)造函數(shù)中纹笼,則該成員對(duì)象自動(dòng)調(diào)用其構(gòu)造函數(shù)。
- 默認(rèn)構(gòu)造函數(shù)
當(dāng)用戶沒有顯式的去定義構(gòu)造函數(shù)時(shí), 編譯器會(huì)為類生成一個(gè)默認(rèn)的構(gòu)造函數(shù), 稱為 "默認(rèn)構(gòu)造函數(shù)", 一旦你為你的類定義了構(gòu)造函數(shù)苟跪,哪怕只是一個(gè)廷痘,那么編譯器將不再生成默認(rèn)的構(gòu)造函數(shù),默認(rèn)構(gòu)造函數(shù)不能完成對(duì)象數(shù)據(jù)成員的初始化, 只能給對(duì)象創(chuàng)建一標(biāo)識(shí)符, 并為對(duì)象中的數(shù)據(jù)成員開辟一定的內(nèi)存空間件已。
- 當(dāng)你使用靜態(tài)分配的數(shù)組笋额,而數(shù)組元素類型是某個(gè)類的對(duì)象時(shí),就要調(diào)用默認(rèn)的構(gòu)造函數(shù)拨齐,比如下面的代碼鳞陨。
Object buffer[10]; // call default constructor
- 當(dāng)你使用動(dòng)態(tài)分配的數(shù)組,而數(shù)組元素類型是某個(gè)類的對(duì)象時(shí)瞻惋,就要調(diào)用默認(rèn)的構(gòu)造函數(shù)厦滤,比如下面的代碼,如果Object沒有默認(rèn)的構(gòu)造函數(shù)歼狼,是無法通過編譯的掏导,因?yàn)閚ew操作符要調(diào)用Object類的無參構(gòu)造函數(shù)類初始化每個(gè)數(shù)組元素。
Object* buffer = new Object[10];
- 當(dāng)你使用標(biāo)準(zhǔn)庫的容器時(shí)羽峰,如果容器內(nèi)的元素類型是某個(gè)類的對(duì)象時(shí)趟咆,那么這個(gè)類就需要默認(rèn)的構(gòu)造函數(shù),原因同上梅屉。
vector<Object> buffer;
- 一個(gè)類A以另外某個(gè)類B的對(duì)象為成員時(shí)值纱,如果A提供了無參構(gòu)造函數(shù),而B未提供坯汤,那么A則無法使用自己的無參構(gòu)造函數(shù)虐唠。下面的代碼將導(dǎo)致編譯錯(cuò)誤。
class B
{
B(int i){}
}; class A
{
A(){}
B b;
}; int main(void)
{
A a(); // error C2512: 'B' : no appropriate default constructor
available
getchar() ;
return 0 ;
}
再比如下面的代碼惰聂,類A定義了拷貝構(gòu)造函數(shù)疆偿,而沒有提供默認(rèn)的構(gòu)造函數(shù),B繼承自A搓幌,所以B在初始化時(shí)要調(diào)用A的構(gòu)造函數(shù)來初始化A杆故,而A沒有默認(rèn)的構(gòu)造函數(shù),故產(chǎn)生編譯錯(cuò)誤溉愁。
class A
{
A(const A&){}
}; class B : public A
{
}; int main(void)
{
B b; //error C2512:'B': no appropriate default constructor available
getchar() ;
return 0 ;
}
帶參構(gòu)造函數(shù)
默認(rèn)的構(gòu)造函數(shù)沒有任何參數(shù)处铛,但如果需要,構(gòu)造函數(shù)也可以帶有參數(shù)。這樣在創(chuàng)建對(duì)象時(shí)就會(huì)給對(duì)象賦初始值撤蟆,如下面的例子所示:
#include <iostream>
using namespace std;
class Point
{
public:
Point(int x = 0, int y = 0) //帶有默認(rèn)參數(shù)的構(gòu)造函數(shù)
{
cout<<"自定義的構(gòu)造函數(shù)被調(diào)用...\n";
xPos = x; //利用傳入的參數(shù)值對(duì)成員屬性進(jìn)行初始化
yPos = y;
}
void printPoint()
{
cout<<"xPos = " << xPos <<endl;
cout<<"yPos = " << yPos <<endl;
}
private:
int xPos;
int yPos;
};
int main()
{
Point M(10, 20); //創(chuàng)建對(duì)象M并初始化xPos,yPos為10和20
M.printPoint();
Point N(200); //創(chuàng)建對(duì)象N并初始化xPos為200, yPos使用參數(shù)y的默認(rèn)值0
N.printPoint();
Point P; //創(chuàng)建對(duì)象P使用構(gòu)造函數(shù)的默認(rèn)參數(shù)
P.printPoint();
return 0;
}
編譯運(yùn)行的結(jié)果:
自定義的構(gòu)造函數(shù)被調(diào)用...
xPos = 10
yPos = 20
自定義的構(gòu)造函數(shù)被調(diào)用...
xPos = 200
yPos = 0
自定義的構(gòu)造函數(shù)被調(diào)用...
xPos = 0
yPos = 0
代碼說明:
在這個(gè)示例中的構(gòu)造函數(shù) Point(int x = 0, int y = 0) 使用了參數(shù)列表并且對(duì)參數(shù)進(jìn)行了默認(rèn)參數(shù)設(shè)置為0篙贸。在 main 函數(shù)中共創(chuàng)建了三個(gè)對(duì)象 M, N, P。
M對(duì)象不使用默認(rèn)參數(shù)將M的坐標(biāo)屬性初始化10和20;
N對(duì)象使用一個(gè)默認(rèn)參數(shù)y, xPos屬性初始化為200;
P對(duì)象完全使用默認(rèn)參數(shù)將xPos和yPos初始化為0枫疆。
-
使用初始化列表來初始化
對(duì)象中的一些數(shù)據(jù)成員除了在構(gòu)造函數(shù)體中進(jìn)行初始化外還可以通過調(diào)用初始化表來進(jìn)行完成, 要使用初始化表來對(duì)數(shù)據(jù)成員進(jìn)行初始化時(shí)使用 : 號(hào)進(jìn)行調(diào)出, 示例如下:
Point(int x = 0, int y = 0):xPos(x), yPos(y) //使用初始化表
{
cout<<"調(diào)用初始化表對(duì)數(shù)據(jù)成員進(jìn)行初始化!\n";
}
在 Point 構(gòu)造函數(shù)頭的后面, 通過單個(gè)冒號(hào) : 引出的就是初始化表, 初始化的內(nèi)容為 Point 類中int型的 xPos 成員和 yPos成員, 其效果和 xPos = x; yPos = y; 是相同的。
初始化列表的成員初始化順序:
C++初始化類成員時(shí)敷鸦,是按照聲明的順序初始化的息楔,
而不是按照出現(xiàn)在初始化列表中的順序。
與在構(gòu)造函數(shù)體內(nèi)進(jìn)行初始化不同的是, 使用初始化表進(jìn)行初始化是在構(gòu)造函數(shù)被調(diào)用以前就完成的扒披。每個(gè)成員在初始化表中只能出現(xiàn)一次, 并且初始化的順序不是取決于數(shù)據(jù)成員在初始化表中出現(xiàn)的順序, 而是取決于在類中聲明的順序值依。
此外, 一些通過構(gòu)造函數(shù)無法進(jìn)行初始化的數(shù)據(jù)類型可以使用初始化表進(jìn)行初始化, 如: 常量成員和引用成員, 這部分內(nèi)容將在后面進(jìn)行詳細(xì)說明。使用初始化表對(duì)對(duì)象成員進(jìn)行初始化的完整示例:
#include <iostream>
using namespace std; 4
class Point
{
public:
Point(int x = 0, int y = 0):xPos(x), yPos(y)
{
cout<<"調(diào)用初始化表對(duì)數(shù)據(jù)成員進(jìn)行初始化!\n";
}
void printPoint()
{
cout<<"xPos = " << xPos <<endl;
cout<<"yPos = " << yPos <<endl;
}
private:
int xPos;
int yPos;
};
int main()
{
Point M(10, 20); //創(chuàng)建對(duì)象M并初始化xPos,yPos為10和20
M.printPoint();
return 0;
}
重載構(gòu)造函數(shù)
在一個(gè)類中可以定義多個(gè)構(gòu)造函數(shù)碟案,以便提供不同的初始化的方法愿险,供用戶選用。這些構(gòu)造函數(shù)具有相同的名字价说,而參數(shù)的個(gè)數(shù)或參數(shù)的類型不相同。這稱為構(gòu)造函數(shù)的重載。
例如:定義兩個(gè)構(gòu)造函數(shù)座菠,其中一個(gè)無參數(shù)姜钳,一個(gè)有參數(shù)。
#include <iostream>
using namespace std;
class Box
{
public : Box( ); //聲明一個(gè)無參的構(gòu)造函數(shù)
//聲明一個(gè)有參的構(gòu)造函數(shù)领迈,用參數(shù)的初始化表對(duì)數(shù)據(jù)成員初始化
Box(int h,int w,int len):height(h),width(w),length(len){ }
int volume( );
private :
int height;
int width;
int length;
};
Box::Box( ) //定義一個(gè)無參的構(gòu)造函數(shù)
{
height=10; width=10; length=10;
}
int Box::volume( ){
return (height*width*length);
}
int main( )
{
Box box1; //建立對(duì)象box1,不指定實(shí)參
cout<<"The volume of box1 is "<<box1.volume( )<<endl;
Box box2(15,30,25); //建立對(duì)象box2,指定3個(gè)實(shí)參
cout<<"The volume of box2 is "<<box2.volume( )<<endl;
return 0;
}
在本程序中定義了兩個(gè)重載的構(gòu)造函數(shù)彻磁,其實(shí)還可以定義其他重載構(gòu)造函數(shù),其原型聲明可以為:
Box::Box(int h); //有1個(gè)參數(shù)的構(gòu)造函數(shù)
Box::Box(int h,int w); //有兩個(gè)參數(shù)的構(gòu)造函數(shù)
在建立對(duì)象時(shí)分別給定1個(gè)參數(shù)和2個(gè)參數(shù)狸捅。
析構(gòu)函數(shù)
與構(gòu)造函數(shù)相反, 析構(gòu)函數(shù)是在對(duì)象被撤銷時(shí)被自動(dòng)調(diào)用, 用于對(duì)成員撤銷時(shí)的一些清理工作, 例如在前面提到的手動(dòng)釋放使用 new 或 malloc 進(jìn)行申請(qǐng)的內(nèi)存空間衷蜓。析構(gòu)函數(shù)具有以下特點(diǎn):
■ 析構(gòu)函數(shù)函數(shù)名與類名相同, 緊貼在名稱前面用波浪號(hào) ~ 與構(gòu)造函數(shù)進(jìn)行區(qū)分, 例如: ~Point();
■ 構(gòu)造函數(shù)沒有返回類型, 也不能指定參數(shù), 因此析構(gòu)函數(shù)只能有一個(gè), 不能被重載;
■ 當(dāng)對(duì)象被撤銷時(shí)析構(gòu)函數(shù)被自動(dòng)調(diào)用, 與構(gòu)造函數(shù)不同的是, 析構(gòu)函數(shù)可以被顯式的調(diào)用, 以釋放對(duì)象中動(dòng)態(tài)申請(qǐng)的內(nèi)存。
當(dāng)用戶沒有顯式定義析構(gòu)函數(shù)時(shí), 編譯器同樣會(huì)為對(duì)象生成一個(gè)默認(rèn)的析構(gòu)函數(shù), 但默認(rèn)生成的析構(gòu)函數(shù)只能釋放類的普通數(shù)據(jù)成員所占用的空間, 無法釋放通過 new 或 malloc 進(jìn)行申請(qǐng)的空間, 因此有時(shí)我們需要自己顯式的定義析構(gòu)函數(shù)對(duì)這些申請(qǐng)的空間進(jìn)行釋放, 避免造成內(nèi)存泄露尘喝。
下面的實(shí)例有助于更好地理解析構(gòu)函數(shù)的概念:
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 這是構(gòu)造函數(shù)聲明
~Line(); // 這是析構(gòu)函數(shù)聲明
private:
double length;
};
// 成員函數(shù)定義磁浇,包括構(gòu)造函數(shù)
Line::Line(void)
{
cout << "Object is being created" << endl;
}
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函數(shù)
int main( )
{
Line line;
// 設(shè)置長(zhǎng)度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果:
Object is being created
Length of line : 6
Object is being deleted
總結(jié):
局部和靜態(tài)對(duì)象瞧省,以聲明順序構(gòu)造
局部和靜態(tài)對(duì)象是指塊作用域和文件作用域的對(duì)象扯夭。它們聲明的順序與它們?cè)诔绦蛑谐霈F(xiàn)的順序是一致的。靜態(tài)對(duì)象只能被構(gòu)造一次
靜態(tài)對(duì)象和靜態(tài)變量一樣鞍匾,文件作用域的靜態(tài)對(duì)象在主函數(shù)開始運(yùn)行前全部構(gòu)造完畢交洗。塊作用域的靜態(tài)對(duì)象,則在首次進(jìn)入到定義該靜態(tài)對(duì)象的函數(shù)時(shí)橡淑,進(jìn)行構(gòu)造构拳。所以全局對(duì)象都在主函數(shù)main()之前被構(gòu)造
和全局變量一樣,所以的全局對(duì)象在主函數(shù)開始運(yùn)行之前,全部已被構(gòu)造置森。全局對(duì)象構(gòu)造時(shí)無特殊順序
成員以其在類的聲明順序構(gòu)造