C++基礎問題
掌握形參默認帶缺省值的函數
函數調用時
#include <iostream>int sum(int a, int b = 20) {return a + b;
}int main() {int a = 10, b = 20;int ret = sum(a, b);cout << "ret: " << ret << endl;ret = sum(a);/*a 使用默認值壓棧: 時壓入 a 的值和 20.push 14Hmov ecx, dowrd ptr[ebp - 4]push ecxcall sum*/ret = sum();// 壓入 10 和 20.
}
總的來說,函數效率有所增長:減少一次 push 指令。
帶有缺省的函數的聲明
// 一個缺省只能被聲明一次,并且只能被從右向左聲明
#if 1 // 編譯通過
int sum(int a = 10, int b = 20);
#elif // 報錯: 默認值只能給一次
int sum(int a = 10, int b = 20);
int sum(int a, int b = 20);
#elif // 編譯通過
int sum(int a, int b = 20);
int sum(int a = 10, int b);
#endifint main() {int a = 10, b = 20;int ret = sum(a, b);cout << "ret: " << ret << endl;ret = sum(a);/*a 使用默認值壓棧: 時壓入 a 的值和 20.push 14Hmov ecx, dowrd ptr[ebp - 4]push ecxcall sum*/ret = sum();// 壓入 10 和 20.
}int sum(int a, int b) {return a + b;
}
掌握內聯函數
內聯函數和普通函數的區別?
- 內聯函數:在編譯過程中沒有函數調用的開銷,因為在函數的調用點函數被直接展開處理。
- 內聯函數將不再產生相應的函數符號。
- 函數定義時加上
inline
并不一定會讓函數變成內聯函數,僅僅是對編譯器的一種建議。如遞歸很可能不會被處理為內聯函數。
#include <iostream>using namespace std;#define IS_INLINE 1#if IS_INLINE
inline
#endifint sum(int a, int b = 20) {return a + b;
}int main() {int a = 10, b = 20;int ret = sum(a, b);// 此處有標準的函數調用過程// 當函數調用的開銷的占比過高,建議使用內聯函數cout << "ret: " << ret << endl;}
注意:inline
在 debug 版本上是不起作用的。
驗證:inline
在 release 版能出現
chipen@ubuntu:~/code/inlineTest$ cat main.cpp
#include <iostream>using namespace std;int sum(int a, int b) {return a + b;}int main() {int a = 10, b = 20;int ret = sum(a, b);return 0;}
chipen@ubuntu:~/code/inlineTest$ g++ -c main.cpp -O2
chipen@ubuntu:~/code/inlineTest$ objdump -t main.omain.o: file format elf64-x86-64SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.cpp
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .text.startup 0000000000000000 .text.startup
0000000000000000 *UND* 0000000000000000 _ZSt21ios_base_library_initv
0000000000000000 g F .text 0000000000000008 _Z3sumii # sum 函數的符號
0000000000000000 g F .text.startup 0000000000000007 mainchipen@ubuntu:~/code/inlineTest$ vim main.cpp # 給 sum 函數加上 inline
chipen@ubuntu:~/code/inlineTest$ g++ -c main.cpp -O2
chipen@ubuntu:~/code/inlineTest$ objdump -t main.omain.o: file format elf64-x86-64SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.cpp
0000000000000000 l d .text.startup 0000000000000000 .text.startup
0000000000000000 *UND* 0000000000000000 _ZSt21ios_base_library_initv
0000000000000000 g F .text.startup 0000000000000007 main
# 可見,添加內聯以后,sum 函數不再有對應的符號,而是被直接替換
chipen@ubuntu:~/code/inlineTest$ g++ -c main.cpp -O0
chipen@ubuntu:~/code/inlineTest$ objdump -t main.omain.o: file format elf64-x86-64SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.cpp
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .text._Z3sumii 0000000000000000 .text._Z3sumii
0000000000000000 l O .rodata 0000000000000001 _ZNSt8__detail30__integer_to_chars_is_unsignedIjEE
0000000000000001 l O .rodata 0000000000000001 _ZNSt8__detail30__integer_to_chars_is_unsignedImEE
0000000000000002 l O .rodata 0000000000000001 _ZNSt8__detail30__integer_to_chars_is_unsignedIyEE
0000000000000000 *UND* 0000000000000000 _ZSt21ios_base_library_initv
0000000000000000 w F .text._Z3sumii 0000000000000018 _Z3sumii
0000000000000000 g F .text 0000000000000033 main
# 在只用 -O0 的低優化等級時,inline 需求依然被忽略,可見 inline 只是一種建議行為
函數重載
chipen@ubuntu:~/code/inlineTest$ cat test01.cpp
#include <iostream>
#include <cstring>using namespace std;bool compare(int a, int b) {cout << "int, int" << endl;return a > b;
}bool compare(double a, double b) {cout << "double, double" << endl;return a > b;
}bool compare(const char *a, const char *b) {cout << "const char*, const char*" << endl;return strcmp(a, b) > 0;
} int main() {compare(10, 20);compare(10.0, 20.0);compare("hello", "world");return 0;
}
chipen@ubuntu:~/code/inlineTest$ ./test01
int, int
double, double
const char*, const char*
注意1: 一組重載函數指的是: 在同一作用域下的, 函數名相同作用域不同的函數
如當嘗試在 main 函數中添加如下聲明
chipen@ubuntu:~/code/inlineTest$ cat test01.cpp
#include <iostream>
#include <cstring>using namespace std;bool compare(int a, int b) {cout << "int, int" << endl;return a > b;
}bool compare(double a, double b) {cout << "double, double" << endl;return a > b;
}bool compare(const char *a, const char *b) {cout << "const char*, const char*" << endl;return strcmp(a, b) > 0;
} int main() {bool compare(int a, int b);compare(10, 20);compare(10.0, 20.0);compare("hello", "world");return 0;
}
chipen@ubuntu:~/code/inlineTest$ g++ test01.cpp -o test01
test01.cpp: In function ‘int main()’:
test01.cpp:26:17: error: invalid conversion from ‘const char*’ to ‘int’ [-fpermissive]26 | compare("hello", "world");| ^~~~~~~| || const char*
test01.cpp:23:26: note: initializing argument 1 of ‘bool compare(int, int)’23 | bool compare(int a, int b);| ~~~~^
test01.cpp:26:26: error: invalid conversion from ‘const char*’ to ‘int’ [-fpermissive]26 | compare("hello", "world");| ^~~~~~~| || const char*
test01.cpp:23:33: note: initializing argument 2 of ‘bool compare(int, int)’23 | bool compare(int a, int b);| ~~~~^
注意2:同一類型,加不加const
在編譯器眼中沒有區別。
void func(int a) {}
void func(const int a) {}
// 報錯void func(int *a) {}
void func(const int *a) {}
// 編譯通過void func(int *a) {}
void func(int const *a) {}
// 報錯
為什么 C++ 支持函數重載,C 語言不支持函數重載?
在對符號表中函數命名時,C++采取了更能準確描述一個函數的命名方式,而 C 語言直接用函數名字作為符號名。
因此,C 和 C++ 中的函數由于函數名不同,不能直接調用,會在鏈接時發生無法解析的外部符號的錯誤,因為在符號表中找不到對應的函數符號名。
要實現 C 和 C++ 之間都可以調用的函數,應該這樣定義
#ifdef __cplusplus
extern "C" {
#endif
int sum(int a, int b) {return a + b;
}
#ifdef __cplusplus
}
#endif
掌握const
的用法
基本理解:const
修飾的常變量不能作為左值使用。
常變量 != 常量,例如:
chipen@ubuntu:~/code/inlineTest$ cat test02.cpp
#include <cstdio>int main() {const int a = 20;int *p = (int *)&a;*p = 30;printf("%d %d %d\n", a, *p, *(&a));return 0;
}
chipen@ubuntu:~/code/inlineTest$ ./test02
20 30 30
這是個不可思議的現象。原因是:在編譯器的前端階段,a 被認為是不可能被修改的,故在后文中被做了直接替換,但是 *p, *(&a)
要求必須訪問內存,故得到了 20 30 30。
證明這是一種優化行為:
chipen@ubuntu:~/code/inlineTest$ cat test02.cpp
#include <cstdio>int main() {volatile const int a = 20; # 不讓編譯器優化 aint *p = (int *)&a;*p = 30;printf("%d %d %d\n", a, *p, *(&a));return 0;
}
chipen@ubuntu:~/code/inlineTest$ ./test02
30 30 30 # 符合預期
const
修飾的量常出現的錯誤是:
- 常量不能再作為左值 <= 直接修改常量的值
- 不能把常量的地址泄露給一個普通的指針或者普通的引用變量 <= 可能間接修改常量的值
const
和一級指針的結合:(const
修飾的是離它最近的類型)
const int * p
-> 指針指向的變量不能修改int const * p
-> 同上int *const p
-> 指針本身不能修改const int *const p
-> 指針本身和指針所指的變量都不能修改
int a = 10;
int *p1 = &a; // 通過, 無類型轉換
const int *p2 = &a; // 隱式類型轉換
int *const p3 = &a; // 隱式類型轉換
const int b = 10;
int *p4 = &b; // 不通過, (const int *) -> (int *)
const int *p5 = &b; // 通過
int *const p6 = &b; // 不通過, (const int *) -> (int *const)// 總結: 權限只能縮小不能擴大
const
和二級/多級指針的結合
int a = 20;
int *p = &a;
const int **p1 = &p;
// 典型錯誤,這樣賦值會讓原來的普通指針 p 指向被 const 修飾的變量
/*
正確寫法:
int a = 20;
const int *p = &a;
cosnt int **p1 = &p;
*/int *const *p2 = &p;
int **const p3 = &p;
左值引用和右值引用
int main() {int a = 10; // 左值,有地址有名字,值可以修改int &b = a;int &&c = 20; // 20 為右值,沒內存,沒名字c = 30; // 右值引用可以修改const int &b = 20;/*int temp = 20;temp -> b*/const int &d = 20;return 0;
}
右值引用:
-
int &&c = 20;
專門用來引用右值類型,指令上可以自動產生臨時量,然后直接引用臨時量。 -
右值引用變量本身是一個左值,只能用左值引用來引用它。
-
不能用一個右值引用變量引用一個左值。
const
、指針和引用的結合應用
int main() {// 寫一句代碼,在內存的 0x0018ff44 處寫一個 4 字節的 10int *p = (int *)0x0018ff44;int *&&p = (int *)0x0018ff44;int *const &p = (int *)0x0018ff44;return 0;
}
深入理解 c++ 的 new 和 delete
new 和 delete 為C++ 的運算符
malloc 和 free 為 C 的庫函數
開辟內存失敗時:
-
malloc 需要返回值與 nullptr 比較,為空時失敗。
-
new 會拋異常。
new 有多少種?
int *p1 = new int(20);
int *p2 = new (nothrow) int;
int *p3 = new const int(40);// 定位 new
int data = 0;
int *p4 = new (&data) int(50);
cout << "data: " << data << endl; // 輸出 data: 0