C++const成員、static成員、成員函數(shù)指針、友元
#include <iostream>
using namespace std;
class Classconst
{
????public:
???? const int i1; //const數(shù)據(jù)成員,c++要求const類型必須顯式初始化,當(dāng)構(gòu)造函數(shù)執(zhí)行到函數(shù)體{}時對象及其成員已經(jīng)被聲明了,所以const成員無法在函數(shù)體內(nèi)初始化
???? Classconst(int val=5) //默認(rèn)參數(shù)兼顧無參構(gòu)造器
???? :i1(val),i2(i1) //const限定的數(shù)據(jù)成員一般通過成員初始化列表進(jìn)行初始化,格式為: 構(gòu)造函數(shù)():數(shù)據(jù)成員(值){函數(shù)體} ,初始化寫在塊前面、形參列表后面以冒號開頭,(對于成員對象來說)效率比在塊中賦值高(因為進(jìn)入塊之前需要默認(rèn)初始化,進(jìn)入塊后再執(zhí)行賦值運算符重載,而直接通過拷貝構(gòu)造器省一次函數(shù)調(diào)用),使用這種方式時如果數(shù)據(jù)成員之間相互賦值(如成員c=成員b),則類中b的聲明需要在c前面/上面才對c可見,這也表明成員的初始化順序是按類中聲明的順序,在聲明、初始化上一個成員時,下一個成員還不存在所以不可見,注意初始化列表中各項的先后順序與初始化順序無關(guān)
???? {}
???? void dis() //普通函數(shù)成員
???? {
???????? i2 = 3;
???????? cout << i1 << endl;
???? }
???? void dis() const //const函數(shù)成員,格式為:函數(shù)頭 const {函數(shù)體} ,聲明const函數(shù)成員保證函數(shù)內(nèi)不修改數(shù)據(jù)成員,包括函數(shù)的子塊、調(diào)用的函數(shù),調(diào)用的函數(shù)也必須為const函數(shù),const函數(shù)與普通函數(shù)構(gòu)成重載
???? {
???????? //i2 = 3; 不能修改i2
???????? //geti2(); 不能調(diào)用非const函數(shù)
???????? cout << i1 << endl;
???????? cout << i2 << endl; //可以使用非const數(shù)據(jù)成員
???????? int local = 20;
???????? local = 40; //數(shù)據(jù)成員外的變量不受const約束
???? }
???? int i3;
???? void geti2()
???? {
???? ????cout << i2;
???? }
????private:
???? int i2 = 2; //c++11類內(nèi)初始化(之前版本不允許)等價于成員初始化列表,該成員會默認(rèn)初始化為指定值,如果構(gòu)造器初始化列表設(shè)定了該成員的初始化則類內(nèi)初始化不起作用(被覆蓋)
};
int mainconst()
{
???? Classconst c(20);
???? c.dis(); //非const對象優(yōu)先調(diào)用非const函數(shù),當(dāng)沒有普通函數(shù)時調(diào)用const函數(shù)
???? const Classconst c2; //const 對象
???? //錯誤:c2.geti2(); const對象只能調(diào)用const函數(shù)
???? c2.dis(); //調(diào)用的是dis() const函數(shù),
???? c2.i3; //可訪問非const數(shù)據(jù)
???? //錯誤:c2.i3 = 30; const對象不能修改任何成員
????
???? int i1=10,i2=20;
???? -i1; //分析int類型變量的operator-() 負(fù)號運算符,取負(fù)不影響原變量
???? i1 = -i1; //-i1可以作為右值(用于賦值)
???? //報錯:-i1 = i2; //-i1不可作為左值(不可修改)
???? -(-i1); //可以對-i1再取負(fù)數(shù),而這個過程不能改變-i1本身
???? //所以-i1產(chǎn)生了一個使用i1數(shù)據(jù)進(jìn)行運算的備份,而這個備份const不可修改,但可以對這個const再產(chǎn)生新的備份
???? return 0;
}
class ClassNum //模擬取負(fù)的邏輯
{
????public:
???? int v1;
???? int v2;
???? ClassNum(int a, int b) :v1(a), v2(b) {}
???? //const ClassNum operator-() //返回const對象,對應(yīng)-c1,返回一個const備份(通過對c1取負(fù)),返回對象用到默認(rèn)的拷貝構(gòu)造器(不涉及堆內(nèi)存/動態(tài)內(nèi)存則不需要手動定義)
???? //{
???? ????// return ClassNum(-v1, -v2); //創(chuàng)建一個新的對象,使用當(dāng)前對象的數(shù)據(jù)取負(fù)。創(chuàng)建對象使用了兩個參數(shù)的構(gòu)造器,返回對象使用了拷貝構(gòu)造器,所以調(diào)用時總共會額外創(chuàng)建兩個對象
???? //}
???? //上面的函數(shù)可以完成-c1;c1=-c1;以及不允許-c1=c2三項。但是-(-c1)不行,因為-c1返回了const對象,const對象不能調(diào)用非const函數(shù)。所以應(yīng)該修改該函數(shù)為const函數(shù)
???? const ClassNum operator-() const //將函數(shù)聲明為const,普通對象可以訪問,const對象也可以訪問
???? {
???? ????return ClassNum(-v1, -v2);
???? }
???? int norint; //普通成員變量,當(dāng)對象聲明為const時不能修改對象的成員
???? mutable int mutint; //mutable關(guān)鍵字修飾的成員,即便是在const對象/結(jié)構(gòu)變量中也可以修改
};
class Cow
{
???? char name[20];
???? char* hobby;
???? double weight;
????public:
???? Cow() :name(""), hobby(nullptr), weight(0.0) {} //成員初始化列表,在創(chuàng)建對象、聲明成員時進(jìn)行初始化,執(zhí)行到函數(shù)體{}中對象已經(jīng)進(jìn)行了默認(rèn)的或指定的初始化,在函數(shù)體中進(jìn)行的為賦值而非初始化,通過name("")就可看出,等價于聲明成員name=""(char數(shù)組只在初始化時可以=字符串常量),而將hobby初始化為nullptr(c++關(guān)鍵字,等價于NULL和0,專門指代空指針),通過列表初始化時不能使用this,也就是說這里的初始化發(fā)生在產(chǎn)生對象之前
???? ~Cow() { delete[]hobby; } //hobby==nullptr時也可以delete[]hobby
???? Cow(const char* nm, const char* ho, double wt) :weight(wt)
???? {
???????? strncpy(name, nm, 19);
???????? name[19] = 0;
???????? hobby = new char[strlen(ho) + 1];
???????? strcpy(hobby, ho);
???? }
???? Cow(const Cow& c) :weight(c.weight)
???? {
???????? strcpy(name, c.name);
???????? if (c.hobby) //需要判斷hobby!=nullptr,雖然為空指針時可以delete[]hobby,但調(diào)用strlen等不接收nullptr的函數(shù)會出錯
???????? {
???????????? hobby = new char[strlen(c.hobby) + 1];
???????????? strcpy(hobby, c.hobby);
???????? }
???????? else
???????? {
???????? ????hobby = nullptr;
???????? }
???? }
???? Cow& operator=(const Cow& c)
???? {
???????? delete[]hobby;
???????? weight = c.weight; //賦值運算符重載無法使用成員初始化列表,因為this是已存在對象
???????? strcpy(name, c.name);
???????? if (c.hobby) //如果將hobby默認(rèn)設(shè)置為new char[1],hobby[0]=0則調(diào)用strlen不會出錯,但默認(rèn)構(gòu)造會占用堆內(nèi)存
???????? {
???????????? hobby = new char[strlen(c.hobby) + 1];
???????????? strcpy(hobby, c.hobby);
???????? }
???????? else
???????? {
???????? ????hobby = nullptr;
???????? }
???????? return *this;
???? }
???? void ShowCow()const //const函數(shù)兼容const對象
???? {
???? ????cout << "name:" << (name[0] ? name : "none") << "\thobby:" << (hobby ? hobby : "none") << "\tweight:" << weight << endl;
???? }
};
/*------ClassStatic.h頭文件--------*/
class ClassStatic
{
????public:
???? ~ClassStatic();
???? static int count; //static數(shù)據(jù)成員,屬于類、唯一,對象可調(diào)用,使用前必須在類以外的地方聲明
???? static const int SIZE = 16; //static const 同時修飾數(shù)據(jù)成員,既屬于類、唯一,又不可修改。只能像這樣在聲明時進(jìn)行初始化
???? ClassStatic(char v = '\0')
???? {
???????? if (count < SIZE) //通過對象調(diào)用static成員
???????? stack[count++] = v; //改變static成員
???? }
???? static void reset() //static函數(shù)成員,屬于類,可通過類調(diào)用,也可由對象調(diào)用,所以沒有this指針,也就是說不能在函數(shù)中訪問屬于對象的數(shù)據(jù)/函數(shù)成員,static函數(shù)只能訪問static數(shù)據(jù)/函數(shù)
???? {
???? ????count = 0;
???? }
???? int data;
???? void func() {}
????private:
???? static char* stack; //static私有成員也在類外聲明才能使用,但除聲明外不能直接訪問(因為私有),類中的static聲明就像是名稱空間中的引用式聲明外部鏈接變量,如果在頭文件中的類聲明中使用了定義式聲明(初始化一個靜態(tài)成員或者寫一個函數(shù)定義),當(dāng)多個源文件都包含該頭文件時會產(chǎn)生多重定義沖突,所以在類聲明中只聲明,在同名cpp文件中進(jìn)行定義
???? int va;
};
/*------ClassStatic.cpp文件--------*/
ClassStatic::~ClassStatic() {} //析構(gòu)略
int ClassStatic::count = 0; //在cpp文件中完成類的靜態(tài)成員的定義式聲明,同樣只能存在一個定義式聲明,靜態(tài)變量不顯式初始化會初始化為0。類的靜態(tài)成員在頭文件、源文件中的引用式聲明、定義式聲明就像名稱空間中的靜態(tài)變量用法一樣
char arr[ClassStatic::SIZE]; //類外使用static const常量
char* ClassStatic::stack = arr; //所有static成員都必須在類外聲明(包括私有的),在類的頭文件配套的cpp文件中聲明,除該聲明外不能直接訪問(因為私有)
/*------用戶使用.cpp文件--------*/
int mainstatic()
{
???? ClassStatic::count = 2; //通過類訪問static成員,這時還沒有創(chuàng)建對象,static成員屬于類
???? //錯誤:ClassStatic::stack;私有不可訪問
???? ClassStatic c;
???? c.reset(); //通過對象調(diào)用static函數(shù)
???? c.count = 5; //通過對象調(diào)用static數(shù)據(jù)
???? ClassStatic::reset(); //通過類調(diào)用static函數(shù)
???? //static成員相當(dāng)于是類的名稱空間下的全局變量,全局/靜態(tài)變量從程序開始到結(jié)束一直存在,所以初始化非0的全局/靜態(tài)變量(RW-data)(read write)隨代碼(code/text)以及常量(RO-data)(read only 只讀的靜態(tài)/全局變量)保存在ROM(硬盤等)的程序文件中,所以載入文件/程序運行后將全局/靜態(tài)變量讀取到RAM(內(nèi)存)即可使用,而未初始化或初始化為0的全局/靜態(tài)變量(ZI-data/.bss)(zero init)只需要記錄大小,在程序運行前開辟好空間備用。所以全局/靜態(tài)變量的大小是固定的,程序執(zhí)行之前就知道應(yīng)該開辟多大,也就不存在溢出的問題
???? //而普通數(shù)據(jù)成員屬于對象,在函數(shù)/棧中創(chuàng)建自動類型變量時每個對象大小為其非靜態(tài)數(shù)據(jù)成員的總和,由于棧有最大限制,如果棧內(nèi)變量過多(如大數(shù)組)可能導(dǎo)致棧溢出。
???? //動態(tài)內(nèi)存/堆的大小隨malloc/free等函數(shù)而變化
????
???? int ClassStatic::* pd = &ClassStatic::data; //聲明一個指向普通數(shù)據(jù)成員的指針,pd的類型為(int ClassStatic::*) 整體
???? //作用域解析運算符優(yōu)先級最高,所以賦值運算符右側(cè)等價于&(ClassStatic::data)
???? //在這個聲明中沒有使用某一個對象,所以成員指針pd只記錄了成員在類中的位置
???? c.data = 5; //正常調(diào)用
???? c.*pd = 10; //通過指針調(diào)用,這里運算符 .* 為一個運算符,pd實際記錄了成員在類中的偏移量信息,c.*pd通過計算偏移量訪問指向的區(qū)域
???? //普通的指針 類型名 * 指針=&變量
???? //指向成員的 類型名 類名::* 指針 = &類名::成員
???? //成員指針可以更改指向相同類型的成員,這里pd類型為(int ClassStatic::*),可以指向類ClassStatic中任意int數(shù)據(jù)成員
???? void (ClassStatic::* pf)() = &ClassStatic::func; //聲明指向函數(shù)成員的指針
???? //類名::* 是一個整體,需要和函數(shù)成員指針括在一起,即 函數(shù)簽名部分 (類名::* 指針)函數(shù)簽名部分。給函數(shù)成員指針賦值必須使用 &函數(shù)名 取址,不同于普通函數(shù)名賦值
???? c.func(); //正常調(diào)用
???? (c.*pf)(); //通過指針調(diào)用,運算符.*優(yōu)先級低于(),需要將 對象.*指針 作為整體括起來
???? //普通的指針 類型名 (*指針)() = [&]函數(shù)名
???? //指向成員的 類型名 (類名::*指針)() = &類名::函數(shù)
???? ClassStatic* pc = new ClassStatic; //在堆中創(chuàng)建對象
???? pc->*pd; //運算符->* 同樣為一個整體
???? (pc->*pf)(); //運算符->*優(yōu)先級同樣低于()
???? //(this->*pf)() 如果是在成員函數(shù)內(nèi)也可以使用this指針加成員指針使用
???? //結(jié)構(gòu)的大小是成員的和,結(jié)構(gòu)訪問成員是 結(jié)構(gòu)名.字段名 可以理解為 結(jié)構(gòu)變量的地址+偏移量 按類型進(jìn)行讀取和解釋
???? //類的大小是成員的和,可以理解為 對象名.成員名 同樣是對象地址+偏移量 按類型大小進(jìn)行讀取,按類型進(jìn)行解釋
???? //.* ->* 也理解為偏移量
????delete pc;
???? return 0;
}
class ClassFriend
{
????public:
???? enum
???? {
???? ????FUNC_ARRAY_SIZE = 5, MAX_ITEM = 1024
???? }; //在類內(nèi)使用枚舉,把類中需要使用的常量集中管理,優(yōu)于逐個const static,方便查找修改,不需要命名枚舉類型,這個枚舉屬于類,不占對象空間,類外通過類::訪問
???? void (*func[FUNC_ARRAY_SIZE])(); //效果同func[5]
???? friend void getmember(ClassFriend& obj); //友元函數(shù),通過友元可無視訪問限制直接訪問對象的私有、公有成員,在其他名稱空間定義、使用,這里聲明的是全局函數(shù),等價于 ::getmember
????private:
???? int mem;
};
void getmember(ClassFriend& obj) //把友元函數(shù)定義為全局函數(shù),因為不屬于ClassFriend類及對象(即沒有this指針),所以需要把對象傳進(jìn)來
{
???? obj.mem = 20;
???? cout << obj.mem << endl;
???? //友元全局函數(shù)需要在類中聲明,將函數(shù)原型寫在類中,不在類外再次聲明函數(shù)原型,如果在類中直接定義友元全局函數(shù)則為內(nèi)聯(lián)的友元全局函數(shù)
}
int mainfriend1()
{
???? ClassFriend c;
???? //錯誤:c.mem = 30;私有不可訪問
???? getmember(c); //通過友元函數(shù)訪問
???? return 0;
}
class ClassFriend1; //前向聲明
class ClassFManage1 //把友元成員函數(shù)定義在另一個類中
{
????public:
???? void getmember(ClassFriend1& obj); //通過CFM1對象.getmember()訪問CF1對象的私有成員,因為函數(shù)形參中需要用到ClassFriend1類型,所以需要在上面前向聲明這個類
};
class ClassFriend1
{
????private:
???? friend void ClassFManage1::getmember(ClassFriend1& obj); //CFM1類的函數(shù)所以要寫名稱空間,因為用到了CFM1::getmember()函數(shù),所以要在上面聲明這個函數(shù),這里不能只在上面寫類{這個函數(shù)的聲明}而在下面寫完整的類聲明。只能將友元函數(shù)所屬的類完整聲明在上面。這意味著兩個類不能相互包含友元成員函數(shù)
???? //friend關(guān)鍵字不受private等限制,可寫在類中任意位置不受影響
???? int mem;
};
void ClassFManage1::getmember(ClassFriend1& obj) //友元函數(shù)的實現(xiàn)因為要用到CF1對象的屬性,所以又必須放在CF1類的聲明的下面(因為前向聲明沒有聲明數(shù)據(jù)成員)
{
???? obj.mem = 30;
???? cout << obj.mem << endl;
}
//所以書寫順序為:先引用CF1類-再聲明CFM1類-再聲明CF1類(內(nèi)含friend CFM1::函數(shù))-再實現(xiàn)CFM1::函數(shù)
int mainfriend2()
{
???? ClassFriend1 c;
???? ClassFManage1 m;
???? m.getmember(c); //通過m訪問c的私有成員
???? return 0;
}
class ClassFriend2
{
????public:
???? friend class ClassFManage2; //友元類,使用友元類則不需要前面先聲明類CFM再在前面引用類CF
????private:
???? int mem;
};
class ClassFManage2 //友元類中所有函數(shù)都可以通過CF2對象訪問成員
{
????public:
???? void getmember(ClassFriend2& obj);
};
void ClassFManage2::getmember(ClassFriend2& obj)
{
???? obj.mem = 40;
???? cout << obj.mem << endl;
}
int mainfriend3()
{
???? ClassFriend2 c;
???? ClassFManage2 m;
???? m.getmember(c);
???? return 0;
}
class ClassFriend3
{
????public:
???? friend ClassFriend3 operator+(ClassFriend3& c1, ClassFriend3& c2); //使用友元重載+運算符
???? //ClassFriend3 operator+(ClassFriend3& c); //之前使用類的函數(shù)實現(xiàn)+運算符,兩種方法會產(chǎn)生二義性,因為表現(xiàn)形式均為c1+c2
????private:
???? int mem;
};
class ClassFriend4
{
???? double value_;
????public:
???? ClassFriend4(double value = 0) :value_(value) {}
???? void display1()const { cout << value_ << endl; } //公共接口,打印值,因為不修改*this對象所以函數(shù)const以兼容const對象
???? friend void display2(const ClassFriend4& cf4) { cout << cf4.value_ << endl; } //友元全局內(nèi)聯(lián)函數(shù),通過友元訪問私有成員
};
inline void display3(const ClassFriend4& cf4) { cf4.display1(); } //內(nèi)聯(lián)的非友元函數(shù),通過調(diào)用類接口函數(shù)來訪問私有成員
class CLSFa
{
???? friend class CLSFb; //fa和fb相互為友元
???? int da;
????public:
???? void setfb(CLSFb& obj); //因為前面有friend聲明所以參數(shù)可以使用CLSFb類型,但這里該類的成員不可見,所以要把定義放在CLSFb類聲明的后面或者.cpp文件中,如果想內(nèi)聯(lián)可以使用inline定義
};
class CLSFb
{
???? friend class CLSFa; //互相友元
???? int db;
????public:
???? void setfa(CLSFa& obj) //因為上面有CLSFa類的成員的聲明,所以這里可以直接寫定義
???? {
???? ????obj.da = 20;
???? }
};
inline void CLSFa::setfb(CLSFb& obj) //通過inline內(nèi)聯(lián)
{
????obj.db = 30;
}
//聲明兩個類共同的友元
class CLSfs1; //前向聲明其中一個類
class CLSfs2
{
???? friend void sync1to2(const CLSfs1& fs1, CLSfs2& fs2); //1to2需要兩個類的類型,因為是friend聲明在一個類中了所以還需要另一個前向聲明,通過這個語句只能知道1to2可以訪問fs2的私有
???? friend void sync2to1(const CLSfs2& fs2, CLSfs1& fs1); //調(diào)換參數(shù)順序
???? int t=20;
};
class CLSfs1
{
???? friend void sync1to2(const CLSfs1& fs1, CLSfs2& fs2); //到這里才知道函數(shù)既可訪問fs2私有也可訪問fs1私有
???? friend void sync2to1(const CLSfs2& fs2, CLSfs1& fs1);
???? int t=30;
};
void sync1to2(const CLSfs1& fs1, CLSfs2& fs2) //全局友元
{
????fs2.t = fs1.t;
}
void sync2to1(const CLSfs2& fs2, CLSfs1& fs1)
{
fs1.t = fs2.t;
}