一、初始化
C++中的初始化指為變量賦予初始值的過程。初始化方式多樣,適用于不同場景。
char cha=0;
char chb={'0'};
char chc=('\0);
char chd=cha;
char che{};
注意事項
- 優先使用列表初始化(
{}
),避免窄化轉換風險。 - 在c++11中{ }在變量,指針,數組,結構體上達到統一。
- { }對數據進行嚴格的數據類型檢查,而在c語言中檢查不嚴格。
- 類成員初始化推薦使用成員初始化列表,效率高于構造函數內賦值。
- 動態內存需手動管理,避免內存泄漏。
二、輸入輸出
c語言的輸入輸出
C語言中標準輸入輸出主要通過stdio.h
頭文件提供的函數實現:
格式化輸入輸出
printf()
:格式化輸出到標準輸出(屏幕)scanf()
:從標準輸入(鍵盤)讀取格式化輸入
#include <stdio.h>
int main() {int num;printf("Enter an integer: ");scanf("%d", &num);printf("You entered: %d", num);return 0;
}
注意事項
- 使用
scanf()
時注意變量前的&
符號(數組名除外) - 輸出浮點數時可使用
%.2f
等格式控制小數位數 - 輸入輸出對數據類型太依賴,初始化的數據類型,輸出也是該類型
c++的輸入輸出
C++ 的輸入輸出主要通過標準庫 <iostream>
提供,包含 cin
(標準輸入)和 cout
(標準輸出)等對象。
頭文件引入
#include <iostream>
using namespace std; // 避免每次寫 std::
標準輸出(cout):cout
用于向控制臺輸出數據,使用 <<
運算符連接內容。
int num = 42;
cout << "Hello, World!" << endl; // endl 換行
cout << "Number: " << num << "\n"; // "\n" 也可換行
格式化輸出:通過 <iomanip>
實現格式控制(如小數位數、對齊):
#include <iomanip>
double pi = 3.1415926;
cout << fixed << setprecision(2) << pi; // 輸出 3.14
?
標準輸入(cin):cin
用于從控制臺讀取數據,使用 >>
運算符分隔輸入。
int age;
string name;
cout << "Enter name and age: ";
cin >> name >> age; // 輸入 "Alice 25"
處理字符串輸入:cin
以空格為分隔符,讀取整行需用 getline
:
string fullName;
cin.ignore(); // 清除緩沖區殘留的換行符
getline(cin, fullName); // 讀取整行
?
總結
- 輸入輸出只針對于基本的數據類型,不包含自定義的類型
- endl相當于'\n'
三、const關鍵字
const關鍵字在C和C++中的區別
總結
const在修飾的整型類型常量時,在讀取其值的時候,都會在編譯的時候將其常量值替換掉;相當于c語言的 #define IP 3.14
而在c語言中,int b=a;從a這個存儲單元取數據放入寄存器中,再將寄存器的值給b這個變量,因此每次都從內存中取而不是替換。作用于編譯的時候做出檢查。a是常量不允許修改,但在運行時a并不是常量值,而是從內存空間中取,這導致在c語言中不能用a定義數組,a仍然是一個變量。
作用域差異
在C語言中,const
修飾的變量默認是外部鏈接(external linkage),除非顯式使用static
限定。在C++中,const
修飾的變量默認是內部鏈接(internal linkage),類似于static
的效果。若需在C++中實現外部鏈接,需顯式添加extern
關鍵字。
初始化要求
C++要求const
變量必須在聲明時初始化,否則會編譯錯誤。C語言允許const
變量稍后初始化,但未初始化的const
變量可能導致未定義行為。
數組大小聲明
C++允許使用const
變量聲明數組大小,且該變量被視為編譯時常量表達式。C語言中,即使使用const
變量,數組大小仍需通過宏或字面量定義,const
變量在C中不被視為常量表達式。
// C++合法,C不合法
const int size = 10;
int arr[size];
指針與常量的關系
C和C++均支持指向常量的指針和常量指針,但類型檢查嚴格性不同。C++禁止將非const
指針賦給const
指針(隱式轉換),而C允許(可能引發警告)。
// C++報錯,C允許(帶警告)
int* p;
const int* cp = p;
與宏的替代關系
C++中const
可完全替代宏定義常量,支持類型檢查和作用域規則。C語言中const
無法完全替代宏,例如在case
語句或位域長度中仍需使用宏。
C++中的擴展用法
C++允許const
用于類成員函數(const
成員函數),表明該函數不修改對象狀態。C語言無此功能,因其不支持面向對象。
class Example {
public:void foo() const; // 合法C++,不修改成員變量
};
類型安全
C++中const
是類型系統的一部分,參與函數重載和模板推導。C語言中const
更多是編譯期提示,缺乏嚴格的類型檢查。
const和指針的區別
在C++中,const
和指針的關系涉及指針的指向和指向內容的可變性,主要通過const
的位置區分。以下是具體區別:
const修飾指針指向的內容
聲明方式為const T*
或T const*
(兩者等價),表示指針指向的內容不可修改,但指針本身可以重新指向其他地址。
int a = 10, b = 20;
const int* ptr = &a;
// *ptr = 30; // 錯誤:內容不可修改
ptr = &b; // 正確:指針可以指向其他地址
const修飾指針本身
聲明方式為T* const
,表示指針本身不可修改(必須初始化),但指向的內容可以修改。
int a = 10, b = 20;
int* const ptr = &a;
*ptr = 30; // 正確:內容可修改
// ptr = &b; // 錯誤:指針不可重新指向
雙const修飾
聲明方式為const T* const
,表示指針本身和指向的內容均不可修改。
int a = 10, b = 20;
const int* const ptr = &a;
// *ptr = 30; // 錯誤:內容不可修改
// ptr = &b; // 錯誤:指針不可重新指向
區別總結
const T*
:內容只讀,指針可變。T* const
:指針只讀,內容可變。const T* const
:指針和內容均為只讀。
舉例
//哪些正確,哪些錯誤?
int a = 10, b = 20;
const int* p = &a; // p是指向常量的指針,不能通過p修改a的值
int* s1 = p; // 錯誤:無法將const int*隱式轉換為int*
const int* s2 = p; // 正確:s2與p類型相同,均為指向常量的指針
int* const s3 = p; // 錯誤:s3是常量指針,但類型不匹配(無法舍棄const)
const int* const s4 = p; // 正確:s4是指向常量的常量指針,與p類型兼容
int a = 10, b = 20;
int* const p = &a;
int* s1 = p;
const int* s2 = p;
int* const s3 = p;
const int* const s4 = p;//可以編譯通過
四、*和?&
*
int c = a * b;//乘法運算符
double* p = &a;//指針的修飾符
*p = 100.100;//解引用
&
a& b;//位與運算符
int* p = &c;//取地址
int& x = a;//引用,別名
注意事項:
當&在引用時,沒有空引用;必須初始化;沒有引用的引用;?
左值引用:可以取地址的標識符是左值引用,&
右值引用:不能取地址的引用。內置類型所產生的右值都有常性,即只可讀不可取,&&
const引用?
const int &;
- 常引用或者萬能引用
- 既可引用左值引用,又可引用右值引用
- 只可以去獲取值,但不可以修改值
int const &;
引用作為形參代替指針
使用指針交換兩個整形值。c語言實現:
void my_swap(int* a, int* b) {assert(a != NULL && b != NULL);int tmp = *a;*a = *b;*b = tmp;
}
int main() {int a = 10, b = 20;cout << "a = " << a << "b = " << b << endl;my_swap(&a, &b);cout << "a = " << a << "b = " << b << endl;return 0;
}
使用引用交換兩個整形值。c++實現
不存在NULL引用,不需要判空,比指針安全。
void my_swap2(int &x, int &y) {int tmp = x;x = y;y = tmp;
}
int main() {int a = 10, b = 20;cout << "a = " << a << "b = " << b << endl;my_swap(&a, &b);cout << "a = " << a << "b = " << b << endl;return 0;
}
?引用的其它格式
失效指針:失效指針(Dangling Pointer)是指向已釋放或無效內存地址的指針。當指針指向的內存被釋放后,指針本身未置空,仍保留原地址,此時訪問該指針會導致未定義行為(如程序崩潰、數據錯誤等)。
失效指針的常見場景
動態內存釋放后未置空
使用free
或delete
釋放內存后,指針未設置為NULL
,導致后續訪問失效內存。int *ptr = (int *)malloc(sizeof(int)); free(ptr); // ptr 成為失效指針 *ptr = 10; // 危險操作!
局部變量作用域結束
指針指向函數內的局部變量,函數返回后局部變量被銷毀,指針變為失效。int *getLocalPointer() {int x = 5;return &x; // 返回局部變量的地址 } int *ptr = getLocalPointer(); // ptr 指向已釋放的棧內存
對象生命周期結束
C++ 中對象被銷毀后,指向其成員或自身的指針變為失效。class MyClass { public:int val; }; MyClass *obj = new MyClass(); delete obj; // obj 成為失效指針 obj->val = 42; // 未定義行為
避免失效指針的方法
釋放后置空
釋放內存后立即將指針設為NULL
或nullptr
,后續可通過判空避免誤用。free(ptr); ptr = NULL; // 安全措施
使用智能指針(C++)
std::shared_ptr
或std::unique_ptr
自動管理內存生命周期,避免手動釋放導致的失效問題。std::shared_ptr<int> ptr = std::make_shared<int>(42); // 無需手動釋放,超出作用域后自動銷毀
避免返回局部變量地址
若需返回指針,應使用動態分配內存或靜態變量。int *getSafePointer() {int *x = (int *)malloc(sizeof(int));*x = 5;return x; // 返回堆內存地址 }
靜態分析工具
使用 Valgrind、AddressSanitizer 等工具檢測代碼中的失效指針問題。
指針和解引用的區別
語法規則:
- 從語法規則上講,指針變量存儲某個實例(變量或對象)的地址;引用是某個實例的別名
- 程序為指針變量分配內存區域;而不為引用分配內存區域。
- 解引用是指針使用時要在前加“*”;引用可以直接使用。
- 指針變量的值可以發生改變,存儲不同實例的地址;引用在定義時就被初始化,之后無法改變(不能是其他實例的引用)。
- 指針變量的值可以為空(NULL,nulptr);沒有空引用。
- 指針變量作為形參時需要測試它的合法性(判空NULL);
- 引用不需要判空對指針變量使用“sizeof"得到的是指針變量的大小。對引用變量使用"sizeof"得到的是變量的大小。
- 理論上指針的級數沒有限制;但引用只有一級。即不存在引用的引用,但可以有指針的指針。
- ++引用與++指針的效果不一樣。例如就++操作而言會使指針變量指向下一個實體(變量或對象)的地址;而不是改變所指實體(變量對指針變量的操作,今或對象)的內容。
- 對引用的操作直接反應到所引用的實體(變量或對象)
- 不可以對函數中的局部變量或對象以引用或指針方式返回。
引用作為函數的返回值類型
不能對函數中的局部變量或對象以引用或指針的方式返回。
語法糖:
語法糖(Syntactic Sugar)指簡化代碼表達的語言特性,使代碼更簡潔易讀。
五、缺省參數?
- 缺省參數指在定義函數時為形參指定缺省值(默認值)。
- 這樣的函數在調用時,對于缺省參數,可以給出實參值,也可以不給出參數值。如果給出實參,將實參傳遞給形參進行調用,如果不給出實參,則按缺省值進行調用。
- 缺省參數的函數調用:缺省實參并不一定是常量表達式,可以是任意表達式,甚至可以通過函數調用給出。如果缺省實參是任意表達式,則函數每次被調用時該表達式被重新求值。但表達式必須有意義;
- 缺省參數可以有多個,但所有缺省參數必須放在參數表的右側,即先定義所有的非缺省參數,再定義缺省參數。這是因為在函數調用時,參數自左向右逐個匹配,當實參和形參個數不一致時只有這樣才不會產生二義性。
- 習慣上,缺省參數在公共頭文件包含的函數聲明中指定,不要函數的定義中指定。
如果在函數的定義中指定缺省參數值,在公共頭文件包含的函數聲明中不能再次指定缺省參數值。缺省實參不一定必須是常量表達式可以使用任意表達式。 - 當缺省實參是一個表達式時在函數被調用時該表達式被求值。
- C語言不支持
1、缺省參數的語法規則
- 缺省參數在函數聲明中指定,格式為
類型 參數名 = 默認值
。 - 若函數有多個參數,缺省參數必須從右向左連續定義。非缺省參數不能出現在缺省參數右側。
void func(int a, int b = 10, int c = 20); // 正確
void func(int a = 5, int b, int c); // 錯誤:非缺省參數b在缺省參數a右側
2、缺省參數的使用示例
#include <iostream>
using namespace std;// 函數聲明時指定缺省參數
void printMessage(const string& msg = "Hello, World!") {cout << msg << endl;
}int main() {printMessage(); // 輸出: Hello, World!printMessage("Hi!"); // 輸出: Hi!return 0;
}
3、缺省參數的注意事項
聲明與定義分離時:缺省參數僅在函數聲明中指定,定義時不能重復指定。
// 聲明(含缺省參數) void logError(const string& file, int line = -1);// 定義(不能重復缺省值) void logError(const string& file, int line) { /* ... */ }
作用域規則:同一作用域內,缺省參數只能指定一次。多次聲明同一函數時,后續聲明可為之前未指定缺省的參數添加默認值,但不能修改已存在的缺省值。
void foo(int x, int y = 10); // 首次聲明 void foo(int x = 5, int y); // 合法:補充x的缺省值 void foo(int x = 8, int y); // 錯誤:試圖修改x的缺省值
避免二義性:缺省參數可能導致函數重載沖突。
void bar(int a); void bar(int a, int b = 0); bar(10); // 錯誤:調用歧義
與函數指針兼容性:函數指針類型必須嚴格匹配參數列表,包含缺省參數信息。
缺省參數的典型應用場景
- 簡化接口:為常用參數提供默認值,減少冗余調用代碼。
- 擴展函數功能:通過逐步添加缺省參數擴展函數,避免破壞現有代碼。
六、函數重載
?
? ? ?函數重載的概念
函數重載(Function Overloading)是指在同一個作用域內,允許定義多個同名函數,但這些函數的參數列表(參數類型、數量或順序)必須不同。編譯器根據調用時提供的實參類型和數量,自動匹配最合適的函數版本。
函數重載的核心規則
如果兩個函數的參數表相同,但返回類型不同,會被標記為編譯錯誤:函數重復聲明
參數表的比較過程與形參名無關
如果在兩個函數的參數列表中,只有缺省實參不同,則第二個聲明被視為第一個的重復聲明
typedef名為現有的數據類型提供了一個替換名,它并沒有創建一個新類型,因此,如果兩個函數參數表的區別只在于一個使用了typedef,而另一個使用了與typedef相應的類型。則該參數表被視為相同的參數列表。
當一個形參類型有const或volatile修飾時,如果形參是按值傳遞方式定義,在識別函數聲明是否相同時,并不考慮const和volatile修飾符.
當一個形參類型有const或volatile修飾時,如果形參定義指針或引用時,在識別函數聲明是否相同時,就要考慮const和volatile修飾符.
注意函數調用的二義性;如果在兩個函數的參數表中,形參類型相同,而形參個數不同,形參默認值將會影響函數的重載.
函數重載解析的步驟
- 確定函數調用考慮的重載函數的集合,確定函數調用中實參表的屬性.
- 從重載函數集合中選擇函數,該函數可以在(給出實參個數和類型)的情況下可以調用函數.
- 選擇與調用最匹配的函數.
函數重載的注意事項
- 默認參數與重載的沖突:默認參數可能導致函數調用歧義。
void func(int a, int b = 0); void func(int a); func(10); // 編譯錯誤:無法確定調用哪個版本
- 類型轉換的影響:隱式類型轉換可能干擾重載匹配。
void print(int x); void print(double x); print(3.14f); // 可能匹配double版本而非預期float
函數重載的應用場景
- 處理不同類型數據:如數學運算支持
int
、float
等。 - 簡化接口設計:同名函數提供不同參數組合的調用方式。
- 構造函數重載:類通過不同參數列表實現多種初始化方式。
?
?
?