C++基礎(三):C++入門(二)

? ? ? ? 上一篇博客我們正式進入C++的學習,這一篇博客我們繼續學習C++入門的基礎內容,一定要學好入門階段的內容,這是后續學習C++的基礎,方便我們后續更加容易的理解C++。

目錄

一、內聯函數

1.0 產生的原因

1.1 概念

1.2 特性

1.3 面試題

二、缺省參數

2.1 缺省參數的概念

2.2 缺省參數的分類

2.2.1?全缺省參數

2.2.2?半缺省參數

2.3 多文件結構的缺省參數函數

2.3.1 缺省參數補充

三、函數重載(重點)

3.0 函數重載的引入

3.1 函數重載概念

3.2?判斷函數重載的規則(編譯器的工作)

3.3?函數重載解析的步驟

3.4 函數重載判斷練習

3.5?C++支持函數重載的原理-名字修飾(name Mangling)

3.5.1 預備知識

3.5.2? ?C語言編譯時函數名修飾約定規則(Windows平臺下)

3.5.2? ?C語言編譯時函數名修飾約定規則(Linux平臺下)

3.5.3? C++編譯時函數名修飾約定規則(Windows平臺下)

3.5.3? C++編譯時函數名修飾約定規則(Linux平臺下)

3.6?C++函數重載的名字粉碎(名字修飾)

3.7?如何指定函數以C方式修飾函數名還是以C++方式修飾函數名

四、函數模板(重點)

4.0 產生的原因? ?

4.1?函數模板定義

4.2?數組的推演及引用的推演

4.3 利用函數模板實現泛型編程

4.4?模板函數的重載與特化(完全特化、部分特化、泛化)

4.5 類模板

五、名字空間:namespace

5.1 C++作用域的劃分

5.2? 命名空間

5.3 命名空間的定義

5.4?命名空間使用

5.4.1?加命名空間名稱及作用域限定符

5.4.2?使用using將命名空間中某個成員引入

5.4.3?使用using namespace 命名空間名稱引入


一、內聯函數

1.0 產生的原因

? ? ? ? 當程序執行函數調用時,系統要建立棧空間,保護現場,傳遞參數以及控制程序執行的轉移等等, 這些工作需要系統時間和空間的開銷。當函數功能簡單,使用頻率很高,為了提高效率,直接將函數的代碼嵌入到程序中。但這個辦法有缺點,一是相同代碼重復書寫,二是程序可讀性往往沒有使用函數的好。因此,便產生了內聯函數,它的作用主要如下:

  • 1、減少函數調用開銷:函數調用通常會有一些額外的開銷,包括壓棧、跳轉、返回等。這些開銷對于頻繁調用的小函數(如訪問器函數、簡單的計算函數)而言,可能會顯得過高。通過將這些小函數定義為內聯函數,可以避免這些開銷,因為內聯函數會在編譯時直接將函數代碼插入到調用點。

  • 2、增強代碼可讀性:通過內聯函數,可以將一些頻繁使用的代碼塊抽象為函數,從而提高代碼的可讀性和可維護性。同時,因為內聯函數在編譯時會被展開,所以在運行時不會引入函數調用的開銷。

#include<ctype.h>  //C語言中的字符處理函數庫
#include<iostream>
using namespace std;boo1 IsNumber(char ch)
{return ch >= 'O' && ch <= '9' ? 1 : 0;//return isdigit(ch) ;
}int main()
{char ch;while (cin.get(ch), ch != '\n'){if (IsNumber(ch)){cout << " 是數字字符" <<endl;}else{cout << "不是數字字符 " <<endl;}}return 0;
}

如果上述代碼判斷數字字符函數在程序頻繁調用,那么將會產生一筆不小的空間開銷和時間開銷。為了協調好效率和可讀性之間的矛盾,C++提供 了另一種方法,即定義內聯函數。

1.1 概念

