C++ review(1)

C++ review (1)
C++ review (2)
C++ review (3)

一、認識 C++

二、數據類型

1. 基本數據類型

2. 變量

(1) 變量的聲明和定義

聲明一個變量只是將變量名稱標識符的有關信息告訴編譯器,使編譯器 “認識” 該標識符,不一定分配記憶體空間。

定義一個變量意味著給變量分配内存空間,變量名就是對相應記憶體單元的命名。
在 C++ 程式中,大多數情況下,聲明變量的同時也就完成了變量的定義,只有聲明外部變量時例外。

(2) 外部變量

外部變量:一個變量除了在定義它的原始檔案中可以使用,還能被其他檔案使用。
命名空間作用域中定義的變量,默認情況下都是外部變量,若其他檔案要使用,則要加上 extern 關鍵字聲明。
定義性聲明(1. 命名空間中不加 extern 關鍵字的聲明 2.extern 聲明同時指定了初值)
引用性聲明(只有使用 extern 關鍵字,extern int i;)

(3) 變量的存儲類型

(a) auto:采用堆棧方式分配記憶體空間,屬於暫時性存儲。
函數中的形參和在函數中定義的局部變量(包括符合語句中的局部變量)都屬於此類。
(b) register:存放在通用寄存器中。(register 存取速度優於 RAM)
register 只是請求寄存器變量,不一定能夠成功
(c) extern:在所有函數和程式段中都可以引用。
(d) static:在記憶體中是以固定地址存放的,在整個程式運行期間都有效。
static 在修飾全局變量時,該變量只能在當前文件中使用,其他文件無法訪問和使用。
希望函數中的局部變量的值在函數調用結束後不消失而繼續保留原值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//extern 用法 
//-------------main.cpp-------------
extern int a;//extern 一定要加
extern void f ();//extern 可加可不加
int main ()
{
a=100;
f ();
return 0;
}

//-------------f.cpp-------------
int a;
void f ()
{
print ("Hello");
}

(a) 靜態局部變量
靜態局部變量的生存期為整個源程式,但是其作用域仍與自動變量相同,只能在定義該變量的函數内使用該變量。雖然離開定義它的函數後不能使用,但如再次調用定義它的函數,它又可繼續使用,且保存了前次被調用後留下的值。
因此,當多次調用一個函數且要求在調用之間保留某些變量的值時,可考慮采用靜態局部變量。雖然用全局變量也可以達到上述目的,但全局變量有時會造成意外的副作用(全局變量在離開函數之後,依舊可以操作)。
(b) 靜態全局變量(與 static 函數類似,内部函數和外部函數,内部函數又稱爲靜態函數)
非靜態全局變量的作用域是整個源程式,當一個源程式由多個原始檔案組成時,非靜態的全局變量在各個原始檔案中都是有效的(只要加上 extern 聲明)。
而靜態全局變量則只在定義該變量的原始檔案内有效,在同一個源程式中的其他原始檔案不能使用它,因此可以避免在其他原始檔案中引起錯誤。
把局部變量改變成靜態變量後是改變了它的存儲方式,即改變了它的生存期。
把全局變量改變成靜態變量,則是改變了它的作用域,限制了它的使用範圍。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 靜態函數 
//-------------foo.h-------------
//f1 被定義爲 static,意思是只可以在這個 Compilation Unit 中生效
static void f1 ()
{
cout << "f1 ()" << endl;
}

//f2 沒有被定義為 static,可以被其他 Compilation Unit 訪問
void f2 ()
{
cout << "f2 ()" << endl;
}


Foo.h 的 f1 雖然在每個 .cpp 檔也被定義了,但經過編譯後,所有的 f1 也會被隱藏在自己的目的檔中,連結器在找尋 symbol 的過程中,是會忽略的。
但 f2 就不同了,所有的 f2 在目的檔中,是不會被隱藏,所以在連結器找尋 symbol,會找到多份的 f2,那連結就會有錯誤了。
所以在大部份的情況下,在 Header 檔中定義函數,也是需要 static 這個 keyword 的。

3. 常量

(1) 宏常量

宏常量也称为符号常量,是指用一个标识符号来表示的常量,宏常量是由宏定义编译预处理命令来定义的。
使用宏定义的优点:可提高源程序的可维护性;可提高源程序的可移植性;减少源程序中重复书写字符串的工作量。
使用宏常量的最大问题是,宏常量没有数据类型。

1
#define day 7

(2) const 修飾的變量

const int month = 12;

4. 關鍵字

5. 標識符命名規則

三、運算符、表達式、語句

四、程式流程控制

五、陣列

1. 一維陣列

(1) 陣列名

在大多數用到陣列的表達式中,陣列會自動轉換成指向陣列首元素的指針,例如:

