C++ 基礎篇
演算法
演算法描述
- 虛擬碼:自然語言 + 程式語言 + 數學語言(只要能描述清除自己的演算法思維即可)
- 流程圖
演算法設計與實現
構造演算法解決問題
按照自頂向下、逐步求精的方式進行
程式語言實現
遞迴演算法
- 理論上,任何遞迴都可以使用迴圈方法解決
- 函式呼叫棧框架
漢諾塔問題
問題分析
- 是否存在某種簡單情形,問題很容易解決
- 只有一個圓盤時最簡單
- 是否可將問題分解成性質相同單規模較小的問題,且新問題的解答對原始問題有關建意義
- 對於n > 1,考慮n-1個圓盤,如果能將n-1個圓盤移動到某塔座上,則可以移動第n個圓盤
- 策略
- 首先將n-1個圓盤移動到Y上,然後將第n個移動到Z上,最後再將n-1個圓盤從Y移動到Z上
- 是否存在某種簡單情形,問題很容易解決
虛擬碼
void MoveHanoi(unsigned int n,HANOI from,HANOI tmp,HANOI to) { if(n==1) 將一個圓盤從from移動到to; else { 將n-1個圓盤從from以to為中轉移動到tmp; 將圓盤n從from移動到to; 將n-1個圓盤從tmp以from為中轉移動到to; } }
程式程式碼
typedef enum{X=1,Y,Z}HANOI; void MovePlate(unsigned int n,HANOI from,HANOI to
遞迴大多數情況以 if 開頭
- 計算複雜度
忽略所有對變化趨勢影響較小的項
- 忽略所有與問題無關的常數
- O(1) – O(logN) – O(sqrt(N) – O(N) – O(N*logN) – O(N^2) …
程式組織與開發方法
- 典型軟體開發流程
- 需求分析
- 方案設計
- 設計程式框架
- 程式設計實現
- 系統測試
複合資料型別
- 字元
- 字元儲存時實際儲存對應的ASCII值
- 0對應ASCII碼48(0x30)
- 回車與換行:win:\r\n Linux:\n Mac:\r
- 陣列
- 結構體
- 不同型別資料物件
指標與引用
指標使用場景
- 函式通訊
- 函式傳參和輸出
- 構造複雜資料結構
- 動態分配記憶體和管理
- 執行特定程式程式碼
- 函式指標
常量指標與指標常量
- 常量指標
- 指向常量的指標,
- 不能通過指標修改目標資料物件的值
- 但可以改變指標值,使其指向其他地方(指向關係可變)
- 函式入參
int n = 10; const int *p = &n; int const *p = &n; void test(const int *p); //*p不能變 p可變
- 指標常量
- 指標指向的位置不可變化(指向關係不可變)
- 可以改變指標指向的目標資料值
int n = 10; int * const p = &n;//p是常量 定義時必須初始化 //*p可變 p不可變
- 常量指標常量
const int n = 10; const int * const p = &n; int const * const p = &n;//const左結合性來記憶
指標與函式返回值
- 不能返回函式內部定義的區域性變數地址
指標與複合資料型別
指標與陣列
陣列基地址:&a 或 &a[0] 或 a (一維陣列)
- 指標運算
- 指標與陣列的可互換性
- 陣列名為常數,不能在陣列格式上進行指標運算:
int a[0]; a++;
- 陣列名為常數,不能在陣列格式上進行指標運算:
如何區分陣列指標與指標陣列:
[]優先順序高於*,所以沒有(),取[]優先順序最高即為陣列,有()則取*優先順序最高為指標
main() { int a[5]={1,2,3,4,5}; // int *ptr=(int *)(a+1); // printf("%d,%d\n",*(a+1),*(ptr-1));//2,1 // int *ptr=(int *)(&a[0]+1); // printf("%d,%d\n",*(a+1),*(ptr-1));//2,1 int *ptr=(int *)(&a+1); printf("%d,%d\n",*(a+1),*(ptr-1));//2,5 }
即 a+1 = &a[0] +1 != &a +1 ,需要強化記憶
- 指標與結構體
字串
三種理解角度
字元指標
- *s(編譯器自動新增
\0
) char *s; s="hello";
//字串文字首先分配空間,然後將基地址賦值給s
- *s(編譯器自動新增
- 字元陣列
- 多個字元陣列連續儲存時無法區分儲存空間(如果沒有字串結束標誌
\0
) char s[6]; s="hello";
//❌不能直接整體複製,初始化賦值才可以
- 多個字元陣列連續儲存時無法區分儲存空間(如果沒有字串結束標誌
抽象的字串整體
- string
char str[] = "hello"; cout << sizeof(str) << " " << strlen(str) << endl;// 6 5
動態儲存管理
- 靜態記憶體分配 - 全域性變數和靜態區域性變數
- 自動記憶體分配 - 區域性變數
- 動態記憶體分配 - 匿名資料物件(指標指向的目標資料物件)
- 確保程式中只有唯一一個指標擁有目標資料物件!
引用
- 引用型別的變數不佔用單獨的儲存空間
- 另一個數據物件的別名,與其共享儲存空間
- 引用型別必須在定義時初始化
- 引用的最大意義:
- 引用傳遞 直接修改實際引數值!
- 引用作為函式返回值,不生成副本
- 常量(const)引用不能改變目標物件
基於範圍的for迴圈 (c++11)
#include <iostream>
int main()
{
int array[3] = {1,2,3};
for(int & e : array)//for(p = array; p < array + sizeof(array)/sizeof(int); ++p)
{
e += 2;
std::cout << e << std::endl;
}
return 0;
}
- 智慧指標(c++11)
- unique_ptr :不允許多個指標共享資源,可以用標準庫中的move函式轉移指標
- shared_ptr :多個指標共享資源
- weak_ptr :可複製shared_ptr,但其構造或者釋放對資源不產生影響
連結串列與程式抽象
- 資料抽象
- 程式抽象
- 資料封裝&資訊隱藏
- 連結串列
面向物件程式設計
- 面向物件的基本特點
- 抽象
- 資料抽象:描述某類物件的屬性或狀態
- 程式碼抽象:描述某類物件的共有的行為特徵或具有的功能
- 封裝
- 增強安全性和簡化程式設計
- 繼承
- 擴充套件新類
- 多型
- 過載函式和虛擬函式
- protected與explit
- protected
- 建構函式、解構函式無返回值!解構函式無形參!
- 預設建構函式
- 引數列表為空,不為資料成員設定初始值;
- 如果類內定義了成員的初始值,則使用內類定義的初始值;
- 如果沒有定義類內的初始值,則以預設方式初始化;
- 基本型別的資料預設初始化的值是不確定的。
- 建構函式和解構函式的呼叫順序
- 使用建構函式建立物件的順序與使用解構函式釋放物件的順序相反
- 建立派生類的物件,基類的建構函式函式優先被呼叫(也優先於派生類裡的成員類)
- 如果類裡面有成員類,成員類的建構函式優先被呼叫
- 基類建構函式如果有多個基類則建構函式的呼叫順序是某類在類派生表中出現的順序而不是它們在成員初始化表中的順序
- 成員類物件建構函式如果有多個成員類物件則建構函式的呼叫順序是物件在類中被宣告的順序而不是它們出現在成員初始化表中的順序
=default
- 如果程式中已定義建構函式,預設情況下編譯器就不再隱含生成預設建構函式。如果此時依然希望編譯器隱含生成預設建構函式,可以使用
=default
- 如果程式中已定義建構函式,預設情況下編譯器就不再隱含生成預設建構函式。如果此時依然希望編譯器隱含生成預設建構函式,可以使用
- c++11 支援類內變數初始化
- 列舉類
enum class 列舉型別名: 底層型別 {列舉值列表};
- 強作用域,其作用域限制在列舉類中
- 轉換限制,列舉類物件不可以與整型隱式地互相轉換
- 可以指定底層型別
資料共享和保護
識別符號作用域
- 函式原型作用域
- 區域性作用域
- 類作用域
對於兩個巢狀的作用域,如果在內層作用域內聲明瞭與外層作用域中同名的識別符號,則外層作用域的識別符號在內層不可見
物件的生存期
- 靜態生存期
- 這種生存期與程式的執行期相同
- 在檔案作用域中宣告的物件具有這種生存
- 在函式內部宣告靜態生存期物件,要冠以關鍵字static
- 動態生存期
- 塊作用域中宣告的,沒有用static修飾的物件是動態生存期的物件(習慣稱區域性生存期物件)
- 開始於程式執行到宣告點時,結束於命名該識別符號的作用域結束處
類的靜態成員
類的友元
友元函式
友元函式是在類宣告中由關鍵字friend修飾說明的非成員函式,在它的函式體中能夠通過物件名訪問 private 和protected成員
友元類
若一個類為另一個類的友元,則此類的所有成員都能訪問對方類的私有成員
- 宣告語法:將友元類名在另一個類中使用friend修飾說明
class A { friend class B; private: int a = 10; }; class B { A i; public: void test() { cout << i.a; } };
- 類的友元關係是單向的
共享資料的保護
- 對於既需要共享、又需要防止改變的資料應該宣告為常型別
對於不改變物件狀態的成員函式應該宣告為常函式
常物件:必須進行初始化,不能被更新; const 類名 物件名
class A { private: int x,y; public: A(int i,int j){x=i,y=j;} } A const a(3,4);
通過常物件只能呼叫它的常成員函式 !!!
- 常成員:const 修飾的類成員;常資料成員和常函式成員
常成員函式:型別 函式名(引數表) const;
- const是函式型別的一個組成部分,因此在實現部分也要帶const關鍵字
- const關鍵字可以被用於參與對過載函式的區分
常引用:const 型別 &引用名
常陣列:型別 const 陣列名[大小]
常指標
#include <iostream> using namespace std; class A { public: int a; public: A(int i):a(i) { } void test() { cout << "this is nonconst" << endl; } void test() const { cout << "this is const" << endl; } }; int main() { A a(10); a.test(); a.a = 100; A const aa(10);//如果有資料成員 必須初始化 aa.test(); //aa.a = 100;//過不了,常物件必須進行初始化,不能被更新 return 0; }
下列關於常成員的說法不正確的是哪一個(C)
A. 常資料成員必須進行初始化,並且不能被更新
B. 常資料成員可以在定義時直接初始化(C++11)
C.
常成員函式不可以被非常物件呼叫(常物件只能呼叫常成員函式,但非常物件都可以呼叫)D. 常資料成員通過建構函式的成員初始列表進行初始化