【C++】類的預設成員函式
1.建構函式 2.拷貝建構函式 3.解構函式、 4.運算子過載---實現一個日期計算器
c++類有6個預設成員函式:建構函式,拷貝建構函式,解構函式,賦值操作符過載。取地址操作符過載,const修飾的取地址操作符過載。其中前四個預設成員函式是我們重點研究物件。
1.建構函式
成員變數為私有的,要對他們進行初始化,必須用一個公有成員函式來進行。同時這個函式有且僅在定義物件時自動執行一次,這時呼叫的函式稱為建構函式。 建構函式是特殊的成員函式,其特徵
1.1函式名與類名相同。 1.2無返回值 1.3物件建構函式(物件例項化)時系統自動呼叫對應的建構函式。 1.4建構函式可以過載。 1.5建構函式可以在類中定義也可以在類外定義。 1.6如果類定義中沒有給出建構函式,則c++編譯器會預設生成一個預設的建構函式,但只要我們定義了一個建構函式,系統就不會自動生成預設的建構函式。 1.7無參的建構函式和全預設值的建構函式都認為是預設的建構函式,並且預設的建構函式只能有一個。
無參的建構函式和帶參的建構函式 class Date { public: Date() //無參的建構函式 {} Date(int year,int month,int day) //帶參的建構函式 { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; } void test { Date d1; //呼叫無參的建構函式 Date d2(2018,9,24); //呼叫帶參的建構函式 Date d3(); //注意這裡沒有呼叫對d3的建構函式定義出d3 }
建構函式最好定義成全預設的,如下:
Date(int year = 1999,int month = 1,int day = 1)//最好定義為全預設 { //檢查日期是否合法 if(year < 1900 || month < 1 || month > 12 || day < 1 || day > GetMonthDay(year,month)) { cout<<"非法日期"<<endl; } _year = year; _month = month; _day = day; } GetMonthDay()是一個獲取一個月有多少天的函式,實現如下: int GetMonthDay(int year,int month) { static int MonthDay[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; int day = MonthDay[month]; if(month == 2 && ((year%400 == 0) || (year%4 == 0 && year%100 != 0))) { day += 1; } return day; }
2.拷貝建構函式建立物件時使用同類物件來進行初始化,這時所用的函式稱為拷貝建構函式,拷貝建構函式是特殊的建構函式。 特徵如下: 2.1拷貝建構函式其實是一個建構函式的過載。 2.2拷貝建構函式的引數必須使用引用傳參,使用傳值方式會引發無窮遞迴。(why?會在下面講解) 2.3若未顯示定義,系統會預設生成一個預設的拷貝建構函式。預設的拷貝建構函式會依次拷貝類成員及進行初始化。
為什麼傳值方式會引發無窮遞迴?
//為什麼傳值會引發無窮遞迴
//Date d2(d1)
Date(Date d)
{
_year = d._year;
}
假如我們要拿d1拷貝構造一個d2,用如上傳值的拷貝建構函式來實現的話是有如下實現模型:把d1傳給d,d這個物件不存在,實際上就是拿d1拷貝構造一個d;拷貝構造的時候又要傳參。。拷貝傳參拷貝傳參這樣一直下去就是一個無窮遞迴呼叫。
我們這裡使用日期類的例子來講解類的預設成員函式,日期類可以不用自己定義拷貝建構函式,但是有的類就必須自己實現一個拷貝建構函式,比如順序表。順序表系統自動生成的用不了,因為會產生記憶體洩漏,這其實是一個淺拷貝的問題,這個問題暫時就不進行詳細說明。
Date(const Date& d) //拷貝建構函式
//引數為了防止在函式內發生意外的改變,最好加上const
{
_year = d._year;
_month = d._month;
_day = d._day;
} 為什麼這裡物件可以直接訪問私有的成員變數?1.在類的成員函式中可以直接訪問同類物件的私有/保護成員。。。。2.c++的訪問限定符是以類為單位的,也就是說在這個單元內的成員可以互相訪問。
Date d1;
Date d2 = d1; //呼叫拷貝建構函式
Date d3(d1); //呼叫拷貝建構函式
3.解構函式 當一個物件生命週期結束時,c++編譯器會自動呼叫一個成員函式,這個特殊的成員函式叫解構函式。特徵: 3.1解構函式在類名前面加~。 3.2解構函式無引數無返回值 3.3一個類有且只有一個解構函式,若未顯示定義,系統會自動生成預設的解構函式。 3.4物件生命週期結束時,c++編譯器會自動呼叫解構函式。 3.5注意解構函式函式體內並不是刪除物件,而是做一些清理工作。這裡的日期類可以不清理,但有的類必須及逆行清理,比如順序表vector,不然會記憶體洩露,清不清理主要看有沒有動態的資源。
4.運算子過載 運算子為了增強程式的可讀性。 4.1特徵: 4.1.1operator+合法的運算子 構成函式名(過載<運算子函式名:operator<) 4.1.2過載運算子以後,不能改變運算子的優先順序/結合性/運算元個數。 4.2考點:5個c++不能過載的運算子:* :: sizeof ?: . 4.3賦值運算子過載 拷貝建構函式是建立的物件,使用一個已有物件來初始化這個準備建立的物件。賦值運算子的過載是對一個已存在的物件進行拷貝賦值。
//d2 = d3 => d2.operator=(&d2,d3)
Date& operator=(const Date& d) //有倆個引數,但只寫一個,因為還有一個引數是隱含的this指標。
{
if(this != &d) //是否是自己給自己賦值。沒有什麼很壞的影響,只是白做了而已。
{
this->_year = d._year; //this可以顯示的寫出來,也可以不寫,寫著this在這裡方便於觀察。
this->_month = d._month;
this->_day = d._day;
}
return *this; //賦值操作已經完成,為什麼有個返回值?因為賦值運算子的過載支援三個數,i=j=k;k先賦給j後有一個返回值j,將這個返回值賦給i,返回的是同類型物件,所以型別為Date
//但此時如果沒有給函式型別加引用,就是傳值返回,不會直接返回,會建立一個臨時物件。。會多一次拷貝構造,拷貝一個臨時物件,再拿這個臨時物件做返回
//傳值返回:返回的是一個臨時物件
//傳引用返回:返回的物件出了作用域還在
}
Date d1;
Date d2(d1); //呼叫拷貝建構函式
Date d3;
d3 = d1; //呼叫賦值運算子的過載
傳值返回和傳引用返回:傳值返回:返回的是一個臨時物件 傳引用返回:返回的物件出了作用域還在
通過運算子的過載我們可以實現一個更好玩的東西:如下圖
要完成這個,我們得實現以下的函式
Date& operator+=(int day)
Date& operator+(int day)
Date& operator-=(int day)
Date& operator-(int day)
int operator-(const Date& d)
實現如下:
Date& operator+=(int day)
{
if(day < 0)
{
return *this -= -day;
}
_day += day;
while(_day > GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year,_month);
_month++;
if(_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
//d+10
Date operator+(int day)
{
Date ret(*this);//*this 是d
ret += day;
return ret;
}
Date& operator-=(int day)
{
if(day < 0)
{
return *this += -day;
}
_day -= day;
while(_day <= 0)
{
--_month;
if(_month == 0)
{
_year--;
_month = 12;
}
_day+=GetMonthDay(_year,_month);
}
return *this;
}
Date operator-(int day)
{
Date ret(*this);
ret -= day;
return ret;
}
int operator-(const Date& d) //不加const d2會被改
{
int flag = 1;
Date max = *this;
Date min = d;
if(*this < d)
{
max = d;
min = *this;
flag = -1;
}
int day = 0;
while(min < max)
{
++(min);
++day;
}
return day*flag;
}
//++d => d.operator++(&d)
Date& operator++() //前置 返回值是++後的值
{
*this += 1;
return *this;
}
//d++ => operator++(&d,0)
Date operator++(int) //後置 int只是為了與前置做一個區分 返回的是++前的值
{
Date ret(*this);
*this += 1;
return ret;
}
Date& operator--()
{
*this -= 1;
return *this;
}
Date operator--(int)
{
Date ret(*this);
*this -= 1;
return ret;
}
bool operator>(const Date& d)
{
if(_year > d._year)
{
return true;
}
else if(_year == d._year)
{
if(_month > d._month)
{
return true;
}
else if(_month == d._month)
{
if(_day > d._day)
return true;
}
}
return false;
}
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&&_day == d._day;
}
bool operator>=(const Date& d)
{
return *this > d || *this == d;
}
bool operator<(const Date& d)
{
return !(*this >= d);
}
bool operator<=(const Date& d)
{
return !(*this > d);
}
bool operator!=(const Date& d)
{
return !(*this == d);
}
void Display()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}