? ? ? ?以inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開(在編譯期間編譯器會用函數體替換函數的調用。,沒有函數調用建立棧幀的開銷,內聯函數提升程序運行的效率。

查看方式:

  1. ?在release模式下,查看編譯器生成的匯編代碼中是否存在call Add
  2. 在debug模式下,需要對編譯器進行設置,否則不會展開(因為debug模式下,編譯器默認不會對代碼進行優化,以下給出vs2019的設置方式)

1.2 特性

  1. inline是一種以空間換時間的做法,如果編譯器將函數當成內聯函數處理,在編譯階段,會用函數體替換函數調用,缺陷:可能會使目標文件變大,優勢:少了調用開銷,提高程序運行效率。
  2. inline對于編譯器而言只是一個建議,不同編譯器關于inline實現機制可能不同,一般建 議:將函數規模較小(即函數不是很長,具體沒有準確的說法,取決于編譯器內部實現)、不是遞歸、且頻繁調用的函數采用inline修飾,否則編譯器會忽略inline特性。下圖為 《C++prime》第五版關于inline的建議:
  3. inline不建議聲明和定義分離,分離會導致鏈接錯誤。因為inline被展開,就沒有函數地址了,鏈接就會找不到。

哪什么情況下采用inline處理合適,什么情況下以普通函數形式處理合適呢?這里有個建議給大家。
? ? ? ? ?如果函數的執行開銷小于開棧清棧開銷(函數體較小),使用inline處理效率高。如果函數的執行開銷大于開棧清棧開銷,使用普通函數方式處理。

1.3 面試題

內聯函數與宏定義區別:

  1. 內聯函數在編譯時展開,帶參的宏在預編譯時展開。
  2. 內聯函數直接嵌入到目標代碼中,帶參的宏是簡單的做文本替換。
  3. 內聯函數有類型檢測、語法判斷等功能,宏只是替換。

二、缺省參數

2.1 缺省參數的概念

? ? ? ? ?一般情況下,函數調用時的實參個數應與形參相同,但為了更方便地使用函數,C++也允許定義具有缺省參數的函數,這種函數調用時,實參個數可以與形參不相同。缺省參數指在定義函數時為形參指定缺省值(默認值)。這樣的函數在調用時,對于缺省參數,可以給出實參值,也可以不給出參數值。如果給出實參,將實參傳遞給形參進行調用,如果不給出實參,則按缺省值進行調用。

? ? ? ?缺省參數是聲明或定義函數時為函數的參數指定一個缺省值。在調用該函數時,如果沒有指定實參則采用該形參的缺省值,否則使用指定的實參。

2.2 缺省參數的分類

2.2.1?全缺省參數

2.2.2?半缺省參數

注意事項:

? ? ? 缺省參數可以有多個,但所有缺省參數必須放在參數表的右側,即先定義所有的非缺省參數,再定義缺省參數(半缺省參數必須從右往左依次來給出,不能間隔著給)。這是因為在函數調用時,參數自左向右逐個匹配(函數調用時同樣從左往右給實參,不能間隔著給),當實參和形參個數不一致時只有這樣才不會產生二義性。

2.3 多文件結構的缺省參數函數


? ? ? ?函數的原型(聲明):由返回值類型+函數名+形參表,形參名稱可以省略,但必須有形參類型
指針變量參數為缺省值的時候,指針變量名不可以省略!

? ? ? 缺省參數不能在函數聲明和定義中同時出現,因為如果函數的聲明與定義位置同時出現,恰巧兩個位置提供的值不同,那編譯器就無法確定到底該用那個缺省值!

? ? ? 習慣上,缺省參數在公共頭文件包含的函數聲明中指定, 不要函數的定義中指定。如果在函數的定義中指定缺省參數值,在公共頭文件包含的函數聲明中不能再次指定缺省參數
值。

2.3.1 缺省參數補充

? ? ? ? ?缺省實參不一定必須是常量表達式,可以使用任意表達式。當缺省實參是一個表達式時在函數被調用時該表達式被求值。

?

C語言不支持缺省參數(編譯器不支持)

三、函數重載(重點)

? ? ? ?自然語言中,一個詞可以有多重含義,人們可以通過上下文來判斷該詞真實的含義,即該詞被重載了。 比如:以前有一個笑話,中國有兩個體育項目大家根本不用看,也不用擔心。一個是乒乓球,一個是男足。前者是“誰也贏不了!”,后者是“誰也贏不了!”

3.0 函數重載的引入

? ? ? C語言實現int, double,char類型的比較大小函數。如下:

int    my_max_i(int a, int b) 
{ return a > b ? a : b;
}double my_max_d(double a, double b)
{ return a > b ? a : b; 
}char   my_max_c(char a, char b)
{ return a > b ? a : b;
}

? ? ? ? ?很容易發現它們的共同特點:這些函數都執行了相同的一般性動作; 都返回兩個形參中的最大值;從用戶的角度來看,只有一種操作,就是判斷最大值,至于怎樣完成其細節,用戶一點也不關心。這種詞匯上的復雜性不是”判斷參數中的最大值“問題本身固有的,而是反映了程序設計環境的一種局限性:在同一個作用域中出現的函數名字必須指向一個唯實體(函數體)。這種復雜性給程序員帶來了一個實際問題,他們必須記住或查找每一個函數名字。函數重載把程序員從這種詞匯復雜性中解放出來。

#include<iostream>
using namespace std;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()
{cout << max(12, 23) << endl;cout << max(12.23, 23.45) << endl;cout << max('a', 'b') << endl;編譯器在編譯的時候就已經確定數據類型,從而匹配相應的函數return 0;
}

3.1 函數重載概念

? ? ? ?函數重載:是函數的一種特殊情況,C++允許在同一作用域中聲明幾個功能類似的同名函數,這些同名函數的形參列表(參數個數 或 類型 或 類型順序)不同,常用來處理實現功能類似數據類型 不同的問題。

#include<iostream>
using namespace std;// 1、參數類型不同構成函數重載
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}double Add(double left, double right)
{cout << "double Add(double left, double right)" << endl;return left + right;
}// 2、參數個數不同構成函數重載
void f()
{cout << "f()" << endl;
}void f(int a)
{cout << "f(int a)" << endl;
}// 3、參數類型順序不同構成函數重載
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{cout << "f(char b, int a)" << endl;
}int main()
{Add(10, 20);Add(10.1, 20.2);f();f(10);f(10, 'a');f('a', 10);編譯器在編譯的時候就已經確定數據類型,從而匹配相應的函數return 0;
}

編譯器的工作:
? ? ? ?當一個函數名在同一個域中被聲明多次時,編譯器按如下步驟解釋第二個(以及后續的)的聲明。如果兩個函數的參數表中參數的個數或類型或順序不同,則認為這兩個函數是重載。而不認為是函數的重復聲明(編譯器報錯!)。

3.2?判斷函數重載的規則(編譯器的工作)

