C++學習(48)

王小東大將軍發表於2017-06-17

1. 當派生類中不含物件成員時

    ·在建立派生類物件時,建構函式的執行順序是:基類的建構函式→派生類的建構函式;

    ·在撤消派生類物件時,解構函式的執行順序是:派生類的建構函式→基類的建構函式。

 

    當派生類中含有物件成員時

    ·在定義派生類物件時,建構函式的執行順序:基類的建構函式→物件成員的建構函式→派生類的建構函式;

    ·在撤消派生類物件時,解構函式的執行順序:派生類的建構函式→物件成員的建構函式→基類的建構函式

 

2. PAT(*ad)[3];

ad首先是個指標;

ad是個指向有著三個PAT元素的陣列的指標;

這裡只是宣告瞭指標,雖然指標指向的陣列有三個PAT物件,但是沒有例項化其中的物件,所以並沒有呼叫建構函式


3.預設引數是靜態繫結的,絕不重新定義繼承而來的預設引數。


記住:virtual 函式是動態繫結,而預設引數值卻是靜態繫結。 意思是你可能會 在“呼叫一個定義於派生類內的virtual函式”的同時,卻使用基類為它所指定的預設引數值。


結論:絕不重新定義繼承而來的預設引數值!(可參考《Effective C++》條款37

 

對於本例:

B*p = newB; p->test();

p->test()執行過程理解:

(1)由於B類中沒有覆蓋(重寫)基類中的虛擬函式test(),因此會呼叫基類A中的test();

(2)A中test()函式中繼續呼叫虛擬函式 fun(),因為虛擬函式執行動態繫結,p此時的動態型別(即目前所指物件的型別)為B*,因此此時呼叫虛擬函式fun()時,執行的是B類中的fun();所以先輸出“B->”;

(3) 預設引數值是靜態繫結,即此時val的值使用的是基類A中的預設引數值,其值在編譯階段已經繫結,值為1,所以輸出“1”;

 

最終輸出“B->1”。所以大家還是記住上述結論:絕不重新定義繼承而來的預設引數值

 

4. 多執行緒呼叫時要進行保護時,主要是針對全域性變數和靜態變數的,函式內的區域性變數不會受到影響。

 

這裡i是全域性變數,j是區域性靜態變數,所以 要進行保護。

 

5.32位機器上,分析程式,輸出10、2、1

void func(char (&p)[10]) {
    cout<<sizeof(p)<<endl;
}
int main() {
    printf("%d\n",sizeof(char[2]));
    printf("%d\n",sizeof(char&));
    return 0;
}

分析:第一個p是對char[10]的引用,所以輸出為10;第二個p是對陣列的操作,與int a[2]sizeof(a);一樣;第三個p是直接對資料型別操作,當在傳入資料的時候,也就轉換為資料型別來操作的,就是簡單sizeofchar的操作。

 

根據C++11, When applied to a reference or a reference type, the result is the sizeof the referenced type.。由於是對於char&amp;取sizeof, 實際上是對於char 去大小,char 是 1 byte 的。

 

6.32位機器,分析下述程式:C

signed char a=0xe0;

unsigned int b=a;

unsigned char c=a;

A a>0&&c>0為真      B a==c為真 C b的十六進位制表示:0xffffffe0 D上面都錯誤

 

分析:同等位數的型別之間的賦值表示式不會改變其在記憶體之中的表現形式,因此通過 unsignedchar c = a;語句,c的位儲存形式還是0xe0

 

對於B選項,編譯器首先檢查關係表示式"=="左右兩邊a ,c的型別,如果某個型別是比int的位寬小的型別,就會先進行Integer Promotion,將其提升為int型別,至於提升的方法,是先根據原始型別進行位擴充套件(如果原始型別為unsigned ,進行零擴充套件,如果原始型別為signed,進行符號位擴充套件)至32位,再根據需要進行unsigned to int 形式的轉換。


因此:

a為signedchar型,位寬比int小,執行符號位擴充套件,被提升為0xffffffe0;

c為unsignedchar型,位寬比int小,執行零擴充套件,被提升為 0x000000e0;

經過以上步驟,再對兩邊進行比較,顯然發現左右是不同的,因此==表示式值為false。

 

再舉一個例子:

-------------------------------------

signed int a = 0xe0000000, unsigned int b = a;cout<< (b == a) <<endl;

-------------------------------------

結果為 1, 因為a、b位寬已經達到了int的位寬,均不需要Integer Promotion,只需要對a執行從unsignedto signed的轉換,然而這並不影響其在記憶體中的表現形式,因此和b比較起來結果為真。

 

分析二:將char轉換為int時關鍵看char是unsigned還是signed,如果是unsigned就執行0擴充套件,如果是signed就執行符號位擴充套件。跟int本身是signed還是unsiged無關

 

分析三:a為負數,c為正數。負數擴充高位用1來補全。

 

7. static_cast 的用法

 

static_cast < type-id > ( expression )

該運算子把expression轉換為type-id型別,但沒有執行時型別檢查來保證轉換的安全性。它主要有如下幾種用法:

用於類層次結構中基類(父類)和派生類(子類)之間指標或引用的轉換

 

進行上行轉換(把派生類的指標或引用轉換成基類表示)是安全的

進行下行轉換(把基類指標或引用轉換成派生類表示)時,由於沒有動態型別檢查,所以是不安全的

 

②用於基本資料型別之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。

③把空指標轉換成目標型別的空指標。

把任何型別的表示式轉換成void型別

 

注意:static_cast不能轉換掉expression的const、volatile、或者__unaligned屬性。

 

C++中的static_cast執行非多型的轉換,用於代替C中通常的轉換操作。因此,被做為顯式型別轉換使用。

static_cast會強制覆蓋掉編譯器的檢查構造,所以轉換時可行的(但一般會先確認基類向派生類轉換時安全的才會使用static_cast)。

 

C++中的reinterpret_cast主要是將資料從一種型別的轉換為另一種型別。所謂“通常為運算元的位模式提供較低層的重新解釋”也就是說將資料以二進位制存在形式的重新解釋

 

四類強制轉換:

static_cast(編譯器可實現的隱式轉換或類層次間的下行轉換)、dynamic_cast(運算元只能為類指標或類引用)、const_cast(去除const)、reinterpret_const(一般意義強制轉換)

 

8. 由於類的構造次序是由基類到派生類,所以在建構函式中呼叫虛擬函式,這個虛擬函式不會呈現出多型; 相反,類的析構是從派生類到基類,當呼叫繼承層次中某一層次的類的解構函式時往往意味著其派生類部分已經析構掉,所以也不會呈現出多型。

 

靜態函式不可以是虛擬函式因為靜態成員函式沒有this,也就沒有存放vptr的地方,同時其函式的指標存放也不同於一般的成員函式,其無法成為一個物件的虛擬函式的指標以實現由此帶來的動態機制。靜態是編譯時期就必須確定的,虛擬函式是執行時期確定的

 

虛擬函式可以宣告為inline。inline函式和virtual函式有著本質的區別,inline函式是在程式被編譯時就展開,在函式呼叫處用整個函式體去替換,而virtual函式是在執行期才能夠確定如何去呼叫的,因而inline函式體現的是一種編譯期機制,virtual函式體現的是一種執行期機制

因此,行內函數是個靜態行為,而虛擬函式是個動態行為,他們之間是有矛盾的。

函式的inline屬性是在編譯時確定的, 然而,virtual的性質則是在執行時確定的,這兩個不能同時存在,只能有一個選擇,檔案中宣告inline關鍵字只是對編譯器的建議,編譯器是否採納是編譯器的事情

我並不否認虛擬函式也同樣可以用inline來修飾,但你必須使用物件來呼叫,因為物件是沒有所謂多型的,多型只面向行為或者方法,但是C++編譯器,無法保證一個內聯的虛擬函式只會被物件呼叫,所以一般來說,編譯器將會忽略掉所有的虛擬函式的內聯屬性。

 

什麼函式不能宣告為虛擬函式?

一個類中將所有的成員函式都儘可能地設定為虛擬函式總是有益的。

設定虛擬函式須注意

1:只有類的成員函式才能說明為虛擬函式;

2:靜態成員函式不能是虛擬函式;

3:行內函數不能為虛擬函式;

4:建構函式不能是虛擬函式;

5:解構函式可以是虛擬函式,而且通常宣告為虛擬函式。

11. 友元函式過載運算子時,因為沒有this指標指向物件,因此引數個數保持和原來一樣,運算子至少有一個引數。

 

友元函式過載時,引數列表為1,說明是1元,為2說明是2元;

成員函式過載時,引數列表為空,是一元,引數列表是1,為2元;

 

12.分析下列程式,輸出:72

#include<iostream>
#include<string.h>
using namespace std;
int main() {
    char a=101;
    int sum=200;
    a+=27;sum+=a;
    cout<<sum;
    return 0;
}

分析:char型別的範圍是-128---+127,當a+=27 ,之後a的值超出可表示範圍會變為-128.接著往下計算就是72.

 

13.假設指標變數p定義為int *p=new int(100); 要釋放p所指向的動態記憶體,應該使用語句:A

A delete p       B delete *p C delete &p  D delete []p;

分析:一般用法是new一個陣列的話一般是delete [] ,其他的直接delete即可。

 

int* p = new int (100) 是建立一個int型的記憶體,並賦值為100;

int *p = new int[100] 是建立100個int型的記憶體;

但是其實對於內建資料型別,其實是delete[] 和delete都可以的。

 

14.分析下述程式:

#include<iostream>
#include<string.h>
using namespace std;
void test(void *data) {
    unsigned intvalue=*((unsigned int *)data);
    printf("%u",value);
}
int main() {
    unsigned intvalue=10;
    test(&value);
    return 0;
}

分析:注意void test(void *data),引數型別是void,所以先要進行指標轉換(unsigned int *)然後再取值。

 

實際上只要是*data,我們就知道了它是指標,如果是32位機器,該指標就指著記憶體中的某個地址,用32位表示,記住這個32位只是初始地址,任何指標都是的。而前面的void 或者int 型別是定義一次讀幾個位元組,如果是int則讀4個位元組,也就是從*data存的地址開始從記憶體往後讀4個位元組就行,而void是空,沒有指定要讀多少個位元組,所以要用指標型別(unsignedint *)強制轉化為知道要讀幾個位元組的int指標,然後再用*從開始地址,讀取unsigned int個位元組出來。

 

15. A 項錯誤,因為使用 inline 關鍵字的函式只是使用者希望它成為行內函數,但編譯器有權忽略這個請求,比如:若此函式體太大,則不會把它作為行內函數展開的。

 

B 項錯誤,標頭檔案中不僅要包含 inline 函式的宣告,而且必須包含定義,且在定義時必須加上 inline 。【關鍵字 inline 必須與函式定義體放在一起才能使函式成為內聯,僅將 inline 放在函式宣告前面不起任何作用】

 

C 項錯誤, inline 函式可以定義在原始檔中,但多個原始檔中的同名 inline 函式的實現必須相同。一般把 inline 函式的定義放在標頭檔案中更加合適。

 

D 項正確,類內的成員函式,預設都是 inline 的。【定義在類宣告之中的成員函式將自動地成為行內函數

 

EF 項無意思,不管是 class 宣告中定義的 inline 函式,還是 class 實現中定義的 inline 函式,不存在優先不優先的問題,因為 class 的成員函式都是 inline 的,加了關鍵字 inline 也沒什麼特殊的

 

16. 類的大小隻與成員變數(非static資料成員變數)和虛擬函式指標有關,還要考慮到對齊。

因為在基類中存在虛擬函式時,派生類會繼承基類的虛擬函式,因此派生類中不再增加虛擬函式的儲存空間(因為所有的虛擬函式共享一塊記憶體區域),而僅僅需要考慮派生類中新增進來的非static資料成員的記憶體空間大小。

 

C++ Primer Plus 第六版中文版,P504:編譯器為每個類物件維持一個隱藏的成員,它是一個指向[虛擬函式地址陣列]的指標。虛擬函式地址陣列中儲存了類物件宣告的虛擬函式的地址,若在派生類中新新增了一個虛擬函式,則該函式地址也會被新增進虛擬函式地址陣列中。

17. A:類方法是指類中被static修飾的方法,無this指標。

C:類方法是可以呼叫其他類的static方法的

D:靜態方法訪問靜態變數,非靜態方法可以訪問靜態變數,錯在絕對二字。

 

成員方法又稱為例項方法

靜態方法又稱為類方法

a,靜態方法中沒有this指標

c,可以通過類名作用域的方式呼叫Class::fun();

d,太絕對化了,在類中申請一個類物件或者引數傳遞一個物件或者指標都可以呼叫。