天天看點

面向對象(1)

簡述一下什麼是面向對象

1、面向對象是一種程式設計思想,把一切東西看成一個個對象。把這些類擁有的屬性變量和操作這些屬性變量的函數打包成一個類。

2、面向過程和面向對象差別

面向過程:根據業務邏輯從上到下編寫代碼

面向對象:将資料和函數進行封裝,可以快速開發,減少重複代碼的重寫。

簡述一下面向對象的三大特征

封裝、繼承、多态

1、封裝

将資料和操作資料方法進行結合,隐藏對象的屬性和實作細節,僅對外公開接口來和對象進行互動。封裝的本質就是管理。

2、繼承

可以使用現有類的所有功能,并在不需要重寫原理類的情況下進行功能擴充。

繼承方式:

繼承方式 private繼承 protected繼承 public繼承
基類的private成員 不可見 不可見 不可見
基類的protected成員 變為private成員 仍為protected成員 仍為protected成員
基類的public成員 變為private成員 變為protected成員 仍為public成員

3、多态

同一操作作用于不同的對象,可以有不同的解釋,産生不同的執行結果,這就是多态性。就是用基類的引用指向子類的對象。

多态實作:重寫、重載

簡述一下 C++ 的重載和重寫,以及它們的差別

1、重寫

指派生類中存在重新定義的函數。其函數名、參數清單、傳回值類型,所有都必須同基類中被重寫的函數一緻,派生類對象調用時會調用派生類的重寫函數,不會調用被重寫函數。

重寫的基類中被重寫的函數必須有virtual修飾。

示例:

#include<bits/stdc++.h>  using namespace std;  class A { public:  virtual void fun()  {   cout << "A";  } }; class B :public A { public:  virtual void fun()  {   cout << "B";  } }; int main(void) {  A* a = new B();  a->fun();//輸出B,A類中的fun在B類中重寫 }           

2、重載

我們在平時寫代碼中會用到幾個函數但是他們的實作功能相同,但是有些細節卻不同。在 C++中人們提出了用一個函數名定義多個函數,也就是所謂的函數重載。

函數重載是指同一可通路區内被聲明的幾個具有不同參數列(參數的類型,個數,順序不同)的同名函數,根據參數清單确定調用哪個函數,重載不關心函數傳回類型。

#include<bits/stdc++.h>  using namespace std;  class A {  void fun() {};  void fun(int i) {};  void fun(int i, int j) {};     void fun1(int i,int j){}; };           

C++ 的重載和重寫是如何實作的

1、C++利用命名傾軋(name mangling)技術,來改名函數名,區分參數不同的同名函數。命名傾軋是在編譯階段完成的。

2、在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運作時将會根據對象的實際類型來調用相應的函數。

對象類型是派生類,就調用派生類的函數;對象類型是基類,就調用基類的函數。

  • 用virtual關鍵字申明的函數叫做虛函數,虛函數肯定是類的成員函數。
  • 存在虛函數的類都有一個一維的虛函數表叫做虛表,類的對象有一個指向虛表開始的虛指針。虛表是和類對應的,虛表指針是和對象對應的。
  • 多态性是一個接口多種實作,是面向對象的核心,分為類的多态性和函數的多态性。
  • 重寫用虛函數來實作,結合動态綁定。
  • 純虛函數是虛函數再加上 = 0。
  • 抽象類是指包括至少一個純虛函數的類。

    純虛函數:virtual void fun()=0。即抽象類必須在子類實作這個函數,即先有名稱,沒有内容,在派生類實作内容。

C 語言如何實作 C++ 語言中的重載

c語言中不允許有同名函數,因為編譯時函數命名是一樣的,不像c++會添加參數類型和傳回類型作為函數編譯後的名稱,進而實作重載。如果要用c語言顯現函數重載,可通過以下方式來實作:

1、使用函數指針來實作,重載的函數不能使用同名稱,隻是類似的實作了函數重載功能。

2、重載函數使用可變參數,方式如打開檔案open函數。

3、gcc有内置函數,程式使用編譯函數可以實作函數重載

示例:

#include<stdio.h>   void func_int(void * a) {     printf("%d\n",*(int*)a);  //輸出int類型,注意 void * 轉化為int }   void func_double(void * b) {     printf("%.2f\n",*(double*)b); }   typedef void (*ptr)(void *);  //typedef申明一個函數指針   void c_func(ptr p,void *param) {      p(param);                //調用對應函數 }   int main() {     int a = 23;     double b = 23.23;     c_func(func_int,&a);     c_func(func_double,&b);     return 0; }           

構造函數有幾種,分别什麼作用

C++中的構造函數可以分為4類:預設構造函數、初始化構造函數、拷貝構造函數、移動構造函數。

1、預設構造和初始化構造。定義類的對象時完成初始化工作。

示例:

class Student { public:  //預設構造函數  Student()  {     num=1001;        age=18;      }  //初始化構造函數  Student(int n,int a):num(n),age(a){} private:  int num;  int age; }; int main() {  //用預設構造函數初始化對象S1  Student s1;  //用初始化構造函數初始化對象S2  Student s2(1002,18);  return 0; }           

有有參構造,編譯器不再提供預設的構造函數。

2、拷貝構造

#include "stdafx.h" #include "iostream.h"  class Test {     int i;     int *p; public:     Test(int ai,int value)     {         i = ai;         p = new int(value);     }     ~Test()     {         delete p;     }     Test(const Test& t)     {         this->i = t.i;         this->p = new int(*t.p);     } }; //複制構造函數用于複制本類的對象 int main(int argc, char* argv[]) {     Test t1(1,2);     Test t2(t1);//将對象t1複制給t2。注意複制和指派的概念不同     return 0; }           
  • 指派構造函數預設實作的是值拷貝(淺拷貝)。
  • 3、移動構造函數。用于将其他類型的變量,隐式轉換為本類對象。下面的轉換構造函數,将int類型的r轉換為Student類型的對象,對象的age為r,num為1004。

    Student(int r) {  int num=1004;  int age= r; }           

    定義一個空類,預設會生成哪些函數

    無參的構造函數、拷貝構造函數、指派運算符、析構函數(非虛)。

    C++ 類對象的初始化順序,有多重繼承情況下的順序

    1. 建立派生類的對象,基類的構造函數優先被調用(也優先于派生類裡的成員類)
    2. 如果類裡面有成員類,成員類的構造函數優先被調用;(也優先于該類本身的構造函數)
    3. 基類構造函數如果有多個基類,則構造函數的調用順序是某類在類派生表中出現的順序而不是它們在成員初始化表中的順序
    4. 成員類對象構造函數如果有多個成員類對象,則構造函數的調用順序是對象在類中被聲明的順序而不是它們出現在成員初始化表中的順序
    5. 派生類構造函數,作為一般規則派生類構造函數應該不能直接向一個基類資料成員指派而是把值傳遞給适當的基類構造函數,否則兩個類的實作變成緊耦合的(tightly coupled)将更加難于正确地修改或擴充基類的實作。(基類設計者的責任是提供一組适當的基類構造函數)

    總結:

    父類構造函數–>成員類對象構造函數–>自身構造函數

    中成員變量的初始化與聲明順序有關,構造函數的調用順序是類派生清單中的順序

    析構順序和構造順序相反