說到面向對象,大家第一反應應該就是它的三大特性:封裝性、繼承性和多態性。那麼我們先簡單的了解一下這三大特性:
(1)封裝性:封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。
在C++中類中成員的屬性有:public,protected,private,這三個屬性的訪問權限依次降低。
(2)繼承性:繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展。
(3)多態性:多態性(polymorphisn)是允許你將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。實現多態,有二種方式,覆蓋,重載。
覆蓋,是指子類重新定義父類的虛函數的做法。
重載,是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。
下面開始我們今天的學習。
1、C++中空類默認產生哪些類成員函數?
答案:
對於一個空類,編譯器默認產生4個成員函數:
(1)默認構造函數
(2)析構函數
(3)拷貝構造函數
(4)賦值函數
2、結構是否可以有構造函數、析構函數及成員函數?如果可以,那麼結構和類還有什麼區別嗎?
答案:
區別是class中變量默認是private,struct中的變量默認是public。class繼承默認是private繼承,而struct繼承默認是public繼承。struct可以有構造函數、析構函數,之間也可以繼承甚至是多重繼承,等等。C++中的struct其實和class意義一樣,唯一不同就是struct裡面默認的訪問控制是public,class中默認訪問控制是private。C++中存在struct關鍵字的唯一意義就是為了讓C程序員有個歸屬感,是為了讓C++編譯器兼容以前用C開發的項目。
3、下面程序打印出的結果是什麼?
01 #include<iostream>
02 using namespace std;
03
04 class base
05 {
06 private:
07 int m_i;
08 int m_j;
09 public:
10 base( int i ) : m_j(i),m_i(m_j) {}
11 base() : m_j(0),m_i(m_j){}
12 int get_i() {return m_i;}
13 int get_j() {return m_j;}
14 };
15
16 int main ()
17 {
18 base obj(98);
19 cout << obj.get_i() <<endl<< obj.get_j() <<endl;
20 return 0;
21 }
解析:本題想得到的結果是“98,98”。但是成員變量的聲明是先m_i ,然後是m_j;初始化列表的初始化變量順序是根據成員變量的聲明順序來執行的,因此,先初始化m_i,但此時m_j 還未初始化,m_i 會被賦予一個隨機值。改變一下成員變量的聲明順序可以得到預想的結果。
答案:
輸出結果第一個為隨機數,第二個是98。
4、下面這個類聲明正確嗎?為什麼?
1 class A
2 {
3 const int Size = 0;
4 };
解析:這道程序題存在著成員變量問題。常量必須在構造函數的初始化列表裡初始化或者將其設置成static。
答案:
正確的程序如下:
1 class A
2 {
3 A()
4 {
5 const int Size = 1;
6 }
7 };
或者:
1 class A
2 {
3 static const int Size = 1;
4 };
5、析構函數可以為virtual 型,構造函數則不能,為什麼?
答案:
虛函數采用一種虛調用的辦法。虛調用是一種可以在只有部分信息的情況下工作的機制,特別允許我們調用一個只知道接口而不知道其准確對象類型的函數。但是如果要創建一個對象,你勢必要知道對象的准確類型,因此構造函數不能為virtual。
6、如果虛函數是非常有效的,我們是否可以把每個函數都聲明為虛函數?
答案:
不行,這是因為虛函數是有代價的:由於每個虛函數的對象都必須維護一個v 表,因此在使用虛函數的時候會產生一個系統開銷。如果僅是一個很小的類,且不行派生其他類,那麼根本沒必要使用虛函數。
7、請看下面一段程序:
01 #include<iostream>
02 using namespace std;
03
04 class B
05 {
06 private:
07 int data;
08 public:
09 B()
10 {
11 cout<<"defualt constructor"<<endl;
12 }
13
14 ~B()
15 {
16 cout<<"destructed "<<endl;
17 }
18
19 B( int i) : data(i)
20 {
21 cout<<"constructed by parameter"<<data<<endl;
22 }
23 };
24
25 B Play( B b )
26 {
27 return b;
28 }
29
30 int main ()
31 {
32 B temp = Play(5);
33 return 0;
34 }
問題:
(1)該程序輸出結果是什麼?為什麼會有這樣的輸出?
(2)B( int i ) : data( i ),這種用法的專業術語叫什麼?
(3)Play( 5 ),形參類型是類,而5是個常量,這樣寫合法嗎?為什麼?
答案:
(1)輸出結果如下:
1 constructed by parameter//在Play(5)處,5通過隱含的類型轉換調用了B::B( int i )
2 destructed //Play(5) 返回時,參數的析構函數被調用
3 destructed //temp的析構函數被調用;temp的構造函數調用的是編譯器生存的拷貝構造函數
(2)待參數的構造函數,冒號後面的是成員變量初始化列表(member initialization list)
(3)合法。單個參數的構造函數如果不添加explicit關鍵字,會定義一個隱含的類型轉換;添加explicit關鍵字會消除這種隱含轉換。
8、編寫類String 的構造函數、析構函數和賦值函數。
已知類String 的原型為:
01 class String
02 {
03 public:
04 String(const char *str = NULL); //普通構造函數
05 String(const String &other); //拷貝構造函數
06 ~String(void); //析構函數
07 String & operate =(const String &other); //賦值函數
08 private:
09 char *m_data; //用於保存字符串
10 };
答案:
1、String 的析構函數:
1 String::~String(void)
2 {
3 delete [] m_data;
4 }
2、String 的構造函數:
01 String::String(const char *str)
02 {
03 if(NULL==str)
04 {
05 m_data = new char[1];
06 *m_data = '\0';
07 }
08 else
09 {
10 int length = strlen(str);
11 m_data = new char[length+1];
12 strcpy(m_data,str);
13 }
14 }
3、String的拷貝構造函數:
1 String::String(const String &other)
2 {
3 int length = strlen(other.m_data);
4 m_data = new char[length+1];
5 strcpy(m_data,other.m_data);
6 }
4、String的賦值函數:
01 String & String::operate =(const String &other)
02 {
03 if(this== &other) //檢查自復制
04 {
05 return *this;
06 }
07 delete [] m_data; //釋放原有的內存資源
08 int length=strlen(other.m_data); //分配新內存資源,並復制內容
09 m_data = new char[length+1];
10 strcpy(m_data,other.m_data);
11 return *this; //返回本對象的引用
12 }
9、重載與覆蓋有什麼不同?
答案:
虛函數總是在派生類中被改寫,這種改寫被稱為“override”(覆蓋)。
override 是指派生類重寫基類的虛函數,重寫的函數必須有一致的參數表和返回值。Override這個單詞好像一直沒什麼合適的中文詞匯來對應。有些人譯為“覆蓋”,還貼切一些。
overload約定成俗地被翻譯為“重載”,是指編寫一個與自己已有函數同名但是參數表不同的函數。例如一個函數既可以接受整型數作為參數,也可以接收浮點數作為參數。重載不是一種面向對象的編程,而是一種語法規則,重載與多態沒什麼直接關系。
作者:IT笨笨