C++類與物件初學
一.基本內容總結
(一)基礎知識
1.類的定義:
類是對具有相同屬性和行為的一組物件的抽象和統一描述。是使用者自定義的資料型別。/類的定義包括行為和屬性兩部分。/屬性以資料表示,行為通過函式實現。
C++類定義的說明語句一般形式為:
class<類名>
{
public:
公有段資料成員和成員函式;
protected:
保護段資料成員和成員函式;
private:
私有段資料成員和成員函式;
};
*分號不可掉。
除了class以外,關鍵字struct也可以用於定義類。用struct定義類時,若不特別指出,則所有成員是共有的。例如,一個日期類的說明如下:
class Date { public: void SwtDate(int y,int m,int d); int IsLeapYear(); void PrintDate(); private: int year,month,day; };
Date類有三個私有資料成員:year,month和day。Date還有三個公有成員函式:SetDate用於獲取物件的值,設定日期;IsLeapYear用於判斷是否是閏年;PrintDate函式用於輸出日期。
*成員函式在類外定義使用作用域區分符進行說明,此時函式頭的形式為:
返回型別 類名::函式名(引數表)
{
函式體 }
*注意事項
1.類的成員可以是其他人的物件,但不能以類自身的物件作為本類的成員,而類自身的指標和引用可以作為類的成員。2.類定義必須以分號“;”結束。3.類與結構體的區別:沒有明確指定類成員的訪問許可權時,C++結構體的成員是公有的,而類的成員是私有的。
2.訪問物件成員:
*物件是類的例項或實體;類與物件的關係,如同C++基本資料型別和該型別的變數之間的關係。
物件的定義(格式如下):
類名 物件1,物件2,.....,物件名n;
定義物件時應注意,必須在定義了類之後,才可以定義類的物件。
例如,訪問物件的公有成員:
#include<iostream> using namespace std; class Tclass { public: int x,y; void print() { cout<<x<<","<<y;}; }; int main() { Tclass test; test.x=100; test.y=200; test.print(); }
*類成員的訪問,物件成員的訪問包括:
圓點訪問形式:物件名.公有成員和指標訪問形式(物件指標變數名->公有成員)
例如:
#include<iostream.h>
class ptr_access {
public:
void setvalue(float a, float b) { x=a; y=b; }
float Getx() {return x;}
float Gety() {return y;}
void print()
{
cout<<"x="<<x<<endl;
cout<<"y="<<y<<endl;
}
private: //私有資料成員
float x,y;
};
int main()
{
float a1,a2;
ptr_access *ptr=new ptr_access;
ptr->setvalue(2,8);
//通過指標訪問公有成員函式
ptr->print();
a1=(*ptr).Getx();
//通過公有成員函式訪問私有資料成員
a2=(*ptr).Gety();
cout<<"a1="<<a1<<endl;
cout<<"a2="<<a2<<endl;
return 0;
}
3.行內函數:
行內函數作用:減少頻繁呼叫小子程式的執行時間的開銷;
行內函數機制:編譯器在編譯時,將行內函數的呼叫以相應程式碼代替;
行內函數宣告:inline函式原型;
注:行內函數僅在函式原型做一次宣告;適用於只有1-5行的小函式;不能含有複雜結構控制語句,不能遞迴呼叫。
(二).簡單建構函式和解構函式
1.原型:
類名::類名(引數表);
類名::~類名();
例如,為類Date建立一個建構函式:
#include <iostream.h>
class Date {
public:
Date(); // 無參建構函式
Date(int y,int m,int d);
void showDate();
private:
int year, month, day;
};
Date::Date() // 建構函式的實現
{ year=0; month=0; day=0; }
Date::Date(int y,int m,int d)
{ year=y; month=m; day=d; }
inline void Date::showDate()
{ cout<<year<<"."<<month<<"."<<day<<endl; }
2.帶引數的建構函式:
帶引數的函式建構函式可以在建立一個物件時,用指定的資料初始化物件的資料成員。
3.過載建構函式:
建構函式與普通函式一樣,允許過載。如果Date類具有多個建構函式,建立物件時,將根據引數匹配呼叫其中一個。例如:
class Date
{ public:
Date();
Date(int);
Date(int,int);
Date(int,int,int);
~Date();
//......
};
//......
void f()
{ Date d; //呼叫 Date();
Date d1(2000); //呼叫 Date(int);
Date d1(2000,1); //呼叫 Date(int,int);
Date d1(2000,1,1); //呼叫 Date(int,int,int);
}
像所以C++函式一樣,建構函式可以具有預設引數。但是,定義預設引數建構函式時,要注意呼叫時可能產生二義性。例如:
class X
{ public:
X();
X(int i=0);
//...
};
void f()
{ X one(10); //正確
X two; //錯誤
}
上述程式在說明X類物件one時,呼叫建構函式X::X(int i=0);但在說明two時,系統無法判斷呼叫X::X()還是X::X(int i=0),因此會出現匹配二義性。
4.複製建構函式:
創造物件時,有時希望用一個已有的同類型物件的資料對它進行初始化。C++可以完成類物件資料的簡單複製。使用者自定義的複製建構函式用於完成更為複雜的操作。
複製建構函式要求有一個類型別的引用函式:
類名::類名(const 類名&引用名,......);
為了保證所引用的物件不被修改,通常把引用引數說明為const引數。例如:
class A
{ public:
A(int);
A(const A&,int=1); //複製建構函式
//......
};
//......
A a(1); //建立物件a,呼叫A(int)
A b(a,0); //建立物件b,呼叫A (const A&,int=1)
A c=b; //建立物件c,呼叫A (const A&,int=1)
第一條語句建立物件a,呼叫一般的建構函式A(int)。第二條和第三條語句都呼叫了複製建構函式,複製物件a建立b,複製物件b建立c,展示了呼叫複製建構函式的兩種典型方法。
*呼叫複製建構函式的時機:
當說明語句建立物件時,可以呼叫複製建構函式進行資料初始化。另外,當函式具類型別傳值數或者函式返回類型別值時,都需要呼叫複製建構函式,完成區域性物件的初始化工作。
如果函式具有類型別傳值引數,那麼,呼叫該函式時,首先要呼叫複製建構函式,用實際引數物件的值初始化形式引數物件。
5.解構函式:
物件生存期結束時,需要做清理工作,比如:釋放成員(指標)所佔有的儲存空間;
解構函式可以完成上述工作;
解構函式自動呼叫(隱式呼叫),解構函式沒有返回值,不能有引數,也不能過載。
定義格式如下(類外實現):
類名::~類名()
{
函式語句
}
*解構函式有以下特點:
1.解構函式與建構函式名字相同,但它前面必須加一個波浪號(~); 2. 解構函式沒有引數,也沒有返回值,而且不能過載。因此在一個類中只有一個解構函式; 3.當撤銷物件時,編譯系統會自動的呼叫解構函式。
6.this指標:
用類定義物件時,系統會為每一個物件分配儲存空間。如果一個類包括了資料和函式,要分別為資料和函式的程式碼分配儲存空間。按理說,如果用同一個類定義了10個物件,那麼就需要分別為10個物件的資料和函式程式碼分配儲存單元。
#include <iostream>
using namespace std;
class Time
{
public:
int hour;
int minute;
int sec;
void setHour( ){ hour=10;}
};
int main( )
{ cout<<sizeof(Time)<<endl; return 0; }
7.深複製和淺複製:
當類的資料成員是簡單的資料型別時,建立物件時的資料複製系統機制工作得很好。但是,如果資料成員資源是由指標指示的堆,系統複製物件資料時只進行指標(地址值)複製,而不會重新分配記憶體空間。這樣,程式執行時會產生物件操作異常;另外,當物件作用域結束後,又會錯誤地重複釋放堆。這種情況下,需要使用使用者自定義的複製建構函式。
*淺複製:
在用一個物件初始化另一個物件時,只複製了資料成員,而沒有複製資源,使兩個物件同時指向了同一資源的複製方式稱為淺複製;
即:對於複雜型別的資料成員只複製了儲存地址而沒有複製儲存內容;
預設複製建構函式所進行的是簡單資料複製,即淺複製 。
*深複製:
通過一個物件初始化另一個物件時,不僅複製了資料成員,也複製了資源的複製方式稱為深複製;
自定義複製建構函式所進行的複製是淺複製。
(三)類的其他成員
1.常成員:
在類中,定義常成員用const約束。常資料成員是指資料成員在例項化被初始化後約束為只讀;常成員函式是指成員函式的this指標被約束為指向常量的常指標,在函式體不能修改資料成員的值。
(1)常資料成員:
在C++類定義中,const可以約束基本型別的資料成員為常資料成員。因為類物件要通過執行建構函式才能建立儲存空間,所以,用建構函式實現資料成員值的初始化是必需的。在C++語言中,使用建構函式引數初始化對常資料成員進行初始化。
**資料成員可以在建構函式中直接用常量進行初始化,這樣,每個物件建立的常資料成員都有相同的值。
**外一種對常資料成員進行初始化的方法是,使用帶引數的建構函式,建立物件時,用實際引數對常數成員賦值。這樣,每個物件的常資料成員就可以有不同的初始值。
(2)常物件:
若在定義物件的說明語句以const作字首,則該物件稱為常物件。這個物件的全部資料成員作用域中約束為只讀。
(3)常成員函式:
在類中使用關鍵字const說明的函式為常成員函式,格式如下:
型別說明符 函式名(引數表)const;
*const是函式型別的一個組成部分,因此在函式的實現部分也要帶關鍵字const;常成員函式不能更新物件的資料,也不能呼叫非const修飾的成員函式(靜態成員函式和建構函式除外)。
#include<iostream>
using namespace std ;
class Simple
{ int x, y ;
public :
void setXY ( int a, int b) { x = a ; y = b ; }
void printXY() { cout << x << "," << y << endl ; }
void constFun ( ) const
{ x ++ ; y ++ ; }//非法
};
2.靜態成員:
(1).靜態資料成員:
靜態資料成員在定義或說明時前面加關鍵字static,如:
class A
{
int n;
static int s;
};
(2).靜態成員函式:
除靜態資料成員以外,一個類還可以有靜態成員函式;
靜態成員函式和靜態資料成員一樣,它們都屬於類的靜態成員,它們都不是物件成員。因此,對靜態成員的引用不需要用物件名;
靜態成員函式沒有this;指標只能對靜態資料操作。
3.友元:
*如果在本類(類A)以外的其他地方定義了一個函式(函式B);*這個函式可以是不屬於任何類的非成員函式;*也可以是其他類的成員函式;*在類體中用friend對其(函式B)進行宣告,此函式就稱為本類(類A)的友元函式;*友元函式(函式B)可以訪問這個類(類A)中的私有成員。*用友元函式計算兩點間距離:
#include<iostream>
using namespace std ;
#include<math.h>
class Point
{ public:
Point(double xi, double yi) { X = xi ; Y = yi ;}
double GetX() { return X ; }
double GetY() { return Y ; }
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 ) ;
}
int main()
{ Point p1( 3.0, 5.0 ) , p2( 4.0, 6.0 ) ;
double d = Distance ( p1, p2 ) ;
cout << "This distance is " << d << endl ;
}
4.類的包含:
*類的包含是程式設計中一種軟體重用技術。即定義一個新的類時,通過編譯器把另一個類“抄”進來;*當一個類中含有已經定義的類型別成員,帶引數的建構函式對資料成員初始化,須使用初始化語法形式;*建構函式 ( 形參表 ) : 物件成員1(形參表 ) , … , 物件成員n (形參表 ) ;
*出現成員物件時,該類的建構函式要包含物件成員的初始化。如果建構函式的成員初始化列表沒有對成員物件初始化時,則使用成員物件的無參(預設)建構函式。建立一個類的物件時,要先執行成員物件自己的建構函式,再執行當前類的建構函式。
*用類包含計算兩點間距離:
#include<cmath>
#include<iostream>
using namespace std;
class Point
{ public:
Point( int xi=0, int yi=0 ) { x = xi; y = yi; }
int GetX() { return x; }
int GetY() { return y; }
private: int x; int y;
};
class Distance
{ public:
Distance( Point xp1, Point xp2 );
double GetDis() { return dist; }
private:
Point p1, p2;
double dist;
};
二.例題總結
如下為student類的一個例項,第一次嘗試,還有些許錯誤和待優化的地方:
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
class student
{
string name;
int no;
int score[3];
float average;
int order;
public:
student(int id,string na,int x,int y,int z):name(na),no(id)
{
score[0]=x;
score[1]=y;
score[2]=z;
order=1,average=(score[0]+score[1]+score[2])/3;
}
student()
{
score[0]=score[1]=score[2]=0;
order=1,average=0;
}
int getNo(){return no;}
float getAverage(){return average;}
void setAverage(int avg){average=avg;}
void setOrder(int x){order=x;}
int getOrder(){return order;}
string getName(){return name;}
void setName(string name){this->name=name;}
void display();
};
void student::display()
{
cout<<name<<"\t"<<no<<"\t"<<score[0]<<"\t"<<score[1]<<"\t"<<score[2]<<"\t"<<average<<"\t"<<order<<endl;
}
bool cmp1(student stu1,student stu2)
{
if(stu1.getAverage()-stu2.getAverage()>=1e-9)return 1;
else return 0;
}
bool cmp2(student stu1,student stu2)
{
return stu1.getNo()<stu2.getNo();
}
class student_list
{
student list[60];
int n;
public:
student_list():n(0){};
void add();
void deleteStu();
void query();
void change();
void display(int flag);
void menu();
int search(int no);
void sortList();
};
void student_list::add()
{
int no,x,y,z;
string name;
system("cls");
cout<<"按學號·姓名·數學·英語·c++順序輸入學生資訊,學號輸入-1表示結束"<<endl;
while((cin>>no)&&no!=-1)
{
cin>>name>>x>>y>>z;
student s(no,name,x,y,z);
list[n++]=s;
sortList();
}
}
void student_list::sortList()
{
sort(list,list+n,cmp1);
int i;
for(i=0;i<n;i++)
list[i].setOrder(i+1);
}
void student_list::display(int flag)
{
if(flag)sort(list,list+n,cmp2);
else sort(list,list+n,cmp1);
cout<<"姓名"<<"\t"<<"學號"<<"\t"<<"數學"<<"\t"<<"英語"<<"\t"<<"c++"<<"\t"<<"平均成績"<<"\t"<<"名次"<<endl;
for(int i=0;i<n;i++)
list[i].display();
}
int student_list::search(int no)
{
int i;
for(i=0;i,n;i++)
if(list[i].getNo()==no)
return -1;
}
void student_list::query()
{
int no,i;
system("cls");
cout<<"請輸入要查詢同學的學號,按-1結束查詢";
while(cin>>no&&no!=-1)
{
i=search(no);
if(i!=-1)
{
cout<<"姓名"<<"\t"<<"學號"<<"\t"<<"數學"<<"\t"<<"英語"<<"\t"<<"c++"<<"\t"<<"平均成績"<<"\t"<<"名次"<<endl;
list[i].display();
cout<<"請輸入要查詢同學的學號,按-1結束查詢";
}
else
cout<<"學號輸入有誤,請重輸,輸入-1結束查詢"<<endl;
}
}
int main()
{
student s(1,"ff",66,77,88);
s.display();
cout<<s.getAverage()<<endl;
s.setName("方法");
s.display();
student_list c;
c.add();
c.display(1);
c.query();
c.display(0);
c.change();
c.display(1);
c.deleteStu();
c.display(1);
return 0;
}
三.學習心得
學習本章內容,主要是思維的轉變,由之前的面向過程編譯到面向物件設計。本章主要是對設計管理系統的初步認知,即通過對方需求來找到系統設計的初步框架,再從框架中的各個分類來細化需求,為需求分類。當然,最重要的是把各類操作與需求轉化成資料並加以修飾,這是本章的關鍵;最後,加以除錯和修改,使之具備增添和刪減的靈活性,以完成思維的轉變。隨著學習的深入,最後的目的是對軟體的設計,相信會越來越難。