最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

c++虛函數(shù)及常見問題匯總

 更新時間:2025年12月01日 10:00:35   作者:王YAN龍  
文章詳細(xì)介紹了面向?qū)ο缶幊讨械乃拇蠡咎匦裕悍庋b、抽象、繼承和多態(tài),重點(diǎn)解釋了多態(tài)的概念及其在C++中的實(shí)現(xiàn)方式,包括虛函數(shù)、虛函數(shù)表、單繼承、多繼承以及純虛函數(shù)的使用,通過代碼示例展示了多態(tài)在不同情況下的表現(xiàn),以及純虛函數(shù)的約束作用,感興趣的朋友一起看看吧

封裝,抽象,繼承,多態(tài)是面向?qū)ο缶幊陶Z言的特點(diǎn)。個人認(rèn)為抽象和繼承都是手段,多態(tài)才是目的,繼承是多態(tài)的基礎(chǔ)。

封裝:

(1)將屬性和函數(shù)封裝到一個類里邊,屬性和函數(shù)組成一個完整的對象

(2)權(quán)限管理,增強(qiáng)安全性

(3)暴露有限的接口,增強(qiáng)了易用性,增強(qiáng)了安全性

抽象:

(1)抽象這個概念存在于很多場景,我們使用函數(shù),本身就是通過函數(shù)隱藏具體的實(shí)現(xiàn)細(xì)節(jié),這也算抽象

(2)c++ 中的抽象,可以使用抽象類來認(rèn)識,抽象類是包含純虛函數(shù)的類,抽象類不能創(chuàng)建對象

繼承:

(1)技術(shù)來源于生活,生活中的對象,比如植物,動物,都存在繼承

(2)繼承帶來的好處是子類可以復(fù)用父類的屬性和函數(shù)

(3)c++ 中繼承是實(shí)現(xiàn)多態(tài)的基礎(chǔ)

多態(tài):

同一個父類,可以派生出多個子類,每個子類都有自己專有的屬性和方法,多姿多態(tài),豐富多彩

1 多態(tài)

提到多態(tài)我們一般會想到這樣一種使用場景:有一個基類,基類里邊有虛函數(shù);不同的類繼承基類,然后分別在虛函數(shù)中實(shí)現(xiàn)自己的邏輯,覆蓋基類的虛函數(shù);派生類的指針也可以賦值給基類指針,通過基類指針調(diào)用虛函數(shù)的時候調(diào)用的是派生類實(shí)現(xiàn)的函數(shù)。

上邊說的是典型的多態(tài)的場景,在有些文章或者書中,也把重載說成是一種多態(tài)。本節(jié)分別舉一個虛函數(shù)多態(tài)的例子,一個重載多態(tài)的例子。

1.1 虛函數(shù)

如下代碼, Phone 是一個基類,類中有一個虛函數(shù) virtual boid CallUp()。Phone 派生出 3 個類,ApplePhone,MiPhone,OppoPhone。在 main 函數(shù)中針對 3 個派生類,分別創(chuàng)建了一個對象。定義了 3 個函數(shù) CallUp1(),CallUp2(),CallUp3(),3 個函數(shù)的形參分別是 Phone 引用,Phone 指針,Phone 對象,在 main() 函數(shù)中分別調(diào)用這 3 個函數(shù)。

#include <iostream>
#include <string>
class Phone {
public:
    virtual ~Phone() {
      std::cout << "~Phone()" << std::endl;
    }
    virtual void CallUp() {
      std::cout << "Phone call up" << std::endl;
    }
};
class ApplePhone : public Phone {
public:
    void CallUp() {
        std::cout << "ApplePhone call up" << std::endl;
    }
};
class MiPhone : public Phone {
public:
    void CallUp() {
        std::cout << "MiPhone call up" << std::endl;
    }
};
class OppoPhone : public Phone {
public:
    void CallUp() {
        std::cout << "OppoPhone call up" << std::endl;
    }
};
void CallUp1(Phone &phone) {
  std::cout << "CallUp(Phone &phone)" << std::endl;
  phone.CallUp();
}
void CallUp2(Phone *phone) {
  std::cout << "CallUp2(Phone *phone)" << std::endl;
  phone->CallUp();
}
// 派生類可以賦值給基類
// 因?yàn)榛愔械臄?shù)據(jù)成員,在派生類中都有
// 派生類賦值給基類的過程,就是成員賦值的過程
void CallUp3(Phone phone) {
  std::cout << "CallUp3(Phone phone)" << std::endl;
  phone.CallUp();
}
int main() {
    ApplePhone apple;
    MiPhone mi;
    OppoPhone oppo;
    CallUp1(apple);
    CallUp1(mi);
    CallUp1(oppo);
    std::cout << std::endl;
    CallUp2(&apple);
    CallUp2(&mi);
    CallUp2(&oppo);
    std::cout << std::endl;
    CallUp3(apple);
    CallUp3(mi);
    CallUp3(oppo);
    std::cout << std::endl;
    return 0;
}

