天天看點

C++類與對象(二)構造函數析構函數拷貝構造函數指派運算符重載const成員取位址及const取位址操作符重載

類的六個預設成員函數

如果一個類中什麼成員都沒有,簡稱為空類,可是空類中就什麼都沒有嗎?并不是的,任何一個類在我們不寫的情況下,都會自動生成下列六個成員函數:

C++類與對象(二)構造函數析構函數拷貝構造函數指派運算符重載const成員取位址及const取位址操作符重載

構造函數

構造函數是一個特殊的成員函數,名字與類型相同,建立類型對象時有編譯器自動調用,保證每個資料成員都有一個合适的初值,并且在對象的生命周期内隻調用一次

特性

構造函數是特殊的成員函數,構造函數雖然名稱為構造,但是需要注意的是構造函數的主要任務并不是開辟空間建立對象,而是初始化對象

具體特征:

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

//該程式會運作出錯。
//原因:由于是編譯器預設生成的拷貝函數,該函數的拷貝形式為淺拷貝,即将一個對象中的内容原封不動的拷貝到另外一個對象中。
//而若目前對象中的内容為指向一片記憶體空間的指針的話,那麼拷貝後的對象内容也指向這一片記憶體空間
//當調用析構函數時,将第一個對象所指向的空間釋放之後,緻使另一個對象為野指針。如下圖:
           
C++類與對象(二)構造函數析構函數拷貝構造函數指派運算符重載const成員取位址及const取位址操作符重載

指派運算符重載

目的:為了增強代碼的可讀性

函數名字:關鍵字operator後面接需要重載的運算符符号

函數原型:傳回值類型 operator操作符 (參數清單)

注意:

1、不能通過連接配接其他符号來建立新的操作符:比如[email protected],關于哪些運算符能被重載,有下圖

C++類與對象(二)構造函數析構函數拷貝構造函數指派運算符重載const成員取位址及const取位址操作符重載

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類

c++

繼續閱讀