重載和重寫的區別
重載是overload,覆蓋是override
重載屬于編譯時多態,覆蓋屬于運行時多態
運行時多態和編譯時多態
運行時多態指的是在運行的時候才知道要調用哪一個函數,編譯時多態是指在編譯的時候就知道調用哪一個函數。
運行時多態
可以使得父類指針調用子類函數,當然子類指針也可以調用父類函數
#include <iostream>class Base {
public:virtual void show() { std::cout << "Base class" << std::endl; }
};class Derived : public Base {
public:void show() override { std::cout << "Derived class" << std::endl; }
};int main() {Base* ptr;Derived obj;ptr = &obj;ptr->show(); // 調用 Derived::show(),發生運行時多態
}
by the way,如果代碼不慎寫為了這樣,編譯器也不會報錯,而是友善提示:
函數 ‘show’ 從類 ‘Base’ 中隱藏了一個非虛擬函數
#include <iostream>class Base {
public:void show() { std::cout << "Base class" << std::endl; }
};class Derived : public Base {
public:void show() { std::cout << "Derived class" << std::endl; }
};int main() {Base* ptr;Derived obj;ptr = &obj;ptr->show(); // 調用 Derived::show(),發生運行時多態
}
因為在C++ 規定,如果子類定義了與基類同名的函數,則基類中的所有同名函數都會被隱藏(即使參數列表不同)。
那如果有一個需求,首先滿足Derived類繼承了Base,同時有自己的show函數(參數列表和Base不一樣,因此單純的override是不行的),可以在Derived類里添加一句using Base::show;
即可。
class Base {
public:virtual void show() { std::cout << "Base class" << std::endl; }
};class Derived : public Base {
public:using Base::show;void show(int a) { std::cout << "Derived class" <<a<< std::endl; }
};int main() {Derived* ptr; //注意這里變為了Derived*Derived obj;ptr = &obj;ptr->show();ptr->show(1);
}
如果有對多態學術不精,只記得在虛函數的加持下可以使得父類指針訪問子類函數,而將上述代碼寫為了
Base* ptr; Derived obj;ptr = &obj;ptr->show();ptr->show(1);
代碼是不會通過檢查的,因為父類指針可以調用子類的函數,但前提是 這個函數必須在父類中聲明為 virtual,這樣才能實現運行時多態(動態綁定)。
雖然我們在父類里有show()這個函數,但show(int) 不是 Base 類的虛函數,所以 Base* 看不到 Derived 里的 show(int),導致編譯錯誤。
編譯時多態
特點:
- 同一作用域內,多個函數同名但參數列表不同(參數個數或類型不同)。
- 在編譯時根據函數調用的參數選擇具體的函數(編譯器做“名字修飾(Name Mangling)”處理)。
- 不會引發運行時開銷,函數的匹配完全在編譯階段完成。
繼承
公有繼承
class Base {
public:int a;
protected:int b;private:int c;
};class Derived : public Base {void print(){cout<<b;}
};int main() {Derived* ptr;Derived obj;ptr = &obj;cout << ptr->a;
}
基類的 public 變成 public,protected 變成 protected,private 仍然是 private。
保護繼承
基類的 public 和 protected 變成 protected,private 不可訪問。
私有繼承
private(私有繼承):基類的 public 和 protected 變成 private,private 不可訪問。
多繼承下的菱形繼承
sizeof
虛函數的size
輸出的結果為:
這是因為虛函數引入了虛表,因此需要額外存儲一個虛表指針,64位系統下size = 8B。
如果再加上一個int,則為16(需要做到對齊,因此還補了4B)
多繼承的情況下:
輸出結果為16,因為繼承了A和B,因為有兩個虛指針
普通函數的size
普通函數size = 1
struct的size
struct也要遵循內存對齊,對齊原則是結構體或類的整體大小必須是其最大對齊數的整數倍(最大成員的對齊值)
因此比如
因為char[]里有一個’\0’,因此size是6
指針與引用
先來個很經典的題
void GetMemory1(char* p){p = (char*)malloc(100);
}
void Test1(void){char* str =NULL;GetMemory1(str);strcpy(str,"hello");printf(str);
}int main() {Test1();
}
這樣會導致程序直接崩潰,原因是GetMemory1傳入的函數是指針。注意,這里進行的是值傳遞,也就是說,我們傳入的是str的復制值,因此在GetMemory1里修改p對外面的str沒有一點用處。
那么如何修改呢?只需要將GetMemory的參數改為指針的引用 or 指針的指針即可
指針的引用版:
void GetMemory1(char* p){p = (char*)malloc(100);
}
void Test1(void){char* str =NULL;GetMemory1(str);strcpy(str,"hello");printf(str);
}int main() {Test1();
}
指針的指針版:
void GetMemory1(char** p){*p = (char*)malloc(100);
}
void Test1(void){char* str =NULL;GetMemory1(&str);strcpy(str,"hello");printf(str);
}
再來個題目
char* GetMemory2(){char p[] = "hello";return p;
}
void Test2(){char* str = NULL;str = GetMemory2();printf(str);
}
這會導致系統崩潰,因為
char p[] = “hello”; 是一個局部數組,存儲在棧上。
當 GetMemory2() 結束后,p 變量的生命周期結束,它所在的棧內存可能被覆蓋或釋放。
str = GetMemory2(); 讓 str 指向了這塊無效的內存。
printf(str); 試圖訪問這塊無效的內存,導致未定義行為(Undefined Behavior),可能程序崩潰。
但如果我們修改為,此時內存被分配到堆上,就不會報錯了。注意還需要對應的free
char* p = (char*)malloc(100);