代碼運(yùn)行結(jié)果如下,可以看到,調(diào)用 CallUp1() 和 CallUp2() 能體現(xiàn)出多態(tài)的特點(diǎn),分別調(diào)用的是類自己的 CallUp();CallUp3() 沒有體現(xiàn)多態(tài),調(diào)用的都是基類的虛函數(shù)。引用傳遞和指針傳遞都能體現(xiàn)多態(tài)的特點(diǎn);值傳遞不能體現(xiàn)多態(tài)的特點(diǎn),還是調(diào)用的基類中的函數(shù)。

2 虛函數(shù)和虛函數(shù)表

2.1 函數(shù)名即地址

在 c 和 c++ 中函數(shù)和全局變量類似,都是有地址的,函數(shù)名就表示函數(shù)的入口地址。

如下代碼,聲明了一個函數(shù)指針 PF,形參和返回值與 Hello 是一致的,這樣就可以直接通過 pf 來對 Hello 做函數(shù)調(diào)用了。

#include <iostream>
#include <string>
void Hello(std::string name) {
  std::cout<< "hello " << name << std::endl;
}
typedef void (*PF)(std::string name);
int main() {
  std::cout<< (void *)Hello << std::endl;
  Hello("marry");
  PF pf = Hello;
  std::cout << "pf = " << (void *)pf << std::endl;
  pf("tom");
  return 0;
}

2.2 虛表

虛函數(shù)表是虛函數(shù)工作的基礎(chǔ),也是實(shí)現(xiàn)多態(tài)的核心。對于有虛函數(shù)的類來說,都會有一個虛函數(shù)表。如下圖所示,有虛函數(shù)的類,這個類的每個對象的第一個 8 字節(jié)(64 位系統(tǒng)是 8 字節(jié),32 位系統(tǒng)是 4 字節(jié)) 中保存的是虛函數(shù)表的地址。

如果一個有虛函數(shù)的類,沒有其它成員變量,那么 sizeof() 計(jì)算這個類的大小是 8(64 位系統(tǒng)上),8 字節(jié)也就是存放虛函數(shù)表的空間。

#include <iostream>
#include <string>
class Base {
public:
  virtual void SayHello1() {
    std::cout << "hello1" << std::endl;
  }
};
int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  return 0;
}

一個類的所有對象共用一張?zhí)摵瘮?shù)表,所以每個對象的第一個 8 字節(jié)里邊存儲的數(shù)據(jù)內(nèi)容是相等的,都是虛函數(shù)表的地址。虛表中保存的是函數(shù)的地址,而函數(shù)又屬于代碼段,代碼段是共享的,所以一個類的對象共享虛表。

如下代碼,有一個包含虛函數(shù)的類 Base,b1 和 b2 都是 Base 的對象,我們獲取對象第一個 8 字節(jié)中的內(nèi)容,發(fā)現(xiàn) b1 和 b2 是相等的,這也驗(yàn)證了一個類的對象指向同一個虛表。

#include <iostream>
#include <string>
class Base {
public:
  virtual void SayHello1() {
    std::cout << "hello1" << std::endl;
  }
  virtual void SayHello2() {
    std::cout << "hello2" << std::endl;
  }
  virtual void SayHello3() {
    std::cout << "hello3" << std::endl;
  }
};
int main() {
  Base b1;
  Base b2;
  printf("&b1 = %p, *(long *)(&b1) = 0x%llx\n", &b1, *(long *)(&b1));
  printf("&b1 = %p, *(long *)(&b2) = 0x%llx\n", &b2, *(long *)(&b2));
  return 0;
}

運(yùn)行結(jié)果如下,從打印信息來看,虛函數(shù)表的地址是 0x55f2dd2dbd58。

虛函數(shù)表中每一個表項(xiàng)都是一個函數(shù)的地址,在 64 位機(jī)器上,一個函數(shù)地址的長度是 8 字節(jié),所以一個表項(xiàng)占  8 字節(jié)。如下代碼,在類 Base 中有 3 個虛函數(shù),另外定義了一個函數(shù)指針類型。在 main 函數(shù)中首先找到了虛函數(shù)表,然后依次取出了虛函數(shù)的地址,通過函數(shù)地址可以直接進(jìn)行調(diào)用。

#include <iostream>
#include <string>
class Base {
public:
  virtual void SayHello1() {
    std::cout << "hello1" << std::endl;
  }
  virtual void SayHello2() {
    std::cout << "hello2" << std::endl;
  }
  virtual void SayHello3() {
    std::cout << "hello3" << std::endl;
  }
};
// 定義一個函數(shù)指針類型
typedef void (*PF)(void);
int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  Base b;
  // vfptr 是指向虛函數(shù)表的地址
  long *vfptr = (long *)*(long *)(&b);
  // 虛函數(shù)表的第一個 8 字節(jié)就是函數(shù) SayHello1 的地址
  long f1_function = *(vfptr + 0);
  // 虛函數(shù)表的第二個 8 字節(jié)是函數(shù) SayHello2 的地址
  long f2_function = *(vfptr + 1);
  // 虛函數(shù)表的第三個 8 字節(jié)是函數(shù) SayHello3 的地址
  long f3_function = *(vfptr + 2);
  printf("function1 = %p\n", f1_function);
  printf("function2 = %p\n", f2_function);
  printf("function3 = %p\n", f3_function);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  return 0;
}

