對象的賦值和復制(轉)


一、對象的賦值和復制

1對象的賦值

如果對一個類定義了兩個或多個對象,則這些同類的對象之間可以互相賦值,或者說,一個對象的值可以賦給另一個同類的對象。這里所指的對象的值是指對象中所有數據成員的值。
對象之間的賦值也是通過賦值運算符"="進行的。本來,賦值運算符"="只能用來對單個的變量賦值,現在被擴展為兩個同類對象之間的賦值,這是通過對賦值運算符的重載實現的(關於運算符的重載將在第4章中介紹)。實際上這個過程是通過成員復制(memberwise copy)來完成的,即將一個對象的成員值一一復制給另一對象的對應成員。

對象賦值的一般形式為:對象名1=對象名2

注意:對象名l和對象名2必須屬於同一個類。
例如
Student studl
stud2//定義兩個同類的對象
stud2=studl
//studl各數據成員的值賦給stud2
通過下面的例子可以了解怎樣進行對象的賦值。

9 對象的賦值。
#include <iostream>
using namespace std;
class Box
{public:
Box(int=10,int=10,int=10); //
聲明有默認參數的構造函數
int volume();
private:
int height;
int width;
int length; };

Box::Box(int h,int w,int len)
{height=h;
width=w;
length=len; }

int Box::volume()
{return(height*width*length); }//
返回體積的值

int main()
{Box box1(15,30,25),box2; //
定義兩個對象box1box2
cout<<"The volume of box1 is "<<box1.volume()<<endl; //
輸山boxl的體積
box2=box1; //
box的值財給1box2
cout<<"The volume of box2 is "<<box2.volume()<<endl; //
輸出box2的體積
return 0; }

運行結果如下:
The volume Of boxl is 11250
The volume Of box2 is 11250

說明:
(1)
對象的賦值只對其中的數據成員賦值,不對成員函數賦值。數據成員是占存儲空間的,不同對象的數據成員占有不同的存儲空間,賦值的過程是將一個對象的數據成員在存儲空間的狀態復制給另一對象的數據成員的存儲空間。而不同對象的成員函數是同一個函數代碼段,不需要、也無法對它們賦值。
(2)
類的數據成員中不能包括動態分配的數據,否則在賦值時可能出現嚴重后果(在此不作詳細分析,只需記住這一結論即可)

2、對象的復制

有時需要用到多個完全相同的對象:

其一般形式為:類名對象2(對象1) //用對象l復制出對象2

有這就是對象的復制機制。用一個已有的對象快速地復制出多個完全相同的對象。

如:Box box2(boxl)// 其作用是用已有的對象boxl去克隆出一個新對象box2

可以看到:它與前面介紹過的定義對象方式類似,但是括號中給出的參數不是一般的變量,而是對象。在建立一個新對象時調用一個特殊的構造函數——復制構造函數(copy constructor)。這個函數的形式是這樣的:

//The copy constructor definition
Box::Box(const Box&b)
{height=b.height

width=b.width

length=b.length
}

說明:
1
復制構造函數也是構造函數,但它只有一個參數,這個參數是本類的對象(不能是其他類的對象),而且采用對象的引用的形式(一般約定加const聲明,使參數值不能改變,以免在調用此函數時因不慎而使對象值被修改)。此復制構造函數的作用就是將實參對象的各數據成員值一一賦給新的對象中對應的數據成員。
2
復制對象的語句 Box box2(boxl);這實際上也是建立對象的語句,建立一個新對象box2由於在括號內給定的實參是對象,因此編譯系統就調用復制構造函數(它的形參也是對象),而不會去調用其他構造函數。實參boxl的地址傳遞給形參b(bboxl的引用),因此執行復制構造函數的函數體時,將boxl對象中各數據成員的值賦給borg中各數據成員。
3
用戶可以在聲明類時定義復制構造函數,如果用戶自己未定義復制構造函數,則編譯系統會自動提供一個默認的復制構造函數,其作用只是簡單地復制類中每個數據成員。
4
、還提供另一種方便用戶的復制形式,用賦值號代替括號.
其一般形式為:類名對象名1=對象名2
Box box2=box1//boxl初始化box2
可以在一個語句中進行多個對象的復制,但是其作用都是調用復制構造函數。

