1. 程式人生 > >c++中使用空指標呼叫成員函式的理解

c++中使用空指標呼叫成員函式的理解

使用空指標呼叫成員函式會如何?

舉個例子:base是基類,裡面有兩個函式:non-virtual func2 以及 virtual func1;
derived是派生類,使用public繼承自base,裡面有四個函式:virtual func1,non-virtual func3,non-virtual func4以及static non-virtual func5,特別注意:func4呼叫了基類的函式func2。具體如下程式碼:

class base
{
public:
    base(string s = "base") : _s(s) {}
    virtual string
func1(string t) { return (_s + t); } string func2(string t) { return (_s + t); } private: string _s; }; class derived : public base { public: virtual string func1(string t) { return t; } string func3(string t) { return t; } string func4(string
t) { return func2(t); } static string func5(string t) { return t; } };

在這中繼承關係的基礎上,我們來看看各種呼叫的結果:

#include <bits/stdc++.h>
using namespace std;
int main()
{
    derived* D = new derived();
    base* B1 = new base();
    base* B2 = D;
    derived* DNull = NULL;
    string
t = "hello"; cout << B1->func1(t) << endl; //basehello cout << B2->func1(t) << endl; //hello cout << D->func1(t) << endl; //hello cout << D->func3(t) << endl; //hello cout << D->func4(t) << endl; //basehello //cout << DNull->func1(t) << endl; //錯誤 cout << DNull->func3(t) << endl; //hello //cout << DNull->func4(t) << endl; //錯誤 cout << DNull->func5(t) << endl; //hello delete B1; delete D; return 0; }

在分析結果之前,有幾個概念需要分清楚:

  1. 靜態型別:宣告時所採用的型別或者表示式生成的型別,在編譯期就確定了。
  2. 動態型別:實際記憶體中的物件型別,在執行期確定。
  3. 靜態繫結:繫結靜態型別,發生在編譯期。
  4. 動態型別:繫結動態型別,發生在執行期,通常通過指標和引用來呼叫虛擬函式是動態繫結,其他一般為靜態繫結。
    有了基本的瞭解,再來看上面的例子:
    cout << B1->func1(t) << endl;  //basehello
    cout << B2->func1(t) << endl;   //hello

這兩行的輸出是比較顯然的,需要注意的是B1的靜態型別和動態型別都是base,採用的是靜態繫結,而B2呼叫的是虛擬函式,B2的靜態型別是base,但是動態型別卻是derived。

    cout << D->func1(t) << endl;    //hello
    cout << D->func3(t) << endl;    //hello
    cout << D->func4(t) << endl;    //basehello
    cout << D->func5(t) << endl;    //hello

這幾行也都比較好理解,都是通過derived的指標物件D呼叫函式,只不過函式不同,有的是虛擬函式,有的是非虛擬函式,還有的是靜態函式。
接下來是重點,使用空指標呼叫函式

    //cout << DNull->func1(t) << endl; //錯誤
    cout << DNull->func3(t) << endl;    //hello
    //cout << DNull->func4(t) << endl; //錯誤
    cout << DNull->func5(t) << endl;    //hello

第一行使用DNull呼叫虛擬函式func1 為什麼會出錯呢?我來談談我的理解。一個指標ptr呼叫虛擬函式在內部會被轉化為:

(*ptr->vptr[1])(ptr)

其中vptr是虛表指標指向virtual table; 1是virtual table slot的索引值,關聯到呼叫的虛擬函式;第二個ptr表示this指標。關於函式語意,參考我這篇讀書筆記。這裡顯式使用this指標,而ptr本身是一個空指標,這就是矛盾的,因此我覺得這個原因導致其出錯。在知乎上也有這個問題的討論,見連結為什麼C++呼叫空指標物件的成員函式可以執行通過?

然後再看第二個DNull->func3(t) 的輸出:可以正確輸出hello。我們從Function語義學來看,一個非靜態成員函式通常的內部轉化有3點,分別是:
1.新增this指標;
2.Name Mangling(函式名稱的特殊處理);
3.NRV(Named Return Value)優化;
func3經過內部轉化可能變成下面的樣子:

void func3_string(derived * const this, string t, string& result) {
    result.string::string(); //defalut ctor
    result = t;
}

轉化之後的函式並沒有對this指標有任何操作,因此使用空指標呼叫該函式可以有正確的結果。
再看第三個DNull->func4(t) ,而func4經過內部轉化可能變成下面的樣子:

void func3_string(derived * const this, string t, string& result){
    result.string::string(); //defalut ctor
    result = this->func4(t);
}

轉化之後的函式顯式呼叫了this指標,因此使用空指標呼叫該函式會出錯。
最後一個 DNull->func5(t),使用空指標呼叫靜態成員函式,靜態成員函式沒有this指標,故肯定能呼叫成功。

總結

我個人使用空指標呼叫成員函式的理解是:如果空指標呼叫成員函式沒有呼叫this指標,就能成功呼叫,否則就會出錯。

**

主要參考自侯捷老師的《深度探索C++物件模型》

**

相關推薦

c++中使用空指標呼叫成員函式理解

使用空指標呼叫成員函式會如何? 舉個例子:base是基類,裡面有兩個函式:non-virtual func2 以及 virtual func1; derived是派生類,使用public繼承自base,裡面有四個函式:virtual func1,non-vi

[C++]類的空指標呼叫成員函式後,會發生什麼事?

類的例項呼叫成員函式的原理 其實不管是通過物件例項或指標例項呼叫,其實底層呼叫的過程都是一樣的,都是把當前物件的指標作為一個引數傳遞給被呼叫的成員函式。通過下面的相關例項程式碼進行檢驗: 實驗的C++程式碼 class Student { private: int age; public: Studen

C++----空指標訪問成員函式

//空指標訪問成員函式; class Person { public: void show() { cout << "Person show" << endl; } void showage() { cout << m_Age <

置空指標呼叫成員函式問題

問題由來:new一個自己的類,用完後delete指標,置空NULL 後,打斷點指標為空,但是依舊可以調用出類裡的函式,且編譯通                    過了; 總結:在java,Python語言中是做不到這樣的,但是C++中可以,原因是其繫結方式不一樣;

成員函式指標和其他型別的強制轉換,使用一般指標呼叫成員函式

 成員函式指標和其他型別之間的轉換,參見如下示例: class test...{public:    void t()...{};};typedef   void   (test::*pMemFnction)(); int main()...{    pMemFnction

[C++]空的物件指標可以呼叫成員函式

include using namespace std; class A{ public: void func() { cout << "hahaha" << endl; } int m_num = 1; }; int main() { A* ptr = NULL; A obj;

C++基礎:類與物件(物件呼叫成員函式 this指標)

1.一個物件的this指標並不是物件本身的一部分,不會影響sizeof(物件)的結果。this作用域是在類內部,當在類的非靜態成員函式中訪問類的非靜態成員的時候,編譯器會自動將物件本身的地址作為一個隱含引數傳遞給函式。也就是說,即使你沒有寫上this指標,編譯器在編譯的時候

c++ 如何把this指標傳入成員函式 像全域性函式一樣呼叫成員函式

測試這個功能的初衷是測試boost裡面的bind boost::bind((&A::sum), &a, _1, _2) 上面的程式碼是我boost bind及多執行緒這篇部落格裡面的一行程式碼。我就想boost是怎麼做到這樣呼叫一個類的成員函式的。其實成員

c++11呼叫成員函式mem_fn和適合普通函式指標

在C++11之前,呼叫一個成員函式指標做為容器的回撥演算法時,可以根據其容器記憶體儲的內容是物件還是指標呼叫相關的mem_fun和_mem_fun_ref函式來與演算法等進行適配,搭配使用。 在c++11中加入mem_fn來對成員函式的呼叫進行相關的封裝,不過也需要對方法

this 指標的地址--呼叫成員函式的所在物件的起始地址

#include<iostream> using namespace std; class Test { int x; public: Test(int a){ x=a; } void get_this(); }; void Test:: get_this() { co

C++類物件空指標訪問成員函式

題目: class A{ public: void test(){printf("test A");} }; int main(){ A*pA=NULL; pA->test(); } 結果是輸出“test A”而不是程式崩潰,原因如下: 一種解

C++類物件空指標訪問成員函式(靜態繫結)

題目: class A{ public: void test(){printf("test A");} }; int main(){ A*pA=NULL; pA->test(); } 結果是輸出“test A”而不是

詳解this指標--為什麼空的物件指標可以呼叫成員函式

引題 class A{ public: void function() { cout << "I can run" << endl; } }; int main() { A* pa =

指標也能呼叫成員函式

    最近各大公司都在招聘實習生,昨晚,微信公眾號推送了網易的筆試題讓小夥伴們參考,看了第一道題,博主就覺得“嗯,我果然還是見識短哈!”為什麼呢,我們先看看這個程式碼~class cal { publ

C++之指向物件成員函式指標

1. 普通函式的指標變數定義    資料型別名 (*指標變數名)(引數列表);    例如:void (*p)( ); //p指向void型函式的指標變數;          p = fun;    

指標成員函式呼叫

我一直認為技術是沒有止境的,不管你怎麼去學,總有你沒有掌握的地方。但是,人,是不能停下腳步的。     今天在檢查一個MFC程式,看到GetSafeHwnd函式,於是讓我想明白到底它比m_hWnd成員變數safe在哪裡?到網上查了一下資料,發現了GetSafeHwnd的

指標可以呼叫成員函式

有下面一個簡單類: class A { public: void fun(){ cout << "I'm class A"<<endl; } }; 用一個空指標呼叫上面的fun函式: A* p

C++ 空指標呼叫函式 靜態繫結

首先看一段程式碼是否知道其正確還是錯誤。 class A{ public:void print(){cout << "Hello" << endl;} }; void main() {A *a = NULL;a.print(); } 問你程式是否正確

C++教程:指向成員函式指標

成員函式指標是C++最少用到的語法之一,甚至有經驗的C++碼農有時候也會被它搞暈。這是一篇針對於初學者的教程,同時也給有經驗的碼農分享了一些我個人對底層機制的挖掘。在開始之前,讓我們先看一段在第一次看時一定會高呼“我++”的程式碼(說明,這些程式碼都是翻譯君重新手敲的,改正了原文程式碼中的一些不太好的空格、

c++學習筆記之成員函式

學了c++才知道什麼是面向物件什麼是面向過程。幼稚的我曾經還覺得c++和C語言差不多,接觸之後才知道c++是多麼的難,光類和物件這個知識點就看了一天。什麼建構函式解構函式,物件陣列物件成員弄得我頭大,現在才知道為什麼老師說c++是最難的語言,學c++就是在地獄裡磨鍊,從地獄出來就是天堂,會有會當凌絕