1. 程式人生 > >C++類與物件初學

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;
}

三.學習心得

學習本章內容,主要是思維的轉變,由之前的面向過程編譯到面向物件設計。本章主要是對設計管理系統的初步認知,即通過對方需求來找到系統設計的初步框架,再從框架中的各個分類來細化需求,為需求分類。當然,最重要的是把各類操作與需求轉化成資料並加以修飾,這是本章的關鍵;最後,加以除錯和修改,使之具備增添和刪減的靈活性,以完成思維的轉變。隨著學習的深入,最後的目的是對軟體的設計,相信會越來越難。