1. 程式人生 > >類的預設成員函式與友元函式

類的預設成員函式與友元函式

在C++中,類與C語言中的結構體類似,類與結構體的不同之處便是在其內部多了幾個成員函式還有幾個訪問限定符,訪問限定符有public(公共)、protected(保護)、private(私有),而成員函式總的來說共包括六大類,他們便是類與結構體的不同之處,六大預設成員函式分別是建構函式,拷貝建構函式,解構函式,賦值操作符過載,取地址操作符過載和const修飾的取地址操作符過載。

建構函式 ##-

——-一種隨著物件建立而自動被呼叫的公有成員函式,有且僅在定義物件時自動執行一次,主要是為物件作初始化

(1)函式名與類名相同;
(2)無返回值;
(3)物件構造(物件例項化)時系統自動呼叫對應的建構函式;
(4)建構函式可以過載(所以在一個類體內,不一定只有一個建構函式);
(5)建構函式可以在類中定義,也可以在類外定義;
(6)如果沒有給出建構函式,則C++編譯系統會自動生成預設建構函式;
(7) 無參與全預設值建構函式都是建構函式,但他們在類體內不能同時出現.
例如:

class Date
{
public:
    //無參
    Date()
    {}
    //帶參---全預設建構函式
    Date(int year = 1996, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //帶參---半預設建構函式
    Date(int year, int month=3, int day=1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private
: int _year; int _month; int _day; };
作用
  1. 完成物件的構造&初始化:
    使用引數列表對資料成員進行初始化:C++還提供了一種初始化資料成員的方式:初始化列表。初始化列表以冒號開頭,後跟一系列以逗號分隔的初始化欄位。例如:
class Date
{
public:
    Date(int year,int month,int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}
private:
    int
_year; int _month; int _day; };
  1. 每個成員在初始化列表中只能出現一次。
  2. 初始化列表僅用於初始化類的資料成員,並不指定這些資料成員的初始化順序,資料成員在類中定義順序就是在引數列表中的初始化順序。
  3. 儘量避免使用成員初始化成員,成員的初始化順序最好和成員的定義順序保持一致,對於類型別來說,使用初始化列表少了一次呼叫預設建構函式的過程。
  4. 這些必須放在初始化列表中:

    1. 常量成員,因為常量只能初始化不能賦值。
    2. 引用型別,因為引用必須在定義的時候初始化,並且不能重新賦值。
    3. 沒有預設建構函式的類型別,因為使用初始化列表可以不必呼叫預設建構函式來初始化,而是直接呼叫拷貝建構函式初始化。

2.在單參建構函式中,有型別轉換的作用,可以將其接受引數轉化成類型別物件。

class Date
{
public:
    Date(int year)
        :_year(year)
    {}
private:
    int _year;
    int _month;
    int _day;
};
void Test()
{
    Date d1(2018);
    //用一個整形變數給日期型別物件賦值
    //實際編譯器背後會用2019構造一個無名物件,最後用無名物件給d1進行賦值
    d1 = 2019;
}

【explicit】用explicit修飾建構函式,將會抑制建構函式的隱式轉換。即禁止單參建構函式型別的轉化。

解構函式

——是特殊的成員函式,做一些物件刪除前的相關清理工作。
(1)解構函式在類名前加上字首“~”;
(2)解構函式無參無返回值;
(3)一個類有且只有一個解構函式;(建構函式不止一個);
(4)物件生命週期結束時,C++編譯系統自動呼叫解構函式;


class Arry
{
public:
    Arry(int size)
    {
        _ptr = (int *)malloc(size*sizeof(int));
    }
    //解構函式
    ~Arry()
    {
        if (_ptr)
        {
            free(_ptr);
            _ptr = NULL;
        }
    }
private:
    int* _ptr;
};

拷貝建構函式

———如果程式想通過已有的物件複製出新的物件,就需要呼叫拷貝建構函式,拷貝建構函式是隻有單個形參,而且該形參是對本類型別的引用(常用const)的建構函式,拷貝建構函式是特殊的建構函式,建立物件時使用已存在的同類物件來進行初始化,當我們沒提供拷貝建構函式時編譯器會自動建立預設的拷貝建構函式。

  • 它是建構函式的過載。
  • 它的引數必須使用同類型物件的引用傳遞。否則會在棧空間一直遞迴的呼叫拷貝建構函式
  • 如果沒有顯式定義,系統會自動合成一個預設的拷貝建構函式。預設的拷貝構造數會依次拷貝類的資料成員完成初始化。
Mystring(const Mystring& s)
    {
        _str = new char[strlen(s._str) + 1];
        strcpy(_str, s._str);
    }
    Mystring s1;
    拷貝建構函式的2種呼叫方式
    Mystring s2(s1);
    Mystring s3=s2;

但為什麼拷貝建構函式傳遞的引數為引用呢?

使用傳值傳遞會引發無窮遞迴呼叫,如上的呼叫方式,拷貝建構函式傳入的引數為Mystring的一個例項s,由於是傳值引數,所以會把形參s複製到實參s1又呼叫拷貝建構函式,所以會無窮的遞迴。

【使用場景】
1、物件例項化物件

Date d1(1990, 1, 1);
Date d2(d1);

2、傳值方式作為函式的引數

int operator-(const Date& d)
{}

3、傳值方式作為函式返回值

Date operator+(int days)
{
    Date date;
    return date;
}

運算子過載
為了增強程式的可讀性,C++支援運算子過載。
運算子過載特徵:

  • operator+ 合法的運算子 構成函式名(過載<運算子的函式名:operator< )。
  • 過載運算子以後,不能改變運算子的優先順序/結合性/運算元個數。

5個C++不能過載的運算子: .*/::/sizeof/?:/.

賦值運算子過載

  • 賦值運算子的過載是對一個已存在的物件進行拷貝賦值。
  • 當程式沒有顯式的提供一個以本類或本類的引用為引數的賦值運算子過載函式時,編譯器會自動生成一個賦值運算子過載函式。
class Date
{
public:
    Date()
    {}
    Date(int year,int month,int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}

    Date& operator=(const Date &d)//賦值運算子過載
    {
       ifthis!=&d)
      {
        _year = date._year;
        _month = date._month;
        _day = date._day;
      }
    return *this;
  }

private:
    int _year; 
    int _month;
    int _day;
};
int main()
{
    Date d1(2017,10,29);
    Date d2;
    d2 = d1;
    return 0;
}

類的取地址操作符過載 及const修飾的取地址操作符過載

一般不用重新定義,編譯器預設會生成。

class Date
{
public :
     Date* operator &()
    {
        return this ;
    }
     const Date * operator&() const
    {
       return this ;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};

友元

友元可以是一個函式,該函式被稱為友元函式;友元也可以是一個類,該類被稱為友元類,在這種情況下,整個類及其所有成員都是友元。

友元函式
類的友元函式是定義在類外部的普通函式,但有權訪問類的所有私有(private)成員和保護(protected)成員。儘管友元函式的原型有在類的定義中出現過,但是友元函式並不是成員函式。因此它不屬於任何類,但需要在類的內部宣告。如果要宣告函式為一個類的友元,需要在類定義中該函式原型前使用關鍵字 friend。
例如用友元函式實現輸出運算子的過載:

#include<iostream>
using namespace std;

class Date
{
    friend ostream& operator<<(ostream& _cout, const Date& d);
public:
    Date(int year, int month, int day)
        :_year(year)
        , _month(month)
        , _day(day)
    {}
private:
    int _year;
    int _month;
    int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
    _cout << d._year << "-" << d._month << "-" << d._day;
    return _cout;
}

int main()
{
    Date d(2018,3,1);
    cout << d << endl;
    system("pause");
    return 0;   
}

注意:

  • 友元函式其宣告可以放在類的私有部分,也可放在共有部分。友元函式的定義在類體外實現,不需要加類限定。
  • 一個函式可以是多個類友元函式。
  • 友元函式可以訪問類中的私有成員和其他資料,不受類訪問限定符限制。
  • 友元函式在呼叫上同一般函式一樣,不必通過對物件進行引用。
  • 友元函式不能用const修飾。

友元類
友元類的所有成員函式都可以是另一個類的友元函式,都可以訪問另一個類中的非公有成員。

class Date;
class Time
{
    friend class Date;//宣告日期類為時間類的友元類
public:
    Time(int hour,int minute,int second)
        :_hour(hour)
        , _minute(minute)
        , _second(second)
    {}
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
public:
    Date(int year=1990, int month=1, int day=3)
        :_year(year)
        , _month(month)
        , _day(day)
    {}
    void  SetTimeOfDate(int hour, int minute, int second)
    {
    //可直接訪問時間類私有的成員變數
        _t._hour = hour;
        _t._minute = minute;
        _t._second = second;
    }
private:
    int _year;
    int _month;
    int _day;
    Time _t;
};

友元的優缺點
優點:可以使定義為友元函式或者友元類的函式直接訪問另一個類中的私有成員和受保護成員,提高效率,方便程式設計。
缺點:破壞了類的封裝性,提供了安全漏洞
注意:

  • 友元關係是單向的,不具有交換性
  • 友元關係不能傳遞
  • 友元關係不能繼承