---
title: '記憶體 & 連結 & 名稱空間'
disqus: kyleAlien
---
記憶體 & 連結 & 名稱空間
===
## OverView of Content
如有引用參考請詳註出處,感謝 :smile:
[TOC]
## 獨立編譯
* C/C++ 鼓勵將函數原件放置於獨立檔案中,在 **修改其中一個檔案後編譯只需做到 ==連結== 就可以使用**
* 大部分的 C++ 環境提供額外的功能幫忙管理程式
> UNIX & Linux 系統提供 make 程式 : 它記錄程式與哪些檔案相關,以及檔案的**最後修改時間** :+1:
### include 檔案
* 如果有多個檔案要同一個結構、Class,要將結構宣告放至每個檔案中,這樣容易導致錯誤,並且不易管理 (如果要修正結構,就必須改所有宣告的地方),這時就可使用 #include
* 以下是標頭檔中**常存在**的內容 (大部分放置在標頭檔中)
| 表示 | 內容 | 補充 |
| -------- | -------- | -------- |
| void print(); | 函數**原型** | Non |
| #define MSG 1 | 定義符號 | Non |
| const int i = 0 | 常數定義 | Non |
| struct Name_T | 結構**宣告** | 只限至於宣告而非定義 |
| class Hello {} | 類別宣告 | Non |
| <template typename T\> class Hello{} | 樣板宣告 | Non |
| inline void print() {} | 內嵌函數 | 可以存在實體內容 |
* include 時使用的符號會影響到 C++ 編譯時期如何找到檔案;**使用正確的符號可以減少編譯所花費的時間**
| 符號 | 表示 | 尋找 |
| -------- | -------- | -------- |
| ==""== | #include "coordin.h" | 工作(Project)目錄 |
| ==<>== | #include <string.h> | 標準標頭檔 |
### include 注意事項
* **不要 include 原始碼**檔案 ( `.c`、`.cpp` 檔案),會**導致多重定義**,所以 **不要將==函數定義== & ==變數宣告==**,寫在標頭檔中
> 假設定義函數在標頭檔中,程式 include 兩個檔案中,會導致兩個 .c 中會存在兩分定義,下圖表示此錯誤
> ![](https://i.imgur.com/QvAJGGQ.png)
```cpp=
ifndef COORDIN_H_
#define COORDIN_H_
#include <iostream>
#define MSG 1
const int i = 0;
inline void print() {
std::cout<< "Hello World" + MSG << std::endl;
}
/* Err: 不可定義函數在標頭檔中
int a = 20;
void print() {
std::cout<< "Hello World" + MSG << std::endl;
}
*/
#endif /* COORDIN_H_ */
```
**--實做--**
> 以下是在標頭檔中**定義**,編譯其間被抓出錯誤,**多重定義** `multiple definition of print()`
> ![](https://i.imgur.com/ox0Lvjs.png)
### include 標頭檔管理
* 為了避免多重匯入檔案,可以使用**兩個方法**
| 方法 | 解釋 |
| -------- | -------- |
| #ifndef、#endif | 使用預編譯判斷 define |
| #pragma once | pragma 也是預編譯,但是由各家 chip 廠商,各自實現 (也就是不一定會有) |
* 以保證可行的方法來實現,使用 #ifndef、#endif,其實它無法避免成式匯入兩次,但**可以保證第二次匯入時 ++不會被編譯++ (==第二次匯入為空==)**,直接跳至 #endif 之後
> ![](https://i.imgur.com/ycvI3cx.png)
### Compiler 編譯
* **編譯後的檔案 C++ 使用翻譯單元 (translation unit) 來==取代== 原本檔案**
:::info
* 多重涵式連結失敗? 修改相同的編譯器
**不同編譯器編譯原始碼 or 函式庫時產生的二進制檔案不盡相同**,所以有可能 **在 Linked 階段不能很好的連接**,所以這時只要使用同個編譯器編碼全部就可進行連接
:::
* C++ 使用的翻譯單元 (translation unit),名稱取名為 檔案 (file);也就是說 **C++ 編譯單位是檔案 file**,這可以包留較大的一般性 (其他平台都有這個單元)
## 儲存期間
**儲存期間**會影響到程式如何跨檔案共用,而 **C++ 有四種儲存期間**
| 儲存期間 | 解釋 | 代表 |
| -------- | -------- | -------- |
| ==自動== 儲存期間 | 當函數進入==函數區塊==時會**自動產生** | 區域變數 |
| ==靜態== 儲存期間 | 使用關鍵字 static 包覆 or 區塊以外的變數,在程式執行期間他們會存在 | 內部 static、外部 static、全域變數 |
| ==執行緒== 儲存期間 | 其變數的生命週期同執行它的 thread 生命週期 | thread |
| ==動態== 儲存期間 | 生命週期由 new 至 delete,並==存於堆區== | `new`/`delete`、`malloc`/`free` |
### 記憶體區塊
| 記憶體區塊 | 補充 |
| -------- | -------- |
| 自由 區塊 | 使用 Stack |
| 動態 區塊 | Heap 區塊 |
| 靜態 區塊 | 內部還會再細分 `data`(靜態數據)、`text`(程式) |
### 範疇 (Scope)
* 變數的 **可見範圍**
```java=
static d = 20;
namespace {
int c = 30;
}
int b = 20; // 變數 b 的可見範圍是整個檔案中
int main()
{
/**
* 變數 a 的可見範圍僅有 main() function 的區塊中
*/
int a = 10;
return 0;
}
```
| 範疇類形 | 意義 | 範例代表 |
| -------- | -------- | -------- |
| 區域範疇 | **區塊** 間才知道它的存在 (區塊代表大括號 {} 之間) | a (區域變數) |
| 全域範疇 | 也稱為 **檔案範疇**,整個檔案都知道其存在 | b (全域變數) |
| 類別範疇 | 宣告在 **類別**中的成員 | [**參考**](https://hackmd.io/32uk2K2ZSUKoCKYlidy-Dg?both#%E9%A1%9E%E5%88%A5%E7%AF%84%E7%96%87) |
| 名稱空間範疇 | 宣告在 **名稱空間** 的成員,不再該空間的成員,就不具有該變數 | c (namespace) |
### 連結性 (Linked)
* 描述不同元素在**不同區塊是否能共用**
```java=
static d = 20;
namespace {
int c = 30;
}
int b = 20; // 變數 b 的可見範圍是整個檔案中
int main()
{
/**
* 變數 a 的可見範圍僅有 main() function 的區塊中
*/
int a = 10;
return 0;
}
```
| 連結性 | 意義 | 範例代表 |
| -------- | -------- | -------- |
| 無連結 | Func 內部一般變數 | a |
| 內部連結 | 檔案內部 static 變數 | d |
| 外部連結 | 檔案內部一般變數 | b |ja
## 自動儲存期間
### 自動儲存
* 變數的**記憶體配置是在,進入區塊內時才會產生**,一離開區塊就自動釋放記憶體
* 進入區塊時才會配置變數,**範疇從==宣告時==就已經存在**
```cpp=
int a = 0;
int main()
{
int a = 10; //"1. "
{
std::cout << a << std::endl;
}
std::cout << a << std::endl; //"2. "
return 0;
}
```
1. 內部變數 a 會 **==暫時覆蓋==外部範疇的 a**,直至大括弧結束
2. 一來開區快就會離開覆蓋,變回外部範疇
> ![](https://i.imgur.com/mvG4ja1.png)
### 自動變數的初始化
* 當 **定義出變數後並不代表已初始化了**,**++自動變數必須要靠指定才能初始化++,尚未初始化則會是不確定值**
```cpp=
#define MY_VALUE 20
void local() {
int a; // indeterminate 不定
int b = 0; // 初始化
int c = MY_VALUE - 1; // 藉由 constant value 初始化
}
```
### 自動變數 & 堆疊
* 進一步了解編譯器是如何處理自動變數 (區域變數)
1. Stack 撥出一塊記憶體
2. 對**自動變數進行堆疊 (Stack、FILO)**,此堆疊大小可改變
3. 離開區塊後變數也從堆疊上移除
* 而它的實現是同於 [**Java 的資料結構 Stack**](https://hackmd.io/Cq9VpmG7RoORzqPKvAdHuA#動態-Stack),並不會真正去清掉其值,而是透過兩個指標 (rear & last) 的移動去儲存值,所以**移除時也指是移動指標,==舊的資料並不會被清除==**
> ![](https://i.imgur.com/QqOgPzR.png)
### 修飾符號 register
* **==變數存入 CPU 暫存器==,而不是內存記憶體內**
* **register 只能用來 ++修飾自動變數++**,**==目的==是為了==更快速地做出存取動作==**,使用 register 的原因有如下
1. 用來**強調該變數是自動變數**
2. **更重要的原因**是,使用 register 存入暫存器 **==避免使用該關鍵字的代碼失效==**
## 靜態儲存時間
:::success
**靜態變數的重點在於連結性 (Linked)**
:::
* 靜態儲存變數不會隨著 RunTime Work 改變,它的生命週期是整個程式的開始 & 結束,**靜態變數不會自動式放**,它跟 Class 對象的生命週期沒有關係
```cpp=
static d = 50; //"3. "
int c = 30; //"2. "
int main()
{
static int a; //"1. "
int b = 20;
return 0;
}
```
1. 靜態期間,無連結
2. 靜態期間,**外部連結**,所以**一般變數就是靜態變數,==差別在連結性==**
3. 靜態期間,內部連結 (檔案內才可使用)
### 靜態變數特性
* **它有特殊的記憶體儲存區塊**,不像自由儲存變數,它**並不使用堆疊 (Stack) 儲存**
* 靜態變數若無指定初始化,**編譯器會將靜態變數++自動初始化為 0++**,稱為 ==0 的初始化==
以下有 `自動變數`、`靜態變數` 的比較
| 儲存方式 | 期間 | ==範疇== | ==連結性== | 宣告方式 | 初始化 |
| -------- | -------- | -------- | -- | -- | -- |
| 自動 Stack | 自動 | 區塊 | 無 | 變數宣告在區塊中 (auto 隨意) Ex: {int a; auto int b} | 指定初始化 |
| CPU 暫存器 | 自動 | 區塊 | 無 | 必須使用關鍵字 register Ex: {register int a;} | 指定初始化 |
| 靜態記憶體區塊 | 靜態 | 區塊 | 無 | 必須使用關鍵字 static Ex: {static int a;} | 0 的初始化 |
| 靜態記憶體區塊 | 靜態 | 檔案 | ==內部== | static **宣告在所有函數外** Ex: static int a; | 0 的初始化 |
| 靜態記憶體區塊 | 靜態 | 檔案 | ==外部== | **宣告在所有函數外** Ex: int a; | 0 的初始化 |
:::info
靜態初始化為 0 或是尚未初始化就會放在 `.bss` 區塊,否則大部分放在 `.data` 區塊
:::
```cpp=
static int apple2 = 10; // 內部連結
int apple = 10; // 外部連結
void local_field(){
int apple = 10; // 無連結
register int banana = 11; // 無連結
static int coconut = 12; // 無連結
}
```
### 靜態初始化
:::info
靜態並不是說 static,而是在編譯期間就決定的程式儲存的區域、連結
:::
* 一開始皆是由 0 初始化,之後會分為靜`態初始化`、`動態初始化`
* 靜態初始化 : 經由**常數運算式初始化** **(==編譯期間==解析運算式)**
* 動態初始化 : 當下沒有足夠資訊可初始化,就以動態初始化 **(==連結期間==函數呼叫)**
```cpp=
#include <iostream>
#include <cmath>
int main() {
int x; //"1. "
int y = 5; //"2. "
int z = 10 * 10;
const double pi = 4.0 * atan(1.0); //"3. "
std::cout << pi << std::endl;
return 0;
}
```
1. 宣告 x 出來後並無指定,為 0 的初始化
2. 宣告 y、z 出來後,解析運算式指定初始化值
3. 由於 pi 變數牽扯到 **`atan` 函數**,會導至**初始化推遲到連結期間,程式運行期間才完成,稱為動態初始化**
### 靜態期間、外部連結
* 這種變數宣告在所有函式之外,又稱為 **外部變數 (external variable)**、**全域變數**
* 單一定義 one definition rule (odf),**變數的定義只能有一次**
> C++ 變數的宣告:
> > 1. **==定義==宣告 (defining declaration)**,==能夠初始化並配置空間==,不用使用 extern
> >
> > 2. **==參考==宣告 (referencing declaratoin)**,這種宣告 ==不提供== **初始化**,一定要使用 extern
```cpp=
#include <iostream>
extern int a = 10; // "1. "Warning: 定義初始化 & 參考
int b = 20; // 定義初始化
extern int c; // "2. "Err: 參考,但沒有定義,編譯器會報錯
int main() {
std::cout << "a : " << a << std::endl;
std::cout << "b : " << b << std::endl;
std::cout << "c : " << c << std::endl;
return 0;
}
```
1. extern 如果做為內部使用的參數就不是必需的
2. 參考宣告沒問題,但是如果沒有定義 (參考來源),就會報錯
> 變數 c **未定義就被參考,會報錯**
> ![reference link](https://i.imgur.com/TrbHpp1.png)
* extern 範例
1. 定義外部連結檔案 `a`
```cpp=
#include <iostream>
extern int a = 10; // 不必 Extern
int b = 20;
void testExtern(); // TestExtern.cpp 函數宣告
int main() {
std::cout << "a : " << a << std::endl;
std::cout << "b : " << b << std::endl;
testExtern();
std::cout << "a : " << a << std::endl;
std::cout << "b : " << b << std::endl;
return 0;
}
```
2. 宣告外部連結,`a`、`b` Symbol 必須在另外一個檔案中定義
```cpp=
#include <iostream>
extern int a;
extern int b;
void testExtern() {
a = 1;
b = 2;
std::cout << "testExtern, a : " << a << std::endl;
std::cout << "testExtern, b : " << b << std::endl;
}
```
* 兩個原始檔 (.c檔),**不用標頭檔 (.h) 連結就可以有關係**,只要有宣告就可呼叫函數
* 定義宣告就不需要 extern,如同上面範例的變數 b
:::info
**遮蔽、範疇運算子**
:::
* 如果全域變數與區域變數同名,可以使用範疇運算子 `::` 來指定全域變數
```cpp=
#include <iostream>
// a、b 會在外部 (另一個檔案) 定義
extern int a;
extern int b;
void testExtern() {
//extern int a; // 可不必,因為外部以參考 a
auto a = 0; //"1. "
std::cout << "區域自動變數, a : " << a << std::endl;
//"2. "
std::cout << "全域靜態變數, ::a : " << ::a << std::endl;
::b = 90;
std::cout << "全域靜態變數,,改 ::b : " << ::b << std::endl;
}
```
1. 同名自動變數 & **外部全域變數碰撞,會暫時遮蔽全域變數**,這時內部呼叫 a 就是區域自動變數 0
2. 使用範籌運算子 **==::==**,**表示獲取全域版本的變數**
> ![reference link](https://i.imgur.com/v3PlRhq.png)
### 靜態期間、內部連結
* 使用 **static 修飾** 變數,該變數就是內部連結的靜態變數,並且該變數記憶體就不存在 Stack 區塊,而是存在靜態區
* 靜態變數會覆蓋 Override 全域變數,自動變數又會 Override 靜態變數
> 優先權 : 自動 (區域) 變數 > 靜態變數 > 全域變數
:::info
**未來內部連結的靜態儲存會被,匿名 namespace 取代**
:::
1. 全域變數、外部連結
```java=
#include <iostream>
int a = 10;
int b = 20;
void testExtern();
int main() {
std::cout << "a : " << a << ", addr: " << &a << "\n";
std::cout << "b : " << b << ", addr: " << &a << "\n";
testExtern();
std::cout << "a : " << a << ", addr: " << &a << "\n";
std::cout << "b : " << b << ", addr: " << &a << "\n";
return 0;
}
```
2. 全域變數、內部連結 (static)
```java=
#include <iostream>
extern int a; // 在另外一個檔案定義
static int b = 99;
void testExtern() {
::a = 20;
std::cout << "外部連結靜態, a : " << a
<< ", addr: " << &a << "\n";
b = 50;
std::cout << "內部連結靜態, b : " << b
<< ", addr: " << &b << "\n";
int b = 999;
std::cout << "自動變數, b : " << b
<< ", addr: " << &b << "\n";
}
```
**--實做--**
> ![](https://i.imgur.com/TbGKVNZ.png)
### 靜態期間、無連結 - 函數中的 Static 變數
* 在 **區塊內使用 static 修飾變數,會使成是內部的變數具有靜態儲存期間**,但是又因為在 **區塊內所以是無連接**,在 **離開區塊** 時又 **++不會釋放靜態變數的記憶體++**
* 靜態區塊 (static) 變數的 ==初始化只會有一次==
```cpp=
int main() {
std::cout << "main call 1 time" << "\n";
testStatic();
std::cout << "main call 1 time" << "\n";
testStatic();
std::cout << "main call 1 time" << "\n";
testStatic();
return 0;
}
void testStatic() {
static int x = 0; //"2. " 靜態初始化,離開函數後,也不會釋放
x += 10;
std::cout << "x value: " << x
<< ", addr: " << &x <<"\n";
/*
int x = 20; //"1. "
std::cout << "x : " << x
<< &x <<"\n";
*/
}
```
1. **自動變數 & 靜態區域變數,兩者都使無連結,所以取同名會相互衝突**
2. static 變數只會初始化一次,**預設也會自己 0 初始化**
> ![](https://i.imgur.com/FmE90xi.png)
### 指示字、修飾字
* 指示字 : **儲存類別指示字**;修飾字 : **常數可變修飾字**
| 描述 | 類型 | 功能 | 補充 |
| -------- | -------- | -------- | - |
| auto | 指示字 | 自動變數 & 自動推導 | |
| rigister | 指示字 | 存入暫存器、防失效 | |
| static | 指示字 | 存入靜態記憶體、內部連結 | |
| extern | 指示字 | 存入靜態記憶體、外部連結 | |
| const | **修飾字** | 定義後不可在被修改 | |
| volatile | **修飾字** | **禁止編譯器優化** | |
| thread_local | 指示字 | 生命週期同 thread | 可與 extern、static 一起使用 |
| mutable | 指示字 | 使被 const 修飾過的變數可改變 | |
1. **volatile** 解釋
> 編譯器對於程式碼覺得多餘的部分會進行優化
```cpp=
int a = 0;
volatile int b = 0;
//"1. "
a = 10; // first time
a = 10; // second time
//"2."
b = 10; // first time
b = 10; // second time
```
* 對 a 指定了 10 兩次,編譯器會覺得是多餘的程式碼,會進行優化省略第二次的指定 (不會更動到記憶體)
* 同樣是對 b 指定了兩次,但是**差別在於,==每次指定的值都會存入記憶體中==**
2. **const 儲存類別**
> 1. **const 會修改全域變數 (一般全域) 為區域性 ==內部連結 ?==**,**C++ 會將 const 定義是為使用了 static** (**可使用 extern 可抓取到**)
>
> 2. const 資料需要初始化
>
> 3. **如果是全域性的 const 變數,則會產生衝突**
:::warning
* **const 修飾全域的變數,連結性 ?**
如果 const 修飾全域的變數,這也代表了它有外部連接的特性 (外部連接) ! 外部檔案如果要使用就必要用 `extern` 關鍵字取得
:::
```cpp=
...Test.cpp 檔案
#include <iostream>
const char* say = "Hello World";
//static char* say = "Hello World"; // Same like upper
void testExternConst();
int main() {
testExternConst();
return 0;
}
// -----------------------------------------------------------------
...TestExtern.cpp 檔案
#include <iostream>
extern char* say; // 可以使用 extern 去抓取到內部連結 const 常數
//const char* say = "Hello World"; 衝突 ~~~~~!!!! (外部連結)
void testExternConst() {
std::cout << "testExternConst, " <<say << "\n";
}
```
3. **mutable** 範例:使某一個 const 的變數轉為可改變數
```cpp=
#include <iostream>
struct MyStruct{
char name[10];
mutable int age; //"使不可修改的變數可以修改"
int c;
};
// m 結構內容不可改
const MyStruct m = {"Kyle", 20};
int main() {
std::cout << "Original name: " << m.name << "\n";
std::cout << "Original age: " << m.age << "\n";
m.age = 21;
std::cout << "Change age: " << m.age << "\n";
return 0;
}
```
### 函數 & 連結性
* **儲存期間** : **所有的函數都有==靜態儲存期間==**,只要程式執行就會一直存在,值到整個程式結束
* **連結性** : 預設為 ==**外部連結**==,表示可跨檔案共用 (當然你可以使用 `static` 將函數、變數限制在檔案內)
```cpp=
...Test.cpp 檔案
#include <iostream>
const char* say = "Hello World";
extern void testExternConst(); // "1. " extern 可加可不加
void showPrint(); // "2. " 自動為外部連結
static void testStaticMethod(); //"3. "
int main() {
testExternConst();
showPrint();
testStaticMethod();
return 0;
}
```
1. 函式宣告,代表此函式存在於別的檔案,可加 **extern,也可不加 extern**
2. 可看到同時宣告了兩個同樣的函式 showPrint(),在 main 中的是外部連結函式,全域中只能有一個 showPrint()
3. 在其他檔案中定義宣告靜態內部連結函式,在 main 中是無發呼叫的,因為**靜態內部連結其他檔案無法連接到**
```cpp=
void showPrint() {
std::cout << "Hello World in main" << std::endl;
}
...TestExtern.cpp 檔案
#include <iostream>
extern char* say;
static void showPrint(); // "4. "
//const char* say = "Hello World";
void testExternConst() {
std::cout << "testExternConst, " <<say << "\n";
showPrint();
}
static void showPrint() {
std::cout << "Hello World in testExternConst" << std::endl;
}
static void testStaticMethod() {
std::cout << "Hello World in testExternConst" << std::endl;
}
```
4. 在其他檔案中的 showPrint(),**使用 ==static 表示為這是內部函數==,==內部連結會覆蓋 (Override) 外部連結==**,導致 testExternConst 在呼叫 showPrint() 是呼叫到內部連結
**--實做--**
> 圖中可以看出同樣函數 `showPrint` 使用 static 修飾,就變成了內部連結函數,`testStaticMethod` 則無法呼叫到外部定義函數 (在其他檔案中)
>
> ![](https://i.imgur.com/DTGEG42.png)
### 語言連結、多檔案連結 - 函數簽名
* **既然是靜態儲存**,對於 **每一個==函數==都需要有不同的符號名稱**,C/C++ 處理的方法也不同,看以下兩個函數
```cpp=
void showPrint();
void showPrint(String);
```
1. **C 的語言連結**:**不關心參數**,因此上面兩個函數都**相同翻譯**,這也就是為何 C 語言中不可以有函數重載
| 函數原型 | C 函數簽名 |
| - | - |
| void showPrint() | \_showPrint |
| void showPrint(String) | \_showPrint |
2. **C++ 的語言連結** :**關心參數**,因此上面兩個函數都 **不同翻譯**
| 函數原型 | C++ 函數簽名 |
| - | - |
| void showPrint() | \_showPrint |
| void showPrint(String) | \_showPrint_S |
* 分辨 C/C++ 函數:如果 C 語言與 C++ 聯合開發 (使用 C++ 編譯器),同時都有一個同名函式,這時可以使用 **==唯一指示字== 來區分**
:::success
C++ 可以調用 C,但 C 不能調用 C++
:::
1. 指定使用 C 函數
```cpp=
#include <stdio.h>
void hello(int times) {
for(int i = 0; i < times; i++) {
printf("C ~ Hello World.\n"); // C 語言實現
}
}
// --------------------------------------------------------
#include <iostream>
using namespace std;
// "C" 代表唯一指示字
extern "C" void hello(int);
int main() {
hello(3);
return 0;
}
```
> ![](https://i.imgur.com/NFtBjED.png)
2. 指定使用 C++ 函數 (預設)
:::warning
* C++/C 不可同時指定同一個函數名稱,會導致衝突
```cpp=
extern "C" void hello(int);
extern "C++" void hello(int);
```
> ![](https://i.imgur.com/cQTrI43.png)
:::
```cpp=
#include <iostream>
using namespace std;
void hello(int times) {
for(int i = 0; i < times; i++) {
cout << "C++ ~ Hello World." << endl;
}
}
// --------------------------------------------------------
#include <iostream>
using namespace std;
// "C++" 代表唯一指示字 (其實也可以省略,因為預設就是 c++)
extern "C++" void hello(int);
int main() {
hello(3);
return 0;
}
```
> ![](https://i.imgur.com/A4HvOOY.png)
### 決定使用函數
1. 指示語 `static` 會在該檔案找尋函數
2. 外部連結 (一般函數) 會檢查所有檔案 (`#include "..."`),如果找到**兩個定義**,編譯器會報錯 **(外部連結只能有一個定義)**
3. 都找不到會搜尋函式庫 (`#include <...>`),**如果定義了與函式庫相同名稱了函式,編譯器會先使用內部函式定義** (C++ 其實會禁止)
```cpp=
#include <iostream>
#include <cmath>
// cmath 內函式原型 double sqrt(double);
double sqrt(double d) {
std::cout << "Define in main" << std::endl;
return 0.00;
}
int main() {
double r = sqrt(5.00);
std::cout << "sqrt: " << r << std::endl;
return 0;
}
```
從結果可以看出同樣式定義了函式庫中的 `sqrt()`,編譯器取用自己定義的 `sqrt()`,而不是使用了標準函式庫中的函數 (內外覆蓋外部定義)
**--實作--**
> ![](https://i.imgur.com/pTZ10wk.png)
## 動態儲存時間
* 動態記憶體分配的記憶體區塊,在 **堆區 (Heap)**,分配出的記憶體 ==**不受範疇、連結結規範**==,**可以函數 ^1.^ 配置,並在函數內 ^2.^ 釋放**
* **分配出的指標位子,會跟隨 delete 一起消失**,但是接收的指標不會消失 (只有內容消失),所以記得要將指標覆值為 nullptr
:::info
**new 失敗時會丟出,==std::bad_alloc==**
:::
| 語言 | 動態分配方法 | 記憶體區塊 |
| -------- | -------- | -------- |
| C | malloc() / free() | 堆區 |
| C | calloc() / free() | 堆區 |
| C | ralloc() / free() | 堆區 |
| C++ | new / delete | 堆區 |
| C++ | new[] / delete[] | 堆區 |
```cpp=
...Test.cpp 檔案 (main)
#include <iostream>
extern void freePtr();
static void location();
float *a; //"1. "
int main() {
float c = 30.0;
std::cout << "靜態儲存, b 分配位置: " << &b << "\n";
std::cout << "自動儲存, c 分配位置: " << &c << "\n";
location();
freePtr();
return 0;
}
static void location() {
a = new float[20]; //"2. "
if(a != nullptr) {
std::cout << "動態儲存,a 分配位置: " << a << "\n";
} else {
std::cout << "動態儲存,a 分配失敗" << "\n";
}
}
// ------------------------------------------------------------
... TestFile.cpp (其他檔案)
#include <iostream>
extern float *a;
void freePtr() {
delete(a); //"3. "
if(a != nullptr) {
std::cout << "動態 a 釋放 sucess" << std::endl;
} else {
std::cout << "動態 a 釋放 fail" << std::endl;
}
}
```
1. 因為 a 宣告為靜態外部指標,其他檔案可以透過 extern 取得該指標
2. 透過 new 動態產生區塊,指標 a 的生命週期與 new/delect 相同
3. 可看出 **指標 a 的內容可在不同檔案中釋放** (只要誰有辦法取得該指標就可以),**但是指標 a 並沒有消失 (還是在靜態儲存區)**
> ![](https://i.imgur.com/95nthED.png)
### new 運算子初始化
* 當配置一個動態空間後,**如果未初始化其值式隨機的**
單個值,可以透過括弧 `()` 初始化 (也可以使用 `{}` ),但如果是 `array`、`struct` 需要透過大括弧 `{}` 初始化
```cpp=
#include <iostream>
struct Mystruct {
char a;
short b;
int c;
};
int *single;
float *array;
Mystruct *mStruct;
int main() {
std::cout<< "single address: " << single << std::endl;
// 1.
single = new int{2};
array = new float[20]{10.9, 1.0, 20.3};
mStruct = new Mystruct {
.a = 1, .b = 10, .c = 100
};
std::cout<< "single value: " << *single << std::endl;
std::cout<< "array[1] value: " << array[1] << std::endl;
std::cout<< "mStruct#c value: " << mStruct->c << std::endl;
delete (single);
if(single == nullptr) {
std::cout<< "指標 single \"內容\"釋放\n";
} else {
std::cout<< "After delete single is not null: " << *single << std::endl;
}
// 2. 靜態指標
if(single != nullptr) std::cout<< "指標 single 仍在\n";
return 0;
}
```
1. **透過動態分配出來的記憶體,都是使用指標接收**,並將該指標存入靜態指標中
2. delete 只刪除指標內容,但是**靜態儲存的指標是無法被刪除的**
> **delete 後指標位子 ==(堆中的位子) 仍然在==,但是其值就會不見**
>
> ![](https://i.imgur.com/JpGViZj.png)
### new 運算子、原型
* new 它屬於運算符號,先看它的 operator 原型,**size_t 為所需的位元組**
| 操作 | 原型 |
| -------- | -------- |
| new() | void* opeartor new(std::size_t); |
| new[]() | void* opeartor new[\](std::size_t); |
```cpp=
// 概念程式
int *pi = new int;
// 同上
int *pi = new(sizeof(int));
// -----------------------------------------------------
int *pArray = new int{40};
int *pArray = new(40 * sizeof(int)); // 可翻譯成這個結果
```
## 定位放置 new 運算子
* 一般來說使用 `new` 運算子,程式會尋找一塊足夠大的位子分配 (足夠容納要存放的資料),而其實 **new 也是可以 ==指定== 其記憶體位置**
:::info
一般來說透過 new 來創建的對象,會自動分配記憶體位置
:::
> 型態 宣告 = new (指定位子) 型態;
```cpp=
#include <iostream>
#include <new>
using namespace std;
struct MyStruct {
char a[10];
short b;
int c;
};
struct MyStruct *ms1;
struct MyStruct *ms2;
char buffer[100];
int main() {
ms1 = new MyStruct;
// 將 `ms2` 指定位子到 `buffer` 的位子
ms2 = new(buffer) MyStruct;
cout<< "ms1 addr: " << ms1 << "\n" << endl;
cout<< "buffer addr: " << &buffer << "\n";
cout<< "ms2 addr: " << ms2 << "\n";
}
```
**--實作--**
> ![](https://i.imgur.com/75izrLA.png)
## 名稱空間
* 當專案大後,**靜態全域儲存就很容易發生衝突的狀況 (函式、變數取名衝突)**,而**解決這個問題的方法就是 namespace**
| 用語 | 解釋 |
| -------- | -------- |
| 宣告區域 | 有關於連結性,**該變數可以被誰使用** |
| 可能範疇 | 有關於範疇,也就是變數的作用域,被宣告出來在結束前就稱為可能範疇,**直接使用到的 ==區塊== 稱為範疇**,被遮蔽時就不是範疇 |
| **名稱空間** | **每一個變數皆有一個持有者,不同持有者可以有相同名稱的變數** |
下圖可看到被區塊遮擋的 a 就不是範疇,被自由變數遮蔽的 temp 也不是範疇
> ![](https://i.imgur.com/3a4b3RX.png)
:::warning
* 可放置於全域空間,但 **不可以放置於區塊內部** (不可放置於 Function 中)
> ![](https://i.imgur.com/z6u19NJ.png)
:::
### using 宣告 & using 指令
* using 關鍵字可以使整個命名空間可用,在 using 之後不必在指定命名空間,使用方式也分為以下兩種,代表了不同功能
| 功能 | using 使用範例 | 意義 |
| -------- | -------- | -------- |
| 宣告 | using std::cout | 宣告之後使用,引入 ==**特定**== 名稱空間的變數,就只需用 cout 即可 |
| 指令 | using namespace | 指令之後使用,就可以使用 namespace 的**所有**函數、變數...,引入 ==**全部**== 名稱空間的變數 |
:::success
using 宣告會比 using 指令更安全點
:::
* **using 宣告、指令**
```cpp=
#include <iostream>
static void UsingDeclare();
static void UsingCommand();
namespace MySpace{
char a;
short b;
int c;
}
namespace AlienSpace {
char a;
short b;
int c;
}
int a; // 0 的初始化
int main() {
std::cout << "Hello World" << std::endl;
UsingDeclare();
UsingCommand();
return 0;
}
static void UsingDeclare() {
// using 宣告
using std::cout;
using std::endl;
using MySpace::a; //"2. "
//using AlienSpace::a; //"1. " 模糊
using MySpace::b;
using MySpace::c;
cout << "Hello World, UsingDeclare" << endl;
a = 'a'; b = 20; c = 30;
cout << "a: " << a
<< "\nb: " << b
<< "\nc: " << c << endl;
}
static void UsingCommand() {
//using 指令
using namespace std;
using namespace MySpace;
cout << "Hello World, UsingCommand" << endl;
cout << "External, ::a: " << ::a //"3. "
<< "\nb: " << b
<< "\nc: " << c << endl;
}
```
1. 雖然 AlienSpace & MySpace 是不同空間 (相同變數名稱也不會衝突),但是 using 宣告 a 就一樣了,這樣會產生模糊
2. 使用 **using 宣告會遮蔽外部連結**的靜態變數,**不會產生錯誤,外部靜態變數會被遮蔽**
3. **using 指令中的變數與外部會產生衝突**,會導致編譯**錯誤**,要使用**外部變數**要 ==**使用範疇運算子 `::`**== 就可取得檔案變數
> ![](https://i.imgur.com/fGFc7VD.png)
* using 可以放置在 function 內 (離開函數就失效),也可放置在 Gobal 區塊,但如果 Function 內有與 Gobal 相同的變數名稱,就需要 使用範疇運算子指定
```cpp=
#include <iostream>
namespace A {
const char* name;
int age;
}
namespace B {
const char* name;
int age;
}
using namespace A;
int main() {
using namespace B;
// 由於名稱重複,所以需要使用範疇運算子指定
B::name = "Alien";
B::age = 10;
std::cout << B::name << ", " << B::age << std::endl;
return 0;
}
```
> ![](https://i.imgur.com/FY11oLN.png)
### 名稱空間特色
1. namespace **巢狀空間**
```cpp=
#include <iostream>
namespace KyleSpace {
const char* name;
int age;
}
namespace MySpace{
const char* name;
int age;
namespace AlienSpace { //巢狀
const char* name;
int age;
}
using namespace KyleSpace;
}
int main() {
using std::cout;
using std::endl;
MySpace::AlienSpace::name = "Alien";
MySpace::AlienSpace::age = 23;
MySpace::name = "MySpace";
MySpace::age = 26;
cout << "Alien name = "
<< MySpace::AlienSpace::name //"1."
<< "\n Alien age = "
<< MySpace::AlienSpace::age <<endl;
cout << "MySpace name = " //"2. "
<< MySpace::name // 與 KyleSpace name 衝突,取用 MySpace name
<< "\n MySpace age = "
<< MySpace::age <<endl; // 與 KyleSpace age 衝突,取用 MySpace age
return 0;
}
```
1. 可有巢狀的命名空間,要指定也可巢狀指定
2. 當**巢狀空間內產生命名衝突時,以最外部的命名會覆蓋內部命名**,導致 `using namespace Kyle` 沒有被指定到
**--實作--**
> ![](https://i.imgur.com/0mWZBPm.png)
2. **無名空間** : **特點無法使用 using 指定**,拿來取代內部連結靜態變數 (也就是全域變數),不同檔案之間可以定義同內容的變數
> 全域變數、無名空間兩者不能同時存在重複的名稱,否則會造成模糊,無法判定
```cpp=
#include <iostream>
// static char* name; // 被 namespace 取代
// static int age;
extern void namespace5();
namespace {
const char* name;
int age;
}
int main() {
using std::cout;
using std::endl;
name = "michelle";
age = 13;
cout << "name = "
<< name
<< "\n age = "
<< age <<endl;
return 0;
}
// -------------------------------------------- 另一個檔案
#include <iostream>
namespace {
const char* name;
int age;
}
void namespace5() {
::name = "namespace5";
::age = 123;
std::cout << ::name << ", " << ::age << std::endl;
}
```
**--實作--**
> ![](https://i.imgur.com/ymPuD7O.png)
3. 直接從已有 namespace 設定新 namespace (簡化 namespace)
```cpp=
#include <iostream>
namespace Book {
char* name;
int page;
}
namespace redefine = Book;
int main() {
// namespace redefine = Book; 定義在 Function 內也是可以的 ~
using namespace std;
redefine::name = "Cpp";
redefine::page = 2110;
cout << "Book name: " << redefine::name << ", page:" << redefine::page << endl;
return 0;
}
```
> ![](https://i.imgur.com/zfhS00S.png)
### namespace 重複定義
* 名稱空間可以重複定義,但 **重複的名稱空間內不能有相同的變數宣告**
```cpp=
#include <iostream>
namespace x{
int a;
}
namespace x {
int x; // ok~
// int a; // fail~ 名稱重複 redefinition
}
int main() {
using std::cout;
using std::endl;
x::a = 10;
x::x = 123;
cout << "x::a = "
<< x::a
<< "\n x::x = "
<< x::x <<endl;
return 0;
}
```
> ![](https://i.imgur.com/AieaL0l.png)
### 名稱空間 - 整理
| 命名空間種類 | 功能 | 補充 |
| -------- | -------- | -------- |
| ==**已命名空間**== | 取代外部連結函數、變數 | |
| ==**匿名命名空間**== | 取代內部靜態變數 | static 關鍵字 |
* using 宣告比起 using 指令安全,因為單個匯入比較不會有名稱衝突問題
* **名稱空間最大的用意在於,幫助我們==簡化大型程式專案的管理==**
## Appendix & FAQ
:::info
:::
###### tags: `C++`