主頁 >  其他 > [C/C++]詳解C++的類和物件

[C/C++]詳解C++的類和物件

2021-06-09 21:56:40 其他

詳解C++的類和物件

  • 1.面向物件
  • 2.類的引入
  • 3.類的定義
  • 4.類的訪問限定符及封裝
    • 4.1 訪問限定符
    • 4.2 封裝
  • 5.類的作用域
  • 6.類的實體化
  • 7.類物件模型
    • 7.1 計算類物件的大小
    • 7.2 結構體記憶體對齊規則
  • 8.類成員函式的this指標
    • 8.1 this指標是什么
    • 8.2 this指標的特性
  • 9.類的6個默認成員函式
    • 9.1 建構式
    • 9.2 解構式
    • 9.3 拷貝建構式
    • 9.4 賦值運算子多載
    • 9.5 取地址及const取地址運算子多載
  • 10.const成員
    • 10.1const修飾類的成員函式
  • 11.建構式的進階
    • 11.1建構式體賦值
    • 11.2 初始化串列
    • 11.3 explicit關鍵字
  • 12.C++11 的成員初始化新操作,
  • 13.友元
    • 13.1 友元函式
    • 13.2 友元類
  • 14.static成員
    • 14.1 概念
    • 14.2 特性
  • 15.內部類
    • 15.1概念
    • 15.2特性
  • 16.再次理解封裝
  • END
  • 寫第一個類(Date類)

類是現實世界或思維世界中的物體在計算機中的反映,它將資料以及這些資料上的操作封裝在一起,物件是具有型別別的變數,類和物件是面向物件編程技術中的最基本的概念,

1.面向物件

首先來理解什么是面向物件編程,
C語言是面向程序的,關注的是程序,分析出求解問題的步驟,通過函式呼叫逐步解決問題,
C++是基于面向物件的,關注的是物件,將一件事情拆分成不同的物件,靠物件之間的互動完成,

2.類的引入

在C++中的結構體內不僅可以定義變數,也可以定義函式,在C++中常用class來代替struct

struct Student
{
	void Set(const char* name, const char* gender, int age)
	{
	}
	void Print()
	{
	}
	char _name[20];
	char _gender[3];
	int _age;
};

3.類的定義

class className
{
// 類體:由成員函式和成員變陣列成
}; 

class為定義類的關鍵字,ClassName為類的名字,{}中為類的主體,類定義結束時一定要加后面分號,
類中的元素稱為類的成員

  1. 資料稱為類的屬性或者成員變數;
  2. 函式稱為類的方法或者成員函式
    類的兩種定義方式
  3. 宣告和定義全部放在類體中,成員函式如果在類中定義,編譯器可能會將其當成行內函式處理,

class className
{
    public:
    	//公共的行為或屬性
 
    private:
    //私有的行為或屬性
};
  1. 宣告放在.h檔案中,類的定義放在.cpp檔案中
    在.h中
class className
{
    public:
    	void show();
    	//公共的行為或屬性
 
    private:
    //私有的行為或屬性
};

在.cpp中

void className::show()
{
	cout<<_name<<endl;
}

一般情況會采用第二種方式,

4.類的訪問限定符及封裝

4.1 訪問限定符

訪問限定符只在編譯時有用,當資料映射到記憶體后,沒有任何訪問限定符上的區別,
C++實作封裝的方式:用類將物件的屬性與方法結合在一塊,讓物件更加完善,通過訪問權限選擇性的將其介面提供給外部的用戶使用,
C++的訪問限定符三種:public(公有) , protected(保護), private(私有)

  1. public修飾的成員在類外可以直接被訪問;
  2. protected和private修飾的成員在類外不能直接被訪問;
  3. 訪問權限作用域從該訪問限定符出現的位置開始直到下一個訪問限定符出現時為止;
  4. class的默認訪問權限為private,struct為public,

4.2 封裝

面向物件的有三大特性:封裝、繼承、多型,在類和物件階段,只研究類的封裝特性,
封裝:將資料和操作資料的方法進行有機結合,隱藏物件的屬性和實作細節,僅對外公開介面來和物件進行互動,封裝本質上是一種管理,

5.類的作用域