運(yùn)行結(jié)果如下:

2.3 單繼承,沒有虛函數(shù)覆蓋

如下代碼, Base 中有 3 個虛函數(shù),Derived 繼承了 Base 類, Derived 中有 2 個虛函數(shù)。Derived  中沒有覆蓋 Base 中的函數(shù),所以 Derived 的虛表有 5 個表項(xiàng),Base 中的虛表有 3 個表項(xiàng)。

#include <iostream>
#include <string>
class Base {
public:
  virtual void faa1() {
    std::cout << "faa1" << std::endl;
  }
  virtual void faa2() {
    std::cout << "faa2" << std::endl;
  }
  virtual void faa3() {
    std::cout << "faa3" << std::endl;
  }
};
class Derived : public Base{
public:
  virtual void fbb1() {
    std::cout << "fbb1" << std::endl;
  }
  virtual void fbb2() {
    std::cout << "fbb2" << std::endl;
  }
};
typedef void (*PF)(void);
void CallPF1() {
  // Base b; // Base 中的虛表只有 3 個函數(shù)
  // 直接對象賦值,b 中的虛表也是只用 3 個表項(xiàng)
  // Derived d;
  // Base b = d;
  Base b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
void CallPF2() {
  Derived b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
void CallPF3() {
  Base *b = new Derived();
  long *vfptr = (long *)*(long *)(b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  std::cout << "sizeof(Derived) = " << sizeof(Derived) << std::endl;
  CallPF2();
  std::cout << std::endl;
  CallPF3();
  std::cout << std::endl;
  CallPF1();
  std::cout << std::endl;
  return 0;
}

運(yùn)行結(jié)果如下,因?yàn)?Base 中的虛函數(shù)表的表項(xiàng)還是 3 個,所以調(diào)用 f4_function 和 f5_function 的時候會出現(xiàn)段錯誤。

2.4 單繼承,有虛函數(shù)覆蓋 --> 表項(xiàng)替換

在子類中,可以覆蓋父類的虛函數(shù),覆蓋虛函數(shù)就是將虛函數(shù)表中的表項(xiàng)覆蓋,改成子類中的虛函數(shù)的地址。當(dāng)調(diào)用成員函數(shù)的時候,如果是虛函數(shù),那么會查虛函數(shù)表,找到表之后再調(diào)用對應(yīng)的函數(shù)。不同的子類,使用自己的虛函數(shù)地址覆蓋了虛函數(shù)表的表項(xiàng),那么函數(shù)調(diào)用的時候當(dāng)然是調(diào)用子類中的函數(shù)。

如下代碼,Base 類中有 3 個虛函數(shù);Derived 繼承了 Base,覆蓋了 Base 中的 faa1 和 faa2;Derived1 繼承了 Derived,覆蓋了 faa1 和 faa2。

#include <iostream>
#include <string>
class Base {
public:
  virtual void faa1() {
    std::cout << "faa1" << std::endl;
  }
  virtual void faa2() {
    std::cout << "faa2" << std::endl;
  }
  virtual void faa3() {
    std::cout << "faa3" << std::endl;
  }
};
class Derived : public Base{
public:
  virtual void faa1() {
    std::cout << "derived faa1" << std::endl;
  }
  void faa2() {
    std::cout << "derived faa2" << std::endl;
  }
  virtual void fbb1() {
    std::cout << "fbb1" << std::endl;
  }
  virtual void fbb2() {
    std::cout << "fbb2" << std::endl;
  }
};
class Derived1 : public Derived {
public:
  void faa1() {
    std::cout << "Derived1 faa1" << std::endl;
  }
  void fbb1() {
    std::cout << "Derived1 fbb1" << std::endl;
  }
};
typedef void (*PF)(void);
void CallPF1() {
  // Base b; // Base 中的虛表只有 3 個函數(shù)
  // 直接對象賦值,b 中的虛表也是只用 3 個表項(xiàng)
  // Derived d;
  // Base b = d;
  Base b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
void CallPF2() {
  Derived b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
void CallPF3() {
  Derived1 b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  std::cout << "sizeof(Derived) = " << sizeof(Derived) << std::endl;
  CallPF2();
  std::cout << std::endl;
  CallPF3();
  std::cout << std::endl;
  CallPF1();
  std::cout << std::endl;
  return 0;
}

運(yùn)行結(jié)果如下:

2.5 多繼承

如下代碼,有 3 個基類,Base1,Base2 和 Base3。3 個基類中都有虛函數(shù),也都有一個虛函數(shù)表。Derived 類繼承 3 個基類,那么 Derived 中就有 3 個虛函數(shù)表。從打印的  Derived 的大小也能看出來,Base1、Base2、Base3 的大小是 8,Derived 的大小是 24。

#include <iostream>
#include <string>
class Base1 {
public:
  virtual void foo1() {
    std::cout << "Base1::foo1" << std::endl;
  }
  virtual void faa2() {
    std::cout << "Base1::faa2" << std::endl;
  }
  virtual void faa3() {
    std::cout << "Base1::faa3" << std::endl;
  }
};
class Base2 {
public:
  virtual void foo1() {
    std::cout << "Base2::foo1" << std::endl;
  }
  virtual void fbb2() {
    std::cout << "Base2::fbb2" << std::endl;
  }
  virtual void fbb3() {
    std::cout << "Base2::fbb3" << std::endl;
  }
};
class Base3 {
public:
  virtual void foo1() {
    std::cout << "Base3::foo1" << std::endl;
  }
  virtual void fcc2() {
    std::cout << "Base3::fcc2" << std::endl;
  }
  virtual void fcc3() {
    std::cout << "Base3::fcc3" << std::endl;
  }
};
class Derived : public Base1, public Base2, public Base3 {
public:
  virtual void fbb1() {
    std::cout << "Derived::fbb1" << std::endl;
  }
  void foo1() {
    std::cout << "Derived::foo1" << std::endl;
  }
  void fbb2() {
    std::cout << "Derived::fbb2" << std::endl;
  }
};
int main() {
  std::cout << "sizeof(Base1) = " << sizeof(Base1) << std::endl; // 8
  std::cout << "sizeof(Base2) = " << sizeof(Base2) << std::endl; // 8
  std::cout << "sizeof(Base3) = " << sizeof(Base3) << std::endl; // 8
  std::cout << "sizeof(Derived) = " << sizeof(Derived) << std::endl; // 24
  Derived d;
  d.foo1();
  d.fbb2();
  d.fcc2();
  return 0;
}

運(yùn)行結(jié)果如下:

2.6 一個類的多個對象是不是共享一個虛函數(shù)表 ?

是,一個包含虛函數(shù)的類的多個對象共享一個虛函數(shù)表。

如下代碼,Base 是基類,Derived 是派生類。兩個類分別有自己的虛函數(shù)表,對于一個類的多個對象來說,多個對象共享這個類的虛函數(shù)表。

對于一個對象來說,第一個 8 字節(jié)保存的就是虛函數(shù)表的地址,代碼中打印出了每個對象的虛函數(shù)表。

#include <iostream>
#include <string>
class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
  }
  ~Base() {
    std::cout << "~Base()" << std::endl;
  }
  virtual void Do() {
    std::cout << "Base() Do()" << std::endl;
  }
};
class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived()" << std::endl;
  }
  ~Derived() {
    std::cout << "~Derived()" << std::endl;
  }
  void Do() {
    std::cout << "Derived() Do()" << std::endl;
  }
};
typedef void (*PF)(void);
void TestBase() {
  Base b1;
  Base b2;
  Base b3;
  long *vfptr1 = (long *)*(long *)(&b1);
  long *vfptr2 = (long *)*(long *)(&b2);
  long *vfptr3 = (long *)*(long *)(&b3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
void TestDerived() {
  Derived d1;
  Derived d2;
  Derived d3;
  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
void TestDerivedBase() {
  Base *d1 = new Derived;
  Base *d2 = new Derived;
  Base *d3 = new Derived;
  long *vfptr1 = (long *)*(long *)(d1);
  long *vfptr2 = (long *)*(long *)(d2);
  long *vfptr3 = (long *)*(long *)(d3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
int main() {
  std::cout << "Base ------------------------" << std::endl;
  TestBase();
  std::cout << "Derived ---------------------" << std::endl;
  TestDerived();
  std::cout << "DerivedBase -----------------" << std::endl;
  TestDerivedBase();
  return 0;
}

打印結(jié)果如下:

(1)對于同一個類的多個對象來說,虛函數(shù)表是相同的

(2)基類和派生類的虛函數(shù)表是不同的,各自有各自的虛函數(shù)表,但是把派生類指針或者引用賦值給基類指針或者基類引用,那么基類中的虛函數(shù)表是派生類中的虛函數(shù)表

2.7 一個基類的多個派生類之間是共享一個虛表嗎 ?

不是,虛函數(shù)表是以類為單位而存在的,一個基類的多個派生類分別有各自的虛函數(shù)表。

如下代碼,Base 是基類,有 3 個派生類 Derived1,Derived2 和 Derived3。分別打印了 3 個派生類的虛函數(shù)表,虛函數(shù)表是不一樣的。

#include <iostream>
#include <string>
class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
  }
  ~Base() {
    std::cout << "~Base()" << std::endl;
  }
  virtual void Do() {
    std::cout << "Base() Do()" << std::endl;
  }
};
class Derived1 : public Base {
public:
  Derived1() {
    std::cout << "Derived1()" << std::endl;
  }
  ~Derived1() {
    std::cout << "~Derived1()" << std::endl;
  }
  void Do() {
    std::cout << "Derived1() Do()" << std::endl;
  }
};
class Derived2 : public Base {
public:
  Derived2() {
    std::cout << "Derived2()" << std::endl;
  }
  ~Derived2() {
    std::cout << "~Derived2()" << std::endl;
  }
  void Do() {
    std::cout << "Derived2() Do()" << std::endl;
  }
};
class Derived3 : public Base {
public:
  Derived3() {
    std::cout << "Derived3()" << std::endl;
  }
  ~Derived3() {
    std::cout << "~Derived3()" << std::endl;
  }
  void Do() {
    std::cout << "Derived3() Do()" << std::endl;
  }
};
typedef void (*PF)(void);
void TestBase() {
  Base b1;
  Base b2;
  Base b3;
  long *vfptr1 = (long *)*(long *)(&b1);
  long *vfptr2 = (long *)*(long *)(&b2);
  long *vfptr3 = (long *)*(long *)(&b3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
void TestDerived1() {
  Derived1 d1;
  Derived1 d2;
  Derived1 d3;
  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
void TestDerived2() {
  Derived2 d1;
  Derived2 d2;
  Derived2 d3;
  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
void TestDerived3() {
  Derived3 d1;
  Derived3 d2;
  Derived3 d3;
  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
int main() {
  std::cout << "Base ------------------------" << std::endl;
  TestBase();
  std::cout << "Derived1 ---------------------" << std::endl;
  TestDerived1();
  std::cout << "Derived2 ---------------------" << std::endl;
  TestDerived2();
  std::cout << "Derived3 ---------------------" << std::endl;
  TestDerived3();
  return 0;
}

運(yùn)行結(jié)果如下:

Base ------------------------
Base()
Base()
Base()
1, virtual function table addr 0x5565ee1b8d08
2, virtual function table addr 0x5565ee1b8d08
3, virtual function table addr 0x5565ee1b8d08
Base() Do()
Base() Do()
Base() Do()
~Base()
~Base()
~Base()
Derived1 ---------------------
Base()
Derived1()
Base()
Derived1()
Base()
Derived1()
1, virtual function table addr 0x5565ee1b8cf0
2, virtual function table addr 0x5565ee1b8cf0
3, virtual function table addr 0x5565ee1b8cf0
Derived1() Do()
Derived1() Do()
Derived1() Do()
~Derived1()
~Base()
~Derived1()
~Base()
~Derived1()
~Base()
Derived2 ---------------------
Base()
Derived2()
Base()
Derived2()
Base()
Derived2()
1, virtual function table addr 0x5565ee1b8cd8
2, virtual function table addr 0x5565ee1b8cd8
3, virtual function table addr 0x5565ee1b8cd8
Derived2() Do()
Derived2() Do()
Derived2() Do()
~Derived2()
~Base()
~Derived2()
~Base()
~Derived2()
~Base()
Derived3 ---------------------
Base()
Derived3()
Base()
Derived3()
Base()
Derived3()
1, virtual function table addr 0x5565ee1b8cc0
2, virtual function table addr 0x5565ee1b8cc0
3, virtual function table addr 0x5565ee1b8cc0
Derived3() Do()
Derived3() Do()
Derived3() Do()
~Derived3()
~Base()
~Derived3()
~Base()
~Derived3()
~Base()

2.8 虛函數(shù)重載

如下代碼 Base 是基類,Derived 是派生類。Base 中有兩個虛函數(shù) func1() 和 func2(),Derived 中也有兩個虛函數(shù) func1() 和 func2()。Derived 中的 func1() 和 Base 中的 func1() 的形參列表是不一樣的,所以 Derived 中的 func1 不會覆蓋 Base 中的 func1。

#include <iostream>
#include <string>
class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
  }
  ~Base() {
    std::cout << "~Base()" << std::endl;
  }
  virtual void func1(int a) {
    std::cout << "Base()::func1(int a), a = " << a << std::endl;
  }
  virtual void func2(int a = 100) {
    std::cout << "Base()::func2(int a = 100), a = " << a << std::endl;
  }
};
class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived()" << std::endl;
  }
  ~Derived() {
    std::cout << "~Derived()" << std::endl;
  }
  virtual void func1(double a) {
    std::cout << "Derived()::func1(double a), a = " << a << std::endl;
  }
  virtual void func2(int a = 200) {
    std::cout << "Derived()::func2(int a = 200), a = " << a << std::endl;
  }
};
typedef void (*PF)(int);
typedef void (*PF1)(double);
int main() {
  Base *b = new Derived;
  Derived *d = new Derived;
  std::cout << "----------------" << std::endl;
  b->func1(10);
  b->func1(1.123);
  b->func2();
  std::cout << "----------------" << std::endl;
  d->func1(10);
  d->func1(1.123);
  d->func2();
  return 0;
}

運(yùn)行結(jié)果如下:

(1)派生類中的虛函數(shù)表如下

第一個表項(xiàng)是 Base 中的 func1。

第二個表項(xiàng)在基類中是 Base 中的 func2,在派生類中,Derived 中的 func2 對 Base 中的 func2 進(jìn)行了覆蓋。

第三個表象是派生類中的 func1,因?yàn)榕缮愔械?func1 和 Base 中的 func1 形參不一樣,所以不會對 Base 中的 func1 進(jìn)行覆蓋。

(2)運(yùn)行結(jié)果分析

① 使用 Base 類型的指針或者 Base 類型的引用,當(dāng)指針指向 Derived 對象的時候,調(diào)用 func1(),不管傳參是 int 類型還是 double 類型,都是調(diào)用的 Base 中的 func1()。當(dāng)傳參是 double 類型的時候也不是調(diào)用的 Derived 中的 func1,也就是派生類和基類形不成重載。

② 使用 Base 類型的指針或者引用,當(dāng)指針指向 Derived 對象的時候,調(diào)用 func2,調(diào)用的函數(shù)是 Derived 中的 func2,但是默認(rèn)參數(shù)還是 Base 中初始化的。這個現(xiàn)象讓人看起來有點(diǎn)奇怪,函數(shù)和默認(rèn)參數(shù)不是配套的。

③ 使用 Derived 指針或者引用,調(diào)用的 func1() 都是 Derived 中的函數(shù),不管入?yún)⑹?int 還是 double。

④ 使用 Derived 指針或者引用,調(diào)用 func2() 調(diào)用的是 Derived 中的 func2(),并且形參默認(rèn)是 200。

#include <iostream>
#include <string>
class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
    pb = new int[8];
  };
  virtual ~Base() {
    std::cout << "~Base()" << std::endl;
    if (pb) {
      std::cout << "delete pb" << std::endl;
      delete[] pb;
    }
  };
  virtual void Do() {
    std::cout << "Base() Do()" << std::endl;
  }
  virtual void F() = 0;
  int *pb = nullptr;
};
class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived()" << std::endl;
    pd = new int[16];
  };
  ~Derived() {
    std::cout << "~Derived()" << std::endl;
    if (pd) {
      std::cout << "delete pd" << std::endl;
      delete[] pd;
    }
  };
  void Do() {
    std::cout << "Derived() Do()" << std::endl;
  }
  virtual void F() {
    std::cout << "Derived() F()" << std::endl;
  }
  int *pd = nullptr;
};
int main() {
  Derived *d1 = new Derived();
  d1->Do();
  delete d1;
  Base *b1 = new Derived;
  b1->Do();
  delete b1;
  return 0;
}

