1.類的6個默認成員函數
類六個默認函數包括構造、拷貝構造、析構、賦值運算符重載、取地址操作符重載、const修飾的取地址操作符重載,默認生成
2.構造函數
構造函數是一個特殊的成員函數,名字與類名相同,創建類類型對象時由編譯器自動調用,保證每個數據成員
都有 一個合適的初始值,並且在對象的生命週期內只調用一次
特性如下
1. 函數名與類名相同。
2. 無返回值。(也沒有void返回值)
3. 對象實例化時編譯器自動調用對應的構造函數。
4. 構造函數可以重載
如果類中沒有顯式定義構造函數,則C++編譯器會自動生成一個無參的默認構造函數,一旦用戶顯式定
義編譯器將不再生成。
構造出的對象元素爲隨機值
重點:無參的構造函數和全缺省的構造函數都稱爲默認構造函數,並且默認構造函數只能有一個。注意:無參構造函數、全缺省構造函數、我們沒寫編譯器默認生成的構造函數,都可以認爲是默認成員
class A
{
private:
int a;
public:
A(int b=0)
{
......
}
A()
{
......
}
}
上面的代碼會編譯錯誤,因爲程序無法判斷,當設置一個無參數傳遞的對象調用哪一個構造函數。
小知識點:C++把類型分成內置類型(基本類型)和自定義類型。內置類型就是語法已經定義好的類型:如int/char...,自定義類型就是我們使用class/struct/union自己定義的類型,編譯器生成默認的構造函數會對自定類型成員t調用的它的默認成員如下
#include <iostream>
using namespace std;
class A
{
int _a;
public:
A(int a=0)
{
a = _a;
cout<<"使用a的構造函數"<<endl;
}
};
class B
{
int _b;
A a;
public:
};
int main()
{
B b;
}
小知識點:作爲成員變量命名最好使用_加名字用來提高代碼的可讀性,也有加m_(member的首字母)的寫法
3.析構函數
與構造函數相對,析構函數:與構造函數功能相反,析構函數不是完成對象的銷燬,局部對象銷燬工作是由編譯器完成的。而對象在銷燬時會自動調用析構函數,完成類的一些資源清理工作,如內存的釋放,指針的歸零等。
析構函數是特殊的成員函數。其特徵如下:
1. 析構函數名是在類名前加上字符 ~。
2. 無參數無返回值。
3. 一個類有且只有一個析構函數。若未顯式定義,系統會自動生成默認的析構函數。
4. 對象生命週期結束時,C++編譯系統系統自動調用析構函數。系統會自動生成默認的析構函數。
4. 拷貝構造函數
屬於構造函數的一種,參數爲類的類型的引用,作用用來拷貝一個對象就像是ctrl c ctrl v的功能一樣,例子如下
#include <iostream>
using namespace std;
class A
{
int _a;
public:
A(int a=0)
{
a = _a;
cout<<"使用a的構造函數"<<endl;
}
A(A& a)
{
_a = a._a;
}
};
int main()
{
B b;
}
注意的是系統默認的拷貝函數使用的是淺拷貝,逐個字節進行拷貝,對於一般類型的成員不會出錯,但是對於指針成員,拷貝出的新的指針成員,會指向相同的地址,產生一系列的麻煩。需要人爲寫拷貝函數,進行深拷貝。
知識點:拷貝函數的形參必須是引用傳遞,否則會引起無窮遞歸問題,
A(A a1)
{
_a = a1._a;
}//值傳遞
//需要給 形參賦值,調用拷貝函數
A(A a1)//上一個的形參
{
_a1 = a1._a;
}//值傳遞給 形參的形參 賦值,調用拷貝函數
A(A a1)
{
_a1 = a1._a;
}//值傳遞給 形參的形參 的形參賦值,調用拷貝函數
A(A a1)
{
_a1 = a1._a;
}//值傳遞給 形參的形參 的形參 的形參賦值,調用拷貝函數
A(A a1)
{
_a1 = a1._a;
}//值傳遞給 形參的形參 的形參 的形參 的形參賦值,調用拷貝函數
5.賦值運算符重載
根據邏輯,有時候需要比較兩個對象的大小或其他屬性,但是C++不支持自定義類型的對象的比較大小等操作需要重載運算符,
函數名字爲:關鍵字operator後面接需要重載的運算符符號。
函數原型:返回值類型 operator操作符(參數列表)
但是重載需要注意以下幾點
1.重載無法創造新的運算符如@等
2.重載操作數至少有一個類類型或者聯合類型
3.無法重載內置類型(int,char等)的操作符
4.共有5個操作符無法重載:.* 、:: 、sizeof 、?: 、. (注意第一個是點*而不是*)
例子如下爲對日期的重載:爲初始版本十分臃腫,精簡的方法爲函數的複用:具體看
這裏:https://blog.csdn.net/cat_want_fly/article/details/86506354
#include <iostream>
using namespace std;
class Date
{
public:
int GetMonthDay(int year, int month)
{
int monthArray[13] = { 0, 31,28,31,30,31,30,31,31,30,31,30,31};
if (month == 2 && year%4 == 0&&year%100 !=0||year%400 ==0)
return 29;
else
return monthArray[month];
}
Date(int year = 2019, int month = 1, int day = 1)
{
if (year > 0
&&month > 0
&& month < 13
&&day > 0
&&day <= GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
// 四個成員函數
else
{
cout << "日期非法" << endl;
_year = 0;
_month = 0;
_day = 0 ;
}
}
print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
Date(Date &date)
{
_year = date._year;
_month= date._month;
_day = date._day;
}
Date& operator+=(int day)
{ _day += day;
while(_day>GetMonthDay( _year, _month))
{
if(_month==12)
{
_month = 0;
_year++;
}
if(_month!=0)
{
_day -= GetMonthDay(_year, _month);
_month++;
}
if(_month==0)
{
_day -= GetMonthDay(_year, 12);
_month++;
}
}
return *this;
}
;
Date &operator-=(int day)
{
_day -= day;
while(_day<1&&_year>0)
{
_month--;
if(_month==0)
{
_month = 12;
_year--;
}
_day+=GetMonthDay( _year, _month);
}
return *this;
};
// 前置
Date operator++()
{
(*this)+=1;
}
;
Date operator--()
{
(*this)-=1;
};
// 後置返回 改變數值之前的對象一個拷貝,
Date operator++(int)
{
Date copy((*this));
(*this)+=1;
return copy;
}
;
Date operator--(int)
{
Date copy((*this));
(*this)-=1;
return copy;
}
;
// d1-d2
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
~Date()
{
//cout<<"調用析構函數"<<endl;
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day;
};
重載完畢後要注意符號優先度如
cout<<d1==d2<<endl;//錯誤,d1先與<<運算,d2與<<運算,最後進行判斷相等操作
cout<< (d1==d2) <<endl;//正確
小知識點:
爲什麼前置++比後置++重載時少了一個int型的形參?
答:因爲前置和後置的運算符一樣,所以即使對其進行運算符重載,編譯器也無法區分兩者,所以這是對同名函數的重載,同名函數的重載需要不同的形參列表,所以int型的形參就是爲了區分兩個操作符函數,int 本身的值無意義。
6.取地址及const取地址操作符重載
這兩個默認函數一般不需要重載,編譯器會默認生成
如果重載,一般時爲了封裝,防止訪問到對象的地址,
Date* operator&()
{
return nullptr;
}
如果設置一個Date類型的對象,並用&訪問它的地址會返回00000000.
7const成員
將const修飾的類成員函數稱之爲const成員函數,const修飾類成員函數,實際修飾該成員函數隱含的this
指針,表明在該成員函數中不能對類的任何成員進行修改。以上面的代碼爲例
相當於將隱含的this指針形參由Date *轉化爲了 const Date *
1.const對象可以調用非const成員函數嗎?
答:不可以,非const成員函數形參爲非const類型,傳參時編譯器試圖將const類型的指針轉化成非const類型,無法實現操作。
2. 非const對象可以調用const成員函數嗎?
答:可以,允許非const變量轉化爲const變量,this指針的類型可以轉化。
3. const成員函數內可以調用其它的非const成員函數嗎?
答:不可以,調用非const成員函數,就可能對const變量進行修改,這是不允許的,
c++規定 const成員函數內不能調用其它的非const成員函數
4. 非const成員函數內可以調用其它的const成員函數嗎?
答:可以,理由和第二題相同
(注意唯一的例外是析構函數,C++標準中說
A destructor can be invoked for a const, volatile or const volatile object.
析構函數可以爲const、volatile或const volatile對象調用析構函數。
因爲析構函數和資源的回收有關,如果無法訪問const函數就會造成內存泄漏)
const也可以用來重載函數
class A{
private:
int i;
public:
void f()
{
cout<<"f()"<<endl;
}
void f() const
{
cout<<"f() const"<<endl;
}
}
const類型的對象調用const修飾的函數,非const類型的對象調用非const修飾函數,
(注意const寫在形參括號的右邊,如果寫在了函數名的左邊,編譯器會認爲函數返回類型爲const,如果是對void修飾,函數本身依然沒有返回值)