? ? ? ?這一小節來學習,編譯器是如何識別函數重載的,只有我們明白了編譯器判斷的規則,我們才能正確使用函數重載!

1.如果兩個函數的參數表相同,但是返回類型不同, 會被標記為編譯錯誤:函數的重復聲明。

int my_max(int a, int b)
{return a > b ? a : b;
}unsigned int my_max(int a, int b) // 編譯報錯,被認為是函數的重復聲明;
{return a > b ? a : b;
}int main()
{int ix = my_max(12, 23);unsigned int =my_max(12, 23); // 編譯無法通過;reutrn 0;
}

為什么呢?

? ? ? ? ?函數的返回類型不能作為函數重載的依據,編譯器區分函數是依靠函數聲明中的函數名和參數的類型,編譯器不知道應該調用哪個函數,編譯器認為是同一個函數重復聲明;

2.參數表的比較過程與形參名無關。

//編譯器會把下面這兩個函數認為是同一個函數,編譯報錯,函數重復聲明
int my_add(int a, int b);
int my_add(int x, int y);

為什么呢?

? ? ? ??函數的參數列表中的參數名不能作為函數重載的依據,同樣,因為編譯器不以參數名稱來區分函數,被認為是同一個函數,編譯器認為是同一個函數重復聲明;

3.如果在兩個函數的參數表中,只有缺省實參不同,編譯器同樣認為是同一個函數重復聲明;也就是說它認為這是一個函數。

//編譯器會把下面這兩個函數認為是同一個函數,編譯報錯,函數重復聲明
void Print(int* br, int n);
void Print(int* br, int len = 10);

4.typedef名為現有的數據類型提供了一個別名,它并沒有創建一個新類型,因此,如果兩個函數參數表的區別只在于一個使用了typedef重命名,而另一個使用了與typedef相應的類型。則該參數表被視為相同的參數列表,編譯器同樣認為是同一個函數重復聲明;也就是說它認為這是一個函數。

//編譯器會把下面這兩個函數認為是同一個函數,編譯報錯,函數重復聲明
typedef unsigned int u_int;
int Print(u_int a);
int Print(unsigned int b);

5.當一個形參類型有const或volatile修飾時,如果形參是按值傳遞方式定義,在識別函數聲明是否相同時,并不考慮const和volatile修飾符.(不考慮CV特性)

//編譯器會把下面這兩個函數認為是同一個函數,編譯報錯,函數重復聲明
void fun(int a);
void fun(const int a);

6.當一個形參類型有const或volatile修飾時,如果形參是按照指針或引用定義時,在識別函數聲明是否相同時,就要考慮const和volatile修飾符.(考慮CV特性),那么又該如何考慮呢?

#include<iostream>
using namespace std;void print(int &a)
{cout<<"左值引用"<<endl;
}void print(const int &b) 
{cout<<"常性左值引用(萬能引用)"<<endl;
}void print(int &&b) 
{cout<<"右值引用"<<endl;
}int main()
{int x = 10;print(x);    //優先匹配左值引用const int y = 10;print(y);    //只匹配常性左值引用/常引用!(萬能引用)print(10);   //優先匹配右值引用return 0;
}

匹配規則如下:

? ? ? ?首先,C++只有三種引用方式:左值引用(引用的是左值:可以取地址)、常性左值引用/萬能引用(既可以引用左值:可以取地址,又可以引用右值:不可以取地址)、右值引用(引用的是右值:不可以取地址),當存在三個引用的函數時,我們應明確編譯器的匹配順序,根據實參的類型進行匹配!!!
? ? ? ? 對于左值,優先匹配左值引用,其次才是常性左值引用!
? ? ? ? 對于常性左值,直接匹配常性左值引用,不存在,編譯器直接報錯!
? ? ? ? 對于右值引用,優先匹配右值引用,其次才是常性左值引用!

  1. ?對于普通的變量(它是左值),編譯器首先優先匹配形參為左值引用的函數,如果不存在該函數,然后,匹配形參為常性左值引用(常引用)的函數,如果二者都不存在,編譯器直接報錯!
  2. ?對于常變量(const修飾),編譯器直接匹配形參為常性左值引用(常引用)的函數,如果該函數不存在,編譯器直接報錯(不能匹配參數為右值引用的函數,右值引用引用的是右值)!
  3. ?對于右值,編譯器首先優先匹配形參為右值引用的函數,如果該函數不存在,然后,匹配形參為常性左值引用(常引用)的函數,如果二者都不存在,編譯器直接報錯!

7.注意函數調用的二義性; 如果在兩個函數的參數表中,形參類型相同,而形參個數不同,形參默認值將會影響函數的重載.

#include<iostream>
using namespace std;/****函數調用的二義性****/
void fun(int a);
void fun(int a, int b);
void fun(int a, int b = 10);int main()
{fun(12);    //這里第一個函數會和第三個函數會發生沖突,編譯器不知道該調用哪一個函數fun(12,13); //這里第二個函數會和第三個函數會發生沖突,編譯器不知道該調用哪一個函數return 0;
}

3.3?函數重載解析的步驟

  1. 確定函數調用考慮的重載函數的集合,確定函數調用中實參表的屬性(由形參表的屬性確定調用那個函數)。
  2. 從重載函數集合中選擇函數,該函數可以在(給出實參個數和類型)的情況下可以調用函數。
  3. 選擇與調用最匹配的函數。

3.4 函數重載判斷練習