3虛函數(shù)和純虛函數(shù)

虛函數(shù),之所以說是虛的,說的是在派生類中,可以覆蓋基類中的虛函數(shù);相對于虛函數(shù)來說,沒有 virtual 修飾的函數(shù)可以叫做實(shí)函數(shù),實(shí)函數(shù)的實(shí)現(xiàn)邏輯是不會變化的,不像虛函數(shù),被繼承之后就可以被覆蓋,實(shí)現(xiàn)邏輯發(fā)生了變化。虛函數(shù)是實(shí)現(xiàn)多態(tài)的核心。虛函數(shù)和純虛函數(shù)比較的話,虛函數(shù)可以在派生類中被覆蓋,但是虛函數(shù)也是有自己的實(shí)現(xiàn)的,可以被直接調(diào)用;純虛函數(shù)沒有自己的實(shí)現(xiàn),在派生類中可以被覆蓋,并且必須實(shí)現(xiàn)。包含純虛函數(shù)的類是抽象類,不能創(chuàng)建對象,如果抽象類的派生類中沒有實(shí)現(xiàn)純虛函數(shù) ,那么派生類也是抽象類,不能創(chuàng)建對象。

虛函數(shù)和純虛函數(shù)并沒有嚴(yán)格的優(yōu)劣之分。

