c++类指针赋值表达式必须是可修改的左值_C++进阶教程系列:全面理解C++中的类...
原標題:C++進階教程系列:全面理解C++中的類
關(guān)注Linux公社
最近刷了一些題,也面試了一些公司,把關(guān)于C++中關(guān)于類的一些概念總結(jié)了一下。
在這里也反思一下,面試前信心滿滿自以為什么都懂,毫無準備就大膽得去了,然后就覺得自己臉都被打腫了。回來認認真真刷題,這陣子都不敢再去面試了~~。
1. 類的訪問屬性:public,protect,private
C++中類的成員變量和函數(shù)都帶有三種屬性中的一種,假如沒有特別聲明,那么就默認是私有的(除了構(gòu)造函數(shù))。public表示是公開的,對象可以直接調(diào)用的變量或者函數(shù);protect表示是保護性的,只有本類和子類函數(shù)能夠訪問(注意只是訪問,本類對象和子類對象都不可以直接調(diào)用),而私有變量和函數(shù)是只有在本類中能夠訪問(有個例外就是友元函數(shù),這個后面會詳細說)。
classA
{
public:
A( intb):m_public(b),m_protected( 1), m_private( 2){}
intm_public;
protected:
intm_protected;
private:
intm_private;
};
intmain( void)
{
A a( 10);
cout<<<<endl; //正確,可以直接調(diào)用
cout<<a.m_protected<<endl; //錯誤,不可以直接調(diào)用
cout<<a.m_private<<endl; //錯誤,不可能直接調(diào)用
}
而子類對父類的繼承類型也有這三種屬性,分別為公開繼承,保護繼承和私有繼承。
classA
{
public:
A( intb):m_public(b),m_protected( 1), m_private( 2){}
intm_public;
protected:
intm_protected;
private:
intm_private;
};
classB: publicA{} //公有繼承
classC: protectedA{} //保護繼承
classD: privateA{} //私有繼承
于是問題來了,父類成員的公開屬性有三種,子類的繼承屬性也有同樣的三種,那么一共就有九種搭配(例如公開繼承父類公開成員,私有繼承父類公開成員,保護繼承父類私有成員等等)。
我們只需要記住這 兩個里取嚴格的那一種。例如私有繼承父類公開成員,那么在子類里父類的所有屬性都變成子類私有的了。
具體如下所示:
public: protected: private:
public繼承 public protected 不可用
protected繼承 protected protected 不可用
private繼承 private private 不可用
classA
{
public:
A( intb):m_public(b){}
intm_public;
};
classB: privateA
{
public:
B( intnum):A(num){}
};
intmain
{
B b( 10);
cout<<b.m_public<<endl; //錯誤,無法直接調(diào)用私有成員
}
2. 類的四個默認函數(shù):構(gòu)造,拷貝構(gòu)造,賦值,析構(gòu)(這一點要背下來,面試直接被問了)
每當構(gòu)建一個新的類,編譯器都會給每個類生成以上四個默認的無參構(gòu)造函數(shù),并且這四個函數(shù)都是默認public的。
{
public:
doublem_a;
};
intmain( void)
{
A a; //調(diào)用默認無參構(gòu)造函數(shù),此時m_a的值是不確定的,不能用
//離開主函數(shù)前調(diào)用析構(gòu)函數(shù),釋放a的內(nèi)存
}
但是一旦程序員自己定了帶參數(shù)的構(gòu)造函數(shù),那么編譯器就 不會再生成默認的無參構(gòu)造函數(shù)了,但是還是有默認的拷貝和賦值構(gòu)造函數(shù)。因此假如只定義了有參數(shù)的構(gòu)造函數(shù),那么這個類就沒有無參構(gòu)造函數(shù)。
classA
{
public:
A( inti):m_a(i){}
intm_a;
};
intmain( void)
{
A a; //錯誤!沒有無參構(gòu)造函數(shù)
A a1( 5); // 調(diào)用了A中程序員定義的有參構(gòu)造函數(shù)
A a2( 6); // 調(diào)用了A中程序員定義的有參構(gòu)造函數(shù)
A a3 = a1; //此處調(diào)用默認的拷貝構(gòu)造函數(shù)
a2 = a1; //此處調(diào)用默認的賦值函數(shù)
}
上面的程序中尤其需要注意的是 A a3 = a1這一句,雖然有等號,但是仍然是拷貝構(gòu)造函數(shù)。拷貝構(gòu)造函數(shù)和賦值函數(shù)的區(qū)別在于等式左邊的對象是否已經(jīng)存在。a2 = a1這一句執(zhí)行的時候,a2已經(jīng)存在,因此是賦值函數(shù),而執(zhí)行A a3 = a1這一句的時候,a3還不存在,因此為拷貝構(gòu)造函數(shù)。
默認的賦值和拷貝構(gòu)造函數(shù)一般只是簡單的拷貝類中成員的值, 這一點當類中存在指針成員和靜態(tài)成員變量的時候就非常危險。例如以下一種情況:
classA
{
public:
A( inti, int* p):m_a(i), m_ptr(p){} intm_a;
int*m_ptr;
};
intmain( void)
{
intm = 10, *p = &m;
A a1( 3, p);
A a2 = a1; //a2 和 a1的m_ptr都指向了同一地址
*p = 100;
cout<<*()<<endl; //輸出為100
}
這也就是C++中由于指針帶來的淺拷貝的問題,只賦值了地址,而沒有新建對象。因此假如類中存在靜態(tài)變量或者指針成員變量時一定要自己手動定義賦值、拷貝構(gòu)造、析構(gòu)函數(shù)。
classA
{
public:
A( intb):m_public(b),m_protected( 1), m_private( 2){}
intm_public;
protected:
intm_protected;
private:
intm_private;
};
intmain( void)
{
A a( 10);
cout<< ""<<endl; //正確,可以直接調(diào)用
cout<<a.m_protected<<endl; //錯誤,不可以直接調(diào)用
cout<<a.m_private<<endl; //錯誤,不可能直接調(diào)用
}
可以理解成不能。子類會繼承父類所有的函數(shù),包括構(gòu)造函數(shù) ,但是子類的構(gòu)造函數(shù)會把父類的構(gòu)造函數(shù)覆蓋了,所以看起來就是沒有繼承。假如子類不定義任何構(gòu)造函數(shù),那么子類只會默認地調(diào)用父類的無參構(gòu)造函數(shù)。當 父類中只定義了有參構(gòu)造函數(shù),從而不存在無參構(gòu)造函數(shù)的話,子類就無法創(chuàng)建對象。
classA
{
public:
A( intb):m_public(b){}
intm_public;
};
classB: publicA
{
};
intmain
{
B b; //出錯,因為父類沒有無參構(gòu)造函數(shù)
}
因此在這種情況必須要顯示定義子類的構(gòu)造函數(shù),并且在子類構(gòu)造函數(shù)中顯示調(diào)用父類的構(gòu)造函數(shù)。
classA
{
public:
A( intb):m_public(b){}
intm_public;
};
classB: publicA
{
public:
B( intnum):A(num){}
};
intmain
{
B b1; //出錯,由于父類沒有無參構(gòu)造函數(shù),因此B也不存在無參構(gòu)造
B b2( 5); //正確
}
- 構(gòu)造函數(shù)的構(gòu)造順序:先構(gòu)造基類,再構(gòu)造子類中的成員,再構(gòu)造子類
{
public:
A{ cout<< "constructing A"<<endl;}
};
classB
{
public:
B{ cout<< "constructing B"<<endl;}
};
classC: publicA
{
public:
B b;
A a;
intnum;
C( intn):num(n){ cout<< "constructing C"<<endl;}
};
intmain
{
C c( 1);
}
運行結(jié)果為:
第一行的constructingA就是在構(gòu)建基類,然后構(gòu)建b對象,再構(gòu)建a對象,最后構(gòu)建c本身。
而析構(gòu)的順序就正好是完全反過來,先析構(gòu)子類,再析構(gòu)子類中的對象,最后析構(gòu)基類。
假如把構(gòu)造函數(shù)和析構(gòu)函數(shù)定義成私有的會怎樣?(被問的時候真的一臉懵,-_-//)
假如把構(gòu)造函數(shù)定義為私有,那么類就無法 直接實例化(還是可以實例化的,只是要轉(zhuǎn)個彎)。來看下面這個例子:
classA
{
public:
intm_public;
staticA* getInstance( intnum)
{
returnnewA(num);
}
private:
A( intb):m_public(b){}
};
intmain
{
A a1( 4); //錯誤
A* pa = A::getInstance( 5); //正確
}
有些時候,我們不希望一個類被過多地被實例化,比如有關(guān)全局的類、路由類等。這時候,我們就可以用這種方法為類設(shè)置構(gòu)造函數(shù)并提供靜態(tài)方法。
假如把類的析構(gòu)函數(shù)定義為私有,那么就無法在棧中生成對象,而必須要通過new來在堆中生成對象。
另外在這里提及一點,對應(yīng)的,如何讓類只能在棧中生成,而不能new呢?就是將new 和delete重載為私有。
原因是C++是一個靜態(tài)綁定的語言。在編譯過程中,所有的非虛函數(shù)調(diào)用都必須分析完成。即使是虛函數(shù),也需檢查可訪問性。因些,當在棧上生成對象時,對象會自動析構(gòu),也就說析構(gòu)函數(shù)必須可以訪問。而堆上生成對象,由于析構(gòu)時機由程序員控制,所以不一定需要析構(gòu)函數(shù)。
classA
{
public:
intm_public;
staticA* getInstance( intnum)
{
returnnewA(num);
}
A( intb):m_public(b){}
private:
~A{}
};
intmain
{
A a( 5); //錯誤,因為系統(tǒng)無法自動調(diào)用析構(gòu)函數(shù)
A *p_a = newA( 5); //正確,此時p_a指向的是堆中的內(nèi)存
}
構(gòu)造函數(shù)的初始化列表(就是構(gòu)造函數(shù)冒號后面的東西,叫初始化列表,需要與{}中的函數(shù)內(nèi)容區(qū)分開)
有幾種情況必須要使用初始化列表:
常量成員
引用類型
沒有默認構(gòu)造函數(shù)的類類型
classA
{
public:
intm_a;
A( intnum):m_a(num){}
};
classB
{
public:
constintm_const;
int&m_ref;
A a;
B( intnum, intb):m_ref(b), m_const( 1), a(num) //初始化列表
{
cout<< "constructing B"<<endl;
}
};
intmain
{
intn = 5;
B b( 1, n);
}
還需要注意的一點是,初始化列表里的真正賦值的順序其實是 按照成員變量的聲明順序,而不是初始化列表中顯示的順序。例如這里是先初始化m_const,然后是m_ref,最后是a。
來看下面一個例子:
classA
{
public:
intm_a;
A( intnum):m_a(num){ cout<< "constructing A"<<endl;}
};
voidtest(A a)
{
cout<<a.m_a+ 1<<endl;
}
intmain
{
A a1= 6; //此時等式右邊的6隱式生成了一個以6為參數(shù)的A類對象
test( 6); //輸出7,入?yún)⒁搽[式生成了一個以6為參數(shù)的A類對象
cout<<a1.m_a<<endl; //輸出6
}
從下面的運行結(jié)果可以看出,A的構(gòu)造函數(shù)被調(diào)用了兩次。
這種隱式轉(zhuǎn)換有時候神不知鬼不覺,為了避免這種情況,于是有了explicit關(guān)鍵字。當構(gòu)造函數(shù)被聲明為explicit后,就必須要顯式調(diào)用構(gòu)造方法來生成對象了,而無法進行隱式轉(zhuǎn)換。
classA
{
public:
intm_a;
explicitA( intnum):m_a(num){ cout<< "constructing A"<<endl;}
};
voidtest(A a) { cout<<a.m_a+ 1<<endl;
}
intmain
{
A a1= 6; //錯誤
test( 6); //錯誤
}
3. 虛函數(shù)
虛函數(shù)是C++實現(xiàn)多態(tài)的方法。虛函數(shù)和普通函數(shù)沒有什么區(qū)別,只有當用基類指針調(diào)用子類對象的方法時才能夠真正發(fā)揮它的作用,也只有在這種情況下,才能真正體現(xiàn)出C++面對對象編程的多態(tài)性質(zhì)。
先來了解一下綁定的概念。函數(shù)體與函數(shù)調(diào)用關(guān)聯(lián)起來叫做綁定。
- 早綁定:早綁定發(fā)送在程序運行之前,也是編譯和鏈接階段。
{
public:
intm_a;
A( intnum):m_a(num){}
intadd( intn)
{
returnm_a + n;
}
};
intmain
{
A a( 1);
cout<<( 5);
}
在上面的代碼中,函數(shù)add在編譯期間就已經(jīng)確定了實現(xiàn)。這就是早綁定。所有的非虛函數(shù)都是早綁定。
晚綁定:晚綁定發(fā)生在程序運行期間,主要體現(xiàn)在繼承的多態(tài)方面。
引用一句Bruce Eckel的話:“不要犯傻,如果它不是晚邦定,它就不是多態(tài)。”
classA
{
public:
intm_a;
A( intnum):m_a(num){}
voidvirtualshow
{
cout<< "base function"<<endl;
}
};
classB: publicA
{
public:
B( intn):A(n){}
voidshow
{
cout<< "derived function"<<endl;
}
};
intmain
{
A *pa = newB( 1);
pa->show; //輸出 derived function
}
父類和子類都定義了show方法,在繼承的過程中,由于父類show方法是虛函數(shù),而父類指針指向的是子類對象,所以會在子類對象中去找show函數(shù)的實現(xiàn)。假如子類中沒有show,那么就還是會調(diào)用父類的show。
這個晚綁定的過程是通過虛指針實現(xiàn)的。只要一個類中聲明了有虛函數(shù),那么編譯器就會自動生成一個虛函數(shù)表。雖然名字叫表,但本質(zhì)是一個存放虛函數(shù)指針的函數(shù)指針數(shù)組。一個虛表對應(yīng)一個指針。當該類作為基類,其派生類對基類的(一個或者多個)虛函數(shù)進行重寫時,派生類的虛函數(shù)表中,相應(yīng)的函數(shù)指針的值就會發(fā)生變化。
構(gòu)造函數(shù)不能聲明為虛函數(shù)。虛函數(shù)是晚綁定,一定是先有了基類對象,才會有對應(yīng)的虛指針,再去本類或者子類對象中去找對應(yīng)的實現(xiàn)。所以一定要先通過構(gòu)造函數(shù)創(chuàng)建了對象,才能去實現(xiàn)虛函數(shù)的作用。
而析構(gòu)函數(shù),則常常被聲明為虛函數(shù)。(記得當時面試官問我,析構(gòu)函數(shù)能不能是虛的,我當時斬釘截鐵得回答,不能!-_-//)
先看下面這個例子。
classA
{
public:
intm_a;
A( intnum):m_a(num){ cout<< "constructing A"<<endl;}
~A{ cout<< "destructing A"<<endl;}
};
classB: publicA
{
public:
B( intn):A(n){ cout<< "constructing B"<<endl;}
~B{ cout<< "destructing B"<<endl;}
};
intmain
{
A* pa= newB( 1);
deletepa;
}
執(zhí)行結(jié)果為:
可以看到是先構(gòu)建了A對象,然后構(gòu)建了B對象。可是卻只析構(gòu)了A對象,B對象的內(nèi)存空間就泄漏了。
現(xiàn)在把A的析構(gòu)函數(shù)置為虛函數(shù)的話,
classA
{
public:
intm_a;
A( intnum):m_a(num){ cout<< "constructing A"<<endl;}
virtual~A{ cout<< "destructing A"<<endl;}
};
classB: publicA
{
public:
B( intn):A(n){ cout<< "constructing B"<<endl;}
~B{ cout<< "destructing B"<<endl;}
};
intmain
{
A* pa= newB( 1);
deletepa;
}
運行結(jié)果為:
可以看到這時B對象也被析構(gòu)了。這正是利用了虛函數(shù)晚綁定的特點,當調(diào)用基類指針析構(gòu)函數(shù)的時候,先調(diào)用B的析構(gòu)函數(shù),再調(diào)用A的析構(gòu)函數(shù)。
對于虛函數(shù)始終要注意只有用指針調(diào)用的時候才會有作用,假如只是普通的對象調(diào)用,虛函數(shù)是不起作用的。
classA
{
public:
intm_a;
A( intnum):m_a(num){}
virtualvoidshow
{
cout<< "A::show"<<endl;
}
virtual~A{}
};
classB: publicA
{
public:
B( intn):A(n){}
voidshow
{
cout<< "B::show"<<endl;
}
~B{}
};
intmain
{
A a( 1); a.show; //輸出A::show
B b( 1); //輸出B::show
}
4. 成員函數(shù)的重載、隱藏與覆蓋
(1)相同的范圍(在同一個類中)
(2)函數(shù)名字相同
(3)參數(shù)不同 ,也可以僅僅是順序不同
(4)virtual 關(guān)鍵字可有可無
classA
{
public:
intm_a;
A( intnum):m_a(num){}
voidshow( intn){} //(1)
virtualvoidshow( intn){} //(2) 錯誤!!不是重載,重復定義了(1),因為virtual關(guān)鍵字不能重載函數(shù)
voidshow( doubled){} //(3)show函數(shù)的重載
voidshow( inta, doubleb){} //(4)show函數(shù)的重載
voidshow( doubleb, inta){} //(5)show函數(shù)的重載
voidshow( inta, doubleb) const{} //(6)show函數(shù)的重載,const關(guān)鍵可以作為重載的依據(jù)
voidshow( constinta, doubleb){} //(7)錯誤!!不是重載, 頂層const不可以作為重載的依據(jù),重復定義了(6)
voidshow( int*a){} //(8)show函數(shù)的重載
voidshow( constint*a){} //(9)show函數(shù)的重載,只有底層const才可以作為重載的依據(jù)
voidshow( int* consta){} //(10) 錯誤!!不是重載,重復定義了(8),因為這里也使用了頂層const
};
至于const能不能成為重載的依據(jù)取決于是頂層const還是底層const。頂層const是指對象本身是常量,而底層const是指指向或引用的對象才是常量。
成員函數(shù)的隱藏,這里“隱藏”是指派生類的函數(shù)屏蔽了與其同名的基類函數(shù)
只要子類函數(shù)的名字與基類的相同,那么不管參數(shù)相同與否,都會直接屏蔽基類的同名函數(shù)。
classA
{
public:
voidshow( inta) { cout<< "A::show"<<endl;} //(1)
};
classB: publicA
{
public:
voidshow{ cout<< "B::show"<<endl;} //(2)將(1)屏蔽了
};
- 假如在子類中仍舊需要用到基類的同名函數(shù),就要用using關(guān)鍵字顯式聲明。
{
public:
voidshow( int) { cout<< "A::show"<<endl;}
};
classB: publicA
{
public:
usingA::show;
voidshow{
show( 0); //一定要在前面顯式聲明using A中的show函數(shù),否則此句會編譯錯誤
cout<< "B::show"<<endl;
}
};
intmain
{
B b;
}
輸出結(jié)果為:
(1)不同的范圍(分別位于派生類與基類)
(2)函數(shù)名字相同
(3)參數(shù)相同
(4) 基類函數(shù)必須有virtual 關(guān)鍵字
這其實就是多態(tài)的實現(xiàn)過程。注意參數(shù)必須要完全一致。之前講過,在此不再贅述。
5. inline關(guān)鍵字
定義在類中的成員函數(shù)默認都是內(nèi)聯(lián)的。內(nèi)聯(lián)函數(shù)和普通函數(shù)的區(qū)別在于:當編譯器處理調(diào)用內(nèi)聯(lián)函數(shù)的語句時,不會將該語句編譯成函數(shù)調(diào)用的指令,而是直接將整個函數(shù)體的代碼插人調(diào)用語句處,就像整個函數(shù)體在調(diào)用處被重寫了一遍一樣。這有助于提高程序的運行效率。但是要注意inline函數(shù)僅僅是一個 對編譯器的建議,所以 最后能否真正內(nèi)聯(lián),看編譯器的意思,它如果認為函數(shù)不復雜,能在調(diào)用點展開,就會真正內(nèi)聯(lián),并不是說聲明了內(nèi)聯(lián)就會內(nèi)聯(lián),聲明內(nèi)聯(lián)只是一個建議而已。
這也是為什么虛函數(shù)可以是內(nèi)連函數(shù)。因為僅僅是建議,當虛函數(shù)需要表現(xiàn)出多態(tài)性質(zhì)的時候,編譯器會選擇不內(nèi)連。
6. 友元函數(shù)、友元類
類的友元函數(shù)是定義在類外部,但有權(quán)訪問類的所有私有(private)成員和保護(protected)成員。
classA
{
public:
A( intn):m_a(n){}
friendclassB; //聲明B為A的友元類
private:
intm_a;
};
classB
{
public:
B(A a){ cout<<} //由于B是A的友元類,所以可以直接調(diào)用a的私有m_a成員
};
intmain
{
A a( 1);
B b = B(a); //輸出1
}
要注意盡管友元類很強大,但是友元類和類本身并沒有任何繼承關(guān)系和成員關(guān)系。友元類或友元函數(shù)都不是本類的成員類和成員函數(shù)。
就如名字定義的那樣,只是朋友,不具有任何親屬關(guān)系, 因此無法使用this指針進行調(diào)用。
友元函數(shù)常用在重載運算符。因為通常重載運算符的時候都要用到私有變量,所以用友元函數(shù)來重載是非常合適的。
7. 運算符的重載
. (成員訪問運算符)
.*, ->* (成員指針訪問運算符)
:: (域運算符)
sizeof (長度運算符)
?: (條件運算符)
# (預處理符號)
- =, [] , ,-> 四個符號只能通過成員函數(shù)來重載,不能通過友元函數(shù)來定義
=,->, [], 為什么不能重載為友元函數(shù),是因為當編譯器發(fā)現(xiàn)當類中沒有定義這4個運算符的重載成員函數(shù)時, 就會自己加入默認的運算符重載成員函數(shù)。而如果這四個運算符寫成友元函數(shù)時會報錯,產(chǎn)生矛盾。
- 不允許用戶定義新的運算符作為重載運算符,不能修改原來運算符的優(yōu)先級和結(jié)合性,不能改變操作對象等等限制
如果是一元操作,就用成員函數(shù)去實現(xiàn)
如果是二元操作,就盡量用友元函數(shù)去實現(xiàn)
如果是二元操作,但是對兩個操作對象的處理不同,那么就盡可能用成員函數(shù)去實現(xiàn)
classA{
public:
A( intn):m_a(n){}
intm_a;
friendA operator+(A const& a1, A const& a2);
};
A operator+(A const& a1, A const& a2)
{
A res( 0);
res.m_a = 1+ a1.m_a + a2.m_a;
returnres;
}
intmain
{
A a1( 1), a2( 2);
A a3 = a1 + a2;
cout<<a3.m_a; //輸出4
}
8. 再談const關(guān)鍵字
classA{
public:
A( intn):m_a(n){}
intm_a;
voidshow{ cout<< "A::show"<<endl;}
};
intmain
{
A a1( 1);
constA a2( 2);
a1.show; //正確
a2.show; //錯誤
}
假如增加const函數(shù)后,就可以正常運行。
classA
{
public:
A( intn):m_a(n){}
intm_a;
voidshow{ cout<< "A::show"<<endl;}
voidshow const{ cout<< "A::show const"<<endl;}
};
intmain
{
A a1( 1);
constA a2( 2);
a1.show; //正確 輸出A::show
a2.show; //正確 輸出A::show const,自動調(diào)用const函數(shù)
}
- A const* 和const A*等價,允許用A* 賦值A(chǔ) const*,但是不允許用A const* 賦值A(chǔ)*
{
public:
A( intn):m_a(n){}
intm_a;
voidchangeValue( int*p){ cout<< "changeValue"<<endl;}
voidchangeValue2( intconst*p){ cout<< "changeValue2"<<endl;}
};
intmain
{
intn = 10;
intconst*p_n_const = &n;
int*p_n = &n;
A a( 1);
(p_n_const); //錯誤,無法把int const*類型,轉(zhuǎn)成int *類型,但是反之可以
2(p_n); //正確,輸出changeValue2
}
這是因為形參const A*表示指向的對象不能改變,所以如果傳入A*實參,只要不改變對象的值就不會有問題。但是如果形參為A*,則有可能改變A*指向的對象,這是const A*辦不到的,所以編譯器不允許傳入const A*作為實參傳入。
暫時總結(jié)到這里。正好最近工作中也寫了不少類的繼承,也實現(xiàn)了一些類的封裝,所以這里也包含了我踩過的一些坑。
參考:
https://github.com/huihut/interview
https://blog.csdn.net/cb673335723/article/details/81231974
合作聯(lián)系微信:linuxg返回搜狐,查看更多
責任編輯:
總結(jié)
以上是生活随笔為你收集整理的c++类指针赋值表达式必须是可修改的左值_C++进阶教程系列:全面理解C++中的类...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 增大iphone音量技巧_原来苹果手机隐
- 下一篇: c++读取txt文件中的数字_在Pyth