5對象的復制和1節介紹的對象的賦值在概念上和語法上的不同。


請注意普通構造函數和復制構造函數的區別。
(1)
在形式上

類名(形參表列) //普通構造函數的聲明,如Box(intint wint leu)
類名(類名&對象名)//復制構造函數的聲明,如Box(Box &b)
(2)
在建立對象時,實參類型不同。系統會根據實參的類型決定調用普通構造函數或復制構造函數。如

Boxboxl(12,15,l6)
//實參為整數(普通數據類型),調用普通構造函數
Boxbox2(boxl)
//實參是對象名(類數據類型),調用復制構造函數
(3)
在什么情況下被調用

普通構造函數在程序中建立對象時被調用。

復制構造函數在用已有對象復制一個新對象時被調用,在以下3種情況下需要克隆對象:
程序中需要新建立一個對象,並用另一個同類的對象對它初始化
函數的參數為類的對象時,在調用函數時需要將實參對象完整地傳遞給形參,也就是需要建立一個實參的拷貝,這就是按實參復制一個形參,系統是通過調用復制構造函數來實現的,這樣能保證形參具有和實參完全相同的值。
函數的返回值是類的對象。在函數調用完畢需要將返回值(對象)帶回函數調用處時。此時需要將函數中的對象復制一個臨時對象並傳給該函數的調用處。

二、靜態成員

學習C語言時已了解全局變量,它能夠實現數據共享。如果在一個程序文件中有多個函數,在每一個函數中都可以改變全局變量的值,全局變量的值為各函數共享。但是用全局變量時安全性得不到保證,由於在各處都可以自由地修改全局變量的值,很有可能偶一失誤,全局變量的值就被修改,導致程序的失敗。因此在實際工作中很少使用全局變量。如果想在同類的多個對象之間實現數據共享,也不要用全局對象,可以用靜態的數據成員。

1、靜態數據成員

靜態數據成員是一種特殊的數據成員。它以關鍵字static開頭。

靜態數據成員定義一般形式:
static
數據類形靜態數據成員

靜態數據成員賦值一般形式:
數據類型類名::靜態數據成員名=初值;//只能在類體外進行初始化。

靜態數據成員引用一般形式:
類名::靜態數據成員名 // 通過類名引用靜態數據成員
對象.靜態數據成員名 //通過對象名引用靜態數據成員

例:
class Box
{public

int volume()

private

static int height
//height定義為靜態的數據成員
int width

int length
}

10 引用靜態數據成員。
#include <iostream>
using namespace std;

class Box
{public:
Box(int,int);
int volume();
static int height; //
height定義為公用的靜態的數據成員
int width;
int length; };

Box::Box(int w,int len)
//
通過構造函數對widthlength賦初值
{width=w;
length=len; }
int Box::volume()
{return(height*width*length); }
int Box::height=10;
//
對靜態數據成員height初始化

int main()
{Box a(15,20),b(20,30);
cout<<a.height<<endl; //
通過對象名a引用靜態數據成員
cout<<b.height<<endl; //
通過對象名b引用靜態數據成員
cout<<Box::height<<endl; //
通過類名引用靜態數據成員
cout<<a.volume()<<endl; //
調用volume函數,計算體積,輸出結果
return 0; }