類定義了一個新的作用域,類的所有成員都在類的作用域中,在類體外定義成員,需要使用 :: 作用域決議符指明成員屬于哪個類域,

class className
{
    public:
    	void show();
    	//公共的行為或屬性
 
    private:
    //私有的行為或屬性
};

void className::show()
{
	cout<<_name<<endl;
}

6.類的實體化

型別別創建物件的程序,稱為類的實體化,類實體化出物件就像現實中使用建筑設計圖建造出房子,類就像是設計圖,與C語言中的結構體相同,
類只是一個模型一樣的東西,限定了類有哪些成員,定義出一個類并沒有分配實際的記憶體空間來存盤它,一個類可以實體化出多個物件,實體化出的物件占用實際的物理空間存盤類成員變數

7.類物件模型

7.1 計算類物件的大小

一個類的大小,實際就是該類中”成員變數”之和,當然也要進行記憶體對齊,注意空類的大小,空類比較特殊,編譯器給了空類一個位元組來唯一標識這個類,
我們通過對下面的不同物件分別獲取大小進行分析:

// 類中既有成員變數,又有成員函式
class A1 {
	public:
	void f1(){}
	private:
	int _a;
};
// 類中僅有成員函式
class A2 {
	public:
	void f2() {}
};
// 類中什么都沒有---空類
class A3
{};

sizeof(A1) = 4; sizeof(A2) = 1; sizeof(A3) = 1;

7.2 結構體記憶體對齊規則

  1. 第一個成員在與結構體偏移量為0的地址處,
  2. 其他成員變數要對齊到某個數字(對齊數)的整數倍的地址處,
  3. 結構體總大小為:最大對齊數(所有變數型別最大者與默認對齊引數取最小)的整數倍,
  4. 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍,

8.類成員函式的this指標

this 是 C++ 中的一個關鍵字,也是一個 const 指標,它指向當前物件,通過它可以訪問當前物件的所有成員,

8.1 this指標是什么

C++編譯器給每個“非靜態的成員函式“增加了一個隱藏的指標引數,讓該指標指向當前物件(函式運行時呼叫該函式的物件),在函式體中所有成員變數的操作,都是通過該指標去訪問,只不過所有的操作對用戶是透明的,即用戶不需要來傳遞,編譯器自動完成, 一般存在堆疊中,

8.2 this指標的特性

  1. this指標的型別:型別 const*
  2. 只能在“成員函式”的內部使用
  3. this指標本質上其實是一個成員函式的形參,是物件呼叫成員函式時,將物件地址作為實參傳遞給this形參,所以物件中不存盤this指標
  4. this指標是成員函式第一個隱含的指標形參,一般情況由編譯器通過ecx暫存器自動傳遞,不需要用戶傳遞

9.類的6個默認成員函式

若類中任何成員都沒有就稱之為空類,但它不會真的什么都沒有,編譯器會自動生成6個默認的成員函式:
建構式:完成初始化作業;
解構式:完成清理函式;
拷貝構造:使用同一個類的物件初始化創建物件;
賦值多載:主要是把一個物件賦值給另一個物件;
取地址多載:有兩個函式,主要是對普通物件和const物件取地址;

class A
{
public:
    A();//建構式
    A(const A& a);//拷貝建構式
    ~A();//解構式
    A& operator=(const A& a);//賦值運算子多載
    A* operator &();//取地址運算子多載
    const A* operator &() const;//const修飾的取地址運算子多載
};

下面我將對這六個默認的成員函式進行介紹,

9.1 建構式

建構式是一個特殊的成員函式在物件的生命周期內只呼叫一次, 建構式名與類名相同,建構式沒有回傳值,在物件進行實體化的時候編譯器自動呼叫對應的建構式,建構式可以多載,一個類可以有多個多載的建構式,創建物件時根據傳遞的實參來判斷呼叫哪一個建構式,建構式的呼叫是強制性的,一旦在類中定義了建構式,那么創建物件時就一定要呼叫,不呼叫是錯誤的,如果有多個多載的建構式,那么創建物件時提供的實參必須和其中的一個建構式匹配;反過來說,創建物件時只有一個建構式會被呼叫,
以Date類為例

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)			//建構式
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "1" << endl;
	}
