詳解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為類的名字,{}中為類的主體,類定義結束時一定要加后面分號,
類中的元素稱為類的成員:
- 資料稱為類的屬性或者成員變數;
- 函式稱為類的方法或者成員函式,
類的兩種定義方式: - 宣告和定義全部放在類體中,成員函式如果在類中定義,編譯器可能會將其當成行內函式處理,
class className
{
public:
//公共的行為或屬性
private:
//私有的行為或屬性
};
- 宣告放在.h檔案中,類的定義放在.cpp檔案中
在.h中
class className
{
public:
void show();
//公共的行為或屬性
private:
//私有的行為或屬性
};
在.cpp中
void className::show()
{
cout<<_name<<endl;
}
一般情況會采用第二種方式,
4.類的訪問限定符及封裝
4.1 訪問限定符
訪問限定符只在編譯時有用,當資料映射到記憶體后,沒有任何訪問限定符上的區別,
C++實作封裝的方式:用類將物件的屬性與方法結合在一塊,讓物件更加完善,通過訪問權限選擇性的將其介面提供給外部的用戶使用,
C++的訪問限定符三種:public(公有) , protected(保護), private(私有),
- public修飾的成員在類外可以直接被訪問;
- protected和private修飾的成員在類外不能直接被訪問;
- 訪問權限作用域從該訪問限定符出現的位置開始直到下一個訪問限定符出現時為止;
- 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 結構體記憶體對齊規則
- 第一個成員在與結構體偏移量為0的地址處,
- 其他成員變數要對齊到某個數字(對齊數)的整數倍的地址處,
- 結構體總大小為:最大對齊數(所有變數型別最大者與默認對齊引數取最小)的整數倍,
- 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍,
8.類成員函式的this指標
this 是 C++ 中的一個關鍵字,也是一個 const 指標,它指向當前物件,通過它可以訪問當前物件的所有成員,
8.1 this指標是什么
C++編譯器給每個“非靜態的成員函式“增加了一個隱藏的指標引數,讓該指標指向當前物件(函式運行時呼叫該函式的物件),在函式體中所有成員變數的操作,都是通過該指標去訪問,只不過所有的操作對用戶是透明的,即用戶不需要來傳遞,編譯器自動完成, 一般存在堆疊中,
8.2 this指標的特性
- this指標的型別:型別 const*
- 只能在“成員函式”的內部使用
- this指標本質上其實是一個成員函式的形參,是物件呼叫成員函式時,將物件地址作為實參傳遞給this形參,所以物件中不存盤this指標,
- 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();
}
- 如果類中沒有顯式定義建構式,則C++編譯器會自動生成一個無參的默認建構式,一旦用戶顯式定義編譯器將不再生成,
- 無參的和全預設的建構式都稱為默認建構式,并且默認建構式只能有一個,
- 建構式會對自定型別成員呼叫的它的默認成員函式,
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.特性
拷貝建構式也是特殊的成員函式,
其特征如下:
- 拷貝建構式是建構式的一個多載形式,
- 拷貝建構式的引數只有一個且必須使用參考傳參,使用傳值方式會引發無窮遞回呼叫,會被認為重新定義了一個物件,并且繼續使用拷貝構造,這樣無限遞回,
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;
};
- 系統會生成默認的拷貝建構式, 默認的拷貝建構式物件按記憶體存盤按位元組序完成拷貝,這種拷貝我們叫做淺拷貝,或者值拷貝,
- 當物件中存在如堆疊這種結構,我們就需要自己寫拷貝建構式,進行深拷貝,
9.4 賦值運算子多載
1.運算子多載
C++為了增強代碼的可讀性引入了運算子多載,運算子多載是具有特殊函式名的函式,也具有其回傳值型別,函式名字以及引數串列,其回傳值型別與引數串列與普通的函式類似,
函式名字為:關鍵字operator后面接需要多載的運算子符號,
函式原型:回傳值型別 operator運算子(引數串列)
需要注意的:
- 不能通過連接其他符號來創建新的運算子:比如operator@
- 多載運算子必須有一個型別別或者列舉型別的運算元
- 用于內置型別的運算子,其含義不能改變,例如:內置的整型+,不能改變其含義
- 作為類成員的多載函式時,其形參看起來比運算元數目少1成員函式的運算子有一個默認的形參this,限定為第一個形參
- .* 、:: 、sizeof 、?: 、. 注意以上5個運算子不能多載,
2.賦值運算子多載
賦值運算子主要有四點:
- 引數型別
- 回傳值
- 檢測是否自己給自己賦值
- 回傳*this
- 一個類如果沒有顯式定義賦值運算子多載,編譯器也會生成一個,完成物件按位元組序的值拷貝,
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 ; // 日
};
下面來一段繞口令:
- const物件不可以呼叫非const成員函式
- 非const物件可以呼叫const成員函式
- const成員函式內不可以呼叫其它的非const成員函式
- 非const成員函式內可以呼叫其它的const成員函式
總結:權限只能縮小不能放大,
11.建構式的進階
11.1建構式體賦值
建構式體中的陳述句只能將其稱作為賦初值,因為初始化只能初始化一次,而建構式體內可以多次賦值,
11.2 初始化串列
建構式的一項重要功能是對成員變數進行初始化,為了達到這個目的,可以在建構式的函式體中對成員變數一一賦值,還可以采用初始化串列,
注意:
- 使用建構式初始化串列并沒有效率上的優勢,僅僅是書寫方便,尤其是成員變數較多時,這種寫法非常簡單明了,
- 初始化串列可以用于全部成員變數,也可以只用于部分成員變數,
- 成員變數的初始化順序與初始化串列中列出的變數的順序無關,它只與成員變數在類中宣告的順序有關,
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;
}
另外的:
- 友元函式可訪問類的私有和保護成員,但不是類的成員函式
- 友元函式不能用const修飾
- 友元函式可以在類定義的任何地方宣告,不受類訪問限定符限制
- 一個函式可以是多個類的友元函式
- 友元函式的呼叫與普通函式的呼叫和原理相同
13.2 友元類
友元類的所有成員函式都可以是另一個類的友元函式,都可以訪問另一個類中的非公有成員,
友元關系是單向的,不具有交換性,
友元關系不能傳遞,
14.static成員
14.1 概念
宣告為static的類成員稱為類的靜態成員,用static修飾的成員變數,稱之為靜態成員變數;用static修飾的成員函式,稱之為靜態成員函式,靜態的成員變數一定要在類外進行初始化,
14.2 特性
- 靜態成員為所有類物件所共享,不屬于某個具體的實體
- 靜態成員變數必須在類外定義,定義時不添加static關鍵字
- 類靜態成員即可用類名::靜態成員或者物件.靜態成員來訪問
- 靜態成員函式沒有隱藏的this指標,不能訪問任何非靜態成員
- 靜態成員和類的普通成員一樣,也有public、protected、private3種訪問級別,也可以具有回傳值
15.內部類
15.1概念
如果一個類定義在另一個類的內部,這個內部類就叫做內部類,注意此時這個內部類是一個獨立的類,它不屬于外部類,更不能通過外部類的物件去呼叫內部類,外部類對內部類沒有任何優越的訪問權限,
注意:內部類就是外部類的友元類,注意友元類的定義,內部類可以通過外部類的物件引數來訪問外部類中的所有成員,但是外部類不是內部類的友元,
15.2特性
- 內部類可以定義在外部類的public、protected、private都是可以的,
- 注意內部類可以直接訪問外部類中的static、列舉成員,不需要外部類的物件/類名,
- 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
標籤:其他