說明:
(1)
如果只聲明了類而未定義對象,則類的一般數據成員是不占內存空間的,只有在定義對象時,才為對象的數據成員分配空間。靜態數據成員不屬於某一個對象,在為對象所分配的空間中不包括靜態數據成員所占的空間。靜態數據成員是在所有對象之外單獨開辟空間。只要在類中定義了靜態數據成員,即使不定義對象,也為靜態數據成員分配空間,它可以被引用。
在一個類中可以有一個或多個靜態數據成員,所有的對象共享這些靜態數據成員,都可以引用它。
(2)
靜態數據成員不隨對象的建立而分配空間,也不隨對象的撤銷而釋放(一般數據成員是在對象建立時分配空間,在對象撤銷時釋放)。靜態數據成員是在程序編譯時被分配空間的,到程序結束時才釋放空間。
(3)
靜態數據成員可以初始化,但只能在類體外進行初始化
注意:不能用參數初始化表對靜態數據成員初始化。如在定義Box類中這樣定義構造函數是錯誤的:
Box(int h
int wint len)
height(h){} //錯誤,height是靜態數據成員如果未對靜態數據成員賦初值,則編譯系統會自動賦予初值0
(4)
靜態數據成員既可以通過對象名引用,也可以通過類名來引用
注意:在上面的程序中將height定義為公用的靜態數據成員,所以在類外可以直接引用,可以看到在類外可以通過對象名引用公用的靜態數據成員,也可以通過類名引用靜態數據成員
如上例中:a.height//通過對象名a引用靜態數據成員。
Box:: height//
通過

如果靜態數據成員被定義為私有的,則不能在類外直接引用,而必須通過公用的成員函數引用。
(5)
有了靜態數據成員,各對象之間的數據有了溝通的渠道,實現數據共享,因此可以不使用全局變量。全局變量破壞了封裝的原則,不符合面向對象程序的要求。
(6)
公用靜態數據成員與全局變量的不同,靜態數據成員的作用域只限於定義該類的作用域內(如果在一個函數中定義類,那么其中靜態數據成員的作用域就是此函數內)。在此作用域內,可以通過類名和域運算符"::"引用靜態數據成員,而不論類對象是否存在。

2靜態成員函數

靜態成員函數定義一般形式:
static
數據類型靜態成員函數

靜態數據成員函數引用一般形式:
類名::靜態函數成員名// 通過類名引用靜態數據成員
對象.靜態函數成員名 //通過對象名引用靜態數據成員

作用:
與靜態數據成員不同,靜態成員函數的作用不是為了對象之間的溝通,而是為了能處理靜態數據成員。

靜態成員函數與非靜態成員函數的根本區別是
非靜態成員函數有this指針,而靜態成員函數沒有this指針。由此決定了靜態成員函數不能訪問本類中的非靜態成員數據。

說明:
靜態成員函數可以直接引用本類中的靜態數據成員,因為靜態數據成員同樣是屬於類的,可以直接引用。在程序中,靜態成員函數主要用來訪問靜態數據成員,而不訪問非靜態成員。並不是絕對不能引用本類中的非靜態成員,只是不能進行默認訪問,因為無法知道應該去找哪個對象。如果一定要引用本類的非靜態成員,應該加對象名和成員運算符"."


11 靜態成員函數的應用。
#include <iostream>
using namespace std;
class Student
//
定義Student
{public:
Student(int,int,int); //
定義構造函數
void total();
static float average(); //
聲明靜態成員函數
private:
int num;
int age;
float score;
static float sum; //
靜態數據成員
static int count;}; //
靜態數據成員

Student::Student(int m,int a,int s)
{num=m;
age=a;
score=s; }

void Student::total()
//
定義非靜態成員函數
{ sum+=score; //
累加總分
count++; } //
計已統計的人數


Static float Student::average() //
定義靜態成員函數
{ return(sum/count); }
//
使用靜態數據成員

float Student::sum=0; //對公用靜態數據成員初始化
int Student::count=0;
//
對公用靜態數據成員初始化

int main()
{Student stud[3]={ //
定義對象數組並初始化
Student(1001,18,70),
Student(1002,19,79),
Student(1005,20,98) };
int n;
cout<<"please input the number of students:";
cin>>n; //
輸入需要求前面多少名學生的平均成績
for(int i=0;i<n;i++) //
調用ntotal函數
stud[i].total();
cout<<"The average score of "<<n<<" students is "<<stud[0].average()<<endl;//
使用對象名調用靜態成員函數,也可通過類名調用:Student::average()
return 0; }

