const對象 & const成員函數
一些對象是支持修改的,而另外一些是堅決不允許修改的。我們可以使用關鍵字const來指定對象爲不可修改,這樣任何試圖修改對象的操作都將導致編譯錯誤。
將變量和對象聲明爲const可以提高性能,編譯器可以對常量提供某些針對const關鍵字的特殊優化
class Time{
public:
Time(int h,int m,int s):hour(h),minute(m),second(s);
private:
int hour,minute,second;
};
...
const Time noon(12,0,0);
在上面的栗子中,我們申請了一個const對象:noon
那麼const對象有什麼特性呢?
const對象只能調用該類的const member function ( 常成員函數 )
對象類型 | 成員函數類型 | 是否可以成功調用 |
---|---|---|
const | const | √ |
const | non-const | - |
non-const | const | √ |
non-const | non-const | √ |
那麼什麼是const成員函數 ( 常成員函數 ) 呢?
class Time{
public:
Time(int h,int m,int s):hour(h),minute(m),second(s) {}
void setHour(int);
void setMinute(int);
void setSecond(int);
int getHour() const;
int getMinute() const;
int getSecond() const;
private:
int hour,minute,second;
};
getHour(),getMinute(),getSecond()是常成員函數
我們憑藉敏銳的觀察力可以發現,常成員函數在定義的時候,函數簽名後會緊跟一個const
注意
- 常成員函數的基本特徵有:
- 顯式地聲明爲const
- 不能修改本對象(的數據成員),但是可以修改非本對象的數據成員
- 不能調用本對象的其他non-const成員函數,但是可以調用非本對象的non-const成員函數
- 構造函數和析構函數不能聲明爲const
- 對象的常量特性體現在初始化(構造)後,析構之前
- 建議將所有不更改對象成員的函數均聲明爲const成員函數,提高程序效率
構造函數初始化列表
初學者在編寫構造函數的碼風:
Time::Time(int h,int m,int s) {
hour=h;
minute=m;
second=s;
}
那麼現在可以告訴你啦~
這樣的寫法叫做賦值,不是真正的初始化
最正宗的初始化方法是構造函數初始化列表:
Time(int h,int m,int s):hour(h),minute(m),second(s) {}
//括號外是private數據成員,括號內是其對應的初始值
//不要忘了後面的{}
所有的類成員都可以用構造函數初始化列表進行初始化,但是有一些特殊情況只能使用初始化列表進行初始化:
- const data member 常數據成員 (特例:const static integer)
- reference data member 引用類型的數據成員
- member objects 數據成員是其他類(而且未提供缺省構造函數)的對象
- base class 繼承類的基類
//Increment.h
class Increment{
public:
Increment(const char*,int =0,int =1);
//帶默認參數的缺省構造函數
void addIncrement() {
count+=increment;
}
void print() const;
//常成員函數
private:
int count;
const int increment;
//常數據成員,只能使用初始化列表進行初始化
int &refCount;
//引用類型的數據成員,只能使用初始化列表進行初始化
const char* _name;
//常量指針,指向char類型常量,字符串不支持修改
};
//Increment.cpp
#include<iostream>
using namespace std;
Increment::Increment(const char* name,int c,int i) : _name(&name),count(c),increment(i),refCount(count)
//注意_name的初始化,是對指針類型變量的初始化,所以要用取地址符&name
//引用類型的數據成員初始化 refCount(count)
{
cout<<"Now count="<<count<<",increment="<<increment<<",refCount="<<refCount<<endl;
refCount=99;
}
void Increment::print() const {
cout<<"count="<<count<<",increment="<<increment<<endl;
}
//Test.cpp
Increment member(10,5);
member.print();
// Output
Now count=10,increment=5,refCount=10
count=99,increment=5
組合:對象作爲類的成員
就像標題一樣,這一部分我們要嘗試將對象作爲類的成員
class Date{
public:
Date(int =2020,int =1,int =1);
private:
int year,month,day;
};
class Time{
public:
Time(int =0,int =0,int =0);
private:
int hour,minute,second;
Date D; //成員對象
};
第一個問題:如何初始化?
普通的數據成員我們已經會用構造函數初始化列表解決了,規範又拉風
但是成員對象怎麼初始化呢?
(爲了方面描述,下面我們就利用Time和Date來描述這兩個類之間的關係)
- 若Date有缺省構造函數(未自定義或有默認實參的構造函數),則允許在Time的構造函數初始化列表中不對成員對象Date進行初始化,隨後通過Time的public類set函數對成員對象進行賦值
因爲在申請成員對象的時候會隱式調用成員對象的缺省構造函數,所以沒有特別的初始化也可以成功 - 若Date無缺省構造函數,要在構造函數中初始化成員對象,就必須使用初始化列表完成成員對象的初始化
這時調用Date的拷貝構造函數 - 若成員對象沒有顯式通過成員初始化列表初始化,則自動隱式調用其缺省構造函數
對象創建的步驟
- 分配內存,數據成員初始化
- 按照類定義的順序,爲數據成員分配內存空間,並初始化每個數據成員
- 如果初始化列表中未指定初始化,則使用系統提供的缺省構造函數進行初始化
- 執行constructor函數體內的語句
構造函數初始化列表
必須使用的情況:
- const data member 常數據成員 (特例:const static integer)
- member object without default constructor 無缺省構造函數的成員對象
- reference data member 引用類型的數據成員
- base class 繼承類的基類
不能使用的情況:
- 常量數組
解決方法:
const static int array[];
const int 類名::array[]={1,2,3};
建議:確保初始化列表中成員列出的順序和成員在類內的聲明順序一致
成員對象的構造和析構順序
- 成員對象的構造先於宿主對象
- 成員對象按照類定義中的聲明順序構造
- 成員對象的析構後於宿主對象
#include<iostream>
using namespace std;
class Date{
public:
Date(int y=2020,int m=3,int d=11)
:year(y),month(m),day(d) {cout<<"Create Date\n";}
//有默認參數的缺省構造函數
~Date() {cout<<"Destroy Date\n";}
Date(Date &d) //拷貝構造函數
{
setData(d.year,d.month,d.day);
cout<<"Copy Constructor is called.\n";
}
void setData(int y,int m,int d) {year=y,month=m,day=d;}
void print() const{
cout<<"Date:"<<year<<"/"<<month<<"/"<<day<<" ";
}
private:
int year,month,day;
};
class Time{
public:
Time(int h,int m,int s,Date &day)
:hour(h),minute(m),second(s),D(day) {cout<<"Create Time\n";}
//初始化列表中D(day)調用的是拷貝構造函數
~Time() {cout<<"Destroy Time\n";}
void print() const {
D.print();
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
private:
int hour,minute,second;
Date D; //成員對象
};
int main()
{
Date Today;
Time Now(22,57,45,Today);
Now.print();
return 0;
}
//Output
Create Date
Copy Constructor is called.
Create Time
2020/3/11 22:57:45
Destroy Time
Destroy Date
友元函數 & 友元類
爲什麼開發C++的前輩總是想搞事情:如果我想要讓類外部(不屬於該類)的函數或類,能夠訪問該類的非公有成員,怎麼辦呢?
Friend Function(友元函數) & Friend Class(友元類 )
使用方法說簡單也很簡單,只要在非公有成員所在類中用friend關鍵字聲明即可
被聲明的內容可以在該類class大括號之外的區域,訪問該類的非公有成員了
(注意我的表述,認真理解參悟friend關鍵字的玄妙之處)
class Count{
friend void setX(Count &,int);
public:
Count():x(0) {}
void print() const {cout<<x<<endl;}
private:
int x;
};
void setX(Count &c,int value) {c.x=value;}
//---
int main()
{
Count counter;
counter.print();
setX(counter,3);
counter.print();
return 0 ;
}
//Output
0
3
class ClassOne {
friend class ClassTwo;
int x,y;
};
class ClassTwo{
public:
void setX(ClassOne &one,int value) {one.x=value;}
void setY(ClassOne &one,int value) {one.y=value;}
void print(ClassOne &one) {
cout<<"one.x="<<one.x<<",one.y="<<one.y<<endl;
}
};
//---
int main()
{
ClassOne one;
ClassTwo two;
two.setX(one,3);
two.setY(one,9);
two.print(one);
return 0 ;
}
//Output
one.x=3,one.y=9
友元函數 & 友元類的存在意義:提高性能
典型應用:運算符重載
this指針
之前我們簡單瞭解了this指針
this指針的原型:<類名> *const this
特例:static member function
我們既可以隱式調用this指針,也可以顯式調用:
this->x
(*this).x
然而,this指針還有一個更玄妙的用法:級聯函數調用
看一下這個栗子:
//Time.h
class Time {
public:
Time(int =0,int =0,int =0);
Time &setTime1(int,int,int);
Time setTime2(int,int,int);
void print() const;
private:
int hour,minute,second;
};
//Time.cpp
Time &Time::setTime1(int h,int m,int s) {
hour=h;
minute=m;
second=s;
return *this;
}
Time Time::setTime2(int h,int m,int s) {
hour=h;
minute=m;
second=s;
return *this;
}
void Time::print() const {
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
//TimeTest.cpp
int main()
{
Time t1,t2;
t1.setTime1(20,20,20)
.setTime1(18,20,22);
t2.setTime2(20,20,20)
.setTime2(18,20,22);
t1.print();
t2.print();
return 0;
}
//Output
18 20 22
20 20 20
簡單解釋一下:
setTime1是Time &
類型的函數,返回值是一個引用
引用是原對象的影之分身,兩者共享內存,所以兩次調用setTime1,修改的都是本對象*this的數據成員
Time &setTime1(int h,int m,int s);
t1.setTime1(20,20,20)
.setTime1(18,20,22);
// equal to
Time &tmp=t1.setTime1(20,20,20);
tmp.setTime1(18,20,22);
setTime2是Time
類型的函數,返回值是一個Time類的對象
tmp享有獨立內存,所以第二次調用setTime2,修改的都是tmp的數據成員
Time setTime2(int h,int m,int s);
t2.setTime2(20,20,20)
.setTime2(18,20,22);
//equal to
Time tmp=t2.setTime2(20,20,20);
tmp.setTime2(18,20,22);
有感而發關於const和&的思考
我要把這些雜七雜八的思考都藏到最後(反正blog寫出來沒有人看QwQ)
我是怎麼想到這個問題的呢?
深夜,我深情凝望着我嘔心瀝血寫出的高精度加減(類)代碼
小小的腦袋裏裝滿了大大的問號:
HugeInteger add(const HugeInteger &);
這個const的幹嘛的?爲什麼要出現在這裏?大佬們好像都這麼寫啊?
(程序姬素質三連)
在函數的參數中使用const,可以讓編譯器知道在函數調用過程中,不會修改這個參數,從而可以提供給編譯器更多的優化機會
然而const屬性加入後,這個參數就會變成常量,如果要將這個參數傳到其他函數中,就必須在形參列表中加入const屬性
同理,我們有時會標明某個成員函數是const,表示這個成員函數不會修改這個類對象的任何數據
對於一個函數,如果某個指針參數指向的內容不會被修改,就應該加上const屬性
此外,對於常數變量,同樣也要在能夠添加const時就添加const
這些都可以增加編譯器對代碼進行優化的機會
我明白啦,那麼~
這個&又是幹嘛的?爲什麼要出現在這裏?大佬們好像都這麼寫啊?
(程序姬再次素質三連)
這就是一個引用!
我們知道,拷貝構造函數的語法中必須有&:類名稱(類名稱 & 形參)
當我們用對象初始化對象的時候,編譯器會自動調用拷貝構造函數
一旦調用拷貝構造函數,就相當於創建了一個新的對象
而引用是原對象的影之分身(引用和原對象共享內存)
使用 &
可以避免調用拷貝構造函數創建新對象,而且我們還可以通過引用修改原對象,簡直不要太爽