類的六個預設成員函數
如果一個類中什麼成員都沒有,簡稱為空類,可是空類中就什麼都沒有嗎?并不是的,任何一個類在我們不寫的情況下,都會自動生成下列六個成員函數:
構造函數
構造函數是一個特殊的成員函數,名字與類型相同,建立類型對象時有編譯器自動調用,保證每個資料成員都有一個合适的初值,并且在對象的生命周期内隻調用一次
特性
構造函數是特殊的成員函數,構造函數雖然名稱為構造,但是需要注意的是構造函數的主要任務并不是開辟空間建立對象,而是初始化對象
具體特征:
1、函數名與類名相同
2、無傳回值
3、對象執行個體化時編譯器自動調用對應的構造函數
4、構造函數可以重載
5、如果類中沒有顯式定義構造函數,則C++編譯器會自動生成一個無參的預設構造函數,一旦使用者顯式定義後編譯器将不再生成
6、無參的構造函數和全預設的構造函數都稱為預設構造函數,并且預設構造函數隻能有一個。注意:無參構造函數、全預設構造函數,我們沒寫編譯器預設生成的構造函數,都可以認為是預設成員函數
7、C++把類型分成内置類型(基本類型)和自定義類型。内置類型就是文法已經定義好的類型:如int、char…,自定義類型就是我們使用class/struct/union自己定義的類型。編譯器生成預設的構造函數會對自定義類型成員_t調用它的預設成員函數
#include<iostream>
using namespace std;
class Date
{
public:
// Date() // 類中如果沒有顯式(使用者是否直接定義)任何構造函數, 編譯器将會生成一個預設的構造函數---無參構造函數
// {
// _year = 1900;
// _month = 1;
// _day = 1;
// }
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int,int,int):" << this << endl;
}
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //調用無參構造函數
Date d2(2019, 9, 15); //調用帶參構造函數
//Date d3(); //注意:如果通過無參構造函數初始化對象時,對象後面不用跟括号,否則就成了函數聲明。而該函數聲明了d3函數,該函數無參,傳回一個日期類型的對象
return 0;
}
構造函數的其他用法
構造函數體指派
在建立對象時,編譯器通過調用構造函數,給對象中的每個成員變量一個合适的初始值
構造函數體中的語句隻能将其稱作為賦初值,而不能稱為初始化。因為初始化隻能初始一次,而構造函數體内可以多次指派
初始化清單
以一個冒号開始,接着是一個以逗号分隔的資料成員清單,每個“成員變量”後面跟一個放在括号中的初始值或表達式
【注意】
1、每個成員變量在初始化清單中隻能出現一次(初始化隻能初始化一次)
2、類中包含以下成員變量,必須放在初始化清單位置進行初始化:
(1)、引用成員變量
(2)、const成員變量
(3)、類類型成員(該類沒有預設構造函數)
3、盡量使用初始化清單初始化。對于自定義類型成員變量,一定會先使用初始化清單初始化
4、成員變量在類中聲明次序就是其在初始化清單中的初始化順序,與其在初始化清單中的先後次序無關
explicit關鍵字
構造函數不僅可以構造與初始化對象,對于單個參數的構造函數,還具有類型轉換的作用
用explicit修飾構造函數,将會禁止單參構造函數的隐式轉換
析構函數
析構函數:與構造函數功能相反,析構函數不是完成對象的銷毀,局部對象銷毀工作是由編譯器完成的。而對象在銷毀時會自動調用析構函數,完成一些資源清理工作
特性
1、析構函數名是在類名前加上字元~
2、無參數無傳回值
3、一個類有且隻有一個析構函數。若未顯式定義,系統會自動生成預設的析構函數
4、對象生命周期結束時,C++編譯系統自動調用析構函數
#include<iostream>
using namespace std;
#include <assert.h>
#include <malloc.h>
typedef int DataType;
class SeqList
{
public:
SeqList(size_t capacity = 10)
{
cout << "SeqList(size_t)" << endl;
_array = (DataType*)malloc(sizeof(DataType)*capacity);
if (nullptr == _array)
{
assert(0);
return;
}
_capacity = 0;
_size = 0;
}
~SeqList()
{
cout << "~SeqList()" << endl;
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main()
{
SeqList s;
_CrtDumpMemoryLeaks(); //檢測是否存在記憶體洩漏
return 0;
}
5、編譯器生成的預設析構函數,會對自定義類型成員調用它們的析構函數。例如:
class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
str = "";
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
if (_str)
{
free(_str);
}
}
private:
char* _str;
};
class Person
{
private:
String _name;
String _gender;
int _age;
};
int main()
{
Person p;
return 0;
}
拷貝構造函數
拷貝構造函數:隻有單個形參,該形參是對本類類型對象的引用(一般常用const修飾),在用已存在的類類型對象建立新對象時由編譯器自動調用
特征
1、拷貝構造函數是構造函數的一個重載形式
2、拷貝構造函數的參數隻有一個且必須使用引用傳參,使用傳值的方式在傳參的位置會引發無窮遞歸調用
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d) //此處,參數必須為引用類型。原因:傳值會形成臨時拷貝,而該函數為對象的拷貝函數,是以會導緻不斷地傳值,不斷形成臨時拷貝...緻使程式陷入死循環
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2019, 9, 14);
Date d2(d1);
return 0;
}
3、若未顯式定義,系統生成預設的拷貝構造函數。預設的拷貝構造函數對象按記憶體存儲按位元組序完成拷貝,這種拷貝我們稱之為淺拷貝,或者值拷貝
class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
str = "";
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
if (_str)
{
free(_str);
}
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2(s1);
return 0;
}
//該程式會運作出錯。
//原因:由于是編譯器預設生成的拷貝函數,該函數的拷貝形式為淺拷貝,即将一個對象中的内容原封不動的拷貝到另外一個對象中。
//而若目前對象中的内容為指向一片記憶體空間的指針的話,那麼拷貝後的對象内容也指向這一片記憶體空間
//當調用析構函數時,将第一個對象所指向的空間釋放之後,緻使另一個對象為野指針。如下圖:
指派運算符重載
目的:為了增強代碼的可讀性
函數名字:關鍵字operator後面接需要重載的運算符符号
函數原型:傳回值類型 operator操作符 (參數清單)
注意:
1、不能通過連接配接其他符号來建立新的操作符:比如[email protected],關于哪些運算符能被重載,有下圖
2、重載操作符必須有一個類類型或者枚舉類型的操作數
3、用于内置類型的操作符,其含義不能改變,例如:内置的整型+,不能重載的時候定義為 - 的作用
4、作為類成員的重載函數時,其形參看起來比操作數數目少1,成員函數的操作符有一個預設的形參this,限定為第一個形參
5、" .* " 、" :: " 、" sizeof " 、" ?: " 、" . ",注意以上的5個運算符不能重載
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int,int,int):" << this << endl;
}
bool operator==(const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;
}
private:
int _year;
int _month;
int _day;
};
指派運算符重載
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator!=(const Date& d) //為了指派運算符重載的順利進行,引入不等于的運算符重載
{
return !(*this == d);
}
Date& operator=(const Date& d)
{
if (this != &d) //禁止自己給自己指派
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
+ +、- -運算符重載
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 前置++
Date& operator++()
{
_day += 1;
return *this;
}
// 後置++
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
Date& operator--()
{
_day -= 1;
return *this;
}
Date operator--(int)
{
Date temp(*this);
_day -= 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
const成員
const修飾類的成員函數
被const修飾的類成員函數稱之為const成員函數,const修飾類成員函數,實際修飾該成員函數隐含的this指針,表明該成員函數中不能對類的任何成員進行修改
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int,int,int):" << this << endl;
}
Date* operator&()
{
_day++;
return this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
cout << &d1 << endl;
const Date d2;
cout << &d2 << endl;
return 0;
}
//該程式運作後,d1中為1900-1-2;d2中為1900-1-1
//可以看出來,由const修飾的類成員函數中的成員變量不會進行任何修改
當然,在const修飾的類成員函數中,若要對成員變量進行修改,隻需在私有成員變量定義時,前面加上mutable即可
思考:
const對象可以調用非const成員函數嗎?
——不可以,const類型類型對象中的成員變量不允許被修改,而非const類型的成員函數中允許修改成員變量,兩者沖突,若允許的話,會不安全
非const對象可以調用const成員函數嗎?
——可以,非const類型的對象允許修改成員變量,但是若要調用const成員函數是被允許的,不會違背安全性的原則
const成員函數内可以調用其他的非const成員函數嗎?
——不可以,同理…
非const成員函數内可以調用其他的const成員函數嗎?
——可以,同理…
C和C++中const的差別
取位址及const取位址操作符重載
1、這兩個預設成員函數一般不用重新定義,編譯器預設會生成
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int,int,int):" << this << endl;
}
~Date()
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
return 0;
}
2、這兩個運算符一般不需要重載,使用編譯器生成的預設取位址的重載即可,隻有特殊情況,才需要重載,比如想讓别人擷取到指定的内容
涵蓋本篇部落格所有知識的Date類