運行結果為
please inputthe numberOfstudents
3/
the average score of3 students is 82.3333

說明:
(1)
在主函數中定義了stud對象數組,為了使程序簡練,只定義它含3個元素,分別存放3個學生的數據(每個學生的數據包括學號、年齡和成績)。程序的作用是先求用戶指定的n名學生的總分,然后求平均成績(n由用戶輸入)
(2)
Student類中定義了兩個靜態數據成員sum(總分)count(累計需要統汁的學生人數),這是由於這兩個數據成員的值是需要進行累加的,它們並不是只屬於某一個對象元素,而是由各對象元素共享的,可以看出:它們的值是在不斷變化的,而且無論對哪個對象元素而言,都是相同的,而且始終不釋放內存空間。
(3)total
是公有的成員函數,其作用是將一個學生的成績累加到snm中。公有的成員函數可以引用本對象中的一般數據成員(非靜態數據成員),也可以引用類中的靜態數據成員。score是非靜態數據成員,sumcount是靜態數據成員。
(4)average
是靜態成員函數,它可以直接引用私有的靜態數據成員(不必加類名或對象名),函數返回成績的平均值
(5)
main函數中,引用total函數要加對象名(今用對象數組元素名),引用靜態成員函數average函數要用類名或對象名。
(6)
請思考:如果不將average函數定義為靜態成員函數行不行?程序能否通過編譯?需要作什么修改?為什么要用靜態成員函數?請分析其理由。
(7)
如果想在average函數中引用stud[l]的非靜態數據成員score,應該怎樣處理?
有人在上面的程序基礎上將靜態成員函數average改寫為
float Student::average() //
定義靜態成員函數
{cout<<stud[1]
score<<endl //引用非靜態數據成員
return(sum/count)
}
結果發現在編譯時出錯。可以將average函數的定義改為

