關于 C++ 面向對象的筆記總結
//類的定義
class <類名>
{
private:
...;
protected:
...;
public:
...;
};
注意:成員變量不能在聲明時初始化(但靜态常成員函數可以);不要與JAVA的混淆
//成員函數的定義
//先搞清楚聲明與定義的差別,聲明與初始化的差別
<傳回類型> <類名>::<成員函數名>([參數表])
{
...;
}
//帶預設值的成員函數
隻有在聲明時才指定預設值
預設參數從右到左逐漸定義
void f(int a1, int a2 =0, int a3 = 9);
//類的内聯成員函數:
隐式implicit: 在類中同時聲明與定義
顯式explicit: inline
類中聲明: inline <傳回類型> <函數名>([參數表]);
類外定義: inline <傳回類型> <類名>::<函數名>([參數表]) //inline可省
{
...
}
//普通的内聯函數:
聲明:inline <傳回類型> <函數名>([參數表]);
定義:inline <傳回類型> <類名>::<函數名>([參數表]) //inline可省
{
...
}
或者聲明定義寫一起
inline <傳回類型> 函數名([參數表]) {...}
//成員函數重載
相同的函數名,不同的參數清單(考慮:參數類型及參數個數)
//用指針或引用通路成員函數(或變量)
<類名> obj;
<類名> *p = &obj; //可以不在聲明時初始化
<類名> &ref = obj; //必須在聲明時初始化(指派)
p->xxx
ref.xxx
//常成員函數:
隻“讀”不可“寫”不能在函數中改變參數的值
類中聲明: <傳回類型> <函數名>([參數表]) const;
類外定義: <傳回類型> <類名>::<函數名>([參數表]) const //const 不可省
{
...
}
//構造函數與析構函數
構造函數:對象生成時調用(如聲明, new 等)
當沒有自己定義構造函數時,編譯器提供一個預設的無參構造函數
這個函數相當于 <類名>(){}
若自己定義了一個構造函數(不管有參無參),則編譯器不再提供預設的無參構造函數
基本資料類型(整型等)的成員變量,不會自動初始化(一個随機的值) //不要與JAVA混淆
析構函數:對象生命周期結束時調用
顯式調用構造函數可以建立一個無名對象,多用于傳參
void fun(Point pt);
fun(Point(x,y));
可以重載多個不同參數的構造函數
可以帶預設參數
用帶預設參數的構造函數重載時,注意避免二義性錯誤如 Point() 與 Point(int x = 0, int y = 0),調用Point()時就不知道調用的是哪一個
注意:以下兩個語句的差別
<類名> obj; //對象聲明
<類名> obj(); //函數聲明
//不同作用域範圍對象的構造與析構順序
局部對象: 按聲明的先後順序(可能出現多次生滅)
局部靜态對象:按聲明的先後順序(隻析構一次)
全局對象: 單檔案(按聲明先後);多檔案(未知)
全局靜态對象:單檔案(按聲明先後);多檔案(未知);隻析構一次
對象成員: 按聲明先後(與冒号文法中初始化表達式的順序無關)
對象生命周期結束時調用析構函數,順序與構造函數的順序相反
//堆對象
可以在用 new 配置設定空間時調用帶參數的構造函數進行初始化 Point* p = new Point(1,2);
delete 時析構
delete p;
delete[] psArray;
先析構對象,再釋放空間
//拷貝構造函數
注意“生成臨時對象”的情況,很多情況下生成臨時對象時會調用拷貝構造函數
1. 在函數參數表中用對象時(非引用)會産生一個臨時對象,并将調用拷貝構造函數
void f(OBJ obj); OBJ obj; f(obj);
2. 在函數傳回對象時,也會産生一個臨時對象,并将調用拷貝構造函數
OBJ f() {OBJ obj; return obj;}
3. 在函數傳回對象引用時,不産生臨時對象,不調用拷貝構造函數
OBJ& f(){OBJ obj; return obj;}
//注意一般不能傳回局部變量的引用,這裡隻作示範,說明傳回引用時不産生臨時對象
4. 在函數傳回臨時無名對象時,不再産生臨時對象,不調用拷貝構造函數
OBJ f(){return OBJ();}
差別:淺拷貝 (隻拷貝棧記憶體,不配置設定堆記憶體) 與深拷貝(一般由自己定義)
編譯器提供一個預設的拷貝構造函數,它以淺拷貝的方式實作,若有動态成員(指針),指派後在析構時會出現問題(同一塊記憶體被釋放了兩次)
class A
{
private:
int a;
public:
A(){a = 0; cout << "construct with no param\n";}
A(int x){a = x; cout << "construct with " << x << "\n";}
A(const A& obj)
{
this->a = obj.a;
cout << "copy construct with " << a << endl;
}
}
A f() {return A();}
A a1; // construct with no param
A a2 = a1; // copy construct with 0
A a3; // construct with no param
a3 = a1; //沒有輸出
A a4; // construct with no param
a4 = f(); // 在f 中輸出construct with no param;= 沒有輸出
A a5 = f(); // 在f 中輸出construct with no param;= 沒有輸出
聲明時
預設的指派操作為淺拷貝
自己定義一個拷貝構造函數
Point::Point(const Point& point);
//無名對象的使用
1. 作為實參傳遞給函數
void fun(OBJ obj); fun(OBJ());
2. 用來構造新對象
Point p = Point(1,2); <=> Point p(1,2);隻調用一次構造函數,不調用拷貝構造函數
3. 初始化一個引用的聲明 Point &ref = Point(3,4);
無名對象将在生産的某個時刻調用構造函數
//構造函數用于類型轉換
class LeiXingZhuanHuan
{
public:
LeiXingZhuanHuan(char * s) {
cout << "construct" << endl;
}
LeiXingZhuanHuan(const LeiXingZhuanHuan& x) {
cout << "copy construct" << endl;
}
};
void f3(const LeiXingZhuanHuan& x){}
void f4(LeiXingZhuanHuan x){}
LeiXingZhuanHuan lx1 = LeiXingZhuanHuan("001"); //先調用構造,再調用拷貝構造
LeiXingZhuanHuan lx2 = "001";//直接調用構造,這裡隐式調用了構造函數,進行類型轉換;隐式調用構造函數用于類于轉換時1.隻會嘗試含有一個參數的構造函數;2.不能存在二義性
LeiXingZhuanHuan lx3("001"); //調用構造
LeiXingZhuanHuan& lxref1 = lx1; //不調用構造函數
f3(LeiXingZhuanHuan("001")); //調用構造函數
f4("004"); //隐式調用了構造函數,相當于實作了類型轉換
//作用域
了解函數原型作用域,塊作用域,類作用域,檔案作用域
//靜态成員
通過 <類名>::XXX 或 <對象>.XXX通路 (當然得符合通路權限public)
靜态資料成員的初始化:
在類中聲明: static int n;
在類外初始化: int A::n = 100; 可以為公有或私有,但必須在全局範圍内初始化(即不能在某個函數中),可以在普通成員函數中通路靜态成員(讀或寫,但一般不在構造函數中初始化,因為它是各個類共享的)
class CHasStaticMember
{
static const int c0 = 0; //靜态常量成員可以在類中聲明時初始化,其他都不行
static const int c1
static int x;
};
//在類外初始化時不加static
const int CHasStaticMember:: c1 = 1;
int CHasStaticMember::x = 0;
靜态成員函數隻能通路靜态成員變量
//友元
1.友元函數:
友元函數是可以直接通路類的私有成員的非成員函數。它是定義在類外的普通函數,它不屬于任何類,但需要在類的定義中加以聲明,這樣就可以通路該類的私有成員了
類中聲明:friend ... ,不管聲明在哪個通路權限下都可以(public,protected,private)
類外定義:同普通函數(其實就是一普通函數,隻是多個一項權利)
class B
{
public:
friend void f(const B& obj);
B():x(0){}
B(int val) : x(val){}
protected:
private:
int x;
};
void f(const B& obj)
{
cout << obj.x << endl;
}
2.友元成員函數(讓類B的某個成員函數可以通路類A的私有成員)
class A;
//這個B必須定義在A的前面,因為A中有用的B::f()的原型,但前面要聲明一下A
class B
{
public:
void f(const A& obj);
};
class A
{
private:
int a;
public:
friend void B::f(const A& obj);
};
void B::f(const A& obj)
{
cout << obj.a << endl;
}
3.友元類
讓類B成為A的友元類
class B;
class A
{
private:
int a;
friend class B;
};
class B
{
public:
void f(const A& obj);
};
void B::f(const A& obj)
{
cout << obj.a << endl;
}
//操作符重載
1.重載為友元函數:
class A;
A operator + (const A& obj1, const A& obj2);
class A
{
public:
A(){a = 0;}
A(int x){a = x;}
void Display()
{
cout << a << endl;
}
friend A operator + (const A& obj1, const A& obj2);
private:
int a;
};
A operator + (const A& obj1, const A& obj2)
{
return A(obj1.a + obj2.a);
}
2.重載為成員函數:
class A
{
public:
A(){a = 0;}
A(int x){a = x;}
void Display()
{
cout << a << endl;
}
A operator + (const A& obj)
private:
int a;
};
A A::operator + (const A& obj)
{
return A(obj.a + a);
}
3.特殊運作符的重載
//前置
A& operator++ ()
{
++a;
return *this;
}
//後置也可以寫成 A operator++(int) ,這樣 obj1++ = obj2 不會報錯,但已經引用不到這個臨時變量了
const A operator++(int) // obj++ = obj2 會報錯,因為常量不能做左值
{
A t(*this);
a++;
return t;
}
寫成友元時
類中:
friend A& operator++ (A& obj);
friend const A operator++ (A& obj, int);
類外:
A& operator++ (A& obj)
{
obj.a++;
return obj;
}
const A operator++(A& obj,int)
{
A t(obj);
obj.a++;
return t;
}
輸出輸入
ostream& operator << (ostream& o, 類名對象名)
{
o << 對象.成員;
return o;
}
istream& operator >> (ostream& i, 類名& 對象名)
{
i >> 對象.成員;
return i;
}
其他操作符還有 +=,-=,*=,/=,[], , , =, ->,()等
= :聯系一下“深拷貝”與“淺拷貝”
[]: 傳回類型& operator [] (int i); 當左值時要傳回引用
->,[],(),= 隻能重載為成員運算符
類型轉換運算符
class Length
{
int meter;
public:
Length(int m) {meter = m;}
operator float()
{
return (1.0 * meter / 1000);
}
};
//"組合",一個類中的某個成員變量為某個類的對象,把該對象叫做 "子對象"
子對象的有參構造要使用冒号文法 <對象名>(參數),預設調用無參構造
有多個子對象時,按聲明的先後順序構造,與冒号語句的順序無關
//繼承
派生類的聲明 class <派生類名> : <繼承方式> <基類1>[, <繼承方式> <基類2>,...]
繼承方式: public protected private
public public protected 隔離
protected protected protected 隔離
private private protected 隔離
隔離的對象,可以通過基類中繼承下來的可見的成員函數通路
繼承分為單繼承與多繼承
在繼承與派生過程中
1. 吸收基類成員(不包括成員函數和析構函數)
2. 改造
a. 繼承方式
b. 同名覆寫(注意:函數參數不同時為重載)
3. 添加新成員
//派生類構造與析構
派生類構造函數的調用順序
1.基類 2.子對象(對象成員) 3.派生類
析構函數的調用順序相反
如果子類的構造函數不顯式調用基類的構造函數的話,會自動調用基類的無參構造函數
如果子類的構造函數顯式調用了基類的一個有參構造函數,則不再調用基類的無參構造函數
顯式調用基類的一個有參構造函數隻能使用冒号文法,用類名
成員對象的構造也要使用冒号文法,但兩者有差別,用變量名
//冒号文法
組合與繼承的冒号文法的差別:
給合:使用對象名(變量名)
繼承:使用類名
另外,常量和引用的初始化要用冒号文法,且隻能使用冒号文法
也就是說使用冒号文法的時機有:
調用子類的有參構造函數,子對象,常量,引用的初始化
//一個單繼承的例子
class Point
{
public:
Point(int x = 0, int y = 0)
{
this->x = x;
this->y = y;
}
int GetX()
{
return x;
}
int GetY()
{
return y;
}
void SetX(int x = 0)
{
this->x = x;
}
void SetY(int y = 0)
{
this->y = y;
}
void Display()
{
cout << "(" << x << ", " << y << ")";
}
protected:
private:
int x, y;
};
class Circle : public Point
{
public:
//派生類的構造函數:注意調用基類構造函數與構造子對象的不同之處
Circle(int x = 0, int y = 0, int r = 0) : Point(x,y), r(r){}
int GetR()
{
return r;
}
void SetR(int r)
{
this->r = r;
}
//覆寫基類的Display();
void Display()
{
//調用基類的Display();
Point::Display(); cout << " r = " << r;
}
void Test()
{
//通過類型的函數通路從基類中繼承下來的但被隔離的對象
cout << GetX() << ", " << GetY() << ", " << r << endl;
}
private:
int r;
};
//一個多繼承的例子
class B1
{
public:
int b, b1;
B1(int b = 0, int b1 = 1) : b(b), b1(b1){}
void f()
{
cout << "f() in B1" << endl;
}
void f1()
{
cout << "f1() in B1" << endl;
}
};
class B2
{
public:
int b, b2;
B2(int b = 0, int b2 = 2) : b(b), b2(b2){}
void f()
{
cout << "f() in B2" << endl;
}
void f2()
{
cout << "f2() in B2" << endl;
}
};
class D : public B1, public B2
{
public:
int d;
Point p;
//從子類中繼承下來的成員不能直接使用冒号文法,而是使用基類類名
D(int b = 0, int b1 = 1, int b2 = 2, int d = 3) : B1(b, b1), B2(b * b, b2), d(d), p(1,2)
{
}
void f()
{
cout << "f() in D" << endl;
//調用子類中的方法
B1::f();
B2::f();
}
void Test()
{
cout << B1::b << endl;
cout << B2::b << endl;
cout << B1::b1 << endl;
cout << B2::b2 << endl;
cout << d << endl;
}
};
D d(999,1,2,3);
d.Test();
d.B1::f(); //通路基類的成員函數
d.B1::f1();
d.B2::f();
d.B2::f2();
//派生類成員的辨別與通路
基類名::成員名
基類名::成員函數([參數清單])
同名覆寫時,将覆寫所有基類的同名成員
若沒有覆寫,多基類的同名成員也要通過::通路才能區分(否則會有二義性)
//虛基類:
B0
B1 B2
D1
使得繼承時隻儲存一份拷貝
"D1 通過 B1 繼承自 B0" 和 "D1 通過 B2 繼承自 B0" 的東西隻儲存一份拷貝
如果派生類中重新義同名函數(非重載),同樣可以同名覆寫
class B0{};
class B1 : virtual public B0 {};
class B1 : virtual public B0 {};
class D1 : public B1, public B2 {};
執行個體:
class B0
{
public:
B0(int x0 = 0) : v0(x0) { cout << "construct B0 with " << x0 << endl; }
~B0(){cout << "~B0\n";}
int v0;
};
class B1 : public B0
{
public:
B1(int x0 = 0, int x1 = 1) : B0(x0),v1(x1) { cout << "construct B1 with " << x0 <<", " << x1 << endl; }
~B1(){cout << "~B1\n";}
int v1;
};
class B2 : public B0
{
public:
B2(int x0 = 0, int x2 = 2) : B0(x0),v2(x2) { cout << "construct B2 with " << x0 <<", " << x2 << endl; }
~B2(){cout << "~B2\n";}
int v2;
};
class D : public B1, public B2
{
public:
D(int x0 = 0, int x1 = 1, int x2 = 2) : B1(x0, x1), B2(x0, x2) { cout << "construct D with " << x0 <<", " << x1 <<", " << x2 << endl; }
~D(){cout << "~D\n";}
};
int main()
{
D d;
}
輸出結果為:
construct B0 with 0
construct B1 with 0, 1
construct B0 with 0
construct B2 with 0, 2
construct D with 0, 1, 2
~D
~B2
~B0
~B1
~B0
當
class B0{};
class B1 : virtual public B0 {};
class B1 : virtual public B0 {};
class D1 : public B1, public B2 {};
時
輸出結果為:
construct B0 with 0
construct B1 with 0, 1
construct B2 with 0, 2
construct D with 0, 1, 2
~D
~B2
~B1
~B0
//虛函數: 用于多态
聲明: 類中: virtual 傳回類型函數名([參數表]);
定義: 類外: 傳回類型類名::函數名([參數表]){...}
注意: 隻有在聲明時才加 virtual
當基類用virtual聲明成員函數為虛函數時,
其派生類的同名函數(重載函數除外)自動成為虛函數(前面可加virtual也可不加)
若在派生類中不重新定義,則直接簡單繼承基類的虛函數
若在派生類中重新定義, 則通過基類的指針通路可以表現為多态
基類指針隻能通路基類的函數或虛函數(其實也是在基類中聲明的函數,隻是可以表現多态性)
基類的指針,引用常用作函數形參實作多态
Void fun(B0* p);
Void fun(B0& p);
不用virtual 時,隻調用基類的函數
工作原理:每個對象多了一個指針空間,指向類中的虛函數表
//虛析構函數:
聲明:virtual ~類名();
定義:...
其派生類的析構函數自動變為虛析構函數
同樣用基類的指針處理時,可以從子類開始析構,而不是調用基類的析構
最好将基類的析構函數聲明為虛析構函數
//幾個不同的"虛"
虛基類:使多個來自同一虛基類的相同的東西隻拷貝一份,其他同一般的繼承,可以進行同名覆寫(覆寫之後若要調用父類的函數父類::函數名)
虛函數:既不覆寫,也不是重載,而是一個函數在不同類中表現出多态;一般通過父類的指針來表現
純虛函數:基類不需要此功能,隻為派生類服務
聲明: virtual 傳回類型函數名(參數表) = 0;
抽象類:包含有純虛函數的類或顯式的用abstract标記的類
class 類名
{
public:
virtual 傳回類型函數名(參數表) = 0;
};
class 類名 abstract { ... };
若抽象類的派生類沒有給出所有純虛函數的實作,則該派生類仍為抽象類
一個虛基類和虛函數同時使用的例子:
B0
B1 B2
B3
B0* p;
若把 p 指向四個不同類的對象時,對B0,B1,B2,B3都能表現出多态,
則必須同時使用虛基類和虛函數
否則無法通過 p 調用 B3 的相應函數(編譯無法通過)
//模闆
函數模闆
template <class T> //可以有多個
傳回類型函數名(參數表)
{
...
}
類模闆
template <class T> //可以有多個
class <類名>
{
...
};
聲明對象 類名<類型> 對象名(參數);
類模闆的對象引用,可作函數參數
類模闆可以作基類
//異常處理
try
{
throw
}
catch(...)
{
}
//幾個"常"
常引用 const int& x; //多用于函數形參
常量 const int PI = 3.14; // <=> int const PI = 3.14;
常函數 void print(int x) const; //隻"讀"不可"寫" ,在函數中不能對x值進行改變
常資料成員
靜态:
在類外初始化
class A{static const int x;}; const int A::x = 100; //注意,這裡不能加static ,非常量的靜态成員初始化時也一樣
在類内聲明時初始化 class A { static const int x = 100; } // 隻有靜态常量資料成員才可以在類中聲明時初始化
非靜态: 在構造時用冒号文法,使用初始化清單初始化
常成員函數
Print(x) const; //隻“讀”不“寫”
//幾個不同指針的差別
指針 | 指向的位置 | 所指向的位置是否可變 | 通過該指針是否可以改變所指向位置的内容 |
int* | &int | 是 | 是 |
int* const | &int | 否 | 是 |
const int* | &(const int) | 是 | 否 |
const int* const | &(const int) | 否 | 否 |