從實(shí)際使用中,純虛函數(shù)有一個優(yōu)點(diǎn),假如一個基類中的函數(shù),派生類中必須實(shí)現(xiàn)自己的邏輯,而不能使用基類中的邏輯,那么就可以使用純虛函數(shù),這樣在派生類中如果忘記實(shí)現(xiàn)了,那么編譯器就會提示錯誤,起到了一個約束的作用;如果用虛函數(shù)實(shí)現(xiàn),那么派生類中忘記實(shí)現(xiàn)的話,編譯器也不會報錯,起不到約束提醒的作用。

純虛函數(shù)有以下兩點(diǎn):

(1)純虛函數(shù)的聲明方式

函數(shù)聲明之后加 = 0,而不是花括號

(2)抽象類不能創(chuàng)建對象,抽象類的派生類如果沒有實(shí)現(xiàn)所有的純虛函數(shù),派生類也是抽象類

(3)抽象類可以定義指針,并且可以使用派生類的指針給它賦值,也就是說抽象類雖然不能創(chuàng)建對象,但是抽象類的指針卻可以指向一個對象

如下是使用抽象類的一個例子:

#include <iostream>
#include <string>
class Phone {
public:
  Phone() {
    std::cout << "Phone()" << std::endl;
  }
  ~Phone() {
    std::cout << "~Phone()" << std::endl;
  }
  virtual void Call() = 0;
  virtual void SendMessage(std::string msg) = 0;
};
class Apple : public Phone {
public:
  Apple() {
    std::cout << "Apple()" << std::endl;
  }
  ~Apple() {
    std::cout << "~Apple()" << std::endl;
  }
  virtual void Call() {
    std::cout << "Apple Call()" << std::endl;
  }
  virtual void SendMessage(std::string msg) {
    std::cout << "apple send msg: " << msg << std::endl;
  }
};
class Oppo : public Phone {
public:
  Oppo() {
    std::cout << "Oppo()" << std::endl;
  }
  ~Oppo() {
    std::cout << "~Oppo()" << std::endl;
  }
  virtual void Call() {
    std::cout << "Oppo Call()" << std::endl;
  }
};
class Vivo : public Phone {
public:
  Vivo() {
    std::cout << "Vivo()" << std::endl;
  }
  ~Vivo() {
    std::cout << "~Vivo()" << std::endl;
  }
  virtual void Call() {
    std::cout << "Vivo Call()" << std::endl;
  }
  virtual void SendMessage(std::string msg) {
    std::cout << "vivo send msg: " << msg << std::endl;
  }
};
int main() {
  // 不能創(chuàng)建 Phone 對象,因?yàn)?Phone 是抽象類
  // Phone phone;
  // 不能創(chuàng)建 Oppo 對象,因?yàn)?Oppo 沒有實(shí)現(xiàn) Phone 中的 SendMessage 函數(shù)
  // 所以 Oppo 也是抽象類
  // Oppo oppo;
  std::cout << "sizeof(Phone) = " << sizeof(Phone) << std::endl;
  std::cout << "sizeof(Apple) = " << sizeof(Apple) << std::endl;
  std::cout << "sizeof(Oppo) = " << sizeof(Oppo) << std::endl;
  std::cout << "sizeof(Vivo) = " << sizeof(Vivo) << std::endl;
  Phone *phone;
  Apple apple;
  Vivo vivo;
  phone = &apple;
  phone->Call();
  phone->SendMessage("this is apple");
  phone = &vivo;
  phone->Call();
  phone->SendMessage("this is vivo");
  return 0;
}