private:
	int _year;		//物件的命名風格
	int _month;
	int _day;
};
class Date
{
public:
	// 1.無參建構式
	Date ()
	{}
	// 2.帶參建構式
	Date(int year , int month, int day)			//建構式
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "1" << endl;
	}
private:
	int _year;		//物件的命名風格
	int _month;
	int _day;
};
void Test()
{
	Date d1; // 呼叫無參建構式
	Date d2 (2021, 5, 30); // 呼叫帶參的建構式
	// 注意:如果通過無參建構式創建物件時,物件后面不用跟括號,否則就成了函式宣告
	Date d3();
}
  1. 如果類中沒有顯式定義建構式,則C++編譯器會自動生成一個無參的默認建構式,一旦用戶顯式定義編譯器將不再生成,
  2. 無參的和全預設的建構式都稱為默認建構式,并且默認建構式只能有一個,
  3. 建構式會對自定型別成員呼叫的它的默認成員函式

9.2 解構式

解構式:與建構式相反,當物件結束其生命周期,如物件所在的函式已呼叫完畢時,系統會自動執行解構式,
解構式名也應與類名相同,只是在函式名前面加一個位取反符~stud( ),以區別于建構式,它不能帶任何引數,也沒有回傳值(包括void型別),只能有一個解構式,不能多載,如果用戶沒有撰寫解構式,編譯系統會自動生成一個預設的解構式(即使自定義了解構式,編譯器也總是會為我們合成一個解構式,并且如果自定義了解構式,編譯器在執行時會先呼叫自定義的解構式再呼叫合成的解構式),它也不進行任何操作,所以許多簡單的類中沒有用顯式的解構式,

typedef int DataType;
class SeqList
{
public :
	SeqList (int capacity = 10)
	{
		_pData = (DataType*)malloc(capacity * sizeof(DataType));
		assert(_pData);
		_size = 0;
		_capacity = capacity;
	}
	~SeqList()				//解構式
	{
		if (_pData)
		{
			free(_pData ); // 釋放堆上的空間
			_pData = NULL; // 將指標置為空
			_capacity = 0;
			_size = 0;
		}
	}
private :
	int* _pData ;
	size_t _size;
	size_t _capacity;
};

會自定型別成員呼叫它的解構式,

9.3 拷貝建構式

1.概念
建構式:只有單個形參,該形參是對本型別別物件的參考(一般常用const修飾),在用已存在的型別別物件創建新物件時由編譯器自動呼叫,
2.特性
拷貝建構式也是特殊的成員函式,
其特征如下:

  1. 拷貝建構式是建構式的一個多載形式,
  2. 拷貝建構式的引數只有一個且必須使用參考傳參,使用傳值方式會引發無窮遞回呼叫,會被認為重新定義了一個物件,并且繼續使用拷貝構造,這樣無限遞回,
class Date
{
public:

	Date(int year = 1900, int month = 1, int day = 1)			//建構式
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "1" << endl;
	}

	Date(Date& d)						//拷貝建構式,不可以Date(Date d)會造成無限遞回
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "2" << endl;
	}
	~Date()														//解構式
	{
		
	}

private:
	int _year;		//物件的命名風格
	int _month;
	int _day;
};
  1. 系統會生成默認的拷貝建構式, 默認的拷貝建構式物件按記憶體存盤按位元組序完成拷貝,這種拷貝我們叫做淺拷貝,或者值拷貝,
  2. 當物件中存在如堆疊這種結構,我們就需要自己寫拷貝建構式,進行深拷貝,

9.4 賦值運算子多載

1.運算子多載
C++為了增強代碼的可讀性引入了運算子多載,運算子多載是具有特殊函式名的函式,也具有其回傳值型別,函式名字以及引數串列,其回傳值型別與引數串列與普通的函式類似,
函式名字為:關鍵字operator后面接需要多載的運算子符號,
函式原型:回傳值型別 operator運算子(引數串列)
需要注意的:

  1. 不能通過連接其他符號來創建新的運算子:比如operator@
  2. 多載運算子必須有一個型別別或者列舉型別的運算元
  3. 用于內置型別的運算子,其含義不能改變,例如:內置的整型+,不能改變其含義
  4. 作為類成員的多載函式時,其形參看起來比運算元數目少1成員函式的運算子有一個默認的形參this,限定為第一個形參
  5. .* 、:: 、sizeof 、?: 、. 注意以上5個運算子不能多載,

