1. 程式人生 > >【C++】類的預設成員函式

【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;
	}