運(yùn)行結(jié)果如下:

抽象類中也有虛表,從上邊的打印來看,sizeof(Phone) 計(jì)算出來的結(jié)果是 8。

3.1 為什么引入純虛函數(shù)

一般在定義抽象類的時候,使用純虛函數(shù),抽象類也可以叫做接口類,起到接口約束的作用,但是這個類有沒有創(chuàng)建對象的必要。

(1)包含純虛函數(shù)的類是抽象類,抽象類不能創(chuàng)建對象

這是符合現(xiàn)實(shí)意義的,因?yàn)樵诂F(xiàn)實(shí)中,有一個抽象類是植物,植物可以派生出玫瑰花,梧桐樹等,但是植物本身創(chuàng)建對象是沒有意義的。顯示世界中沒有任何一種生物,它就叫植物,而沒有屬于自己的名字。

(2)抽象類起到接口約束的作用

抽象類的派生類,如果不實(shí)現(xiàn)抽象類中所有的純虛函數(shù),那么派生類也是不能創(chuàng)建對象的,如果創(chuàng)建了對象,那么在編譯的時候會報錯誤,在編譯的時候就可以提醒開發(fā)者不能這么使用。如果使用虛函數(shù),而不使用純虛函數(shù),那么起不到這樣的規(guī)范作用。