2.賦值運算子多載
賦值運算子主要有四點:

  1. 引數型別
  2. 回傳值
  3. 檢測是否自己給自己賦值
  4. 回傳*this
  5. 一個類如果沒有顯式定義賦值運算子多載,編譯器也會生成一個,完成物件按位元組序的值拷貝,
    3.一點點示例
class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1);				//1.建構式,全預設,
	void Print();		//列印

	//析構,拷貝構造,賦值多載,可以不寫,默認生成
	//stack才寫

	//運算子多載,日期加減
	Date& operator+=(int day);			//+=之后,day這個物體還是存在的,所以可以回傳參考
	Date operator+(int day);

	Date& operator-=(int day);
	Date operator-(int day);

	//()為空是前置++,增加(int)是為了構成多載,后置++;
	Date& operator++();								//++d 前置可以參考,因為是先加自己身上再回傳
	Date operator++(int);							//d++ 用int占位,不需要給實參

	Date& operator--();
	Date operator--(int);

	// 運算子多載
	bool operator>(const Date& d)const;
	bool operator<(const Date& d)const;
	bool operator==(const Date& d)const;
	bool operator<=(const Date& d)const;
	bool operator>=(const Date& d)const;
	bool operator!=(const Date& d)const;

	// 日期-日期 回傳天數
	int operator-(const Date& d);

private:				//私有
	int _year;
	int _month;
	int _day;
};

inline int GetDay(int year, int month)									//回傳當月最大日期,頻繁呼叫所以行內	
{
	static int days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };		//12個月的日期,只創建一次,放在靜態區
	int day = days[month - 1];
	if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) )		//判斷閏年
	{
		day = 29; 
	}
	return day;
}

Date::Date(int year, int month, int day)				//指定類域,預設函式只能在一個地方出現
{
	if (year >= 0
		&& month <= 12 && month >= 1
		&& day > 0 && day <= GetDay(year, month))		//判斷日期的合法性
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << " no " << endl;
		assert(false);
	}

}
void Date::Print()
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

Date& Date::operator+=(int day)						//
{
	if (day < 0)									//day<0
	{
		_day -= -day;
	}
	else
	{
		_day += day;

		while (_day > GetDay(_year, _month))			//判斷day是否合法,不合法就月份+1,月份超出就置1,年++;
		{
			cout << GetDay(_year, _month) << _month<<endl;
			_day -= GetDay(_year, _month);
			_month++;
			if (_month > 12)
			{
				_month = 1;
				_year++;
			}
		}
	}

	return *this;
}

Date Date::operator+(int day)			//需要創建一個新的物件,回傳這個物件
{
	Date ret(*this);					//使用默認拷貝建構式(淺拷貝)

	ret += day;					//直接進行復用,直接操作物件,不要操作成員,

	return ret;

}

Date& Date::operator-=(int day)
{
	if (day < 0)								//減負數
	{
		_day += -day;
	}
	else										
	{
		_day -= day;

		while (_day <= 0)
		{
			_month--;							//先-月,因為要往上一個月算,
			if (_month < 0)						//邏輯參考+=
			{
				_month = 12;
				_year--;
			}
			_day += GetDay(_year, _month);
		}
	}

	return *this;
}

Date Date::operator-(int day)
{
	Date ret(*this);

	ret -= day;

	return ret;
}



