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修飾,函數本身依然沒有傳回值)