3.2 純虛函數(shù)和虛函數(shù)的相同點(diǎn)和不同點(diǎn)

相同點(diǎn):

(1)在函數(shù)聲明時都用 virtual 來修飾

(2)都有虛函數(shù)表,都可以用于實(shí)現(xiàn)多態(tài)

聲明虛函數(shù)的類和聲明純虛函數(shù)的類中都有虛函數(shù)表,抽象類雖然不能創(chuàng)建對象,但是也有虛函數(shù)表。

不同點(diǎn):

(1)聲明方式不同

純虛函數(shù)的聲明方式為  virtual ReturnType FunctionName(Parameter) = 0,純虛函數(shù)不需要實(shí)現(xiàn);虛函數(shù)在基類中的聲明方式為 virtual ReturnType FunctionName(Parameter)  {},虛函數(shù)在基類中必須實(shí)現(xiàn),如果虛函數(shù)沒有實(shí)現(xiàn)會報編譯錯誤。

#include <iostream>
#include <string>
class Shape {
public:
  Shape() {
    std::cout << "Shape()" << std::endl;
  }
  /*
  ~Shape() {
    std::cout << "~Shape()" << std::endl;
  }
*/
  virtual double Area(); /*{
    std::cout << "Shape() Area()" << std::endl;
    return 0;
  }; */
};
class Square : public Shape {
public:
  Square(int length) : length_(length) {
    std::cout << "Square(), length_ " << length_ << std::endl;
  }
  virtual double Area() {
    std::cout << "Sauqre() area " << length_ * length_ << std::endl;
    return length_ * length_;
  }
private:
  int length_;
};
int main() {
  Square s1(10);
  Square s2(20);
  s1.Area();
  s2.Area();
  return 0;
}