//Date:: 宣告域
bool Date::operator>(const Date& d)const
{
	if (_year < d._year)
	{
		return false;
	}
	else if (_year > d._year)
	{
		return true;
	}
	else
	{
		if (_month < d._month)
		{
			return false;
		}
		else if (_month > d._month)
		{
			return true;
		}
		else
		{
			if (_day > d._day)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	}

}
bool Date::operator<(const Date& d)const
{
	if (_year > d._year)
	{
		return false;
	}
	else if (_year < d._year)
	{
		return true;
	}
	else
	{
		if (_month > d._month)
		{
			return false;
		}
		else if (_month < d._month)
		{
			return true;
		}
		else
		{
			if (_day < d._day)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	}
}
bool Date::operator==(const Date& d)const
{
	if (*this > d || *this < d)
		return false;
	else
		return true;
}
bool Date::operator<=(const Date& d)const
{
	if (*this > d)
		return false;
	else
		return true;
}
bool Date::operator>=(const Date& d)const
{
	if (*this < d)
		return false;
	else
		return true;
}
bool Date::operator!=(const Date& d)const
{
	if (*this == d)
		return false;
	else
		return true;
}

Date& Date::operator++()
{
	*this += 1;

	return *this;
}
Date Date::operator++(int)
{
	Date ret(*this);
	*this += 1;					//這里不能對ret操作,因為要做回傳值,this實際上要加,但為了回傳一個加之前,所以回傳一個ret

	return ret;
}

Date& Date::operator--()
{
	*this -= 1;

	return *this;
}

Date Date::operator--(int)
{
	Date ret(*this);
	*this -= 1;

	return ret;
}

int Date::operator-(const Date& d)			//設定兩端,然后遍歷整個區間,++i就可以,
{
	int flag = 1;					//判斷日期正負,默認為正
	Date max = *this;				//默認d小,如果不采取這種方式,下面while會存在編譯錯誤
	Date min = d;

	if (*this < d)
	{
		max = d;				//拷貝構造的另一種表達法,不能重復構建物件,重復定義根據就近原則會以上面的為最終結果
		min = *this;
		flag = -1;
	}


	int n = 0;

	while (max > min)
	{
		++n;
		++(min);
	}

	return n * flag;			//大小*正負
}

9.5 取地址及const取地址運算子多載

這兩個默認成員函式一般不用重新定義 ,編譯器默認會生成,并且非常少使用
這兩個運算子一般不需要多載,使用編譯器生成的默認取地址的多載即可,只有特殊情況,才需要多載,比如想讓別人獲取到指定的內容,

class Date
{
public :
	Date* operator&()
	{
		return this ;
	}
	const Date* operator&()const
	{
		return this ;
	}
private :
	int _year ; // 年
	int _month ; // 月
	int _day ; // 日
};

10.const成員

10.1const修飾類的成員函式

將const修飾的類成員函式稱之為const成員函式,const修飾類成員函式,實際修飾該成員函式隱含的this指標,表明在該成員函式中不能對類的任何成員進行修改,

class Date
{
public :
	void Display ()
	{
		cout<<"Display ()" <<endl;
		cout<<"year:" <<_year<< endl;
		cout<<"month:" <<_month<< endl;
		cout<<"day:" <<_day<< endl<<endl ;
	}
	void Display () const
	{
		cout<<"Display () const" <<endl;
		cout<<"year:" <<_year<< endl;
		cout<<"month:" <<_month<< endl;
		cout<<"day:" <<_day<< endl<<endl;
	}
private :
	int _year ; // 年
	int _month ; // 月
	int _day ; // 日
};

下面來一段繞口令:

  1. const物件不可以呼叫非const成員函式
  2. 非const物件可以呼叫const成員函式
  3. const成員函式內不可以呼叫其它的非const成員函式
  4. 非const成員函式內可以呼叫其它的const成員函式
    總結:權限只能縮小不能放大

11.建構式的進階

11.1建構式體賦值

建構式體中的陳述句只能將其稱作為賦初值,因為初始化只能初始化一次,而建構式體內可以多次賦值,

11.2 初始化串列

建構式的一項重要功能是對成員變數進行初始化,為了達到這個目的,可以在建構式的函式體中對成員變數一一賦值,還可以采用初始化串列,
注意:

  1. 使用建構式初始化串列并沒有效率上的優勢,僅僅是書寫方便,尤其是成員變數較多時,這種寫法非常簡單明了,
  2. 初始化串列可以用于全部成員變數,也可以只用于部分成員變數,
  3. 成員變數的初始化順序與初始化串列中列出的變數的順序無關,它只與成員變數在類中宣告的順序有關,
class Date
{
public:
	Date(int year, int month, int day)		//以冒號開始,接著是逗號分隔的資料成員串列,每個成員變數后面跟一個放在括號中的初始值或運算式, 	
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

特別的:
5. 每個成員變數在初始化串列中只能出現一次(初始化只能初始化一次)
6. 類中包含這些成員,必須放在初始化串列位置進行初始化:參考成員變數,const成員變數,自定義型別成員(該類沒有默認建構式)
7. 盡量使用初始化串列初始化,因為不管你是否使用初始化串列,對于自定義型別成員變數,一定會先使用初始化串列初始化,
8. 成員變數在類中宣告次序就是其在初始化串列中的初始化順序與其在初始化串列中的先后次序無關

11.3 explicit關鍵字

建構式不僅可以構造與初始化物件,對于單個引數的建構式,還具有型別轉換的作用,
explicit建構式是用來防止隱式轉換的,用explicit修飾建構式,將會禁止單參建構式的隱式轉換,

class Date
{
public:
	Date(int year)
		:_year(year)
	{}
	explicit Date(int year)
		:_year(year)
	{}
private:
	int _year;
	int _month:
	int _day;
}

12.C++11 的成員初始化新操作,

C++11支持非靜態成員變數在宣告時進行初始化賦值,但這里不是初始化,這里是給宣告的成員變數預設值

class B
{
public:
	B(int b = 0)
	:_b(b)
	{}
	int _b;
	};
	
class A
{
public:
	void Print()
	{
		cout << a << endl;
		cout << b._b<< endl;
		cout << p << endl;
	}
private:
	// 非靜態成員變數,可以在成員宣告時給預設值,
	int a = 10;
	B b = 20;
	int* p = (int*)malloc(4);
	static int n;
};

int A::n = 10;

int main()
{
	A a;
	a.Print();
	return 0;
}

13.友元

友元分為:友元函式和友元類
在 C++ 中,一個類中可以有 public、protected、private 三種屬性的成員,通過物件可以訪問 public 成員,只有本類中的函式可以訪問本類的 private 成員,現在,我們來介紹一種例外情況——友元(friend),借助友元(friend),可以使得其他類中的成員函式以及全域范圍內的函式訪問當前類的 private 成員,
friend 的意思是朋友,或者說是好友,與好友的關系顯然要比一般人親密一些,我們會對好朋友敞開心扉,傾訴自己的秘密,而對一般人會謹言慎行,潛意識里就自我保護,在 C++ 中,這種友好關系可以用 friend 關鍵字指明,中文多譯為“友元”,借助友元可以訪問與其有好友關系的類中的私有成員,

13.1 友元函式

為了方便使用cout,cin 這種輸入輸出,嘗試去多載operator<<,然后發現我們沒辦法將operator<<多載成成員函式,因為cout的輸出流物件和隱含的this指標在搶占第一個引數的位置,this指標默認是第一個引數也就是左運算元了,但是實際使用中cout需要是第一個形參物件,才能正常使用,所以我們要將operator<<多載成全域函式,但是這樣的話,又會導致類外沒辦法訪問成員,那么這里就需要友元來解決,operator>>同理,
友元函式可以直接訪問類的私有成員,它是定義在類外部的普通函式,不屬于任何類,但需要在類的內部宣告,宣告時需要加friend關鍵字,

class Date
{
	friend ostream& operator<< (ostream& out,const Date& d);					//友元函式,可以訪問私有的
	friend istream& operator>> (istream& in, Date& d);
public:

	Date(int year = 1900, int month = 1, int day = 1)			//建構式
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "1" << endl;
	}
private:
	int _year;		//物件的命名風格
	int _month;
	int _day;
};

ostream& operator<< (ostream & out,const Date&d)				//設為全域,避免搶位置			
{
	out << d._year << "-" << d._month << "-" << d._day;

	return out;
}

istream& operator>> (istream& in, Date& d)						//<<>>是輸入輸出運算子,所以多載后就用了
{
	in>> d._year >> d._month >> d._day;
	
	return in;
}

另外的:

  1. 友元函式可訪問類的私有和保護成員,但不是類的成員函式
  2. 友元函式不能用const修飾
  3. 友元函式可以在類定義的任何地方宣告,不受類訪問限定符限制
  4. 一個函式可以是多個類的友元函式
  5. 友元函式的呼叫與普通函式的呼叫和原理相同

13.2 友元類

友元類的所有成員函式都可以是另一個類的友元函式,都可以訪問另一個類中的非公有成員,
友元關系是單向的,不具有交換性,
友元關系不能傳遞

14.static成員

14.1 概念

宣告為static的類成員稱為類的靜態成員,用static修飾的成員變數,稱之為靜態成員變數;用static修飾的成員函式,稱之為靜態成員函式,靜態的成員變數一定要在類外進行初始化

14.2 特性

  1. 靜態成員為所有類物件所共享,不屬于某個具體的實體
  2. 靜態成員變數必須在類外定義,定義時不添加static關鍵字
  3. 類靜態成員即可用類名::靜態成員或者物件.靜態成員來訪問
  4. 靜態成員函式沒有隱藏的this指標,不能訪問任何非靜態成員
  5. 靜態成員和類的普通成員一樣,也有public、protected、private3種訪問級別,也可以具有回傳值

15.內部類

15.1概念

如果一個類定義在另一個類的內部,這個內部類就叫做內部類,注意此時這個內部類是一個獨立的類,它不屬于外部類,更不能通過外部類的物件去呼叫內部類,外部類對內部類沒有任何優越的訪問權限,
注意:內部類就是外部類的友元類,注意友元類的定義,內部類可以通過外部類的物件引數來訪問外部類中的所有成員,但是外部類不是內部類的友元,

15.2特性

  1. 內部類可以定義在外部類的public、protected、private都是可以的,
  2. 注意內部類可以直接訪問外部類中的static、列舉成員,不需要外部類的物件/類名,
  3. sizeof(外部類)=外部類,和內部類沒有任何關系,
class A
{
private:
	static int k;
	int h;
public:
	class B
	{
	public:
		void foo(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
	};
};
int A::k = 1;

16.再次理解封裝

C++是基于面向物件的程式,面向物件有三大特性即:封裝、繼承、多型,
C++通過類,將一個物件的屬性與行為結合在一起,使其更符合人們對于一件事物的認知,將屬于該物件的所有東西打包在一起;通過訪問限定符選擇性的將其部分功能開放出來與其他物件進行互動,而對于物件內部的一些實作細節,外部用戶不需要知道,知道了有些情況下也沒用,反而增加了使用或者維護的難度,讓整個事情復雜化,

END

類和物件的基本概念的介紹就到此為止,希望大家多多指正,一鍵三連,

寫第一個類(Date類)

介紹完了類和物件的知識,就可以寫自己的類了,下面以Date類為例做個示范:包括了建構式,運算子的多載等,
.h檔案

#pragma once
#include<iostream>
#include<cassert>

using std::cout;
using std::endl;
using std::cin;

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1);				//1.建構式,全預設,
	void Print();		//列印

	//析構,拷貝構造,賦值多載,可以不寫,默認生成
	//stack才寫

	//運算子多載,日期加減
	Date& operator+=(int day);			//+=之后,day這個物體還是存在的,所以可以回傳參考
	Date operator+(int day);

	Date& operator-=(int day);
	Date operator-(int day);

	//()為空是前置++,增加(int)是為了構成多載,后置++;
	Date& operator++();								//++d 前置可以參考,因為是先加自己身上再回傳
	Date operator++(int);							//d++ 用int占位,不需要給實參

	Date& operator--();
	Date operator--(int);

	// 運算子多載
	bool operator>(const Date& d)const;
	bool operator<(const Date& d)const;
	bool operator==(const Date& d)const;
	bool operator<=(const Date& d)const;
	bool operator>=(const Date& d)const;
	bool operator!=(const Date& d)const;

	// 日期-日期 回傳天數
	int operator-(const Date& d);

private:				//私有
	int _year;
	int _month;
	int _day;
};

.cpp

#include"Date.h"

inline int GetDay(int year, int month)									//回傳當月最大日期,頻繁呼叫所以行內	
{
	static int days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };		//12個月的日期,只創建一次,放在靜態區
	int day = days[month - 1];
	if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) )		//判斷閏年
	{
		day = 29; 
	}
	return day;
}

