一. 前言
? ? ? ? 老樣子,先來回顧一下上期的內(nèi)容:上期我們著重學了C++類中的六大默認成員函數(shù),并自己動手實現(xiàn)了一個日期類,相信各位對C++中的類已經(jīng)有了一定程度的了解。本期就是類和對象的最后一篇啦,終于要結束咯,吧唧吧唧
? ? ? ? 話不多說,開吃咯?。?!
二. 初始化列表
2.1 引入
? ? ? ? 我們先來看看下面的代碼:
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
const int _year;
const int _month;
const int _day;
};
int main()
{
Date d;
return 0;
}
當我們編譯代碼時,發(fā)現(xiàn)編譯器報了一大堆錯誤。報錯的主要原因主要有兩個
1、const變量定義時需要進行初始化
2、const變量不能作為左值
? ? ? ? 欸,可能有些小伙伴就納悶了:我們不是在構造函數(shù)中對const成員變量進行初始化了嗎??實際上,在構造函數(shù)函數(shù)體內(nèi)進行的并不是初始化,而是賦值操作。因為初始化只能初始化一次,而構造函數(shù)體內(nèi)可以進行多次賦值。
? ? ? ? 出于這個原因,于是編譯器就會報出以上兩種錯誤。那怎么辦呢?眾所周知,初始化是在定義變量時進行的,那變量又是在哪定義的呢?答案是:初始化列表
2.2?概念
? ? ? ? 在C++中,初始化列表可以認為是成員變量定義的地方。
????????初始化列表:以一個冒號開始,接著是一個以逗號分隔的數(shù)據(jù)成員列表,每個"成員變量"后面跟一個放在括號中的初始值或表達式。舉例如下:
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1)
:_year(year) //初始化列表,是每個成員變量定義的地方,可以進行初始化
,_month(month) //用month的值初始化成員變量_month
,_day(day)
{}
private:
//成員變量的聲明
const int _year = 0;
const int _month = 0;
const int _day = 0;
};
int main()
{
Date d;
return 0;
}
2.3 注意事項
- 變量的初始化只能初始化一次,故每個成員變量在初始化列表中只能出現(xiàn)一次
- 當類中包含以下成員時,必須放在初始化列表位置進行初始化
class A { public: A(int a) //顯式定義構造函數(shù),不自動生成默認構造函數(shù) :_a(a) {} private: int _a; }; class B { public: B(int a, int ref) :_a(a) //調(diào)用有參構造函數(shù)初始化 , _ref(ref) //初始化引用變量 , _n(10) //初始化const變量 {} private: A _a; // 沒有默認構造函數(shù)的類 int& _ref; // 引用變量 const int _n; // const變量 };
-
建議盡量使用初始化列表初始化,因為初始化列表是成員變量定義的地方,無論你是否顯式地寫,每個成員都要走初始化列表
class Time { public: Time(int hour = 0) :_hour(hour) { cout << "Time()" << endl; } private: int _hour; }; class Date1 { public: Date1(int day) :_day(day) //使用初始化列表進行初始化 ,_t(day) {} private: int _day; Time _t; }; class Date2 { public: Date2(int day) { _day = day; //在構造函數(shù)內(nèi)部進行賦值 _t = day; } private: int _day; Time _t; }; int main() { Date1 d1(3); cout << "-----------------------" << endl; Date2 d2(3); return 0; }
-
C++11支持在聲明處給缺省值,這個缺省值就是給初始化列表的。如果初始化列表沒有顯式給值,則使用這個缺省值;如果顯式給了,就用給的值進行初始化。
-
初始化列表對成員變量的初始化順序與其聲明的次序相同,與初始化列表的先后次序無關。舉個小例子
class A { public: A(int a) :_a1(a) //初始化列表的順序和聲明一樣,即也是先初始化_a2再初始化_a1 , _a2(_a1) //那么,這里用_a1初始化_a2會發(fā)生什么?_a1的值是多少 {} void Print() { cout << _a1 << " " << _a2 << endl; } private: //成員變量的聲明,先_a2再_a1 int _a2; int _a1; }; int main() { A aa(1); aa.Print(); }
上面代碼的輸出結果是1 隨機值。
解析:由于_a2的聲明在_a1前,_a2會先于_a1進行初始化,因此_a2初始化時_a1還是個隨機值,故_a2會被初始化為隨機值,然后_a1再初始化為1。
我們也可以使用調(diào)試來觀察初始化順序,如下所示:
三. explicit關鍵字
????????構造函數(shù)不僅可以構造與初始化對象,對于單個參數(shù)或者除第一個參數(shù)無默認值其余均有默認值的構造函數(shù),還具有隱式類型轉(zhuǎn)換的作用,如下:
class Date
{
public:
// 1. 單參構造函數(shù),具有隱式類型轉(zhuǎn)換作用
Date(int year)
:_year(year)
{}
//2. 雖然有多個參數(shù),但是后兩個參數(shù)可以不傳遞,具有類型轉(zhuǎn)換作用
//用explicit修飾構造函數(shù),可以禁止類型轉(zhuǎn)換
//explicit Date(int year, int month = 1, int day = 1)
//: _year(year)
//, _month(month)
//, _day(day)
//{}
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;
};
int main()
{
Date d1(2022); //使用單參構造函數(shù)初始化d1
// 用一個整形變量給日期類型對象賦值
// 實際編譯器背后會用2023構造一個匿名的臨時對象,最后用這個臨時對象給d1對象賦值
d1 = 2023;
return 0;
}
像上面這種運算符左右兩邊類型不匹配,運算時編譯器背后進行處理的過程,稱之為隱式類型轉(zhuǎn)換。
但是,這樣的代碼往往可讀性不好,我們更希望書寫代碼時左右兩邊的類型是一致的,那有沒有什么辦法可以禁止編譯器進行隱式類型轉(zhuǎn)換呢?有,就是explicit關鍵字。
使用 explicit(顯式的) 修飾構造函數(shù),將會禁止構造函數(shù)的隱式類型轉(zhuǎn)換。很簡單,直接在構造函數(shù)前面加上explicit即可,這里就不再進行演示了。
四. static成員
4.1 概念
? ? ? ? 用static修飾的成員變量稱為靜態(tài)成員變量;用static修飾的成員函數(shù)稱之為靜態(tài)成員函數(shù)。一般來說,靜態(tài)成員變量一定要在類外進行初始化,但在C++11中允許const靜態(tài)成員變量在類內(nèi)初始化,如下所示:
class A
{
static int GetCount() //靜態(tài)成員函數(shù)
{
return count;
}
private:
static int count; //靜態(tài)成員變量,必須類外初始化
const static int num = 10; //const靜態(tài)成員變量,可以在類內(nèi)初始化,但不建議
};
int A::count = 10; //靜態(tài)成員變量要在類外進行初始化
4.2 特性
- 靜態(tài)成員為所有類對象所共享,不屬于某個具體的對象,存放在靜態(tài)區(qū)
- 靜態(tài)成員變量必須類內(nèi)聲明、類外定義。定義時不用添加static關鍵字,類中的只是聲明
- 類的靜態(tài)成員可以用 類名::靜態(tài)成員 或者 對象名.靜態(tài)成員 來訪問
- 靜態(tài)成員函數(shù)沒有隱藏的this指針,不能訪問任何非靜態(tài)成員
- 靜態(tài)成員也是類的成員,受public、protected、private 訪問限定符的限制
?
小問題:靜態(tài)成員函數(shù)可以調(diào)用非靜態(tài)成員函數(shù)嗎?反過來呢?
問題解答:答案是不行,靜態(tài)成員函數(shù)不能調(diào)用非靜態(tài)成員函數(shù),因為靜態(tài)成員函數(shù)沒有隱藏的this指針,而非靜態(tài)成員函數(shù)需要通過this指針來調(diào)用。但是非靜態(tài)成員函數(shù)可以調(diào)用靜態(tài)成員函數(shù),因為靜態(tài)成員函數(shù)的特點是沒有this指針,故可以直接進行調(diào)用。
五. 友元
5.1 概念
? ? ? ? 在C++中,為了封裝性我們一般將成員變量聲明為【private】私有的,只允許在類內(nèi)訪問成員變量。但是有時候我們需要在類外訪問這些成員變量,此時有兩種方法:1.將成員變量聲明為【public】共有;2.利用友元。
????????友元提供了一種突破封裝的方式,為代碼的編寫提供了便利。友元分為友元類和友元函數(shù),當一個函數(shù)/類聲明為某個類的友元函數(shù)/類時,這個函數(shù)/類訪問類中成員時不受訪問限定符限制。下面是函數(shù)/類聲明為友元的方式,用到了friend關鍵字
class A
{
friend void GetCount(const A& a); //將全局函數(shù)GetCount聲明為A類的友元函數(shù)
friend class B; //將B類聲明為A類的友元類
private:
int count = 10;
int num = 20;
};
class B
{
public:
void GetNum(const A& a)
{
cout << a.num << endl; //b類中可以訪問a類的私有成員
}
};
void GetCount(const A& a)
{
cout << a.count << endl; //可以訪問A類的私有成員
}
int main()
{
A a;
B b;
GetCount(a);
b.GetNum(a);
return 0;
}
小貼士:雖然友元提供了便利,但是友元會增加耦合度,破壞程序的封裝性,故不建議使用友元。
5.2?友元函數(shù)? ?
? ? ? ? 友元函數(shù)一般用作于流提取運算符>>以及流插入運算符<<的重載,這兩個運算符的重載比較特殊,不能當做成員函數(shù)進行重載。
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
//如果重載為Date的成員函數(shù),第一個參數(shù)為隱藏的this指針,但cout是ostream類的對象,第一個參數(shù)應該是ostream類型,互相矛盾
// ostream& operator<<(const Date& d);
private:
int _year;
int _month;
int _day;
};
//為了讓第一個參數(shù)類型為ostream,故當做全局函數(shù)重載
const ostream& operator<<(const ostream& out, const Date& d)
{
out << d._year << "年" << d.month << "月" << d.day << "日";
return out;
}
int main()
{
Date d;
cout << d; //重載流插入運算符使其可以輸出日期類
return 0;
}
? ? ? ? 那么問題就來了,既然不能聲明為成員函數(shù),那我們在全局函數(shù)中要怎么訪問Date的私有成員呢?
? ? ? ? ?這時候就不得不使用我們上面說的友元了,將operator<<聲明為Date類的友元函數(shù)后,代碼成功運行:
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
friend ostream& operator<<(ostream& out, const Date& d); //將operator<<全局函數(shù)聲明為Date類的友元函數(shù)
private:
int _year;
int _month;
int _day;
};
注意事項
- 友元函數(shù)可以訪問類中的私有成員,它是定義在類外部的普通函數(shù),但需要在類的內(nèi)部進行聲明,聲明時需要加friend關鍵字
- 友元函數(shù)不能用const修飾,const只能修飾成員函數(shù)
- 友元函數(shù)可以在類定義的任何地方聲明,不受類訪問限定符限制
- 一個函數(shù)可以是多個類的友元函數(shù)
5.3?友元類
? ? ? ? 友元類中的所有成員函數(shù)都可以訪問另一個類的非公有成員。友元關系是單向的,不具有交換性。例如B是A的友元類,B中的所有成員函數(shù)可以訪問A中的私有成員,但A中的成員函數(shù)不能訪問B中的私有成員。舉例如下:
class A
{
friend class B; //定義B是A的友元類
void GetSum(B& b)
{
cout << b.sum << endl; //這里會報錯,A類的成員函數(shù)無法訪問B類的私有成員,不具有交換性
}
private:
int count = 20;
};
class B
{
void GetCount(A& a)
{
cout << a.count << endl; //通過編譯,B是A的友元類,B中成員函數(shù)可以訪問A的私有成員
}
private:
int sum = 10;
};
? ? ? ? 以上程序編譯時會報錯如下
? ? ? ? 友元關系也不具有傳遞性。例如:C是B的友元類,B是A的友元類,無法說明C是A的友元。舉例如下
class A
{
friend class B; //定義B是A的友元類
private:
int a_sum = 10;
};
class B
{
friend class C; //定義C是B的友元類
private:
int b_sum = 20;
};
class C
{
void GetBSum(B& b)
{
cout << b.b_sum << endl; //編譯通過,C是B的友元類
}
void GetASum(A& a)
{
cout << a.a_sum << endl; //這里編譯器會報錯,C不是A的友元類,無法訪問私有成員,友元關系不具有傳遞性
}
private:
int c_sum = 30;
};
?? ? ? ? 以上程序編譯時會報錯如下?
六. 內(nèi)部類
? ? ? ? 一個類不僅可以定義在全局范圍內(nèi),還可以定義在另一個類的內(nèi)部。我們將定義在某個類內(nèi)部的類稱之為內(nèi)部類。下面的B類就是一個內(nèi)部類:
class A //A稱為外部類
{
public:
class B //B類在A類的內(nèi)部定義,稱之為內(nèi)部類
{
private:
int sum; //b類的成員變量
};
private:
int count; //a類的成員變量
};
????????內(nèi)部類是一個獨立的類,它不屬于外部類,更不能通過外部類的對象去訪問內(nèi)部類的成員。外部類對內(nèi)部類沒有任何特殊的訪問權限。有以下兩個具體體現(xiàn)
- 內(nèi)部類可以定義在外部類的的任何位置,不受外部類訪問限定符的限制。
- sizeof(外部類)=外部類,和內(nèi)部類沒有任何關系
? ? ? ? 內(nèi)部類是外部類的友元類,內(nèi)部類可以通過外部類的對象訪問外部類的所有成員。但外部類不是內(nèi)部類的友元類,無權訪問內(nèi)部類的私有成員。
class A //A是外部類
{
public:
class B //B是內(nèi)部類
{
int GetACount(A& a)
{
return a.count; //可以訪問外部類的私有成員
}
private:
int sum;
};
int GetBSum(B& b)
{
return b.sum; //這里會報錯,外部類不能訪問內(nèi)部類的私有成員
}
private:
int count; //a類的成員變量
};
????????內(nèi)部類可以直接訪問外部類中的static成員,不需要外部類的對象/類名,如下所示
class A //A是外部類
{
public:
class B //B是內(nèi)部類
{
int GetACount()
{
return _count; //可以直接訪問外部類的靜態(tài)成員變量,無需類名/類對象
}
private:
int sum;
};
private:
static int _count; // A中的靜態(tài)成員變量
};
int A::_count = 10; //類外進行初始化
七. 匿名對象
? ? ? ? C++支持我們不給對象起名字,這樣的對象我們稱為匿名對象,其定義方式如下:
int main()
{
//對象類型+():創(chuàng)建一個匿名對象
A(); //這里就是創(chuàng)建一個匿名對象A
return 0;
}
? ? ? ? 匿名對象的聲明周期只在當前行,當前行結束后會自動調(diào)用析構函數(shù)進行銷毀:
? ? ? ?匿名對象具有常屬性,即不能對匿名對象中的成員變量進行修改:
int main()
{
A().count = 10; //編譯器報錯:表達式必須是可修改的左值
return 0;
}
? ? ? ? 可以給匿名對象取別名,這樣可以延長匿名對象的聲明周期:
int main()
{
//給匿名對象取別名
const A& cla1 = A(); //注意:這里必須是const引用,因為匿名對象具有常性,權限不能放大
cout << "程序即將結束" << endl;
return 0;
}
? ? ? ? 匿名對象經(jīng)常用在僅需調(diào)用某個類的成員函數(shù)的情況,可以簡化我們代碼的編寫。舉例如下?
class Solution //Solution類用來求兩數(shù)之和
{
public:
int Sum_Solution(int x,int y) //返回兩數(shù)之和
{
return x + y;
}
};
int main()
{
//不使用匿名對象
Solution s1; //要先定義一個類對象,這個對象僅僅只是用來調(diào)用方法
s1.Sum_Solution(2, 2); //然后再去調(diào)用成員函數(shù)
//使用匿名對象
Solution().Sum_Solution(2, 3); //代碼更加簡便
}
? ? ? ? 上面的Solution類是不是很熟悉?沒錯,在我們使用C++進行刷題時每次能夠遇到它
?以上,就是本期的全部內(nèi)容啦??文章來源:http://www.zghlxwxcb.cn/news/detail-715877.html
制作不易,能否點個贊再走呢??文章來源地址http://www.zghlxwxcb.cn/news/detail-715877.html
到了這里,關于【C++深入淺出】類和對象下篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!