C++基礎知識
2.1 結構體和類
在C語言中通過struct關鍵字定義結構體,C語言中的結構體可以包含多個類型任意的數據成員,但是不能包含函數成員。在C++中同樣可以通過struct關鍵字定義結構體,與C語言不同的是該結構體支持定義成員函數。
struct point{
int x;
int y;
void output(){
cout<<x<<endl<<y<<endl;
}
};
int main(){
point pt;
pt.x = 0;
pt.y = 0;
pt.output();
return 0;
}
除此之外,C++還提供了class關鍵字用於定義類,其基本語法和定義結構體大抵類似:
class point{
public://默認是private,私有成員無法在類外訪問
int x;
int y;
void output(){
cout<<x<<endl<<y<<endl;
}
};
int main(){
point pt;//實例化point類(創建point類的對象)
pt.x = 0;
pt.y = 0;
pt.output();
return 0;
}
二者的主要區別在於struct定義的類成員默認訪問權限是public,而class定義的類成員默認訪問權限是private。
2.2 構造函數和析構函數
構造函數用於初始化類成員,這種特殊的函數和類名相同,沒有返回值。
class point{
public://默認是private,私有成員無法在類外訪問
int x;
int y;
void point(){//定義構造函數
x = 0;
y = 0;
}
void output(){
cout<<x<<endl<<y<<endl;
}
};
int main(){
point pt;//實例化point類,會自動調用構造函數
pt.output();
return 0;
}
有些情況下,編譯器會爲沒有定義構造函數的類創建默認構造函數。倘若手動創建了構造函數,無論該構造函數什麼樣,編譯器都不會創建默認構造函數。
析構函數用於釋放對象佔據的內存空間,析構函數名字爲在類名前面加上~,析構函數沒有參數也沒有返回值:
class String{
private:
char* p;
public:
String(int n){
p = new char[n];
}
//析構函數,對象消亡時自動調用該函數
~String(){
delete[] p;//構造函數中在堆內存中開闢了一段空間,如果不釋放便會引起內存泄漏
}
}
2.3 函數重載
在C++中允許在同一作用域中出現同名函數,這種行爲即函數重載,函數重載發生在同名函數具有不同數量或者不同類型的參數情形下:
class Point{
private:
int x;
int y;
public:
Point(){
x = 0;
y = 0;
}
//重載構造函數
Point(int x, int y){
this->x = x;
this->y = y;
}
}
僅僅返回值類型不同的兩個函數不能構成函數重載。
2.4 類繼承
類是可以繼承的,通過繼承可以讓子類(派生類)重用基類(父類)的成員變量和函數。
從成員訪問權限的角度來看,共有三種繼承方法,他們分別是公有繼承(public),私有繼承(private),保護繼承(protected),private繼承時父類的全部成員在子類中都變爲private,public繼承時父類的全部成員在子類都維持原有訪問級別,protected繼承時基類的全部成員在子類都變爲protected。值得注意的是,私有成員只能在當前類中訪問,無法被繼承。
#include<iostream>
using namespace std;
class Animal {
private:
const char *name;
public:
Animal(const char *name) {
this->name = name;
cout << "construct Animal" << endl;
}
~Animal() {
cout << "deconstruct Animal" << endl;
}
};
class Dog :public Animal {
public:
Dog(const char *name):Animal("Hello") {//調用基類構造函數,如果基類構造函數無參會自動調用
cout << "construct Dog" << endl;
}
~Dog() {
cout << "destruct Dog" << endl;
}
};
int main() {
Dog dog("dog1");
}
該例子展示了派生類如何調用基類的構造函數,並且說明實例化派生類時,基類構造函數先於派生類構造函數調用。清除派生類對象時,派生類析構函數先於基類析構函數被調用。
2.5 多態性
多態性是一種在運行時決定調用基類方法還是調用重寫的派生類方法的技術,因此多態性的實現總是和成員函數覆蓋關聯在一起,注意函數覆蓋和函數重載是完全不同的兩個概念,重載指的是在相同作用域下定義多個名字相同的不同函數,而覆蓋指得是派生類覆蓋基類中定義的同名函數,要求兩個函數包括函數名和參數列表在內的東西完全一致,且基類函數是虛函數。
如果子類函數和基類函數同名(不考慮參數列表),那麼無論基類函數是否是虛函數,結果都是派生類函數隱藏了基類函數。
如果子類函數和基類函數完全相同,但基類函數非虛函數,這種情形也是隱藏。
隱藏是無法用來實現多態的。
#include<iostream>
using namespace std;
class Animal {
private:
const char *name;
public:
Animal(const char *name);
~Animal();
void talk();
};
Animal::Animal(const char *name) {
this->name = name;
cout << "construct Animal" << endl;
}
Animal::~Animal() {
cout << "deconstruct Animal" << endl;
}
void Animal::talk() {
cout << "an animal is talking..." << endl;
}
class Dog :public Animal {
public:
Dog(const char *name);
~Dog();
void talk();
};
Dog::Dog(const char *name) :Animal("Hello") {
cout << "construct Dog" << endl;
}
Dog::~Dog() {
cout << "destruct Dog" << endl;
}
void Dog::talk() {
cout << "dog is barking" << endl;
}
int main() {
Dog dog("dog1");
Animal *pAnimal;
pAnimal = &dog;
pAnimal->talk();
}
從內存空間上看,派生類是包含着繼承的基類的,因此派生類地址可以不通過強制類型轉換直接賦值給父類指針,轉換以後編譯器會把該指針所指向的對象當成是基類對象(忽略掉派生類附加的內存空間)。因此如果通過基類指針來調用一個非虛函數,編譯期間就會綁定基類函數的地址(early binding),如果調用的是一個虛函數(virtual修飾),編譯器會在運行時根據實際對象的類型來決定調用哪個函數(late binding)。
要實現多態,要做的就是給基類函數修飾上virtual,然後在派生類中重寫該函數。
2.6 引用
引用就是一個變量的別名,引用類型變量一經創建就要初始化,初始化之後就和引用到的變量代表同一塊內存,不能再做修改。
int a = 5;
int &b = a;
//從此之後b就是變量a的別名,訪問b和訪問a是一樣的,他們具有相同的內存地址。
引用通常用於函數形參,通過引用傳參可以實現函數內部直接修改實參的內存,同時還比指針傳參具有更高的可讀性。