Date::Date(int year, int month, int day)				//指定類域,預設函式只能在一個地方出現
{
	if (year >= 0
		&& month <= 12 && month >= 1
		&& day > 0 && day <= GetDay(year, month))		//判斷日期的合法性
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << " no " << endl;
		assert(false);
	}

}
void Date::Print()
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

Date& Date::operator+=(int day)						//
{
	if (day < 0)									//day<0
	{
		_day -= -day;
	}
	else
	{
		_day += day;

		while (_day > GetDay(_year, _month))			//判斷day是否合法,不合法就月份+1,月份超出就置1,年++;
		{
			cout << GetDay(_year, _month) << _month<<endl;
			_day -= GetDay(_year, _month);
			_month++;
			if (_month > 12)
			{
				_month = 1;
				_year++;
			}
		}
	}

	return *this;
}

Date Date::operator+(int day)			//需要創建一個新的物件,回傳這個物件
{
	Date ret(*this);					//使用默認拷貝建構式(淺拷貝)

	ret += day;					//直接進行復用,直接操作物件,不要操作成員,

	return ret;

}

Date& Date::operator-=(int day)
{
	if (day < 0)								//減負數
	{
		_day += -day;
	}
	else										
	{
		_day -= day;

		while (_day <= 0)
		{
			_month--;							//先-月,因為要往上一個月算,
			if (_month < 0)						//邏輯參考+=
			{
				_month = 12;
				_year--;
			}
			_day += GetDay(_year, _month);
		}
	}

	return *this;
}