1
2
int ia [10];
int *p = ia;

但在以下情形,上述轉換不會發生
(a) 當陣列被用作 decltype 關鍵字的參數時
(b) 作爲取地址符 (&)、sizeof、typeid 等運算符的運算對象時

補充:類型轉換
(1) 舊有 C 風格的强制轉換是不推薦的,因爲不顯眼,容易被忽略
    (int) x;
    int (x);
(2) 隱式轉換
    (a) 將比較小的整型值提升到較大的整數類型
    (b) 在條件判斷中,將非 bool 類型轉換成 bool 類型
    (c) 初始化過程中,初始化值轉換成變量的類型;賦值語句中,右側運算對象轉換成左側運算對象的類型
    (d) 函數調用的時候會發生實參類型轉換
(3) 顯式轉換:C++ 四種 explicit 關鍵字
    (a) static_cast <new_type> (expression)
        static_cast 也不能去掉 expression 的 const、volitale、或者__unaligned 屬性
    (b) const_cast <new_type> (expression)
        它僅僅把一個它作用的表達式轉換成常量。它可以使一個本來不是 const 類型的數據轉換成 const 類型的,或者把 const 屬性去掉。
        const int p = 0;
        int &rb = const_cast<int&>(p);// 正確
        rb =10;
    (c) dynamic_cast <new_type> (expression)
    (d) reinterpret_cast <new_type> (expression)
(2) 找最大 (小) 值

要找一個陣列中的最大值,可以設定一個變量,然後遍歷整個陣列,如果找到更大的數就更新該變量

(3) 排序

冒泡排序

2. 二維陣列

3. 從函數返回數組

如果想要從函數返回一個一維數組,必須聲明一個返回指針的函數

1
int* myFunction ();

實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
#include<cstdlib> //rand () 函數
#include<ctime>
using namespace std;

int* getRandom ()
{
static int r [10];
// 設置種子
srand ((unsingned) time (NULL));
for (int i=0; i<10; i++)
r [i] = rand ();
return r;
}

int main ()
{
int *p;
p = getRandom ();
for (int i=0; i<10; i++)
{
cout << *(p+i) << endl;
}
}

六、函數

1. 函數的分檔案編寫:讓程式結構更加清晰

(1) 創建後綴名為.h 的頭檔案,並在頭檔案中寫函數的聲明,例如:swap.h
(2) 創建後綴名為.cpp 的原始檔案,在原始檔案中 #include “swap.h” 並寫函數的定義
(3) 在 main 函數檔案中,#include “swap.h”,並可以使用該函數
 補充:
#include 實質的作用是預編譯的時候 copy include 標頭檔案的內容到當前列
#include 的路徑 ("" 和 <> 的區別)
#include "swap.h" 通常為自定義函數,在 Project 所在的路徑,若 complier 找不到則會去系統設定的目錄底下找
#include <iostream> 為 C++ 系統的函數庫,complier 會去系統設定的目錄底下去找

將需要分配記憶體空間的定義放在原始檔案中,例如:函數的定義,命名空間作用域中變量的定義;
而將不需要分配空間的聲明放在頭檔案中,例如:類聲明、外部函數的原型聲明、外部變量的聲明、基本數據類型常量的聲明。
内聯函數由於需要嵌入到每個調用它的函數之中,應該被編譯單元可見,定義應該出現在頭檔案當中。

2. 外部函數

非成員函數,都是命名空間作用域的,如果沒有特殊説明,可以在不同的編譯單元中被調用,只要在調用之前聲明即可。

3.static 修飾命名空間作用域的變量或函數

命名空間作用域中聲明的變量或函數,在默認情況下都可以被其他編譯單元訪問,但有時並不希望被其他原始檔案引用,可以使用 static。
然而,ISO C++ 2.0 標準中,不鼓勵使用這種方式隱藏,而是使用匿名空間:

1
2
3
4
5
6
7
8
namespace
{
int n;
void f ()
{
n++;
}
}

七、指針

1. 指針的概念與使用

(1) 可以通過 & 符號獲取變量的地址
(2) 利用指針可以記錄地址
(3) 對指針變量解引用 (*),可以操作指針指向的記憶體存放的數據

2. 指針所佔的記憶體空間

所有指針類型在 32 位作業系統下是 4 字節;在 64 位作業系統是 8 字節

3. 空指針和野指針

空指針指向記憶體中編號為 0 的空間,是用來初始化指針變量的

1
2
int *p = NULL;
cout << *p << endl; // 訪問空指針報錯

空指針指向的記憶體是不可以訪問的(記憶體編號 0-255 為系統占用記憶體,不允許用戶訪問)
野指針指向非法的記憶體空間,也是不可以訪問的