#include<iostream>
using namespace std;* /*******下面兩個函數構成重載***********/
void func(int *p) {}         //可讀可寫
void func(const int *p) {}  //只可讀,不可解引用修改int main()
{int x = 10;         //普通變量:可讀可寫func(&x);           //優先匹配普通指針的函數(第一個),其次才是const修飾的指針的函數(第二個)const int y = 10;   //常變量:只可讀func(&y);          //只能匹配const修飾的指針的函數(第二個),如果不存在該函數,直接報錯!return 0;
}
#include<iostream>
using namespace std;
/*******下面兩個函數不構成重載***********///編譯器會報錯,這不是函數的重載,第二個在編譯的時候會直接忽略const ,認為是同一個函數重復聲明void func(int *p) {}            //可讀可寫,可以解引用修改數據void func( int* const p) {}    //可讀可寫,可以解引用修改數據,const修飾指針自身的時候,編譯器可以忽略const的存在/**如果修改第二個為:void func( const int* const p) {}   那么這便又是函數的重載**********/int main(){int x = 10;    //普通變量func(&x);     //兩個函數功能一樣,對傳入的數據都可讀可寫,匹配兩個都可以!編譯器不知道調用哪個,就會報錯,認為是同一個函數重復聲明!return 0;}#endif
#include<iostream>
using namespace std;/*****下面兩個函數是重載函數:參數的個數不同******/void func(int a);void func(int a,int b);int main(){func(12);func(12, 23);}
#include<iostream>
using namespace std;void func(int a);
void func(int a, int b=20);int main()
{func(12);    //這里第一個函數會和第二個函數會發生沖突,編譯器不知道該調用哪一個函數func(12, 23);//這里第一個函數會和第二個函數會發生沖突,編譯器不知道該調用哪一個函數
}由于函數調用的二義性,不是重載函數,這種問題只要不調用函數就不會報錯,但是只要調用就會出現編譯錯誤!!!

3.5?C++支持函數重載的原理-名字修飾(name Mangling)

3.5.1 預備知識

? ? ? ?“C"或者"C++”函數在內部(編譯和鏈接)通過修飾名識別。修飾名是編譯器在編譯函數定義或者
原型時生成的字符串。修飾名由函數名、類名、調用約定、返回值類型、參數表共同決定。不同的調用方式,形成的修飾名也不同。

  1. _stdcall調用:Pascal程序的缺省調用方式,通常用于Win32 Api中,函數采用從右到左的壓棧方式,自己在退出時清空堆棧。
  2. C調用(即用_cdecl 關鍵字說明):按從右至左的順序壓參數入棧,由調用者把參數彈出棧。對于傳送參數的內存棧是由調用者來維護的(正因為如此,實現可變參數的函數只能使用該調用約定)。
  3. _fastcall 調用:"人”如其名,它的主要特點就是快,因為它是通過寄存器來傳送參數的(實際上,它用ECX和EDX傳送前兩個雙字( DWORD )或更小的參數,剩下的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的內存棧),在函數名修飾約定方面,它和前兩者均不同。
  4. thiscall調用:僅僅應用于“C++ "類的成員函數。this 指針存放于ECX寄存器,參數從右到左壓。thiscall不是關鍵詞,因此不能被程序員指定。

在C/C++中,一個程序要運行起來,需要經歷以下幾個階段:預處理、編譯、匯編、鏈接。

  1. ?實際項目通常是由多個頭文件和多個源文件構成,而通過C語言階段學習的編譯鏈接,我們 可以知道,【當前a.cpp中調用了b.cpp中定義的Add函數時】,編譯后鏈接前,a.o的目標文件中沒有Add的函數地址,因為Add是在b.cpp中定義的,所以Add的地址在b.o中。那么怎么辦呢?
  2. 所以鏈接階段就是專門處理這種問題,鏈接器看到a.o調用Add,但是沒有Add的地址,就會到b.o的符號表中找Add的地址,然后鏈接到一起。
  3. 那么鏈接時,面對Add函數,鏈接接器會使用哪個名字去找呢?這里每個編譯器都有自己的 函數名修飾規則。

3.5.2? ?C語言編譯時函數名修飾約定規則(Windows平臺下)

? ? ? ?C語言的名字修飾規則非常簡單,_ cdec是C/C++的缺省調用方式,調用約定函數名字前面添加了下劃線前綴。

_stdcall調用約定在輸出函數名前加上一個下劃線前綴,后面加上一個"@”符號和其參數的字節數。

_fastcall調用約定在輸出函數名前加上一個” @ "符號,函數名后面也是一個" @ "符號和其參數的字節數。

3.5.2? ?C語言編譯時函數名修飾約定規則(Linux平臺下)

通過下面我們可以看出gcc的函數修飾后名字不變。

3.5.3? C++編譯時函數名修飾約定規則(Windows平臺下)

3.5.3? C++編譯時函數名修飾約定規則(Linux平臺下)

3.6?C++函數重載的名字粉碎(名字修飾)

面試題:? ? ?

? ? ? ? ?編譯器編譯完是按照修飾名訪問這些函數的,不同的調用方式,編譯器編譯時函數名修飾約定規則不同。

1、什么是函數重載?
? ? ?函數名相同而參數列表不同(類型或者數量),叫函數重載!
2、返回值可不可以作為函數重載的依據?
? ? 不可以!C++的函數調用解析機制僅基于參數類型和數量來匹配函數,而不考慮返回值類型!如果兩個函數函數名和參數是一樣的,返回值不同是不構成重載的,因為調用時編譯器沒辦法區分。

3、為什么C++可以進行函數的重載,而C語言不可以?
? ? ?原因在于:二者的修飾名規則不同!(名字粉碎技術不同),?C語言只是在函數名的 前面加個下劃線,那么對于多個同名函數,他們的修飾名都相同,都是在函數名前面加個下劃線!編譯器在編譯階段無法區分!C++把形參列表的類型和數量作為修飾名的一部分,這樣相同的函數名(函數重載)就會得到不同的修飾名,這樣編譯器在編譯階段就可以區分出來!

3.7?如何指定函數以C方式修飾函數名還是以C++方式修飾函數名

extern "C" int add(int x, int y) { return x + y; }    //<==>  按照C的修飾規則,修飾名為:_add
double add(double x, double y) { return x + y; }//<==>  按照C++的修飾規則,修飾名為:?add@@YAHHH@Z
//上面編譯可以通過,因為C和C++修飾名規則不同,編譯器可以區分!extern "C" int    add(int x, int y) { return x + y; }    //<==>按照C的修飾規則,修飾名為:_add
extern "C" double add(double x, double y) { return x + y; }  //<==>按照C的修飾規則,修飾名為:_add
//上面編譯不可以通過,因為二者使用的都是C的修飾名規則,編譯器無法區分!//如何將一批函數指定相同的修飾規則:加個大括號
extern "C" 
{int add(int x, int y) //<==>  按照C的修飾規則,修飾名為:_add{ return x + y;}    double add(double x, double y)  //<==>  按照C的修飾規則,修飾名為:_add{ return x + y; }   
}

四、函數模板(重點)

4.0 產生的原因? ?

? ? ? ?為了代碼重用, 代碼就必須是通用的;通用的代碼就必須不受數據類型的限制那么我們可以把
數據類型改為一個設計參數。這種類型的程序設計稱為參數化(parameterize) 程序設計。

? ? ? ? 軟件模塊由模板構造。包括函數模板(function template)和類模板(class template)。

函數模板可以用來創建一個通用功能的函數, 以支持多種不同形參, 簡化重載函數的設計

4.1?函數模板定義

? ? ? ?<模板參數表> 尖括號中不能為空,參數可以有多個,用逗號分開。模板參數主要是模板類型參數。模板類型參數代表一種類型,由關鍵字class 或typename 后加一個標識符構成,在這里兩個關鍵字的意義相同,它們表示后面的參數名代表一個潛在的內置或用戶設計的類型。
?

#include<iostream>
using namespace std;template<class T>      T my_max(T a,T b){return a > b ? a : b;}int main(){my_max(12, 23);my_max('a', 'b');my_max(12.23, 34.45);return 0;}

? ? ? ? ?編譯階段,編譯器根據實參類型自動的生成如下函數代碼:這也叫模板實參推演,其本質上還是函數重載,由編譯器自動生成代碼。

 typedef int T;T my_max<int>(T a, T b){return a > b ? a : b;}typedef char T;T my_max<char>(T a, T b){return a > b ? a : b;}typedef double T;T my_max<double>(T a, T b){return a > b ? a : b;}

在編譯過程中,根據函數模板的實參構造出獨立的函數,稱為模板函數。這個構造過程被稱為模板實例化。
?

#if 0
#include<iostream>
using namespace std;
#include <typeinfo>template<class T>
void print(T a)
{cout << typeid(a).name() << endl;cout << typeid(a).name() << endl;cout << typeid(a).name() << endl;}int main()
{int a = 10;int arr[] = { 1,2,3,4,5 };print(a);        //編譯階段,編譯器推演出:T為整型 intprint(&a);       //編譯階段,編譯器推演出:T為整型指針 int *print(arr);      //數組名代表首元素的地址,也就是一個指針,編譯階段,編譯器推演出:T為整型指針 int *
}
#endif

4.2?數組的推演及引用的推演

#if 1
#include<iostream>
using namespace std;
#include <typeinfo>
template<class T>
void print(T &a)                              //函數的參數為實參類型的引用(實參的引用)
{cout << typeid(T).name() << endl;cout << typeid(a).name() << endl;cout << sizeof(a) << endl;              //20}int main()
{int a = 10;int arr[] = { 1,2,3,4,5 };print(a);                                 //編譯階段,實參a為一個整型, 編譯器推演出:T為整型 int ,函數參數a為整型引用類型:int &print(&a);                               //編譯階段,編譯器無法推演,實參為一個整型指針也就是:int *, 所以a就是這個整型指針的引用,按道理推演a為:int* & ,在實參里面可以a++,但是這里的&a是一個地址,是一個常量(帶有常性const),所以編譯器無法推演!,print(arr);                              //編譯階段,數組名代表首元素的地址,也就是一個整型指針,編譯階段,編譯器推演出:數組引用,int [5]&
}

4.3 利用函數模板實現泛型編程


下面這個打印數組的函數代碼適用于一切類型的數組,我都可以打印整個數組,類型由編譯器自動推演#include<iostream>
using namespace std;// typedef int T      ==>int
// #define N  5        ==>5template<class T, int N>void print(T(&br)[N])            /*模板類型參數推演時:類型是重命名規則,非類型是宏的規則直接拿數值替換*/{for (int i = 0; i < N; i++){cout << br[i] << " ";}count << endl;}int main(){int arr[5] = { 1,2,3,4,5 };double brr[3] = { 1.2,3.4,5.3 };print(arr);               //數組引用:int (&br)[5]=arr;print(brr);               //數組引用:double (&br)[3]=brr;}

4.4?模板函數的重載與特化(完全特化、部分特化、泛化)

  1. ?模板函數的重載是C++中允許通過模板實現多個函數版本的功能。在C++中,函數可以通過相同的名字但不同的參數列表來重載。模板函數也可以通過這種方式實現重載。
  2. ?函數模板特化是指為特定類型提供一個專門的模板實現,與上面相比,而不是使用通用的模板版本。
#include<iostream>
using namespace std;/*泛化:無任何限制*/template<class T>void func(T a){}/*部分特化:限制常性的任意類型指針*/template<class T>void func(const T *p){}/*完全特化:限制常性的字符類型指針*/void func(const char* p)            {}int main(){const char* str = "hello";int a = 10;func(a);func(&a);func(str);return  0;}

4.5 類模板

? ? ? ? ?類模板是C++中一種強大的工具,用于定義通用的類。通過類模板,可以創建能夠處理任意數據類型的類,而無需為每種數據類型編寫單獨的類。類模板使用模板參數來表示類中的數據類型,使得類的實現能夠適用于多種類型。容器是一種數據結構,用于存儲和管理一組對象。在C++標準庫(STL,Standard Template Library)中,容器是實現了特定接口的類模板。這些類模板提供了存儲、訪問和操作其包含的元素的功能。

C++容器庫包括序列容器、關聯容器和無序容器。
?? ? 序列容器(Sequence Containers):用于按照線性順序存儲數據。

  1. std::vector:動態數組,支持快速隨機訪問和在末尾插入 / 刪除元素。
  2. std::deque:雙端隊列,支持快速隨機訪問和在兩端插入 / 刪除元素。
  3. std::list:雙向鏈表,支持在任何位置快速插入 / 刪除元素。
  4. std::array:固定大小的數組,大小在編譯時確定。
  5. std::forward_list:單向鏈表,支持在任何位置快速插入 / 刪除元素,但只允許單向遍歷。

?容器庫的特點

  1. 泛型編程:通過模板實現,容器可以存儲任意類型的對象。
  2. 自動管理內存:容器會自動管理其所需的內存,開發者無需手動分配和釋放內存。
  3. 迭代器支持:所有容器都提供迭代器,用于遍歷和操作元素。
  4. 算法兼容性:STL中的算法可以與容器無縫配合使用,提供諸如排序、搜索、復制等操作。
使用類模板生成任意類型的順序棧template<class T>class SeqStack
{private:T* data;int top;public:SeqStack(int sz = 100){data = (T*)malloc(sizeof(T) * sz);top = -1;}
};int main()
{SeqStack<int> ist;          //類模板的類型必須給定SeqStack<double> dst;}/*******編譯器會根據上述模板生成如下代碼:**/class SeqStack<int>
{typedef int T;private:T* data;int top;public:SeqStack(int sz = 100){data = (T*)malloc(sizeof(T) * sz);top = -1;}
};class SeqStack<double>
{typedef double T;private:T* data;int top;public:SeqStack(int sz = 100){data = (T*)malloc(sizeof(T) * sz);top = -1;}
};

五、名字空間:namespace

5.1 C++作用域的劃分

? ? ? ? 在C++中把作用域劃分為:全局作用域,局部作用域、塊作用域,名字空間作用域和類作用域。注意:作用域是針對編譯器來說的,生存周期針對運行的時候來說的。函數被調用時,被調用函數內部的變量才會分配內存空間,當函數執行完畢,被調用函數內部的變量將會歸還給操作系統。我們定義的變量存儲在棧區或者堆區。

  1. ?全局變量:函數之外定義的變量:存儲在數據區,
  2. ?局部變量:函數內部定義的變量:存儲在棧區,
  3. ?靜態局部變量:函數內部定義的變量加static關鍵字修飾,只創建一次,保存上次的值,不會重新初始化:存儲在數據區(字符串常量也是)
  4. ?花括號內部的變量,只在花括號內部有效:存儲在棧區。
  5. ?類作用域是指在類定義內部的作用域,決定了類中的成員(包括成員變量和成員函數)的可見性和生命周期。
  6. ?名字空間作用域:多文件編程時:多個源文件定義相同的全局變量名字或者函數名,在項目進行編譯鏈接時候就會發生全局命名沖突!名字空間域是隨標準C++而引入的。它相當于一個更加靈活的文件域(全局域)。

5.2? 命名空間

? ? ? ?在C/C++中,變量、函數和后面要學到的類都是大量存在的,這些變量、函數和類的名稱將都存 在于全局作用域中,可能會導致很多沖突。使用命名空間的目的是對標識符的名稱進行本地化, 以避免命名沖突或名字污染,namespace關鍵字的出現就是針對這種問題的。

? ? ? ?名字空間域的引入,主要是為了解決全局名字空間污染(global namespace pollution)問題,即防止程序中的全局實體名與其他程序中的全局實體名的命名沖突。于是,便產生了命名空間。

5.3 命名空間的定義

? ? ? ?定義命名空間,需要使用到namespace關鍵字,后面跟命名空間的名字,然后接一對{}即可,{} 中即為命名空間的成員。

// 1. 正常的命名空間定義
namespace p1
{// 命名空間中可以定義變量/函數/類型int rand = 10;int Add(int left, int right){return left + right;}struct Node{struct Node* next;int val;};
}//2. 命名空間可以嵌套
// test.cpp
namespace N1
{int a;int b;int Add(int left, int right){return left + right;}namespace N2{int c;int d;int Sub(int left, int right){return left - right;}}
}//3. 同一個工程中允許存在多個相同名稱的命名空間,編譯器最后會合成同一個命名空間中。
// ps:一個工程中的test.h和上面test.cpp中兩個N1會被合并成一個
// test.h
namespace N1
{int Mul(int left, int right){return left * right;}
}

注意:一個命名空間就定義了一個新的作用域,命名空間中的所有內容都局限于該命名空間中

5.4?命名空間使用

? ? ? ?命名空間中成員該如何使用呢?比如:

5.4.1?加命名空間名稱及作用域限定符

int main()
{int a = yhp: :my_ add(12,23) ;printf("%1f \n",Primer: :pi);printf ("%f \n",yhp: :pi);Primer: :Matrix: :my_ _max('a','b');return 0;
}

5.4.2?使用using將命名空間中某個成員引入

   using yhp::pi;using Primer: :Matrix: :my_max;
//名字空間類成員matrix的using聲明
//以后在程序中使用matrix時,就可以直接使用成員名,而不必使用限定修飾名。
int main()
{printf("%1f \n",Primer: :pi) ;printf("%f \n",pi); // yhp::my_max('a','b');return 0;
}

5.4.3?使用using namespace 命名空間名稱引入

? ? ? ? 使用using指示符可以一次性地使名字空間中所有成員都可以直接被使用,比using聲明方便。using指示符;以關鍵字using開頭,后面是關鍵字namespace,然后是名字空間名。
using namespace名字空間名;

using namespace yhp;
int main()
{printf("%1f n",Primer: :pi ) ;printf("%f n" ,pi) ;// yhp: :my_add(12,23); // yhp::return 0;
}

多文件結構示例
?

std命名空間的使用慣例: .
? ? ? ?std是C++標準庫的命名空間,如何展開std使用更合理呢?
1.在日常練習中,建議直接using namespace std即可,這樣就很方便。
2. using namespace std展開,標準庫就全部暴露出來了,如果我們定義跟庫重名的類型/對
象/函數,就存在沖突問題。該問題在日常練習中很少出現,但是項目開發中代碼較多、規模
大,就很容易出現。所以建議在項目開發中使用,像std::cout這樣使用時指定命名空間+
using std::cout展開常用的庫對象/類型等方式。

?

? ? ? ? 這篇博客內容較為豐富,為后續學習好C++做好準備,?如果對此專欄感興趣,點贊加關注!?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/38865.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/38865.shtml
英文地址,請注明出處:http://en.pswp.cn/web/38865.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

用隨機森林算法進行的一次故障預測

本案例將帶大家使用一份開源的S.M.A.R.T.數據集和機器學習中的隨機森林算法&#xff0c;來訓練一個硬盤故障預測模型&#xff0c;并測試效果。 實驗目標 掌握使用機器學習方法訓練模型的基本流程&#xff1b;掌握使用pandas做數據分析的基本方法&#xff1b;掌握使用scikit-l…

三大常用集合

1.Set集合 在Java中&#xff0c;Set是一種集合類型&#xff0c;它是一種不允許包含重復元素的集合&#xff0c;每個元素在Set中是唯一的。Set接口的常用實現類有HashSet、TreeSet和LinkedHashSet。以下是關于Set集合的一些重要特點和用法&#xff1a; 特點&#xff1a; 不允…

什么是mysql的回表操作

MySQL中的“回表”操作是指在執行查詢時&#xff0c;由于索引結構的限制&#xff0c;數據庫系統需要從非聚集索引&#xff08;Secondary Index&#xff09;中找到主鍵值&#xff0c;然后使用這些主鍵值回溯到聚集索引&#xff08;Clustered Index&#xff09;中獲取完整的行數據…

珠江電纜,承載您夢想的每一度電

在現代社會&#xff0c;電力無處不在&#xff0c;它不僅是經濟發展的動力&#xff0c;更是每個人生活中不可或缺的能量來源。而在這個電力驅動的世界里&#xff0c;有一家企業默默地承載著千家萬戶的夢想&#xff0c;它就是珠江電纜。 連接夢想的每一度電 珠江電纜成立于2001…

使用Java實現單元測試:JUnit教程

使用Java實現單元測試&#xff1a;JUnit教程 大家好&#xff0c;我是微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 在軟件開發中&#xff0c;單元測試是保證代碼質量和功能正確性的重要手段之一。JUnit是Java語言中最流行…

絕區零國際服下載 一鍵下載絕區零國際服教程

絕區零是一款米哈游傾情打造的全新都市幻想動作角色扮演游戲。在游戲中&#xff0c;我們將扮演一名繩匠&#xff0c;這是為出于各種原因需要進入危險空洞的人提供指引的專業人士。您將與獨特的角色一起踏上冒險之旅&#xff0c;攜手探索空洞&#xff0c;對戰強大敵人&#xff0…

【狀態估計】線性高斯系統的狀態估計——離散時間的遞歸濾波

前兩篇文章介紹了離散時間的批量估計、離散時間的遞歸平滑&#xff0c;本文著重介紹離散時間的遞歸濾波。 前兩篇位置&#xff1a;【狀態估計】線性高斯系統的狀態估計——離散時間的批量估計、【狀態估計】線性高斯系統的狀態估計——離散時間的遞歸平滑。 離散時間的遞歸濾波…

ollama將模型永遠加載在顯存里

問題解析 我們在使用ollma部署大語言模型的時候,如果部署的模型尺寸較大,往往在第一次加載的時候需要花費大量的時間加載模型;等加載完成后,如果長時間不調用模型,我們會發現模型已經被釋放掉了,又要重新加載,導致體驗感極差. 這是為什么呢?因為在沒被調用時,ollama默認在顯…

Steam夏促怎么注冊 Steam夏促賬號注冊教程

隨著夏日的炙熱漸漸充斥著每一個角落&#xff0c;Steam平臺也趕來添熱鬧&#xff0c;推出了一系列讓人眼前一亮的夏季促銷活動。如果你也是游戲愛好者&#xff0c;我們肯定不能錯過這次的steam夏促。正直本次夏日促銷有著很多的游戲迎來史低和新史低&#xff0c;有各種各樣的游…

20240703在飛凌OK3588-C開發板上刷Rockchip原廠的Buildroot20220811

20240703在飛凌OK3588-C開發板上刷Rockchip原廠的Buildroot20220811 2024/7/3 18:25 詳細的刷機LOG&#xff1a; [BEGIN] 2024/7/3 18:18:49 rootRK3588:/# DDR Version V1.07 20220412 LPDDR4X, 2112MHz channel[0] BW16 Col10 Bk8 CS0 Row16 CS1 Row16 CS2 Die BW16 Size204…

TP8/6 更改后臺入口地址admin改為myadmin 隱藏真實后臺網址

原來www.xxx.com/admin 改后www.xxx.com/myadmin config/app.php // 應用映射&#xff08;自動多應用模式有效&#xff09;app_map > [admintest>admin],

JavaScript 實用技巧(二)

JavaScript 實用技巧&#xff1a;提升你的編程效率 JavaScript 是一種廣泛使用和極具表現力的編程語言。為了幫助你更高效地編寫代碼&#xff0c;本文將介紹一些實用的 JavaScript 技巧。掌握這些技巧將有助于提高你的編程速度和代碼質量。 1. 交換變量的值 不需要使用臨時變…

為何同一PDF文檔用不同軟件打印效果不同?

通過掃描儀生成的同一PDF文檔&#xff0c;同樣的設置&#xff0c;為什么別的電腦打出來是白底我的打出來有灰色格子背景&#xff1f;這種情況通常是由于PDF閱讀軟件的不同造成的差異。 ### 可能的原因和解決方法&#xff1a; 1. **PDF閱讀軟件的不同**&#xff1a; - **解決方…

Vue3輕松創建交互式儀表盤

本文由ScriptEcho平臺提供技術支持 項目地址&#xff1a;傳送門 基于 Plotly.js 的 Vue 儀表盤組件 應用場景介紹 儀表盤是一種交互式可視化工具&#xff0c;用于監控和分析關鍵指標。它廣泛應用于各種行業&#xff0c;例如金融、醫療保健和制造業。 代碼基本功能介紹 本…

FFmpeg 命令行 音視頻格式轉換

&#x1f4da;&#xff1a;FFmpeg 提供了豐富的命令行選項和功能&#xff0c;可以用來處理音視頻文件、流媒體等&#xff0c;掌握命令行的使用&#xff0c;可以有效提高工作效率。 目錄 一、視頻轉換和格式轉換 &#x1f535; 將視頻文件轉換為另一種格式 &#x1f535; 指定…

12個驚艷的可視化大屏:解鎖數據之美,洞見未來趨勢

在數字化轉型的浪潮中&#xff0c;可視化大屏以其獨特的魅力和強大的功能&#xff0c;成為了企業展示數據、洞察趨勢的重要窗口。我們將一同探索12個驚艷的可視化大屏案例&#xff0c;感受數據之美&#xff0c;洞見未來趨勢。 可視化大屏&#xff0c;作為數據可視化的高級形態…

vue3繪制廣東深圳地圖使用echarts

<!-- 餅圖 --> <template><el-card><template #header> 地級市分類圖 </template><div :id"id" :class"className" :style"{ height, width }"></div></el-card> </template><script …

徹底學會Gradle插件版本和Gradle版本及對應關系

看完這篇&#xff0c;保你徹底學會Gradle插件版本和Gradle版本及對應關系&#xff0c;超詳細超全的對應關系表 需要知道Gradle插件版本和Gradle版本的對應關系&#xff0c;其實就是需要知道Gradle插件版本對應所需的gradle最低版本&#xff0c;詳細對應關系如下表格&#xff0…

Lua、AB包熱更新總結

1.AB包熱更新 &#xff08;1&#xff09;AB包是一種特定的壓縮文件&#xff0c;可以放模型貼圖音效等等 &#xff08;2&#xff09;Resources目錄下打包時只讀 無法修改&#xff1b;而AB包存儲的位置是自定義的&#xff0c;能夠動態更新&#xff0c;同時可以決定資源包初始的大…

0703_ARM7

練習&#xff1a; 封裝exti&#xff0c;cic初始化函數 //EXTI初始化 void hal_key_exti_init(int id,int exticr,int mode){//獲取偏移地址int address_offset (id%4)*8;//獲取寄存器編號int re_ser (id/4)1;//printf("address_offset%d,re_ser%d\n",address_o…