Date Date::operator-(int day)
{
	Date ret(*this);

	ret -= day;

	return ret;
}



//Date:: 宣告域
bool Date::operator>(const Date& d)const
{
	if (_year < d._year)
	{
		return false;
	}
	else if (_year > d._year)
	{
		return true;
	}
	else
	{
		if (_month < d._month)
		{
			return false;
		}
		else if (_month > d._month)
		{
			return true;
		}
		else
		{
			if (_day > d._day)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	}

}
bool Date::operator<(const Date& d)const
{
	if (_year > d._year)
	{
		return false;
	}
	else if (_year < d._year)
	{
		return true;
	}
	else
	{
		if (_month > d._month)
		{
			return false;
		}
		else if (_month < d._month)
		{
			return true;
		}
		else
		{
			if (_day < d._day)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	}
}
bool Date::operator==(const Date& d)const
{
	if (*this > d || *this < d)
		return false;
	else
		return true;
}
bool Date::operator<=(const Date& d)const
{
	if (*this > d)
		return false;
	else
		return true;
}
bool Date::operator>=(const Date& d)const
{
	if (*this < d)
		return false;
	else
		return true;
}
bool Date::operator!=(const Date& d)const
{
	if (*this == d)
		return false;
	else
		return true;
}

Date& Date::operator++()
{
	*this += 1;

	return *this;
}
Date Date::operator++(int)
{
	Date ret(*this);
	*this += 1;					//這里不能對ret操作,因為要做回傳值,this實際上要加,但為了回傳一個加之前,所以回傳一個ret

	return ret;
}

Date& Date::operator--()
{
	*this -= 1;

	return *this;
}

Date Date::operator--(int)
{
	Date ret(*this);
	*this -= 1;

	return ret;
}

int Date::operator-(const Date& d)			//設定兩端,然后遍歷整個區間,++i就可以,
{
	int flag = 1;					//判斷日期正負,默認為正
	Date max = *this;				//默認d小,如果不采取這種方式,下面while會存在編譯錯誤
	Date min = d;

	if (*this < d)
	{
		max = d;				//拷貝構造的另一種表達法,不能重復構建物件,重復定義根據就近原則會以上面的為最終結果
		min = *this;
		flag = -1;
	}


	int n = 0;

	while (max > min)
	{
		++n;
		++(min);
	}

	return n * flag;			//大小*正負
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286293.html

標籤:其他

上一篇:FPGA的學習:數碼管靜態顯示的實作(一)

下一篇:【C語言】萬字肝爆!建議收藏!深度剖析資料在記憶體中的存盤

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more