文章目錄
08類與對象
類的定義
注意一些符號。
class Student
{
private:
int age;
void setAge(int);
protected:
double height;
void setHeight(double);
public:
char* name;
void setName(char*);
char* getName();
double getHeight();
int getAge();
};
注意 :修飾符缺省,爲private
。
成員函數與成員數據的定義不分先後。
類與結構體
結構體類型與類的唯一的區別在於可見性:
類成員的缺省的存取權限是私有的;
結構體類型成員的缺省的存取權限是公有的。
結構體存在的主要原因:與C語言保持兼容
什麼時候用結構體而不用類:定義主要用來保存數據、沒有什麼操作的類型,這時用結構體更方便。
類與對象
- 在定義類時,只是定義了一種數據類型,並不爲類分配存儲空間。
- 只有在定義了類的實例(對象)後,系統纔會爲類的變量分配空間。
- 在建立對象時,只爲對象分配用於保存數據成員的內存空間,而成員函數的代碼爲該類的每一個對象所共享。
類的成員函數
①在類裏面聲明,外面實現,注意ClassName::
主要考慮到
(::
是域運算符,就如std::
是一樣,要指明是哪裏的函數,因爲函數可能有重名)
class Student
{
double height;
public:
void setHeight(double);
double getHeight();
};
void Student::setHeight(double h)
{
height = h;
}
double Student::getHeight(){
return height;
}
②直接在類裏面聲明並實現
class Student
{
double height;
public:
void setHeight(double h){
height = h;
}
double getHeight(){
return height;
}
};
- 內聯成員函數的實現方式
①如上面的情況,在類內聲明,在外面實現時,加上inline
class Student
{
double height;
public:
double getHeight();
};
inline double Student::getHeight(){
return height;
}
②在類內實現
這時簡單的成員函數在編譯時自動作爲內聯函數來實現的。
- 對象成員的使用
①對象名+.
運算符
A a;
a.成員名
②指針+->
運算符
A a;
A *pa = &a;
pa->成員名
③引用+.
運算符;
A &b = a;
b.成員名
也就是說;
class Student
{
public:
double height;
void setHeight(double);
};
inline void Student::setHeight(double h){
height = h ;
}
int main(){
Student s,*ps;
s.setHeight(1.75);
ps = &s;
cout<<s.height<<endl;
cout<<(*ps).height<<endl;
cout<<ps->height<<endl;
}
/*
1.75
1.75
1.75
*/
注意這三種是等價的,但一般不用(*ps).height
。
對象做參數
下面的小demo揭示了c++和java中對象作參數的差異性
(1)
#include<iostream>
using namespace std;
class Clock
{
public:
int time = 1;
};
int main(){
void setTime(Clock&);
void showTime(Clock&);
Clock t1;
setTime(t1);
showTime(t1);
return 0;
}
void setTime(Clock& t)
{
cin>>t.time;
}
void showTime(Clock& t)
{
cout<<t.time<<endl;
}
輸出結果
10(來自鍵盤)
10
(2)
#include<iostream>
using namespace std;
class Clock
{
public:
int time = 1;
};
int main(){
void setTime(Clock);
void showTime(Clock);
Clock t1;
setTime(t1);
showTime(t1);
return 0;
}
void setTime(Clock t)
{
cin>>t.time;
}
void showTime(Clock t)
{
cout<<t.time<<endl;
}
輸出結果
10(來自鍵盤)
1 (默認初始化值)
注意:
在java裏面,對象都是傳引用,就是控制那個對象;
但在c++裏面,如果沒有&
,相當於重新創建一個對象。
這裏在給出一個小栗子,供初學者思考
(i)
#include<iostream>
using namespace std;
class Clock
{
public:
int time = 1;
};
int main(){
void setTime(Clock,int time = 0);
void showTime(Clock);
Clock t1;
setTime(t1);
showTime(t1);
return 0;
}
void setTime(Clock t,int time)
{
t.time = time;
}
void showTime(Clock t)
{
cout<<t.time<<endl;
}
輸出結果
1
(ii)
#include<iostream>
using namespace std;
class Clock
{
public:
int time = 1;
};
int main(){
void setTime(Clock&,int time = 0);
void showTime(Clock&);
Clock t1;
setTime(t1);
showTime(t1);
return 0;
}
void setTime(Clock &t,int time)
{
t.time = time;
}
void showTime(Clock &t)
{
cout<<t.time<<endl;
}
輸出結果
0
(iii)
#include<iostream>
using namespace std;
class Clock
{
public:
int time = 1;
};
int main(){
void setTime(Clock&,int time = 0);
void showTime(Clock&);
Clock t1;
setTime(t1,10);
showTime(t1);
return 0;
}
void setTime(Clock &t,int time)
{
t.time = time;
}
void showTime(Clock &t)
{
cout<<t.time<<endl;
}
輸出結果
10
09類與對象的使用
構造函數和析構函數是類的兩種特殊的成員函數。
構造函數:在創建對象時,使用給定的值將對象的數據成員初始化,將對象初始化爲一個特定的初始狀態。
析構函數:在系統釋放對象前,做一些清理工作。
構造函數
格式:
ClassName:: ClassName(){}
- 構造函數的函數名必須與類名相同。
- 不能指定函數返回值的類型,也不能指定爲void類型,不能有return語句
- 一個類可以定義若干個構造函數,可以帶參數,也可以沒有參數。當定義多個構造函數時,必須滿足函數重載的原則。
- 構造函數可以指定參數的缺省值。
- 可以是內聯函數。
- 構造函數屬於類的成員函數,對私有數據成員、保護的數據成員和公有的數據成員均能進行初始化。
- 在建立類對象時自動調用構造函數。構造函數不需要用戶調用,也不能被用戶主動調用。 要注意這一點和java的區別!
比如:A a;
,就已經創建了一個類A的實例a,
不允許向java一樣A a = new A();
- demo
注意: A a1(); 是錯的,這相當於在聲明一個返回值類型爲A的函數。
#include<iostream>
using namespace std;
class A{
int x,y;
public:
A(int a = 1,int b = 2){
x = a ;
y = b ;
}
void print(){
cout<<"x "<<x<<" y "<<y<<endl;
}
};
int main(){
A a1;
a1.print();
// A a1(); 是錯的,這相當於在聲明一個返回值類型爲A的函數
A a2(3);
a2.print();
A a3(3,4);
a3.print();
return 0;
}
輸出結果
x 1 y 2
x 3 y 2
x 3 y 4
- 對象的分類
- 局部對象
函數內定義的auto型對象(auto一般省略)。
局部對象的生命週期就是那個代碼塊,或者是函數體。
在函數內每次定義對象時,都要調用構造函數。 - 靜態對象
static修飾的局部對象。首次定義對象時,只調用一次構造函數,對象一直存在。 - 全局對象
在還沒有調用main函數時,就創建好了。
全局對象: 在函數之外定義對象時,調用構造函數。
- 局部對象
一張圖區分這個過程:
-
缺省的構造函數
參數列表爲空,不爲數據成員設置初始值;
如果類內定義了成員的初始值,則使用類內定義的初始值;
如果沒有定義類內的初始值,則以默認方式初始化:基本類型的數據默認初始化的值是不確定的。在定義類時,只要顯式定義了一個類的構造函數,則編譯器就不產生缺省的構造函數。如果此時依然希望編譯器隱含生成默認構造函數,
可以使用A() = default;
。否則仍要使用缺省的就報錯。
要注意下面這種情況:
Case 1.參數表爲空的構造函數
Case 2.全部參數都有默認值的構造函數
編譯器無法區分,報錯!
- 用參數初始化表(member initializer list )對數據成員初始化
注意語法格式。
當然放在類聲明外面實現也是可以的。
class A{
int x,y;
public:
A(){}
A(int a,int b):x(a),y(b){}
};
- 委託構造函數
爲了避免代碼重複,可以使用委託構造函數。
委託構造函數使用類的其他構造函數執行初始化過程。
(一種方式)
class A{
int x,y,z;
public:
A():x(1),y(1),z(1){}
A(int a,int b,int c):x(a),y(b),z(c){}
};
(使用委託構造函數)
class A{
int x,y,z;
public:
A(int a,int b,int c):x(a),y(b),z(c){}
A():A(0,0,0){}
//實際上A()的構造委託給了A(int,int,int)
};
析構函數
析構函數的作用與構造函數正好相反,是在對象的生命期結束時,釋放系統爲對象所分配的空間。
在程序的執行過程中,當某一對象的生存期結束時,系統自動調用析構函數,收回爲對象分配的存儲空間。
格式:
ClassName:: ~ClassName(){}
- 析構函數的幾點說明:
- 析構函數是成員函數,函數體可寫在類體內,也可寫在類體外。
- 析構函數名必須是在類名前面加上字符“~”。
- 析構函數不能帶有任何參數,沒有返回值,不指定函數類型。
- 一個類只能定義一個析構函數,不允許重載。
- 析構函數是在撤消對象時由系統自動調用的。
- 若在類的定義中沒有顯式定義析構函數,則編譯器自動產生一個缺省的析構函數,其函數體爲空。ClassName::~ClassName() { };
一般情況下,調用析構函數的次序與調用構造函數的次序相反:
最先創建的對象,其對應的析構函數最後被調用
最後創建的對象,其對應的析構函數最先被調用。
demo
#include<iostream>
#include <string>
using namespace std;
class A{
int x,y;
string name;
public:
A(int a,int b):x(a),y(b){}
A():A(0,0){}
~A(){
cout<<name<<"調用析構函數"<<endl;
}
void setName(string s){
name = s;
}
void showName(){
cout<<name<<endl;
}
};
int main(){
A a1;
a1.setName("1");
a1.showName();
A a2;
a2.setName("2");
a2.showName();
cout<<"退出主函數"<<endl;
return 0;
}
輸出結果
1
2
退出主函數
2調用析構函數
1調用析構函數
-
構造函數與
new
、析構函數與delete
-
可以使用new運算符來動態地建立對象。建立時自動調用構造函數,以便完成初始化對象的數據成員, 最後返回這個動態對象的起始地址。(用指針保存)
-
用new建立類的對象時,可以使用帶參數的構造函數,也可以使用無參構造函數。
-
用new運算符產生的動態對象,當不再使用這個對象時,必須用delete運算符來釋放對象所佔用的存儲空間,有多少new,就有多少delete(不同於之前的對象創建,之前的對象生命週期結束,自動調用析構函數)。(這可沒有java的gc自動清理)
-
-
new
運算符
在程序執行期間,申請用於存放T類型對象的內存空間,並依初值列表賦以初值。
返回值:成功時,T類型的指針,指向新分配的內存; 失敗時,拋出異常。(i)有參數列表
new A(1,2,3);
(ii)無參數列表
new A;
注意,這裏和java的區別!!!
寫成`new A();`可就錯了!
delete
運算符
功能:釋放指針p所指向的內存。p必須是new操作的返回值。
A *pa;
pa = new A;
delete pa//調用了析構函數;
- 注意
如果在構造函數中用new爲對象的數據成員分配了動態存儲空間,則在類中 應該定義一個析構函數,並在析構函數中使用delete,收回由new分配的動態存儲空間。
demo
class String{
char *str;
int length;
public:
String(char* pstr)
{
if(pstr) //判斷是否爲空指針,注意和 if(*str) 的區別
{
length = (int)strlen(pstr);
str = new char[length+1]; //+1是爲了給'\0'留出空間
strcpy(str, pstr);
}
else{
str = 0;
length = 0;
}
}
void show(){cout<<str<<endl;}
//str的空間是new出來的,析構函數裏面必須delete掉
~String(){
if(str){
cout<<"調用析構函數"<<endl;
delete []str; //刪除連續的空間
}
}
};
int main()
{
String s("hello world");
s.show();
cout<<"主函數結束R"<<endl;
return 0;
}
hello world
主函數結束R
調用析構函數
- 不同存儲類型的對象調用構造函數及析構函數
1、對於全局對象(在函數外定義的對象),在程序開始執行時,調用構造函數;到程序結束時,調用析構函數。
2、對於局部對象(在函數內定義的對象),當程序執行到定義對象的地方時,調用構造函數;在退出對象的作用域時,調用析構函數。
3、用static定義的局部對象,在首次到達對象的定義時調用構造函數;程序結束時,調用析構函數
4、對於用new運算符動態生成的對象,在產生對象時調用構造函數,只有使用delete運算符釋放對象時,才調用析構函數。若不使用delete來撤消動態生成的對象,程序結束時,對象仍存在,並佔用相應的存儲空間,系統不能自動撤消動態生成的對象。
注意:
對象數組
demo:
class Box
{
private:
int h,w,l;
public:
Box(int h,int w,int l):h(h),w(w),l(l){}
int getV(){
return h*w*l;
}
};
int main()
{
Box boxes[3] = {Box(1,2,3),Box(2,3,4),Box(3,4,5)};
for(Box box:boxes)
{
cout<<box.getV()<<endl;
}
return 0;
}
-
動態創建對象數組
用new運算符來動態生成對象數組時,自動調用構造函數,
用delete運算符來釋放指針所指向的對象數組佔用的存儲空間時,在指針變量的前面必須加上[ ], 才能將數組元素所佔用的空間全部釋放。否則,只釋放第0個元素所佔用的空間。- 分配:
new 類型名T[數組長度]
數組長度可以是任何表達式,在運行時計算
- 分配:
-
釋放 delete[] 數組名p
釋放指針p所指向的數組。p必須是用new分配得到的數組首地址。
A *pa;
pa = new A[3];
……
delete []pa;
-
當使用delete時,僅僅調用了對象數組中第一個對象的析構函數
當使用delete [ ]時,將會逐個調用析構函數。
對象數組的內存釋放,需要做兩件事情:一是釋放最初申請的那部分空間,二是調用析構函數完成清理工作。
-
使用帶參數的構造函數初始化對象數組
語法
A *pa;
pa = new A[3]{{},{},{}};
#include<stdio.h>
#include<iostream>
#include<string>
#include<string.h>
using namespace std;
class A{
int x,y;
public:
A(int nx,int ny=1)
{
x = nx;
y = ny;
}
A():A(0,0){} //委託構造函數
void show(){
cout<<"x "<<x<<" y "<<y<<endl;
}
};
int main()
{
A *p = new A[2]; //必須在類聲明裏補上無參構造函數,否則找不到
for(int i=0;i<2;i++)
{
(p+i)->show();
}
A *pa = new A[3]{{1,20},{29,3},{2}};
for(int i=0;i<3;i++)
{
(pa+i)->show();
}
return 0;
}
x 0 y 0
x 0 y 0
x 1 y 20
x 29 y 3
x 2 y 1
對象指針
- 指向對象的指針
class A{
public:
void show(){cout<<"ok"<<endl;}
};
A *pa;
A a;
pa = &a;
//調用
a.show();
(*pa).show();
pa->show();
- 指向對象成員的指針
對象中的成員也有地址,存放對象成員地址的指針變量就是指向對象成員的指針變量。
class A{
public:
int x;
……
};
A a;
int *p = &a.x;
//調用
*p
this指針
- 在每一個成員函數中都包含一個特殊的指針,這個指針的名字是固定的,稱爲this。它是指向本類對象的指針,它的值是當前被調用的成員函數所屬對象的起始地址。
例如,當調用成員函數a.volume()時,編譯系統就把對象a的起始地址賦給this指針,在成員函數引用數據成員時,就按照this的指向找到對象a的數據成員。a.volume()函數要計算heightwidthlength的值,實際上是執行:(this->height)*(this->width)*(this->length)
當前this指向a, 相當於執行:(a.height)*(a.width)*(a.length)
const數據保護
要使數據能在一定範圍內共享,又要防止它不被修改,可以使用關鍵字const,把相關的數據定義爲常量。
措施:
- 對於既需要共享、又需要防止改變的數據應該聲明爲常類型(用const修飾)。
- 對於不改變對象狀態的成員函數應該聲明爲常函數。
常對象
通過常對象只能調用它的常成員函數,(哪怕這個函數實際上並沒有去修改對象的數據)。常成員函數可以訪問常對象中的數據成員,但仍然不允許修改常對象中數據成員的值。
必須在成員函數加上cons
const的位置。
-
可變的數據成員
有時編程時一定要修改常對象中的某個數據成員的值,ANSI C++考慮到實際編程時的需要,對此作了特殊的處理,對該數據成員聲明爲mutable, 如:mutable int count;把count聲明爲可變的數據成員後,在聲明爲const的成員函數中可以修改它的值。 -
mutable
關鍵字
class A{
mutable int x,y;
public:
……
};
常對象只保證其數據成員是常數據成員,其值不被修改。如果在常對象中的成員函數沒有加const聲明,編譯系統把它作爲普通成員函數處理。
不要誤認爲常對象中的成員函數都是常成員函數。
常成員
+ 1常數據成員
用關鍵字const來聲明常數據成員。常數據成員的值是不能改變的。
只能通過構造函數的參數初始化表對常數據成員進行初始化,不能採用在構造函數中對常數據成員賦初值的方法。如在類體中定義了常數據成員hour:const int hour; //聲明hour爲常數據成員
demo
class A{
const int x,y;
public:
A(int x,int y=1):x(x),y(y){}
};
- 2常成員函數
普通對象也能調用const函數。
一般的成員函數可以引用本類中的非const數據成員,也可以修改它們。
常成員函數只能引用本類中的數據成員,而不能修改它們。
常成員函數可以引用const數據成員,也可以引用非const的數據成員。
const數據成員可以被const成員函數引用,也可以被非const的成員函數引用。
數據成員 | 普通函數 | const 函數 |
---|---|---|
普通成員 | 可以引用,可以改變 | 可以引用,不能改變 |
const成員 | 可以引用,不能改變 | 可以引用,不能改變 |
const 對象 | 不能調用 | 可以調用,不能改變 |
如果要求所有的數據成員的值都不允許改變,則可以將所有的數據成員聲明爲const, 或將對象聲明爲const(常對象),然後用const成員函數引用數據成員,這樣起到“雙保險”的作用,切實保證了數據成員不被修改。
const
與函數重載
class A{
public:
int f()const{return 1;}
int f(){return 0;}
};
int main()
{
const A a;
A b;
cout<<a.f()<<endl;
cout<<b.f()<<endl;
return 0;
}
1
0
指向對象的常指針
- 指向對象的常指針變量的值不能改變,即始終指向同一個對象,但可以改變其所指向對象的值。
- 往往用常指針作爲函數的形參,目的是不允許在函數執行過程中改變指針變量的值,使其始終指向原來的對象。
- 如果想將一個指針變量固定地與一個對象相聯繫(即該指針變量始終指向一個對象),可以將它指定爲const型指針變量。
語法
類名 * const 指針變量名
注意const的位置!
A a1,a2;
A * const pa;
pa = &a1;
//違法
//pa = &a2;
指向常對象的指針變量
- 指向常變量的指針變量
注意const的位置
const 類型 * 指針變量名
int x = 1;
const int* px = &x;
*px = 2//違法,不能通過px改變x的值,但x可變
x = 3;
如果一個變量已被聲明爲常變量,只能用指向常變量的指針變量指向它,而不能用一般的(指向非const型變量的)指針變量去指向它。
-
如果函數的形參是指向非const型變量的指針,實參只能用指向非const變量的指針,而不能用指向const變量的指針。這樣,在執行函數的過程中可以改變形參指針變量所指向的變量(也就是實參指針所指向的變量)的值。
因爲如果傳入指向const型變量的指針,可能會報錯 -
如果函數的形參是指向const變量的指針,在執行函數過程中不能改變指針變量所指向的變量的值,因此允許實參是指向const變量的指針,或指向非const變量的指針。
無風險,函數正常工作。 -
指向常對象的指針變量
指向常對象的指針變量的概念和使用與指向常變量的指針變量類似- 如果一個對象被聲明爲常對象,只能用指向常對象的指針變量指向它,而不能用一般的(指向非const型對象的)指針變量去指向它。
- 如果定義了一個指向常對象的指針變量,不能通過它改變所指向的對象的值,但指針變量本身的值是可以改變的。
- 如果定義了一個指向常對象的指針變量,不能通過它改變所指向的對象的值,但指針變量本身的值是可以改變的。
對象的常引用
- 概念:
常引用:不能修改被引用的對象。
- const數據小結
形式 | 含義 |
---|---|
A const a | a 是常對象,在任何情況下不允許修改 |
void A::f() const | f()是A類中常成員函數,可以引用,但不允許修改類的成員數據 |
A * const p | p是指向A的實例的常指針,不能改變指向 |
const A * p | p 是指向A類常對象的指針,不能通過p修改此對象(此對象本身也許可以修改) |
const A &a1 = a | a1是對象a的別名,但不可以通過a1改變a(a自己也許可以改變) |