類與對象(第二部分)
類的6個默認成員函數
如果一個類中什麼成員都沒有,簡稱爲空類。空類並不是什麼都沒有,任何類在我們不寫的情況下,都會自動生成6個默認成員函數
構造函數
構造函數的概念
構造函數是一個特殊的成員函數,名字與類名相同,創建類類型對象時由編譯器自動調用,保證每個數據成員都有一個合適的初始值,並且在對象的聲明週期內只調用一次。
構造函數的特性
構造函數是特殊的成員函數,需要注意的是,構造函數雖然名稱叫構造,但是它的主要任務並不是開空間創建對象,而是初始化對象。
特徵如下:
1、函數名與類名相同
2、無返回值
3、對象實例化時編譯器自動調用對應的構造函數
4、構造函數可以重載
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 d;
Date d1(1900,1,1);
//注意:通過無參構造函數創建對象時,對象後面不用跟括號,否則就成了函數聲明
//以下代碼的函數,聲明瞭d3函數,該函數無參,返回一個日期類型的對象
Date d3();
}
5、如果類中沒有顯式定義構造函數,則C++編譯器會自動生成一個無參的默認構造函數,一旦用戶顯示定義,那麼編譯器將不在生成。
class Date
{
public:
/*
// 如果用戶顯式定義了構造函數,編譯器將不再生成
Date (int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
*/
private:
int _year;
int _month;
int _day;
};
void Test()
{
// 沒有定義構造函數,對象也可以創建成功,因此此處調用的是編譯器生成的默認構造函數
Date d;
}
6、默認構造函數只能有一個,不管是無參的,還是全缺省的。
// 默認構造函數
class Date
{
public:
Date()
{
_year = 1900 ;
_month = 1 ;
_day = 1;
}
Date (int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private :
int _year ;
int _month ;
int _day ;
};
// 以下測試函數能通過編譯嗎?
void Test()
{
Date d1;
}
在VS2013下可以編譯通過,說明在VS內編譯器對這種重載錯誤並不是特別在意。
但是在g++中,出現了編譯器不知道執行哪個構造函數。
7、關於編譯器生成的默認成員函數,我們可能會想到這個默認的構造函數有什麼用?我們直接聲明一個對象,返回的_year
值都是隨機數,那麼還有什麼意義呢?
C++把類型分成內置類型和自定義類型。內置類型就是語法已經定義好的類型,如int/char…;自定義類型就是我們通過class/struct/union等定義的類型
#include <iostream>
class Time
{
public:
Time()
{
std::cout << "Time()" << std::endl;
// 比特科技
// 8. 成員變量的命名風格
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本類型(內置類型)
int _year;
int _month;
int _day;
// 自定義類型
Time _t;
};
int main()
{
Date d;
return 0;
}
8、成員變量風格
通過以上代碼能看出,在C++中,我們一般定義成員變量時,會在成員變量之前加上一個下劃線
int _year;
int _month;
int _day;
析構函數
析構函數的概念
與構造函數功能相反,析構函數不是完成對象的銷燬,局部對象銷燬工作由編譯器來完成。而對象在銷燬時會自動調用析構函數,完成類的一些資源清理工作。
析構函數的特性
1、析構函數名就是構造函數名之前加上字符~。
2、無參數無返回值
3、一個類只有一個析構函數。若未顯示定義,系統會自動生成默認的析構函數
4、對象生命週期結束時,C++編譯系統自動調用析構函數
#include <iostream>
#include <assert.h>
typedef int DateType;
class SeqList
{
public:
SeqList(int capacity = 10){
_pData = (DateType*)malloc(capacity * sizeof(DateType));
assert(_pData);
_size = 0;
_capacity = capacity;
}
~SeqList()
{
if(_pData)
{
free(_pData); //釋放棧上資源
_pData = NULL;
_size = 0;
_capacity = 0;
}
}
private:
int* _pData;
size_t _size;
size_t _capacity;
};
5、編譯器自動生成的默認析構函數,對會自定類型成員調用它的析構函數
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
class Person
{
private:
String _name;
int _age;
};
int main()
{
Person p;
return 0;
}
拷貝構造函數
拷貝構造函數概念
只有單個形參,該形參是對本類類型對象的引用(一般常用const修飾),在用已存在的類類型對象創建新對象時,新對象由編譯器自動調用
拷貝構造函數的特徵
1、拷貝構造函數是構造函數的一個重載
2、拷貝構造函數的參數只有一個且必須使用引用傳參,使用傳值的方式會引發無窮遞歸調用
3、若未顯示定義,系統生成默認的構造拷貝函數。默認的拷貝構造函數對象按內存存儲字節序完成拷貝,這種拷貝我們叫做淺拷貝,或者值拷貝
#include <iostream>
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print(){
std::cout<<_year<<"-"<<_month<<"-"<<_day<<std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
// 這裏d2調用的默認拷貝構造完成拷貝,d2和d1的值也是一樣的。
Date d2(d1);
d2.Print();
return 0;
}
賦值運算符重載
運算符重載
C++爲了增強代碼的可讀性引入了運算符重載,運算符重載是具有特殊函數名的函數,也具有其返回值類型,函數名字以及參數列表,其返回值類型與參數列表與普通的函數類似。
函數名字爲:關鍵字operator後面接需要重載的運算符符號
函數原型:返回值類型operator操作符(參數列表)
注意:
- 不能通過連接其他符號來創建新的操作符:比如operator@
- 重載操作符必須有一個類類型或者枚舉類型的操作數
- 用於內置類型的操作符,其含義不能改變,例如:內置的整型+,不能改變其含義
- 作爲類成員的重載函數時,其形參看起來比操作數數目少1成員函數的
- 操作符有一個默認的形參this,限定爲第一個形參
- .* 、:: 、sizeof 、?: 、. 注意以上5個運算符不能重載。這個經常在筆試選擇題中出現。
// 全局的operator==
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
// 這裏會發現運算符重載成全局的就需要成員變量是共有的,那麼問題來了,封裝性如何保證?
// 這裏其實可以用我們後面學習的友元解決,或者乾脆重載成成員函數。
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year;
&& d1._month == d2._month
&& d1._day == d2._day;
}
void Test ()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
cout<<(d1 == d2)<<endl;
}
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 這裏需要注意的是,左操作數是this指向的調用函數的對象
bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
void Test ()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
cout<<(d1 == d2)<<endl;
}
賦值運算符重載
class Date
{
public :
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date (const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
}
private:
int _year ;
int _month ;
int _day ;
};
賦值運算符主要有四點:
1、參數類型
2、返回值
3、檢測是否自己給自己賦值
4、返回*this
5、一個類如果沒有顯示定義賦值運算符重載,編譯器也會生成一個,完成對象按字節序的值拷貝
const成員
將const修飾的類成員函數稱之爲const成員函數,const修飾類成員函數,實際修飾該函數隱含的this指針,表明在該成員函數中不能對類的任何成員進行修改
取地址及const取地址操作符重載
這兩個默認成員函數一般不用重新定義,編譯器默認會生成
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};