floatStudent::average(Studentstu) //
函數參數為對象
{cout<<stu.score<<endl
//通過對象名引用非靜態數據成員
return(sum/count)

以上是在例11基礎上順便說明靜態成員函數引用非靜態數據成員的方法,以幫助理解。但是在程序中最好養成這樣的習慣:只用靜態成員函數引用靜態數據成員,而不引用非靜態數據成員。這樣思路清晰,邏輯清楚,不易出錯。

三、友元

友元定義:友元可以訪問與其有好友關系的類中的私有成員。友元包括友元函數和友元類。
定義格式:friend 友元函數和友元類
友元說明:在一個類中可以有公用的(public)成員和私有的(pnvate)成員,我們曾用客廳比喻公用部分,用卧室比喻私有部分。在類外可以訪問公用成員,只有本類中的函數可以訪問本類的私有成員。現在,我們來補充介紹——個例外——友元(friend)

1、友元函數

如果在本類以外的其他地方定義了一個函數(這個函數可以是不屬於任何類的非成員函數,也可以是其他類的成員函數),在對本類進行聲明時在類體中用friend對該函數進行聲明,此函數就稱為本類的友元函數。一個類的友元函數可以訪問這個類中的私有成員。

將普通函數聲明為友元函數
通過下面的例子可以了解友元函數的性質和作用。

12 友元函數的簡單例子。
#include <iostream>
using namespace std;

class Time
{public:
Time(int,int,int);
friend void display(Time &); //
聲明display函數為Time類的友元函數
private: //
以下數據是私有數據成員
int hour;
int minute;
int sec; };
Time::Time(int h,int m,int s)
//
定義構造函數,給hourminutesec賦初值
{hour=h;
minute=m;
sec=s; }
void display(Time &t)
//
這是友元函數,形參tTime類對象的引用
{ cout<<t.hour<<":"<<t.minute<<":"<<t.sec<<endl; }

int main()
{ Time t1(10,13,56);
display(t1); //
調用display函數,實參t1Time類對象
return 0; }

程序輸出結果如下:1023:56

注意:
1
display是一個在類外定義的且未用類Time作限定的函數,它是非成員函數,不屬於任何類。它的作用是輸出時間(時、分、秒)。如果在Time類的定義體中未聲明display函數為friend函數,它是不能引用Time中的私有成員hourminutesec
2
、由於聲明了displayTime類的friend函數,所以display函數可以引用Time中的私有成員hourminutesec。但注意在引用這些私有數據成雖時,必須加上對象名。因為display函數不是Time類的成員函數,不能默認引用Time類的數據成員,必須指定要訪問的對象。

友元成員函數

friend函數不僅可以是一般函數(非成員函數),而且可以是另一個類中的成員函數。

13 友元成員函數的簡單應用。
在本例中除了介紹有關友元成員函數的簡單應用外,還將用到類的提前引用聲明,請讀者注意。請閱讀下面的程序:

#include <iostream>
using namespace std;
class Date;
//
Date類的提前引用聲明
class Time
//
定義Time
{public:
Time(int,int,int);
void display(const Date&);//display
是成員函數,形參是Date類對象的引用
private:
int hour;
int minute;
int sec; };

class Date
//
聲明Date
{public:
Date(int,int,int);
friend void Time::display(const Date &);//
聲明Time類中的display函數為本類的友元成員函數
private:
int month;
int day;
int year; };

Time::Time(int h,int m,int s)
//
定義類Time的構造函數
{ hour=h;
minute=m;
sec=s; }
void Time::display(const Date &da)
//display
函數的作用是輸出年、月、日和時、分、秒
{cout<<d.month<<"/"<<d.day<<"/"<<d.year<<endl;//
引用Date類對象中私有數據
cout<<hour<<":"<<minute<<":"<<sec<<endl; } //
引用本類對象中的私有數據

Date::Date(int m,int d,int y)
//
Date的構造函數
{month=m;
day=d;
year=y; }

int main()
{ Time t1(10,13,56); //
定義Time類對象t1
Date d1(12,25,2004); //
定義Date類對象dl
t1.display(d1); //
調用tl中的display函數,實參是Date類對象dl
return 0; }

運行時輸出:
12/25/2004 (
輸出Date類對象dl中的私有數據)
l0
1356 (輸出Time類對象11中的私有數據)

注意在本程序的主函數中調用友元函數防問有關類的私有數據方法:
(1)
在函數名display的前面要加display所在的對象名(t1)
(2)display
成員函數的實參是Date類對象d1,否則就不能訪問對象dl中的私有數據;
(3)
Time::display函數中引用Date類私有數據時必須加上對象名,如dmonth

一個函數(包括普通函數和成員函數)可以被多個類聲明為"朋友",這樣就可以引用多個類中的私有數據

2、友元類

聲明友元類的一般形式為:friend 類名;

例:A類的定義體中用以下語句聲明B類為其友元類:
friend B

說明:
1
、友元的關系是單向的而不是雙向的。如果聲明了B類是A類的友元類,不等於A類是B類的友元類,A類中的成員函數不能訪問B類中的私有數據。
2
、友元的關系不能傳遞,

友元利弊的分析:
面向對象程序設計的一個基本原則是封裝性和信息隱蔽,而友元卻可以訪問其他類中的私有成員,不能不說這是對封裝原則的一個小的破壞。但是它能有助於數據共享,能提高程序的效率,在使用友元時,要注意到它的副作用,不要過多地使用友元,只有在使用它能使程序精練,並能大大提高程序的效率時才用友元。也就是說,要在數據共享與信息隱蔽之間選擇一個恰當的平衡點。

四、類模板

允許使用函數模板,對於功能相同而數據類型不同的一些函數,不必一一定義各個函數,可以定義一個可對任何類型變量進行操作的函數模板,在調用函數時,系統會根據實參的類型,取代函數模板中的類型參數,得到具體的函數。這樣可以簡化程序設計。

類模板一般定義形式:
template <class
虛擬類型參數>//聲明一個模板,虛擬類型名為numtype
class
類模板名
{
類體定義 }

請將此類模板和類定義作一比較,可以看到有兩處不同:

(1)
聲明類模板時要增加一行: template<class虛擬類型參數>
(2)
原有的類型名換成虛擬類型參數名numtype。在建立類對象時,如果將實際類型指定為int型,編譯系統就會用int取代所有的numtype,如果指定為float型,就用float取代所有的numtype。這樣就能實現"一類多用"
(3)
由於類模板包含類型參數,因此又稱為參數化的類。如果說類是對象的抽象,對象是類的實例,則類模板是類的抽象,類是類模板的實例。利用類模板可以建立含各種數據類型的類。
(4)
類模板的引用:類模板名<實際類型名> 對象名(實參表列)

14 聲明一個類模板,利用它分別實現兩個整數、浮點數和字符的比較,求出大數和小數。
#include <iostream>
using namespace std;
template<class numtype>
//
定義類模板
class Compare
{public:
Compare(numtype a,numtype b)
{x=a;y=b;}
numtype max()
{return (x>y)?x:y;} //
引用C語言中條件運算符
numtype min()
{return (x<y)?x:y;}
private:
numtype x,y; };

int main()
{ Compare<int> cmp1(3,7); //
定義對象cmpl,用於兩個整數的比較
cout<<cmp1.max()<<" is the Maximum of two inteder numbers."<<endl;
cout<<cmp1.min()<<" is the Minimum of two inteder numbers."<<endl<<endl;
Compare<float> cmp2(45.78,93.6); //
定義對象cmp2,用於兩個浮點數的比較
cout<<cmp2.max()<<" is the Maximum of two float numbers."<<endl;
cout<<cmp2.min()<<" is the Minimum of two float numbers."<<endl<<endl;
Compare<char> cmp3('a','A');//
定義對象cmp3,用於兩個字符的比較
cout<<cmp3.max()<<" is the Maximum of two characters."<<endl;
cout<<cmp3.min()<<" is the Minimum of two characters."<<endl;
return 0;}

運行結果如下:
7 is the Maximum Of two integers
3 is the Minimum Of two integers

93.6 is the Maximum Of two float numbers

45.78 is the Minimum OftWO float numbers

a is the Maximum Of two characters

A is the Minimum Of two characters

可以這樣聲明和使用類模板:
(1)
先寫出一個實際的類(如本節開頭的Compare int)。由於其語義明確,含義清楚,一般不會出錯。
(2)
將此類中准備改變的類型名(int要改變為floatchar)改用一個自己指定的虛擬類型名(如上例中的numtype)
(3)
在類聲明前面加入一行,格式為
template<class
虛擬類型參數>
template<classnumtype> //注意本行末尾無分號
class Compare
{
}//類體

(4)用類模板定義對象時用以下形式:
類模板名<實際類型名>對象名;
類模板名<實際類型名> 對象名(實參表列)

(5)
如果在類模板外定義成員函數,應寫成類模板形式:
template<class
虛擬類型參數>
函數類型類模板名<虛擬類型參數>::成員函數名(函數形參表列){}

說明:
(1)
類模板的類型參數可以有一個或多個,每個類型前面都必須加class,如
template<class Tl
class T2>
class someclass
{
}
在定義對象時分別代人實際的類型名,如
someelass<int,double> obj

(2)
和使用類一樣,使用類模板時要注意其作用域,只能在其有效作用域內用它定義對象。如果類模板是在A文件開頭定義的,則A文件范圍內為有效作用域,可以在其中的任何地方使用類模板,但不能在B文件中用類模板定義對象。
(3)
模板可以有層次,一個類模板可以作為基類,派生出派生模板類。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM