}
int main()
{
//參數(shù)順序不同
Print(5,'a');
Print('a', 5);
return 0;
}
注意: 如果兩個函數(shù)函數(shù)名和參數(shù)是一樣的,返回值不同是不構成重載的,因為調用時編譯器無法區(qū)分。
我們知道C語言不支持函數(shù)重載,在鏈接時,直接用函數(shù)名去尋找地址,當有同名函數(shù)的時候,是區(qū)分不開的。
C++是采用了名字修飾規(guī)則,函數(shù)名字引入?yún)?shù)類型(在不同平臺上的名字修飾規(guī)則是不同的)
在linux環(huán)境下,采用gcc編譯完成后,函數(shù)名字的修飾沒有發(fā)生改變 (編譯C語言)
采用C++編譯器編譯后結果 ( 編譯C++ )
g++ -o testcpp test.cc
objdump -S testcpp
我們可以看出gcc的函數(shù)修飾后名字不變。而g++的函數(shù)修飾后變成【_Z+函數(shù)長度+函數(shù)名+類型首字母】
在vs下的命名規(guī)則
windows下vs編譯器對函數(shù)名字修飾規(guī)則相對復雜難懂。
五、 引用
1. 引用的概念
引用(取別名):引用是給已經存在的變量取了一個別名,不是新定義一個變量,編譯器不會為引用變量開辟內存空間,和所引用的變量共用一塊內存空間。
例如:張三,小名叫:小張 。張三和小張都是只一個人。
類型& 引用變量名 = 引用實體;//這里引用變量名就是該變量的別名
例:
//引用
#include
using namespace std;
int main()
{
int a = 10;
int& b = a; //引用,b是a的別名
cout << b << endl;
return 0;
}
輸出結果: 10
我們通過打印a,b這兩個地址進行打印,查看地址是否一致
注意:引用類型必須和引用實體是同種類型的
2. 引用的特性
引用在定義時必須初始化一個變量可以有多個引用引用一旦引用一個實體,再不能引用其他實體
引用在定義時必須進行初始化 int& b; //這種寫法是錯誤的
int& b = a; //引用在定義時必須初始化
一個變量可以有多個引用(就像一個人的外號可以有多個,但是都是指的是同一個人) int a = 10;
int& b = a; //引用,b是a的別名
int& c = a; //引用,c也是a的別名
引用一旦引用一個實體,再不能引用其他實體 也就是當引用一個變量時,該引用不能再去引用其他變量 int a = 10;
int x = 20;
int& b = a; //引用,b是a的別名
int& b = x; //error ,這樣會導致重定義,多次初始化
關于常量引用
//int& z = 10; //error 常量不能引用
const int a = 100;
//int& b = a; //error,編譯時出錯,a為常量
const int& b = a; //正確寫法
int ra = 10;
//double& b = ra; //error, 引用類型不同
3. 引用的使用
(1) 引用作參數(shù)
例如: 交換兩個數(shù)
在C語言中,
void swap(int* x ,int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 10;
int b = 20;
swap(&a,&b); //把地址傳過去
return 0;
}
交換兩個數(shù)要把地址傳過去,形參是實參的臨時拷貝,只有把地址傳過去,參數(shù)才可以改變。
在C++中,使用引用,引用和原值是共用一塊內存空間
void swap(int& x,int &y) //引用做參數(shù)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10;
int b = 20;
swap(a,b);
return 0;
}
這里的x和y分別是a和b的別名
上述是給整型取別名,也可以給指針取別名
//void PushBack(struct Node** pphead,int x){} //C語言使用二級指針
void PushBack(struct Node*& phead,int x) //C++,使用引用給指針取別名
{
//phead = newnode;
//...
}
小總結:引用做參數(shù),a.做輸出型參數(shù) b.對象比較大時,減少拷貝,提高效率。
(2) 引用作返回值
int& func() //返回值類型 int& ,傳引用返回
{
int a = 1000;
return a;
}
int main()
{
int ret = func();
cout << ret << endl;
return 0;
}
int func 傳值返回時(返回的是拷貝),函數(shù)棧幀的銷毀,會導致a的值可能是隨機值。
int& func 傳引用返回,返回a的別名,但是指向的內存空間沒有改變,函數(shù)棧幀的銷毀,內存返回給操作系統(tǒng),引用的值也變成了隨機值。(出現(xiàn)了野引用)
什么情況下可以使用引用返回?
全局變量/靜態(tài)變量/堆上的空間等可以作引用返回
小總結:
傳值返回,傳的是返回值的拷貝;傳引用返回,傳的是返回值的別名。
返回變量出了函數(shù)作用域,生命周期就到了要銷毀 (局部變量),不能用引用返回。
引用作返回值 a.修改返回對象 b.減少拷貝調高效率
4. 引用和指針的區(qū)別
int main()
{
//引用
int a = 10;
int& ra = a; //語法層面不開空間
ra = 20;
//指針
int* pa = &a; //語法層面開空間
*pa = 20;
return 0;
}
語法上(規(guī)定用法) :
(1) 引用是別名, 不開空間,指針是地址, 需要開辟內存空間
(2) 引用必須初始化,指針可以初始化也可以不初始化
(3) 引用不能改變指向,指針可以改變指向
(4) 引用相對更安全,沒有空引用,但是有空指針,容易出現(xiàn)野指針,但不容易出現(xiàn)野引用
(5) 在sizeof中含義不同,引用結果引用類型的大小,但指針始終是地址空間所占字節(jié)個數(shù)
(6) 引用++,引用的實體增加1,指針++即指針向后偏移一個類型的大小
(7) 有多級指針,但是沒有多級引用
底層上(匯編) :
引用的底層使用指針實現(xiàn)的
匯編層面上,沒有引用,都是指針,引用編譯后也轉換成指針了。
引用和指針的共能類似,但是引用不能完全替代指針,因為引用定義后不能改變指向。例如:在單鏈表中,刪除鏈表中的某一個結點,需要讓指針指向要刪除結點的下一個結點(改變指針的指向)。但是引用是無法進行改變指向的。
然而在java和python中實現(xiàn)鏈表是通過引用,java和python中的引用時允許改變指向的。
六、 內聯(lián)函數(shù)
內聯(lián)函數(shù)概念
內聯(lián)函數(shù):以inline修飾的函數(shù)叫做內聯(lián)函數(shù),編譯時C++編譯器會在調用內聯(lián)函數(shù)的地方展開,沒有函數(shù)調用建立棧幀的開銷,內聯(lián)函數(shù)可以提升程序運行的效率。
在C語言中,如果進行多次調用函數(shù),那么會多次建立函數(shù)棧幀,C語言是通過宏函數(shù)來解決這個問題。因為宏會在預處理階段進行宏的替換,這樣就不會建立函數(shù)棧幀了。
宏的優(yōu)點:
沒有函數(shù)棧幀的創(chuàng)建,提供效率
但是宏也是有一些缺點
宏的缺點: 語法復雜,坑很多,不容易控制;不能調試;沒有 類型安全的檢查
C++替代宏的技術:可以使用常量定義 (const enum) ; 短小函數(shù)定義 換用內聯(lián)函數(shù)
為了減少使用宏,C++給出了內聯(lián)函數(shù)
在C++中,在調用內聯(lián)函數(shù)的地方展開,沒有函數(shù)調用建立棧幀的開銷
為什么內聯(lián)函數(shù)適合短小函數(shù)呢?
假設 func函數(shù)有100行,項目中有1w個調用該函數(shù)的位置
內聯(lián)展開了 100*1w = 100w行
不使用內聯(lián)展開 100+1w = 1.01w行
大的函數(shù)就影響很大,所以 短小函數(shù)可以換用內聯(lián)函數(shù)
在內聯(lián)函數(shù)在長一點的函數(shù)和遞歸函數(shù)是不會展開的
內聯(lián)函數(shù)特性
內聯(lián)函數(shù)(inline)是一種以空間換時間的做法,在編譯階段,會把函數(shù)展開。優(yōu)點:減少了函數(shù)棧幀的開銷,提高效率;缺點:目標文件可能會增大。 內聯(lián)函數(shù)適合較小代碼量的函數(shù) inline不建議聲明和定義分離,分離會導致鏈接錯誤,因為inline被展開,就沒有函數(shù)地址,鏈接時的符號表就找不到了。 例如: //test.cpp
#include "test.h"
int main()
{
func();
return 0;
}
//test.h
#pragma once //避免頭文件被重復包含,可以提高運行效率
#include
using namespace std;
inline void func(); //error
//func.cpp
#include "test.h"
void func()
{
cout << "func()" << endl;
}
因為inline函數(shù)展開,匯編call調用時沒有函數(shù)地址
如果要在test.h文件寫函數(shù),代碼量較小的可以用內聯(lián)(聲明和定義不分離),大的就使用static (鏈接屬性,只在當前文件可見)
七、auto關鍵字
在C++中,auto有許多作用,但是最常用的是用于類型推導。
int i = 0;
auto k = i; //auto 在這里的作用就是自動(識別)推導類型
void(*pf1)(int, int) = func; //函數(shù)指針
auto pf2 = func; //自動識別函數(shù)指針
//打印類型進行驗證一下、
cout << typeid(pf1).name() << endl;//void (__cdecl*)(int,int)
cout << typeid(pf2).name() << endl;//void (__cdecl*)(int,int)
//std::map::iterator it = dict.begin();
//auto it = dict.begin();
當然上述也可以使用C語言中的typedef 類型重命名,但是當計算出表達式的值的時候,進行賦值( 就需要知道數(shù)據(jù)類型 ),在一些項目中使用typedef沒有那么容易,但是 auto關鍵字可以進行自動推導。
auto從C++11標準及更高標準下可以作返回類型
auto func(int a,int b) {}
注意:
用auto定義變量時必須對其進行初始化,在編譯階段編譯器需要根據(jù)初始化表達式來推導auto的實際類型。因此auto并非是一種“類型”的聲明,而是一個類型聲明時的“占位符”,編譯器在編譯期會將auto替換為變量實際的類型。
auto的缺點:
(1) auto不能作為函數(shù)的參數(shù)
// 此處代碼編譯失敗,auto不能作為形參類型,因為編譯器無法對a的實際類型進行推導
void TestAuto(auto a){}
(2) auto不能用來直接聲明數(shù)組
int a[] = {1,2,3};
auto b[] = {4,5,6}; //error
八、 基于范圍的for循環(huán)(C++11)
范圍for的語法
C++11中引入了基于范圍的for循環(huán)。for循環(huán)后的括號由冒號“ :”分為兩部分:第一部分是范圍內用于迭代的變量,第二部分則表示被迭代的范圍。
int a[] = {1,2,3,4,5,6};
int b[] = {1,2,3,4,5,6};
//普通的for循環(huán)
for (int i = 0; i < sizeof(a) / sizeof(a[0]);i++)
{
a[i] *= 2;
}
for (int i = 0;i< sizeof(a) / sizeof(a[0]);i++)
{
cout << *(a + i) <<' '; //打印結果:2 4 6 8 10 12
}
//范圍for的使用 (C++11)
for (int e:b) //依次取數(shù)組中賦值給e,自動迭代,自動判斷結束
e *= 2;
for (int e : b)
cout << e << ' '; //打印結果:1 2 3 4 5 6
上述范圍for的使用C++11也進行了數(shù)值乘2,但是打印結果沒有發(fā)生變化。原因是: 因為是從數(shù)組中取出賦值給e,并沒有改變數(shù)組中的數(shù)據(jù),所以打印還是原值。
要想修改原數(shù)組的值,則需要使用引用
for (int& e : b) //依次取數(shù)組中賦值給e,自動迭代,自動判斷結束
e *= 2;
for (int e : b)
cout << e << ' '; //打印結果2 4 6 8 10 12
//當然也可以換成auto
for (auto& e : b) //依次取數(shù)組中賦值給e,自動迭代,自動判斷結束
e *= 2;
for (auto e : b)
cout << e << ' '; //打印結果2 4 6 8 10 12
范圍for循環(huán)可以用continue來結束本次循環(huán),也可以用break來跳出整個循環(huán)。
for循環(huán)迭代的范圍必須是確定的
九、 指針空值
聲明一個變量及時初始化是一個C/C++的一個好的編程習慣,否則可能會出現(xiàn)不可預料的錯誤,例如未初始化指針。
//初始化指針
int *p1 = NULL;
int* p2 = 0;
然而在C++98標準中出現(xiàn)了關于指針的bug
void f(int i)
{
cout << "f(int)" << endl;
}
void f(int* i)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
return 0;
}
打印結果:
f(int)
f(int)
發(fā)現(xiàn)都調用了第一個函數(shù)
分析:
f((int *)NULL); //打印 f(int*)
隨后在C++11標準中出現(xiàn)了 nullptr
f(nullptr); //打印 f(int*)
int* p = nullptr;
nullptr 的主要優(yōu)勢 在于它提供了更好的類型安全性。它只能被賦值給指針類型,而不能被賦值給整數(shù)類型,這有助于減少因錯誤地將NULL或0用于非指針類型而引起的問題。此外,使用 nullptr 可以避免與某些平臺上的空指針值沖突。
在C++11標準中,sizeof(nullptr) 和sizeof((void*)0)所占字節(jié)數(shù)相同,后續(xù)表示指針空值時,建議使用nullptr
12
范圍for循環(huán)可以用continue來結束本次循環(huán),也可以用break來跳出整個循環(huán)。
for循環(huán)迭代的范圍必須是確定的
# 九、 指針空值
聲明一個變量及時初始化是一個C/C++的一個好的編程習慣,否則可能會出現(xiàn)不可預料的錯誤,例如未初始化指針。
```cpp
//初始化指針
int *p1 = NULL;
int* p2 = 0;
然而在C++98標準中出現(xiàn)了關于指針的bug
void f(int i)
{
cout << "f(int)" << endl;
}
void f(int* i)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
return 0;
}
打印結果:
f(int)
f(int)
發(fā)現(xiàn)都調用了第一個函數(shù)
分析:
[外鏈圖片轉存中…(img-tjxJy0Wa-1706927812224)]
f((int *)NULL); //打印 f(int*)
隨后在C++11標準中出現(xiàn)了 nullptr
f(nullptr); //打印 f(int*)
int* p = nullptr;
nullptr 的主要優(yōu)勢 在于它提供了更好的類型安全性。它只能被賦值給指針類型,而不能被賦值給整數(shù)類型,這有助于減少因錯誤地將NULL或0用于非指針類型而引起的問題。此外,使用 nullptr 可以避免與某些平臺上的空指針值沖突。
在C++11標準中,sizeof(nullptr) 和sizeof((void*)0)所占字節(jié)數(shù)相同,后續(xù)表示指針空值時,建議使用nullptr
柚子快報邀請碼778899分享:算法 初識cpp【C++】
http://yzkb.51969.com/
好文鏈接