注意:這是 bilibili 上侯捷老師的 C++ 基礎 上 部分,自己做的筆記,以供複習。
C++ 學習筆記
01 基礎知識點
1.1 學習目地
-
培養正規的、大氣的編程習慣
-
以良好的方式編寫 C++ class Object Based (基於對象)
- 沒有指針的類
- 帶指針的類
-
學習 class 之間的關係 Object Oriented (面向對象)
- 繼承 inheritance
- 複合 composition
- 委託 delegation
面向對象是一種觀念
Object Based: 面對的是單一 class 設計
Object Oriented: 面對的是多重 class 的設計,存在 class 和 class 之間的關係
1.2 c++ 基礎說明
- C
- Data - Functions
- C++
- class, struct
- Data Members - Member Function
- class 兩大分類
- 不帶指針
- 帶指針
- 一些基本書籍
- C++ Primer
- The C++ Programming Language
- Effective C++ (55 個有效做法)
- The C++ Standard Library
- STL 源碼剖析 候捷
02 構造一個 complex 類
2.1 頭文件與類的聲明
2.1.1 C++ programs 代碼的基本形式
- .h (header files)
- .cpp
- .h (Standard Library)
2.1.2 如何編寫一個頭文件
必須按照下面格式聲明 guard (防禦式聲明)
// complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif
2.1.3 Header (頭文件) 的佈局
- forward declarations 前置聲明
- class declarations 類-聲明
- class definition 類-定義
// complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
// forward declarations(前置聲明)
class ostream;
class complex;
complex&
__doapl(complex* ths, const complex& r);
// class declarations(類 - 聲明)
class complex
{
...
}
// class definition(類 - 定義)
// 有沒有 complex 就知道是 成員函數 還是 全域 函數
complex::fuction ...
#endif
2.1.4 clsee 類
- class head
- class body
class complex // class head
{
public: // class body
... // 有些函數在 body 內直接定義
// 有些函數在 body 外定義
private:
...
};
2.1.5 class template(模板)簡介
模板: 類型不確定,使用的時候才確定
template<typename T> // 模板
class complex
{
public:
...
private:
T re, im; // 模板
...
};
2.2 如何編寫構造函數
2.2.1 inline(內聯)函數
若類中的函數在 class 中的 boday 中定義,則函數爲 inline
inline 定義的函數具體的實現還要看編譯器能否實現 inline
2.2.2 訪問級別
- public: 外部可以使用
- 函數
- private: 外部不可以使用
- 數據
- 函數
- protected:
在類的定義中,public 同 private 隨處都可以,不一定非要是兩段
2.2.3 創建一個類的三種方式
{
complex c1(2, 1); // 棧中分配
complex c2 = complex(2, 2); // 棧中分配
complex* pc3 = new complex(4); // 堆中分配
delete pc3;
...
}
2.2.4 constructor(ctor, 構造函數)
- 函數名稱和類的名稱相同
- 函數可以有參數
- default argument 默認實參
- 沒有返回值類型 和 返回
complex (double r = 0, double i = 0)
:re (r), im (i) // initialization list (初始列,初始值) 只構造函數纔有這種語法
{
NULL;
}
初始化和賦值的區別:initiallzation and assignments
一個數據的數值設定有兩個階段,一個是初始化,一個是賦值階段,如上所示代碼,雖然同在大括號內的賦值結果是一樣的,但是效率不一樣,已經慢了。
2.2.5 ctor(構造函數)可以有很多個 - overloading(重載)
// 取值
double real()
const {
return re;
}
// 賦值
void real(double r)
{
re = r;
}
函數重載: 所謂的函數重載,就是同名函數,但是類型卻不相同,整體上說,即使有多個同名的函數,但是當調用函數的輸入唯一時,也可以確定唯一的使用哪個函數。
對於編譯器而言,沒有相同的兩個函數
2.2.6 ctors 放在 private 中
A::getInstance().setup(); // 使用
// Singleton 設計模式
class A
{
public:
static A& getInstance();
void setup(void)
{
std::cout << "生成類成功" << std::endl;
}
private:
A(void)
{
std::cout << "構造 A 成功" << std::endl;
}
// A(const A& rhs);
};
A& A::getInstance()
{
static A a;
n++;
std::cout << "第 " << n << " 次調用 getInstance" << std::endl;
return a;
}
{
A::getInstance().setup(); // 使用
}
2.2.7 const member functions(常量成員函數)
函數的後面是否需要加 const
不改變類中的數據 就應該加
否則就不加
當該加沒加時,定義爲 const 的類變量 調用該函數會報錯
// 加 和 沒加 const
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
// 加沒加 const
double real()
const
{
return re;
}
double real()
{
return re;
}
private:
double re, im;
}
// 使用 加不加 const 都可以
{
complex c1(2, 1);
count << c1.real();
}
// 使用 不加 const 會出錯,函數必須加 const 纔可以
{
complex c1(2, 1);
count << c1.real();
}
2.2.8 參數傳遞: pass by value vs. pass by reference(to const)
// pass by value
complex(double r, double i);
// pass by reference
complex(double& r, double& i);
// pass by reference(to const)
complex(const double& r, const double& i);
儘量所有的參數傳遞傳引用
根據是否改變該參數,決定加不加 const
2.2.9 返回值傳遞: pass by value vs. pass by reference(to const)
返回值的傳遞儘量傳 reference
儘量是在可以的情況下
return by refence 語法分析
傳遞者 無需知道 接收者 是以 refence 形式接收
好處是,返回值設置爲 refence
注意
- 返回值的問題 reference
- 當連續使用 操作符 的時候,返回值該如何設計
2.2.10 friend(友元)
可以直接拿類的私有數據
class complex
{
public:
complex { double r = 0, double i = 0 }
: re (r), im (i)
{ }
complex& operator += (const complex)
{
return __doapl(this, r);
}
double real() const
{
return re;
}
double imag() const
{
return im;
}
private:
double re, im;
friend complex& __doapl(complex*, const complex&);
};
inline complex& __doapl(complex* ths, const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
{
complex c1(2, 3);
complex c2(3, 4);
c1 += c2;
}
2.2.11 相同 class 的各個 object 互爲 friends(友元)
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
int func(const complex& param)
{
return param.re + param.im;
}
private:
double re, im;
};
{
complex cl(2, 1);
complex c2;
c2.func(cl);
}
2.2.11 class body 外的各種定義 (definitions)
編寫一個類
- 數據一定放在 private
- 參數以 reference 來傳 要不要加 const,看狀況
- 返回值也儘量以 reference 來傳
- 在類的本體的函數,該加 const 就要加,不加有可能報錯
- 構造函數特殊賦初值語法
2.3 操作符重載
2.3.1 operator overloading(操作符重載 - 1,成員函數)this
每個成員函數都有 this 不需要定義,可以直接用
// do assignment plus
inline complex&
__doapl(complex* ths, const complex& r)
{
ths->re + r.re;
ths->im + r.im;
return *ths;
}
inline complex
complex::operator += (const complex& r)
{
return __doapl(this, r);
}
2.3.2 operator overloading(操作符重載 - 2,非成員函數)(無 this)
什麼時候需要
其實其相應的功能也可以通過成員函數實現,但是隻能二存一
有的時候可能有問題,比如下面的 7 + c1,將函數編寫爲成員函數,7 將無法調用,會有問題
{
complex c1(2, 1);
complex c2;
c2 = c1 + c2;
c2 = c1 + 5;
c2 = 7 + c1;
}
符號重載注意
- 要根據情況將符號重載爲 成員函數 或者 類全局函數
- 如果操作符 第一個 參數的對象不是該類型的對象,則該函數一定要重載爲 類全局函數
- 具體的需要後面深入學習
2.3.3 temp object(臨時對象)typename();
下面這些函數返回值不可以是 return by reference
下面這三個函數 return 後面的內容時一種特殊的語法,創建了一個臨時對象,但是沒有名字,因爲是直接返回的。
注意臨時對象只能返回 vaule 不能返回 reference
// 空格問題
inline complex
operator + (const complex& x, const complex& y)
{
return complex (real(x) + real(y),
imag(x) + imag(y));
}
inline complex
operator + (const complex& x, double y)
{
return complex (real(x) + y, imag(x));
}
inline complex
operator + (double x, const complex& y)
{
return complex (x + real(y), imag(y));
}
2.4 構造 complex 小結
- 頭文件的格式
- 防禦式聲明
- 構造函數
- 初始化值
- 加不加 const
- 參數的傳遞儘量考慮 reference
- return 的時候可以的話 reference
- 數據放在 private
- 函數主要放在 public
- 局部函數
- 全局函數
- 各種細節語法
03 構造一個 string 類
class with pointer member 必須要有 copy ctor 和 copy cp=
三大函數
- 析構函數
- 拷貝構造函數
- 拷貝賦值函數
3.1 析構函數
// 析構函數
inline String::~String()
{
delete[] m_data;
std::cout << "m_data 已經釋放" << std::endl;
}
3.2 copy ctor 拷貝構造
// 拷貝構造函數
inline String::String(const String& str)
{
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
3.3 copy op= 拷貝賦值
// 拷貝賦值
inline String& String::operator = (const String& str)
{
if (this == &str)
{
return *this;
}
delete[] m_data;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
return *this;
}
04 內存管理
4.1 new
new: 先分配 memory,再調用 ctor
Complex* pc = new Complex(1, 2);
// 編譯器轉化
1 void* mem = operator new(sizeof(Complex)); // 分配內存
// 其內部調用 malloc(n)
2 pc = static_cast<Complex*>(mem); // 轉型
3 pc->Complex::Complex(1, 2); // 構造函數
Complex::Complex(pc, 1, 2);
this
4.2 delete
delete: 先調用 dtor,再釋放 memory
String* ps = new String("Hello");
...
delete ps;
// 編譯器轉化
String::~String(ps); // 析構函數
operator delete(ps); // 釋放內存
// 其內部調用 free(ps)
4.3 動態分配所得的內存塊(memeory block),in VC
- 調試模式下會有 36 個字節用於調試,數據區上面有 32 個字節,下面有 4 個字節
- 整個內存區域開始和結束各有 4 個字節的標識
- 整體內存大小爲 16 的倍數
- 非調試模式下,沒有那 36 個字節
- 最開始和最後的數字 是整個內存的大小
- 64 對應 41h 1 表示內存分配出去了
- 數組相對於單個對象只是多了對象的內存 和 四個字節的存放數組的大小
4.4 array new 一定要搭配 array delete
String* p = new String[3];
...
delete[] p; // 喚起 3 次 dtor
String* p = new String[3];
...
delete p; // 喚起 1 次 dtor
對於沒有 指針 的 new[ ] 使用 delete 也不會造成泄露
但是養成匹配使用的好習慣
05 補充
5.1 static
- static 必須在類外面定義
- static 函數沒有 this,只能處理靜態數據
- 調用靜態函數的方式,通過 object 和 通過 class name 兩種方式調用
class Account
{
public:
static void set_rate(const double& x)
{ m_rate = x;}
void print(void)
{
std::cout << "m_rate = " << m_rate << std::endl;
}
private:
static double m_rate;
};
// 沒有下面這行會報錯,這行是定義
double Account::m_rate;
// double Account::m_rate = 3.0;
5.2 class template 類模板
class complex
{
public:
complex(T r = 0, T i = 0)
: re(r), im(i)
{ }
T real() const {
return re;
}
T imag() const {
return im;
}
private:
T re, im;
};
{
complex<double> c1(2.5, 1.5);
complex<int> c2(3, 4);
// test 類模板的使用
}
5.3 函數模板
class complex
{
public:
complex(double r = 0, double i = 0)
: re(r), im(i)
{ }
double real() const {
return re;
}
double imag() const {
return im;
}
private:
double re, im;
};
bool operator < (const complex& a, const complex& b)
{
return a.real() < b.real();
}
template <class T>
inline
const T& min(const T& a, const T& b)
{
return b < a ? b : a;
}
{
complex c1(2.5, 1.5);
complex c2(3.2, 4.2);
complex a = min(c1, c2);
}
// test 函數模板的使用
5.4 namespace
// 使用格式
namespace std
{
...
}
// 三種使用方式
// 1
using namespace std;
cin << ..;
cout << ..;
// 2
using std::cin;
std::cout ...;
cin ..;
// 3
std::cout ...;
std::cin ...;
5.5 更多深入細節
- operator type() const;
- explicit complex(…) : initialization list { }
- opinter-like object
- function-like object
- Namespace
- template specializaton
- Standard Library
- variadic template (since C++11)
- move ctor (since C++11)
- Rvalue reterence (since C++11)
- auto (since C++11)
- lambda (since C++11)
- rang-base for loop (since C++11)
- unordered containers (since C++11)
- …
06 Object Oriented Programming, Object Oriented Design OOP, OOD
- Inheritance(繼承)
- Compositon(複合)
- Delegation(委託)
6.1 Compositon(複合),表示 is-a
- Adapte 設計模式,適配模式,可以通過複合實現
- 包含一個對象 真正意義的包含
- 構造由內而外
- 析構由外而內
- 生命一致
6.2 Delegation(委託)Composition by reference
- Handle / Body (pImpl)
- 包含一個對象的指針 形式上的包含
- 生命不一致
6.3 Inheritance(繼承)表示 is-a
- 構造由內而外
- 析構由外而內
- 如果類可能被繼承,將析構函數設置爲 virtual
6.4 Inheritance with virtual functions
- non-virtual 函數:你不希望 derived class 重新定義(override 覆寫)它。
- virtual 函數:你希望 derived class 重新定義(override, 覆寫)它,它已有默認定義。
- pure virtual 函數:你希望 derived class 一定要重新定義(override,覆寫)它,你對它沒有定義。
- Inheritance with virtual 實現 Template Method 模式