4.const 和指針

(1) 指向常量的指針,稱爲常量指針

只能防止通過指針修改記憶體中的數據,並不能保護指針所指向的對象。
可以將一個常量的地址賦值給常量指針。

1
2
3
4
int a = 10, b = 20; //const 在星號 * 前
const int *p1 = &a;
p1 = &b; // 正確
*p1 = 100; // 報錯

(2) 指針本身是一個常量,指針指向的記憶體位置不能改變,稱爲指針常量

指針常量必須在聲明的時候同時初始化,這和聲明一般的常量是一樣的

1
2
3
4
int a = 10, b = 20;
int * const p2 = &a; //const 在星號 * 後
*p2 =100; // 正確
p2 = &b; // 報錯

(3) const 既修飾指針又修飾常量
1
2
3
4
int a = 10, b = 20;
const int * const p2 = &a; //const 在星號 * 前後都有
*p2 =100; // 報錯
p2 = &b; // 報錯

5. 指針和陣列

利用指針訪問陣列中的元素

1
2
3
4
5
6
7
int arr [] = {1,2,3,4,5};
int *p = arr; // 陣列名轉換成指向陣列第一個元素的指針
for (int i=0; i<5; i++)
{
cout << *p << endl;
p++;
}

6. 指針和函數

利用指針作爲函數的參數,可以修改實參的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void swap (int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}

int main ()
{
int a = 10, b=20;
swap (&a, &b);
cout << "a =" << a << endl;
cout << "b =" << b << endl;
}

7. 當陣列名傳入到函數作爲參數時,被退化為指向陣列首元素的指針

例如:可以封裝一個函數,利用冒泡排序,實現對整型陣列的升序排序

1
2
3
void bubbleSort (int* arr, int len);
void bubbleSort (int arr [], int len);
// 以上兩者是一樣的

8. 函數返回指針

如果想要從函數返回一個一維數組,必須聲明一個返回指針的函數

9. 多級指針

八、結構體

1. 結構體變量通過 “.” 訪問結構體成員

1
2
3
4
5
6
7
8
9
10
11
12
struct Student // 類型名的首字母習慣大寫
{
string name; // 姓名
int age; // 年齡
int score; // 分數
};

int main ()
{
struct Student stu1 = { " 李四 ",19,60 };
stu1.name = " 王五 "
}

2. 結構體陣列

把自定義的結構體放到陣列中方便維護

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Student
{
string name; // 姓名
int age; // 年齡
int score; // 分數
};

int main ()
{
Student arr [3]=
{
{" 张三 ",18,80 },
{" 李四 ",19,60 },
{" 王五 ",20,70 }
};

for (int i = 0; i < 3; i++)
{
cout << " 姓名:" << arr [i].name << " 年龄:" << arr [i].age << " 分数:" << arr [i].score << endl;
}
}

3. 結構體指針可以通過 -> 操作符來訪問結構體成員

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Student
{
string name; // 姓名
int age; // 年齡
int score; // 分數
};

int main ()
{
Student stu1 = { " 李四 ",19,60 };
Student* p = &stu1;
p->acore = 80;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void weekday (struct week *wd)
{
wd->sleep_time = 7.0; // 7 小时
wd->work_time = 8.5; // 8.5 小时
}
void weekend (struct week *we)
{
we->sleep_time = 9.0; // 9 小时
we->work_time = 2.5; // 2.5 小时
}
int main ()
{
struct week w;
weekday (&w);
cout << "weekday: sleep time =" << w.sleep_time; << ", work time =" << w.work_time;

weekend (&w);
cout << "weekend: sleep time =" << w.sleep_time; << ", work time =" << w.work_time;
return 0;
}

4. 結構體嵌套結構體

結構體中的成員可以是另一個結構體
例如:每一個老師輔導一個學員,一個老師的結構體中,記錄一個學生的結構體

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Student
{
string name;
int age;
int score;
}

struct Teacher
{
int id;
string name;
int age;
Student stu;
}

// 訪問老師輔導的學生的成績
//Teacher t1 = {...};
//t1.stu.score = 80;

5. 結構體作爲函數參數

如果不想修改主函數中的數據,用值傳遞,反之用地址傳遞
(還有一種情形是爲了避免占用太多記憶體,用址傳遞,但是不想修改數據,則可以使用 const)
void printStudent (Student stu);
void setStudent (Student* stu);

6. 結構體中 const 使用場景

若結構體數據量太大,用值傳遞會占用太多記憶體,用址傳遞,指針只占用 8 個字節 (64 位作業系統)
加入 const 修飾,若在函數中不小心修改實參,編譯器會檢測出問題,讓 programmer 修改

1
void printStudent (const Student *stu);