上一篇博客中只是瞭解一下java文件是怎麼編譯的,但是一般來說大家都是使用編程軟體來進行開發,我是使用IntelliJ IDEA進行開發的官網下載IDEA(自行安裝哈):地址:https://www.jetbrains.com/idea/download/other.html使用IDEA我使用的id ...
C++初探索
前言
C++ 和 C 的區別主要在8個方面:
- 輸入和輸出
- 引用
- inline函數
- 函數預設值
- 函數重載
- 模板函數
- new 和 delete
- namespace
我僅對印象不深的地方做了總結。
目錄
一、引用初探索
1.引用的定義與區別
定義:類型& 引用變數的名稱 = 變數名稱
'&' 不是取地址符嗎,怎麼又成為引用了呢?下麵將常見的 '&' 做一個區分:
C中的 '&'
c = a && b; //此處 && 是 邏輯與
c = a & b; //此處的 & 是 按位與
int *p = &a; //此處的 & 是 取地址符
int &x = a; //此處的 & 是 引用
void fun(int &a); //此處的 & 也是引用
疑問:int &fun()
這個是函數的引用嗎?
回答:不是函數的引用,表示函數的返回值是一個引用。
2.引用的要求
- ①當定義引用時,必須初始化
//正確用法: int a = 10; int &x = a;
- ② 沒有引用的引用(沒有二級引用)
//錯誤用法: int &x = a; int &&y = x;
- ③ 沒有空引用
int &x; //error:不存在空引用
3.引用與指針的區別
引用 | 指針 |
---|---|
必須初始化 | 可以不初始化 |
不可為空 | 可以為空 |
不能更換目標 | 可以更換目標 |
沒有二級引用 | 存在二級指針 |
-
1、引用必須初始化,而指針可以不初始化
int &s; //error:引用沒用初始化 int *p; //right:指針不強制初始化
-
2、引用不可以為空,指針可以為空
int &s = NULL; //error:引用不可以為空,右值必須是已經定義過的變數名 int *p = NULL; //right:可以定義空指針。 int fun_p(int *p) { if(p != NULL) //因為指針可以為空,所以在輸出前需要判斷。 { cout << *p << endl; } return *p; } int fun_s(int &s) { cout << s << endl; //引用不為空,可以直接輸出 return s; }
-
3、引用不能改變目標,指針可以更換目標
int main() { int a = 20; int b = 10; int &s = a; int *p = &a; s = b; //引用只能指向初始化時的對象,如果改變,原先對象也跟著改變。 p = &b; //指針可以改變指向的對象,不改變原先對象。 cout << s << endl; cout << a << endl; return 0; }
4.常引用
我們可以能力收縮,不可能力擴展
//error:能力擴展
int a = 10;
int& b = a; //a b c都是一個東西
const int& c = a; //常引用
b += 10;
a += 100; //可以通過a b 去修改a 和 b
//c += 100; //error:不可通過 c 來修改 a 或者 b
//能力收縮
const int a = 100;
int& b = a; //不可編譯成功
//a += 100; //error:a 自身不可改變
b += 100;
int a = 100;
const int& x = a; //可以編譯成功
5.何時使用引用
引用可以作為函數參數
void Swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 10, y = 20;
Swap(x, y); //僅僅Swap內部交換,並不能影響到實參
}
//加上引用,對a和b的改變會影響實參
void Swap(int &a, int &b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 10, y = 20;
Swap(x, y); //僅僅Swap內部交換,並不能影響到實參
}
二、inline內聯函數
1.內聯函數的定義
為瞭解決一些頻繁調用小函數消耗大量棧空間的問題,引入了inline內聯函數。
inline int fun(int a ,int b)
{
return a + b;
}
2.內聯函數的處理流程
處理步驟
- 將 inline 函數體複製到 inline 函數調用點處;
- 為所用 inline 函數中的局部變數分配記憶體空間;
- 將 inline 函數的的輸入參數和返回值映射到調用方法的局部變數空間中;
- 如果 inline 函數有多個返回點,將其轉變為 inline 函數代碼塊末尾的分支(使用 GOTO)。
int fun(int a,int b)
{
return a + b;
}
//普通調用
int main()
{
int a = 10;
int b = 20;
int c = fun(10,20);
}
//內聯函數
int main()
{
int a = 10;
int b = 20;
int c = 10 + 20; //此處相當於直接展開函數
}
3.內聯函數與三者的區別
3.1 與普通函數的區別
內聯函數在函數的調用點直接展開代碼,沒有開棧和清棧的開銷。普通函數有開棧和清棧的開銷。
內聯函數要求代碼簡單,不能包含複雜的結構控制語句。
若內聯函數體過於複雜,編譯器將自動把內聯函數當成普通函數來執行。
3.2 與static函數的區別
static修飾的函數處理機制只是將函數的符號變成局部符號,函數的處理機制和普通函數相同,都有函數的開棧和清棧的開銷。內聯函數沒有函數的開棧和清棧的開銷。
inline函數是因為代碼直接在調用點展開導致函數只在本文件可見。而static修飾的函數是因為函數法符號從全局符號變成局部符號導致函數本文件可見。
3.3 與巨集定義的區別
inline函數的處理時機是在編譯階段處理的,有安全檢查和類型檢查。而巨集的處理是在預編譯階段處理的。沒有任何的檢查機制,只是簡單的文本替換。
inline函數是一種更安全的巨集。
4.僅realese版本才會產生內聯
代碼如下:
inline int Add_Int(int x, int y)
{
return x + y;
}
int main()
{
int a = 10, b = 20;
int c = 0;
c = Add_Int(a, b);
cout << c << endl;
return 0;
}
debug版本的反彙編:仍是以函數調用的形式
release版本的反彙編:在編譯時期展開
5.inline函數使用的限制
-
一般寫在頭文件中
-
只在release版本中生效
-
inline只是一個建議,是否處理由編譯器決定
如果函數體內的代碼比較長,使得內聯將導致記憶體消耗代價比較高。 如果函數體內出現迴圈,那麼執行函數體內代碼的時間要比函數調用的開銷大。
-
基於實現的,不是基於聲明的
int fun(int x,int y); // 函數聲明 inline int fun(int x,int y) // 函數定義 { return x+y; } // 定義時加inline關鍵字 inline void fun1(int x) { }
三、函數的重載
在C語言中,函數名 是 函數的唯一標識
在C++中,函數原型 是 函數的標識。
函數原型
函數原型 = 函數返回類型 + 函數名 + 形參列表(參數的類型和個數)
使用extern關鍵字指定為C語言編譯
extern"C" int Max(int a, int b)
{
return a > b ? a : b;
}
extern"C" int fun(int a, int b)
{
return a + b;
}
int main()
{
Max(10, 20);
fun(20, 30);
return 0;
}
VS2022中
以C語言編譯:函數名仍是原來的函數名
以C++編譯:也是一樣的情況
將函數的形參類型進行修改:
int Max(int a, int b)
{
return a > b ? a : b;
}
int Max(double a, int b)
{
return a > b ? a : b;
}
int main()
{
Max(10, 20); //編譯正常
Max(10.00, 20); //編譯正常
return 0;
}
發現函數名仍是一樣,但是存放double類型的寄存器與存放int類型的寄存器不一樣
再將返回值類型也進行修改:
int Max(int a, int b)
{
return a > b ? a : b;
}
double Max(double a, int b)
{
return a > b ? a : b;
}
int fun(int a, int b)
{
return a + b;
}
int main()
{
Max(10, 20); //編譯正常
Max(10.10, 20); //編譯正常
return 0;
}
發現並沒有任何問題,調用的也並非是同一個函數
在VS2019中:
以C語言編譯:函數名前加 _ ,_fun 和 _Max
以C++編譯:沒有下劃線,與VS2022中C++編譯相同
在VC6.0中:
int Max(int a, int b)
{
return a > b ? a : b;
}
double Max(double a, double b)
{
return a > b ? a : b;
}
char Max(char a, char b)
{
return a > b ? a : b;
}
int main()
{
Max(10, 20);
Max(10.0, 10.0);
Max('a','b');
return 0;
}
返回值為int 類型的Max函數:
返回值為double類型的Max函數:
返回值為char類型的Max函數:
為什麼函數名發生了如此大的改變
?
這個就是名字粉碎技術
四、函數模板
模板的定義:
template <模板參數名>
返回類型 函數名(形式參數表)
{
//函數體
}
<模板參數表> 尖括弧中不能為空,參數可以有多個,用,隔開
template<class Type>
void Swap(Type& a, Type& b)
{
Type tmp = a;
a = b;
b = tmp;
}
註意1
:<>中只能以C++的方式寫,不能出現如 template <struct Type>
註意2
:下麵調用Swap不是簡單的替換(巨集替換),是重命名規則
//普通類型
template <class Type> typedef int Type;
void Swap(Type &a ,Type &b) void Swap<int>(Type &a,Type &b)
{ {
Type tmp = a; Type tmp = a;
a = b; a = b;
b = tmp; b = tmp;
} }
int main()
{
int a = 10, b = 20;
Swap(a, b); //此處的調用如同右邊函數
}
//指針類型
template<class Type> typedef int Type
void fun(Type p) void fun<int *>(Type p)
{ {
Type a, b; Type a, b;
} }
int main()
{
int x = 10;
int* ip = &x;
fun(ip);
return 0;
}
註意3
:編譯時進行重命名,非運行時。
五、new和malloc
//C語言與C++申請空間:
int main()
{
int n = 10;
//C:malloc free
int* ip = (int*)malloc(sizeof(int) * n);
//地址空間與NULl進行對比,判斷是否申請失敗
if (NULL == ip) exit(1);
free(ip);
ip = NULL;
//C++:new delete
ip = new int;
*ip = 100;
delete ip;
ip = NULL;
}
//new申請連續空間
int main()
{
//new申請連續空間,要釋放連續空間
ip = new int[n];
//此處的delete不是把ip刪除,而是將ip指向堆區的空間換給系統
delete[]ip;
}
new申請失敗時候的處理:
//錯誤處理:
ip = new int;
if (ip == NULL) exit(1);
delete ip;
ip == NULL;
//new 如果分配記憶體失敗,預設是拋出異常的。
//分配成功時,那麼並不會執行 if (ip == NULL)
//若分配失敗,也不會執行 if (ip == NULL)
//分配失敗時,new 就會拋出異常跳過後面的代碼。
//正確處理1:強制不拋出異常
int *ip = new (std::nothrow) int[n];
if (ip == NULL)
{
cout << "error" << endl;
}
//正確處理2:捕捉異常
try
{
int* ip = new int[SIZE];
}
catch (const bad_alloc& e)
{
return -1;
}
總結
仍遺留下很多問題:
- new和malloc的區別
- 命名空間
- 引用的深入
- const