c++學習總結(三)——類與物件
一、心得感悟
c語言的課程學習後,開始c++的學習,首先就是學習類。在學習類時,類的使用與c語言有著極大的差別,一開始學習十分別扭。c語言的學習直接定義幾個形參、函式就可以寫程式了;而到了c++學習,關於類,首先必須定義類。具有相同性質和功能的東西構成的集合,通常歸成一“類”。例如,“人”是類的概念。為了描述人的特點,有姓名、性別、年齡、身高、體重等特徵,稱為“屬性”。人還有各種生活技能和工作技能,稱為“方法”。類是抽象的,當屬性賦給具體的值,方法有具體的內容時,才成為物件,如具體的張三、李四。物件時某個類的能動實體。
類的定義包括行為和屬性兩個部分,屬性以資料表示,行為通過函式實現。類的例項稱為物件,正如基本資料型別的例項稱為變數一樣,在程式設計中,“變數”與“物件”兩個屬於常常可以互換使用。在面向物件程式設計中,程式的基本單位時類。類時使用者定義的資料和操作這些資料的函式的封裝。類的物件使用自己的方法完成對資料的操作。c++中,屬性用資料的儲存結構實現,稱為類的資料成員;方法用函式實現,稱為成員函式。它們都是類的成員。使用物件包括訪問物件的資料成員和呼叫成員函式。類中的成員函式可以使用自身不同性質的資料成員和呼叫成員函式。
總之,學習c++,類是最重要的,沒有之一。不會定義和使用類,在今後的學習可能會寸步難行。
二、內容總結及例題與分析
1.1. 首先定義類的說明語句一般形式為:
class<類名>
{
public:
公有段資料成員和成員函式;
protected:
保護段資料成員和成員函式;
private:
私有段資料成員和成員函式;
};←分號不得省略!
類成員用關鍵字指定不同的訪問特徵,決定其在類體系中或類外的可見性。
關鍵字private用宣告私有成員。私有成員只能在類中可見,不能在類外或派生類中使用。
protected宣告保護成員。保護成員在類和它的派生類中可見。
pulibc宣告共有成員。共有成員是類的介面,在類中和類外可見。
例如:
class Date
{
public:
void SetDate(int y,int m,int d);
int IsLeapYear();
void PrintDate();
private:
int year,month,day;
};
Date類有三個私有資料成員:year,month,和day。
另外,Date還有三個共有成員函式:SetDate函式是用於獲取物件的值,設定日期;函式IsLeapYear用於判斷是否閏年:PrintDate函式用於輸出日期。在類的說明語句中沒有給出函式的實現。
成員函式在類外定義使用作用域區分符進行說明,此時函式頭的形式為:
返回型別 型別::函式名(引數名)
其中,作用域區分符由兩個冒號構成,它用於標識屬於什麼類的成員。
Date類的成員函式定義在類外寫為:
void Date::SetDate(int y,int m,int d)
{
year=y;
month=m;
day=d;
}
int Date::IsLeapYear()
{ return(year%4==0&&year%100!=0)||(year%400==0); }
void Date::PrintDate()
{ cout<<year<<"."<<month<<"."<<day; }
簡單的成員函式實現可以在類中定義,此時,編譯器作為行內函數處理。例如,成員函式SetDate在類中寫成:
public:
void SetDate(int y,int m,int d)
{
year=y;
month=m;
day=d;
}
成員函式有兩個作用:一是操作資料成員,包括訪問和修改資料成員;二是用於協同不同的物件操作,稱為傳遞訊息。成員函式通過引數與其它物件協同操作。
物件是類型別的變數,說明方法與普通變數相同。物件沒有成員函式的副本,類成員函式可以被物件呼叫。
類的資料成員除了可以是基本型別外,還可以是陣列、結構、類等自定義的資料型別。如果一個類的成員是一個已經定義的類型別,則稱為類的包含(或組合)。例如,定義Student類:
class Student
{ public:
char name[10];
Date birthday;
long code;
char *address;
char phone[20];
//...
};
1.2. 訪問物件成員
使用物件包括訪問物件的資料成員和呼叫成員函式。類中的成員函式可以使用自身不同性質的資料成員和呼叫成員函式。公用成員是提供給外部的介面,即,只有公用成員在類體系外可見。物件成員的訪問形式與訪問結構的形式相同,運算子“.”和“->”用於訪問物件成員。
例:1.訪問物件的公用成員。
#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();//呼叫共用段成員函式
}
2.用指標訪問物件成員。
#include<iostream>
using namespace std;
class Tclass
{ public:
int x,y;
void print()
{ cout<<x<<"."<<y; };
};
int add(Tclass *ptf)
{ return(ptf->x+ptf->y); }
int main()
{
Tclass test,*pt=&test;//說明一個物件test和物件指標pt
pt->x=100; //用物件指標訪問資料成員
pt->y=200;
pt->print(); //用物件指標呼叫成員函式
test.x=150;
test.y=450;
test.print();
cout<<"x+y="<<add(&test)<<endl;//把物件地址傳給指標引數
}
1.3.this指標
需要顯式引用this指標的三種情況
(1)在類的非靜態成員函式中返回類物件本身或物件的引用的時候,直接使用return *this,返回本物件的地址是,return this。
(2)當引數與成員變數名相同時,如this->x=x,不能寫成x=x。
(3)避免對同一物件進行賦值操作,判斷兩個物件是否相同時,使用this指標。
函式返回物件的引用
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:
Person(string n,int a)
{
name=n; //這裡的name等價於this->name
age=a; //這裡的age等價於this->age
}
int getage(void)const{return age;}
Person& addage(int i){age+=i; return *this;}
private:
string name;
int age;
};
int main()
{
Person Li("Li",20);
cout<<"Li age ="<<Li.getage()<<endl;
cout<<"Li add age ="<<Li.addage(1).getage()<<endl;//增加歲的同時,可以對新的年齡直接輸出;
return 0;
}
程式執行結果為:
Li age=20
Li add age=21
2.1.建構函式和解構函式
建構函式時用於建立物件的特殊成員函式,當一個物件作用域結束時,系統自動呼叫解構函式。解構函式的作用是進行物件消亡時的清理工作。沒用使用者定義解構函式時,系統提供預設版本的解構函式。解構函式沒用引數,也沒有返回型別。
建構函式和解構函式的原型是:
類名::類名(引數表);
類名::~類名();
2.2如果類中沒有定義建構函式,系統將自動生成一個預設形式的建構函式,用於建立物件,預設建構函式形式:
類名::類名(){}
預設建構函式時一個空函式
例:為類Date建立一個建構函式
#include<bits/stdc++.h>
using namespace std;
class Date
{
public:
Date(); //無參建構函式
Date(int y,int m,int d);
void showDate();
private:
int year,month,day;
};
Date::Date() //建構函式的實現
{year=y;month=m;day=d;}
inline void Date::showDate()
{cout<<year<<"."<<month<<"."<<endl;}
通常,利用建構函式建立物件有以下兩種方法:
(1)利用建構函式直接建立物件,其一般形式為:
類名 對姓名{(實參表)};
這裡的“類名”與建構函式名相同,“實參表”是為建構函式提供的實際引數。
(2)利用建構函式建立物件時,通過指標和new來實現。其一般語法形式為:類名 *指標變數=new類名[(實參表)];
2.3.建構函式的初始化列表——資料成員的初始化
建構函式初始化成員有兩種方法
A.使用建構函式的函式體進行初始化
class Date
{
int d,m,y;
public:
Date(int dd,int mm,int yy)
{
d=dd;
m=mm;
y=yy;
}
Date(int dd,int mm)
{
d=dd;
m=mm;
}
};
B.使用建構函式的初始化列表進行初始化
格式:
funname(引數列表):初始化列表
{函式體,可以是空函式體}
初始化列表的形式:
成員1(形參名1),成員名2(形參名2),成員名n(形參名n)
class Date
{
int d,m,y;
public:
Date(int dd,int mm,int yy):d(dd),m(mm),y(yy)
{}
Date(int d,int mm):d(dd),m(mm)
{ }
};
**必須使用引數初始化列表對資料成員進行初始化的幾種情況
1.資料成員為常量
2.資料成員為引用型別
3.資料成員為沒有無參建構函式的類的物件
情況1和2:
#include<iostream>
using namespace std;
class A
{
public:
A(int i):x(i),rx(x),pi(3.14)
{}
void display()
{cout<<"x="<<x<<"rx="<<rx<<"pi="<<pi<<endl;}
private:
int x,℞
const float pi;
};
int main()
{
A aa(10);
aa.display();
return 0;
}
情況3:
#include<iostream>
using namespace std;
class A
{
public:
A(int x):a(x){}
int a;
};
class B
{
public:
B(int x,int y):aa(x),b(y){}
void out()
{cout<<"aa="<<aa.a<<endl<<"b="<<b<<endl;}
private:
int b;
A aa;
};
int main()
{
B objB(3,5);
objB.out();
}
類成員的初始化的順序:
按照資料成員在類中的宣告順序進行初始化,與初始化成員列表中出現的順序無關
2.3.建構函式的過載
class Box
{
public:
Box();
Box(int,int,int);
int volume();
private:
int height,width,length;
};
Box::Box()
{height=10;width=10;length=10;}
Box::Box(int h,int w,int l):height(h),width(w),length(l)
{}
int Box::volume()
{return width*length*height;}
int main()
{
Box box1;
cout<<"The volume is"<<box1.volume();
Box box2(13,30,25);
cout<<"The volume is"<<box2.volume();
return 0;
}
2.4.複製建構函式
4.1.複製建構函式用一個已有同類物件建立新物件進行資料初始化C++為型別提供預設版本的複製建構函式。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&,int=1)
A c=b;//建立物件c,呼叫A(const A&,int=1)
特點:
1.複製建構函式名與類名相同,並且也沒有返回值型別。
2.複製建構函式可寫在類中,也可以寫在類外。
3.複製建構函式要求有一個類型別的引用函式。
4.如果沒有顯示定義複製建構函式,系統自動生成一個預設形式的複製建構函式。
一下三種情況下有編譯系統自動呼叫:
1.宣告語句中用類的一個已知物件初始化該類的另一個物件時。
2.當物件作為一個函式實參傳遞給函式的形參時,需要將實參物件去初始化形參物件時,需要呼叫複製建構函式。
3.當物件時函式的返回值時,由於需要生成一個臨時物件作為函式返回結果,系統需要將臨時物件的值初始化另一個物件,需要呼叫複製建構函式。
4.2.淺複製和深複製
淺複製:
在用一個物件初始化另一個物件時,只複製了資料成員,而沒有複製資源,是兩個物件同時指向了同一資源,使兩個物件同時指向了同一資源的複製方式成為淺複製。即:對於複雜型別的資料成員只複製了儲存地址而沒有複製儲存內容。預設複製建構函式所進行的是簡單資料複製,即淺複製。
深複製:
通過一個物件初始化另一個物件時,不僅複製了資料成員,也複製了資源的複製方式成為深複製。自定義複製建構函式所進行的複製是深複製。
定義支援深複製的複製建構函式
1.深複製建構函式必須顯式定義
2.深複製建構函式的特點:
定義:類名::類名([const]類名&物件名);
成員變數的處理:對複製型別的成員變數,使用new操作符進行空間的申請,然後進行相關的複製操作
2.5.解構函式
物件生存期結束時,需要做清理工作,比如:釋放成員(指標)所佔有的儲存空間解構函式可以完成上述工作。
解構函式自動呼叫(隱式呼叫)
解構函式沒有返回值,不能有引數,也不能過載,因此在一個類中只能有一個解構函式,當撤銷物件時,編譯系統會自動呼叫解構函式。
格式:
類名::~類名()
{
函式語句
}
預設解構函式:若沒有顯式定義解構函式,則系統自動生成一個預設形式的解構函式。系統自動生成的預設解構函式形式如下:類名::~類名(){}
一般情況下,可以不定義解構函式,但如果類的資料成員中包含指標變數時從堆上進行儲存空間分配的話,需要在解構函式中進行儲存空間的回收
class Student
{
public:
Student(int n,string a_name,char s)
{
num=n;
name=a_name;
sex=s;cout<<"Constructor called."<<endl;
}
~Student()
{cout<<"Destructor called."<<endl;}
void display(){cout<<name<<endl;cout<<num<<endl;cout<<sex<<endl;}
private:
int num;string name;char sex;
};
3.類的其它成員
3.1常成員
常資料成員是指資料成員在例項化被初始化後約束為只讀;常成員函式是指成員函式的this指標被約束為指向常量的常指標,在函式體內不能修改資料成員的值。在類的成員函式說明後面可以加const關鍵字,則該成員函式成為常量成員函式。
1.常資料成員
使用const說明的資料成員成為常資料成員。如果在一個類中說明了常資料成員,那麼建構函式就只能通過初始化列表對該資料成員進行初始化,而任何其它函式都不能對該成員賦值。
(1)常資料成員可以在建構函式中直接用常量進行初始化,這樣,每個物件建立的常資料成員都有相同的值。
#include<iostream>
using namespace std;class Mclass
{
public:
int k;
const int M; //說明常資料成員
Mclass():M(5){} //用初始化對常資料成員賦值
void testFun()
{
//M++; //錯誤,不能在成員函式中修改常資料成員
k++;
}
};
int main()
{
Mclass t1,t2;
t1.k=100;
//ti.M=123; //錯誤,不能在類外修改常資料成員
cout<<"t1.k="<<t1.k<<'\t'<<"t1.M"<<endl;
t2.k=200;t2.testFun();
cout<<"&t1.M="<<&t1.M<<endl;
cout<<"t2.k="<<t2.k<<'\t'<<"t2.M"<<t2.M<<endl;
cout<<"&t2.M"<<&t2.M<<endl;
}
程式執行結果:
t1.k=100 ti.M=5
&t1.M=0012FF4C
t2.k=201 t2.M=5
&t2.M=0012FF3C
(2)另外一種對常資料成員進行初始化的方法是,使用到引數的建構函式,建立物件是,用實際引數對常資料成員賦值。這樣,每個常資料成員就可以有不同的初始值。
#include<iostream>
#include<cstring>
using namespace std;
struct Date
{int year,month,day;};
class Student
{
public:
Student(int y,int m,int d,int num=0,char *pname="no name");
void PrintStudent()const; //常成員函式
private:
const int code; //常資料成員
char name[20];
Date birthday; //結構資料成員
};
void Student::PrintStudent()const
{
cout<<"序號:"<<code<<"\t姓名:"<<name<<"\t"<<"出生日期:"<<birthday.year<<"-"<<birthday.month<<"-"<<birthday.day;
cout<<endl;
}
//帶引數結構函式完成資料成員初始化
Student::Student(int y,int m,int d,int num,char *pname):code(num)
{
strcpy_s(name,pname);
name[sizeof(name)-1]='\0';
birthday.year=y;
birthday.month=m;
birthday.day=d;
}
int main()
{
Student stu1(1997,5,30,20171769,"王克復");
stu1.PrintStudent();
Student stu2(1998,10,4,20171265,"生物能");
stu2.PrintStudent();
}
程式執行結果:
序號:20171769 姓名:王克復 出生日期:1997-5-30
序號:20171265 姓名:生物能 出生日期:1998-10-4
在上述程式中,student類有3個數據成員,其中,birthday是結構Date型別成員,code是常資料成員。在帶引數的建構函式中,特別地用引數初始化code(num)對this->code賦初值。這個值在建立物件後被約束為只讀。
2.常物件
若在定義物件的說明語句以const作字首,則該物件成為常物件。這個物件的全部資料成員在作用域中約束為只讀。
說明形式如下:類名const物件名[(引數表)];或者const 類名 物件名[(引數表)]
在定義常物件時必須進行初始化,而且不能被更新。
說明:(1)C++不允許直接或間接更改常物件的資料成員。
(2)C++規定常物件只能呼叫它的常成員函式、靜態成員函式、建構函式(具有共有訪問許可權)。
#include<iostream>
#include<cstring>
using namespace std;
class T_class
{
public:
int a,b;
T_class(int i,int j)
{ a=i;b=j;}
};
int main()
{
const T_class t1(1,2); //t1是常物件
T_class t2(3,4);
//t1.a=5; //錯誤,不能修改常物件的資料成員
//t1.b=6; //錯誤,不能修改常物件的資料成員
t2.a=7;
t2.b=8;
cout<<"t1.a="<<t1.a<<'\t'<<"t1.b="<<t1.b<<endl;
cout<<"t2.a="<<t2.a<<'\t'<<"t2.b="<<t2.b<<endl;
}
程式執行結構:
t1.a=1 t1.b=2
t2.a=7 t2.b=8
3.常成員函式
常成員函式的this指標被約束為指向常量的常指標。由於this指標隱含定義,因此常成員函式在函式頭以關鍵字const作為字尾。例如:
class X
{
int a;
int f() //常用函式
{ return a++;} //合法
int g()const //常成員函式
{return a++;} //錯誤,不能修改資料成員
};
常成員函式格式如下:
型別說明符 函式名(引數表) const;
3.2靜態成員
類成員冠以static宣告時,成為靜態成員。靜態資料成員為同類物件共享。靜態成員函式與靜態資料成員協同操作。靜態成員不屬於某一個單獨的物件,而是為類的所以物件所共有。靜態成員函式的作用不是為了物件之間的溝通,而是為了能處理靜態資料成員:保證在不依賴與某一個物件的情況下,訪問靜態資料成員
靜態資料成員在定義或說明是前面加關鍵字static,如:
class A
{
int n;
static int s;
};
呼叫靜態成員函式:
viod g()
{
X obj;
X::StaFun();
obj.StaFun();
}
說明:
(1)靜態成員函式在類外定義是不用static字首
(2)靜態成員函式主要用來訪問同一類中的靜態成員
(3)私有靜態成員函式不能在類外部或用物件訪問
(4)可以在建立物件之前處理靜態資料成員
(5)編譯系統將靜態成員函式限定為內部連線(在其他檔案中不可知)
(6)靜態成員函式中是沒有this指標的
(7)靜態成員函式不訪問同類中的非靜態資料成員。如有需要,只能通過物件名(或指向物件的指標)訪問該物件的非靜態成員
3.3友元
如果在本類以外的其它地方定義了一個函式
這個函式可以是不屬於任何類的非成員函式
也可以是其它類的成員函式
在類體中用friend對其進行宣告,此函式就稱為本類的友元函式
友元函式可以訪問這個類的私有成員
class A
{private:
int i;
friend void FriendFun(A *,int)
public:
void MemberFun(int);
};
...
void FriendFun(A * ptr,int x)
void A::MemberFun(int x)
{i=x;};
4.類的包含
類的包含是程式設計總一種軟體重要技術。即定義一個新的類是,通過編譯器把另一個類“抄“進來。
當一個類中含有已經定義的類型別成員,到引數的建構函式對資料成員初始化,必須使用初始化語法形式。
建構函式(形參表):物件成員1(形參表),...,物件粗n(形參表);
4.1物件成員初始化
出現粗物件是,該類的建構函式要包含物件成員的初始化。如果建構函式的初始化列表沒有對成員初始化是,則使用成員物件的無參(預設)建構函式。
建立一個類的物件時,要先執行成員物件自己的建構函式,再執行當前類的建構函式。
成員物件的建構函式呼叫次序和成員物件在類中的說明次序一致,與它們在成員初始化列表中出現的次序無關。
解構函式的呼叫順序相反。
簡單物件成員初始化:
#include<iostream>
using namespace std;
class A
{
public:
A(int x):a(x){}
int a;
};
class B
{
public:
B(int x,int y):aa(x) //用引數初始化呼叫成員類建構函式
{b=y;}
void out(){cout<<"aa="<<aa.a<<endl<<"b="<<b<<endl;}
private:
int b;
A aa; // 類型別成員
};
int main()
{
B objB(3,5);
objB.out();
}
程式執行結果:
aa=3
b=5
4.2.物件陣列
所謂物件陣列是指每一陣列元素都是物件的陣列。
定義一個以為物件陣列的格式如下:
類名 陣列名 [下表表示式];
成員物件陣列的初始化:
#include<iostream>
using namespace std;
class A
{
public:
A(int i=0):x(i){}
int x;
};
class B
{
public:
B():a[0](0),a[1](1){} //error C2059:語法錯誤:"["
A a[2];
};
int main()
{
B b;
cout<<b.a[0].x<<endl;
cout<<b.a[1].x<<endl;
return 0;
}