C++類&&對象的二次深入研究

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
這些都可以增加編譯器對代碼進行優化的機會

我明白啦,那麼~

這個&又是幹嘛的?爲什麼要出現在這裏?大佬們好像都這麼寫啊?

(程序姬再次素質三連)

這就是一個引用!
我們知道,拷貝構造函數的語法中必須有&:類名稱(類名稱 & 形參)
當我們用對象初始化對象的時候,編譯器會自動調用拷貝構造函數
一旦調用拷貝構造函數,就相當於創建了一個新的對象
而引用是原對象的影之分身(引用和原對象共享內存)
使用 & 可以避免調用拷貝構造函數創建新對象,而且我們還可以通過引用修改原對象,簡直不要太爽

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章