實驗5 類和物件的應用
實驗5 類和物件的應用
這個很重要想要改行JAVA的注意了要理解好類和物件,到後期做專案它可能就變成了1+1的事了
一、實驗目的
- 掌握類定義的基本格式和類成員的訪問控制。
- 掌握物件定義和利用建構函式對物件的資料成員進行初始化的方法。
- 掌握成員函式的定義與使用。
- 理解並掌握靜態資料成員和靜態成員函式的使用方法。
- 理解友元函式的使用方法。
二、知識要點
1. 類的定義
在面向物件程式設計中,類(class)就是對現實世界中抽象出的“類”的模擬描述,是使用者自己定義的資料型別,它的一般定義格式如下:
class <類名>
{
private:
<私有資料成員和成員函式>;
protected:
<保護資料成員和成員函式>;
public:
<公有資料成員和成員函式>;
};
<類中各個成員函式的實現>
說明:protected訪問控制符主要用在類的繼承中,後續課繼承中會講到
- 類成員的訪問控制
類的成員訪問許可權總體上可分為:公有的(public)、私有的(private)和保護的(protected)三類。若某個成員未作任何訪問許可權宣告,則預設為私有的。
公有的成員用public來說明,這部分通常是一些操作(即成員函式),作為類與外界的介面,所有公有的成員可以在程式中的任何位置被訪問。
私有的成員用private來說明,這部分通常是一些資料成員,這些成員用來描敘該類中物件的屬性的,只有成員函式或經過特殊說明的函式(如友元函式)才可以引用它們,它們是特意被使用者隱藏起來的部分,使用者在類外其他地方是無法訪問它們的。
保護的成員用protected來說明,它的限定能力介於私有和公有之間,除了類本身的成員函式、友元函式可以訪問成員外,只有該類的派生類(子類)可以訪問。
關鍵字public、private和protected被統稱為訪問許可權修飾符或訪問控制修飾符。它們在類體(即一對花括號內)出現的先後順序沒有要求,並且允許多次出現。
- 物件的定義
C++規定:必須先定義類,然後定義物件,用類來定義物件在格式上與普通型別定義變數是完全相同的.
定義物件的一般形式:
<類名> <物件名錶>;
- 物件成員的訪問方式
物件成員訪問的一般形式是:
<物件名>.<資料成員名>
或者
<物件名>.<成員函式名>([<實參表>])
- 建構函式的定義和分類
建構函式是類的一種特殊的成員函式,它的主要作用於是為物件分配空間和進行初始化工作。除了具有一般成員函式的特徵外,還具有以下一些特殊的性質:
(1) 建構函式的名字必須與類名字相同;
(2) 建構函式可以有任意型別和任意個數的引數,所以建構函式可以過載,但不能指定返回值型別;
(3) 建構函式的函式體可以寫在類體內,也可以寫在類體外;
(4) 建構函式被宣告為公有函式,但它不能像其他成員函式那樣被顯示的呼叫,而是在用類宣告物件的同時被系統自動呼叫。
呼叫建構函式的一般形式是:
類名 物件名(引數表);
建構函式分為4類,分別是普通建構函式、預設建構函式、有預設引數的建構函式和複製(拷貝)建構函式。
- 預設建構函式
預設建構函式是指沒有任何引數的建構函式。如果在設計類時沒有定義建構函式,C++編譯程式會自動為該類建立一個預設的建構函式。這個預設建構函式沒有任何形式引數,並且函式體為空。其格式如下:
<類名>::<預設建構函式名>() { }
按建構函式規定,預設建構函式名與類名相同。預設建構函式也可以由程式設計師直接定義在類體中。另外,如果建構函式的引數具有預設值時,這樣的建構函式被稱為有預設引數的建構函式。
- 拷貝建構函式
拷貝建構函式是一種特殊的建構函式,用於依據已存在的物件建立一個新物件。其一般形式為:
class T
{
public:
T(const T & 物件名);
……
}
T::T(const T &物件名 )
{ 函式體 }
其中,T代表任何一個類的名字。const是一個型別修飾符,被它修飾的物件是不能被改變的常量.
拷貝建構函式的拷貝分為淺拷貝和深拷貝。一般來說,只需淺拷貝時最好利用系統自動生成的拷貝函式,這樣效率高。若需要在建構函式中開闢新的記憶體空間,則需要我們自己編寫這樣的建構函式以完成深拷貝。
拷貝建構函式主要在下面3種情況中起到初始化作用。
(1) 宣告語句中用一個物件初始化另外一個物件,例如:
Person student2 (student1);
(2) 函式的引數是值引數時,若用物件作為函式實參傳遞給函式形參,這時需要呼叫拷貝建構函式。
(3) 當物件作為函式返回值時,如執行return R 時,系統將用物件R來初始化一個匿名物件,這時需要呼叫拷貝建構函式。
- 解構函式
解構函式是類的一種特殊成員函式,其功能是用來釋放一個物件的記憶體空間。它的功能與建構函式正好相反.
解構函式的特點如下:
(1)解構函式是成員函式,函式體可以寫在類體內,也可以寫在類體外;
(2)解構函式是一個特殊的函式,它的名字是在類名的前面加“~”字元;
(3)解構函式沒有引數,沒有返回值,所以不能過載。
在下面兩種情況下,解構函式會被自動呼叫:
A.當一個物件的作用域結束時,該物件的解構函式被自動呼叫。
B.當一個物件是使用new運算子被動態建立的,那麼在使用delete運算子釋放它時,delete將會自動呼叫解構函式。
- 過載成員函式
建構函式可以過載,而解構函式不能過載,原因是建構函式可以有引數,解構函式不帶任何引數,所以無法過載。可以說,帶有不同型別和數量引數的成員函式都可以進行過載。
- 子物件
將一個類的物件作為另一個類的成員,該物件就被稱為子物件。
在一個類中出現了子物件時,該類的建構函式就要考慮子物件的初始化問題。在C++中,通常採用初始化列表的方法來初始化子物件。在初始化列表中可以包括對子物件的初始化及對類中其他成員的初始化。
- 指向物件的指標
每個物件在聲明後都會在記憶體中佔有一定的空間,就會有地址。因此,可以用指標變數來儲存這個地址。指向物件的指標(變數)就是用於存放物件地址的變數。宣告物件指標的一般形式為:
<類名>*<物件指標名>;
宣告物件指標的語法和宣告其他資料型別指標的語法相同。使用物件指標時,首先要把它指向一個已建立的物件,然後才能以“間接方式”訪問該物件。
- this指標
this指標是一個隱含於每個類的成員函式中的特殊指標。該指標是一個指向正在操作的成員函式的物件。當一個物件呼叫成員函式時,編譯程式先將物件的地址賦給this指標,然後呼叫成員函式。
- 物件陣列
物件陣列是指將物件作為陣列元素的陣列。該陣列中若干個元素必須是同一個類的若干個物件。物件陣列的定義、賦值和引用與普通陣列一樣,只是陣列的元素與普通陣列不同,它是具有相同類的若干個物件。
定義一個物件陣列的一般形式是:
<類名><陣列名>[<陣列長度>];
定義二維物件陣列的一般形式是:
<類名><陣列名>[<第一維陣列長度>][<第二維陣列長度>];
- 常物件
如果在說明物件時用const修飾,則被說明的物件為常物件。常物件的資料成員值在物件的整個生命期內不能改變,常物件的宣告形式如下:
<類名>const<物件名>[(初值)]; 或者
const<類名><物件名>[(初值)];
在定義常物件時必須進行初始化,而且常物件不能被更新。
- 常資料成員
在類中使用關鍵字const來說明某個資料成員為常資料成員。如果在一個類中說明了常資料成員,那麼建構函式就只能通過初始化列表對該資料成員進行初始化,而任何其他函式都不能對該資料成員賦值。
類的常資料成員即可以是常量也可以是常引用,由於必須初始化,因此,類中這些常資料成員必須也只能通過建構函式的成員初始化列表來實現初始化工作。
- 常成員函式
在類中使用關鍵字const說明的函式稱為常成員函式,它的一般說明形式是:
<型別><成員函式名><([引數表])>const;
const是函式型別的一個組成部分,因此在函式的實現部分也要帶關鍵字const。
關於常成員函式的說明:
(1)常成員函式不能呼叫該類中的普通成員函式,因而也不能更新物件的資料成員;
(2)如果將一個物件設為常物件,則該物件只能呼叫它的常成員函式,而不能呼叫普通的成員函式,這是C++在安全機制上的考慮。
- 常型別的函式引數傳遞
將形參設定為const引用形參或const地址(指標)形參,這樣就可以保障安全快捷的傳遞物件了。將函式形參設為const型引用和指標的一般形式是:
const<型別說明符>&<引用名>
const<型別說明符>*<指標變數名>
18.靜態資料成員
C++中同一個類定義多個物件時,每個物件都擁有各自的成員,而靜態資料成員是類的所有物件中共享的成員,它不因物件的建立而產生,也不因物件的消失而刪除,它是類定義的一部分,屬於整個類,即屬於所有物件。
由於靜態資料成員不專屬於任何一個具體物件,但任何一個物件在宣告前都需要它提前擁有一個值,因此C++規定:必須對類的靜態資料成員初始化,並且它的初始化不能在建構函式中進行。
靜態資料成員初始化的方法一般採用如下形式:
<型別><類名>::<靜態資料成員>=<值>;
關於靜態資料成員初始化的進一步說明:
(1) 初始化在類體外進行,其前面不加static;
(2) 初始化時不加該成員的訪問許可權控制符private、public或protected;
(3) 即使靜態資料成員是私有的,也可以在類外有檔案作用域的地方直接初始化,一般在類的定義之後馬上初始化。
在引用公有的靜態資料成員時採用下面的形式:<類名>::<靜態資料成員>
19.靜態成員函式
靜態成員函式的定義和其他成員函式相同。但它的特點與靜態資料成員類似,不專屬於任何一個物件,為整個類所共享。靜態成員函式的定義方法是在一般成員函式的定義前面加上static關鍵字。
關於靜態成員函式的說明如下:
(1) 呼叫靜態成員函式的格式一般採用如下形式:
<類名>::<靜態成員函式名>(<引數表>);
(2) 靜態成員函式只能訪問靜態資料成員、其他靜態成員函式和類以外的函式與資料,不能訪問類中的非靜態資料成員,因為非靜態資料成員只有物件存在時才有意義。
(3) 靜態成員函式不能宣告為虛擬函式。
20.友元
友元是一種定義在類外部的普通函式或類,但它需要在類體內宣告為“朋友”。友元的作用在於提高程式的執行效率,但是從某種程度上講,它破壞了類的封裝性和隱藏性,使得非成員函式可以訪問類的私有成員,因此在程式設計時應該嚴格限制使用。
友元可以是一個函式,稱之為友元函式,它也可以是一個類,該類被稱為友元類。下面分別介紹這兩種友元。
(1) 友元函式
為了與該類的成員函式加以區別,在類內說明友元函式時需要在前面加上關鍵字friend。需要注意的是友元函式不是成員函式,但是它可以訪問類中的私有成員。
定義友元函式的方式是在類定義中用關鍵字friend說明該函式,其格式如下:
friend<型別><友元函式名>([<引數表>]);
說明:友元函式說明的位置可以在類中的任何位置,意義完全一樣。友元函式定義則必須在類的外部,一般與類的成員函式定義在一起。宣告類的友元函式的目的就是為普通函式提供直接方便的訪問該類的所有成員的許可權。
(2) 友元類
友元也可以是一個類,即將一個類作為另一個類的友元。當一個類作為另一個類的友元時,意味著這個類的所有成員函式都是另一個類的友元函式。
三、實驗內容和步驟
【例項1】定義一個關於日期的類,然後宣告物件,判斷該日期是否為閏年並輸出。
#include <iostream>
using namespace std;
class TDate
{
public :
void SetDate(int y,int m,int d);
int isLeapYear();
void Print();
private:
int year,month,day;
};
void TDate::SetDate(int y,int m,int d)
{
year = y;
month = m;
day = d;
}
int TDate::isLeapYear()
{
return (year%4==0&&year%100!=0) || (year%400==0);
}
void TDate::Print()
{
cout<<year<<"."<<month<<"."<<day<<endl;
}
void main()
{
TDate date1,date2;
date1.SetDate(2004,5,4);
date2.SetDate(2005,4,9);
int leap = date1.isLeapYear();
date1.Print();
if (leap == 1)
cout<<"是閏年!"<<endl;
else
cout<<"不是閏年!"<<endl;
date2.Print();
}
注意:
(1)類定義的末尾有一個分號。
(2)成員函式常採用在類內宣告,在類外定義的方法。
(3)體會類的定義方法以及類的成員函式的呼叫方法。
(4)採用多檔案的方式來實現該程式。將類定義放在標頭檔案中,將類的實現單獨放在一個實現檔案中,並將主程式單獨放在一個實現檔案中,如:
//Lab5_1.h
#include <iostream>
class TDate
{
public :
void SetDate(int y,int m,int d);
int isLeapYear();
void Print();
private:
int year,month,day;
};
//Lab5_1.cpp
#include"Lab6_1.h"
using namespace std;
void TDate::SetDate(int y,int m,int d)
{
year = y;
month = m;
day = d;
}
int TDate::isLeapYear()
{
return (year%4==0&&year%100!=0) || (year%400==0);
}
void TDate::Print()
{
cout<<year<<"."<<month<<"."<<day<<endl;
}
//main.cpp
#include"Lab8_1.h"
void main()
{
TDate date1,date2;
date1.SetDate(2004,5,4);
date2.SetDate(2005,4,9);
int leap = date1.isLeapYear();
date1.Print();
if (leap == 1)
cout<<"是閏年!"<<endl;
else
cout<<"不是閏年!"<<endl;
date2.Print();
}
【例項2】編寫一個程式,定義一個日期時間類,並在類中定義建構函式和解構函式。
#include <iostream>
using namespace std;
class TDateTime
{
public:
TDateTime(int y,int m,int d,int h,int mi,int s);
~TDateTime();
void Print();
private:
int year,month,day,hour,minite,second;
};
TDateTime::TDateTime(int y,int m,int d,int h,int mi,int s)
{
year = y;
month = m;
day = d;
hour = h;
minite = mi;
second = s;
cout<<"Constructor called."<<endl;
}
TDateTime::~TDateTime()
{
cout<<"Destructor called."<<endl;
}
void TDateTime::Print()
{
cout<<year<<"/"<<month<<"/"<<day<<"/"<<hour<<":"<<minite<<":"<<second<<endl;
}
void main()
{
TDateTime Now(2007,10,31,9,30,35);
cout<<"Now is ";
Now.Print();
}
注意:
(1)建構函式和解構函式都沒有返回值。
(2)建構函式可以有引數,解構函式不可以有引數。
(3)建構函式和解構函式都是系統自動呼叫的。
(4)為類TDateTime增加一個無參建構函式,並在主函式中進行測試。
(5)在(4)的基礎上,將有參建構函式
TDateTime(int y,int m,int d,int h,int mi,int s);
改為有預設引數的建構函式
TDateTime(int y=2008,int m=8,int d=8,int h=24,int mi=0,int s=0);
編譯程式,看一下有什麼錯誤提示資訊?錯誤的原因是什麼?
【例項3】拷貝建構函式的使用。
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(char* pN);
Person(Person &p);
~Person();
private:
char * pName;
};
Person::Person(char *pN)
{
cout<<"建構函式: "<<pN<<endl;
pName = new char[strlen(pN)+1];
strcpy(pName,pN);
}
Person::Person(Person &p)
{
cout<<"拷貝"<<p.pName<<"到新的堆空間\n";
pName = new char[strlen(p.pName)+1+8]; //8是字串"Copy of "的長度
strcpy(pName,"Copy of ");
strcat(pName,p.pName);
}
Person::~Person()
{
cout<<"解構函式: "<<pName<<endl;
delete pName;
}
void main()
{
Person p1("Jack");
Person p2(p1); //或者Person p2=p1;
}
【例項4】使用靜態資料成員。
#include <iostream>
using namespace std;
class Static_Myclass
{
public :
static int a; //靜態資料成員a
};
int Static_Myclass::a = 5; //初始化靜態資料成員a
void main()
{
Static_Myclass obj1,obj2;
obj1.a = 10; //靜態資料成員的引用方法一
Static_Myclass::a = 20; //靜態資料成員的引用方法二
cout<<obj1.a<<endl;
cout<<obj2.a<<endl;
cout<<Static_Myclass::a<<endl;
}
本程式的執行結果為:
20
20
20
【例項5】使用靜態成員函式。
#include <iostream>
using namespace std;
class Myclass
{
private :
static int b;
public :
int a;
static void fun()
{
//a=5; //靜態成員函式不能直接使用類內的一般資料成員a
int a = 5; //這是在該函式體內的區域性變數
b = 7; //可直接使用類的靜態資料成員b
cout<<"a="<<a<<" 類的靜態資料成員 b="<<b<<endl;
}
};
int Myclass::b; //靜態資料成員的初始化
void main()
{
Myclass::fun(); //不必通過物件操作靜態成員函式即可使用
Myclass obj;
obj.a = 30;
obj.fun(); //通過物件操作靜態成員函式
}
本程式的執行結果為:
a=5 類的靜態資料成員 b=7
a=5 類的靜態資料成員 b=7
【例項6】使用友元函式。
#include <iostream>
#include <math>
using namespace std;
class Point
{
public:
Point(double i,double j)
{
x = i;
y = j;
}
void Getxy()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
friend double Distance(Point &a,Point &b);
private:
double x,y;
};
double Distance(Point &a,Point &b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx*dx + dy * dy);
}
void main()
{
Point p1(3.0,4.0),p2(6.0,8.0);
p1.Getxy();
p2.Getxy();
double d = Distance(p1,p2);
cout<<"Distance:"<<d<<endl;
}
本程式的執行結果為:
(3,4)
(6,8)
Distance:5
四、思考與練習
1. 對於【例項1】,定義指向物件的指標,體會用指標來間接訪問類的成員的方法。
2. 若類的建構函式中有new命令,則該類必須有解構函式嗎?且在解構函式內使用delete命令嗎?
3. 拷貝建構函式的應用場合是什麼?
4. 在類的建構函式中可以對類的靜態資料成員初始化嗎?
5. 靜態成員函式中可以直接使用類的所有的資料成員嗎?
6. 使用友元的目的是什麼?
7. 仿照【例項1】編寫程式,設計一個學生類,完成輸入/輸出的功能,要求:
A.資料成員包括:學號、姓名、性別、成績等;
B.成員函式要實現資訊的錄入和顯示。
8. 仿照【例項2】改造7中的程式,要求:
A.增加建構函式和解構函式;
B.建構函式中除了需要完成正常的初始化外,還要用new為姓名資料成員開闢空間;
C.解構函式要釋放己開闢的空間。