(2)純虛函數(shù),在派生類中必須要實(shí)現(xiàn),否則派生類也是抽象類,不能創(chuàng)建對象;虛函數(shù)沒有這個要求。

3.3 abstract class 官方文檔 —— 純虛函數(shù)可以有實(shí)現(xiàn)

Abstract class - cppreference.com

我們上邊說的虛函數(shù)必須有實(shí)現(xiàn),那么純虛函數(shù)是不是就不能有實(shí)現(xiàn) ?其實(shí)不是的,純虛函數(shù)也可以有實(shí)現(xiàn)。

(1)純虛函數(shù)可以有實(shí)現(xiàn)

(2)純虛函數(shù)的實(shí)現(xiàn)不能在類內(nèi)聲明的時候?qū)崿F(xiàn),而是可以在類外實(shí)現(xiàn)

(3)純虛函數(shù)有實(shí)現(xiàn),那么包含純虛函數(shù)的類仍然是抽象類,不能實(shí)例化;繼承抽象類的派生類,如果不實(shí)現(xiàn)基類中的純虛函數(shù),那么派生類也是抽象類,不能實(shí)例化

(4)純虛函數(shù)如果沒有實(shí)現(xiàn),那么這個函數(shù)是不能直接調(diào)用的

(5)純虛函數(shù)如果有實(shí)現(xiàn),那么這個函數(shù)可以通過  類::函數(shù) 這樣的方式調(diào)用,仍然不能直接調(diào)用函數(shù),必須通過類來調(diào)用才可以

#include <iostream>
#include <string>
class Abstract
{
public:
    // 純虛函數(shù),不能在聲明的時候定義
    virtual void f() = 0; /* {
      std::cout << "A::f()\n";
    } */
    virtual void g() {
      // 純虛函數(shù)不能這么調(diào)用, 會拋異常,pure virtual method called
      // f();
      // 如果純虛函數(shù)沒有實(shí)現(xiàn),那么不能這么調(diào)用
      Abstract::f();
      std::cout << "Abstract() g()" << std::endl;
    }   // non-pure virtual
    ~Abstract() {
        std::cout << "~Abstract() ----------------" << std::endl;
        g();           // OK: calls Abstract::g()
        // 純虛函數(shù),即使有實(shí)現(xiàn),也不能這么調(diào)用
        // f();        // undefined behavior
        // 如果純虛函數(shù)沒有實(shí)現(xiàn),則不能這么調(diào)用
        Abstract::f(); // OK: non-virtual call
    }
};
// 純虛函數(shù)不能在聲明的時候定義,但是可以在類的外部定義
// definition of the pure virtual function
void Abstract::f() {
    std::cout << "A::f()\n";
}
struct Concrete : public Abstract
{
    void f() override {
        // 如果純虛函數(shù)沒有實(shí)現(xiàn),那么不能這么調(diào)用
        Abstract::f(); // OK: calls pure virtual function
    }
    void g() override {
      std::cout << "Concrete() g()" << std::endl;
    }
    ~Concrete() {
        std::cout << "~Concrete() ----------------" << std::endl;
        g(); // OK: calls Concrete::g()
        f(); // OK: calls Concrete::f()
    }
};
int main() {
  // 雖然純虛函數(shù)有實(shí)現(xiàn),但是 Abstract 仍然會抽象類,不能創(chuàng)建對象
  // Abstract a;
  Concrete c;
  return 0;
}

到此這篇關(guān)于c++虛函數(shù)及常見問題的文章就介紹到這了,更多相關(guān)c++虛函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

嘉义县| 昆明市| 静海县| 遂昌县| 五峰| 于都县| 张家口市| 克拉玛依市| 松滋市| 收藏| 正镶白旗| 平武县| 林西县| 苍梧县| 错那县| 安新县| 龙海市| 靖江市| 承德县| 海宁市| 福清市| 建德市| 天津市| 墨脱县| 安福县| 牡丹江市| 和田县| 兴隆县| 日土县| 临颍县| 如皋市| 白朗县| 进贤县| 白山市| 岢岚县| 山东| 互助| 阿拉尔市| 鄂尔多斯市| 睢宁县| 峨边|