A.函數是具有用途的自包含的代碼塊。函數名既是函數的標識,用來在程序中調用函數。如果函數名不在名稱空間中定義,它就是全局的,否則就要用名稱空間的名稱來限定他。
B.函數的主要優點之一是根據需要可以在程序的不同位置執行任意次。如果不能將代碼塊封裝到函數中,則程序將最終成為龐然大物,因為那樣通常需要再程序的不同位置復制相同的代碼。使用函數還可以將程序分為易於管理的代碼塊,以方便開發和測試,復雜的大型程序如果包含幾個小代碼塊,就比編寫為一個大代碼塊更容易理解和測試。
C.函數頭
int num(double a,int b);
本行由三部分組成:
a.返回值的類型(本例中int)
b.函數名(本例中num)
c.圓括號中的函數形參(本例中是a和b,分別為double和int類型)
注意:函數頭末尾和函數體右大括號後面都不需要分號。如果函數沒有返回值,則由void來指定返回類型。(void myNum(int a));
簡單介紹完函數,下面其實是好幾個例子組成的:
下面這是main主函數和一些要調用函數的聲明:
#include "stdafx.h" using std::cin; using std::cout; using std::endl; //1.值傳遞(pass by value ) int _value(int a,int b); //2.地址傳遞(pass by pointer) int _pointer(int* a); //3.引用傳遞(pass by reference) int _reference(int &a,int &b); //4.函數返回指針 int* _rpointer(int a,int b); //5.函數中的靜態變量 int _countNum(int a,int b); //6.遞歸函數調用 int _chengNum(int n); int _tmain(int argc, _TCHAR* argv[]) { int a{ 5 }; int b{ 5 }; int _vNum{}; int* _pNum{&a}; int _rNum{}; int* _pRNum{}; int _cNum{}; //1.值傳遞 printf("調用傳值前:a=%d,b=%d,vNum=%d\n", a, b, _vNum); _vNum = _value(a, b); printf("傳值後:a=%d,b=%d,vNum=%d\n", a, b, _vNum); //2.地址傳遞 printf("調用傳地址前:a=%d,*_pNum=%d\n", a, *_pNum); *_pNum = _pointer(&a); printf("傳地址後:a=%d,*_pNum=%d\n", a, *_pNum); //3.引用傳遞 printf("調用傳引用前:a=%d,b=%d,_rNum=%d\n", a, b, _rNum); _rNum = _reference(a, b); printf("傳引用後:a=%d,b=%d,_rNum=%d\n", a, b, _rNum); //4.函數返回指針 _pRNum = _rpointer(a,b); cout << "_pRnum = " << *_pRNum << endl; delete _pRNum; //釋放掉內存 _pRNum = nullptr; //5.函數中的靜態變量 _vNum = _countNum(a, b); _vNum = _countNum(a, b); _vNum = _countNum(a, b); _vNum = _countNum(a, b); //6.遞歸 int c{ 5 }; _cNum = _chengNum(c); cout << c << "的階乘是:" << _cNum << endl; system("pause"); return 0; }
實現被調用的函數
1.給函數傳遞實參:
int _value(int a, int b) { a += 5; //改變形參a的值 b += 5; //改變形參b的值 return a + b; }
2.地址傳遞
當使用指針作為實參時,按值傳遞機制仍然像以前一樣工作。但指針是另一個變量的地址,如果創建該地址的副本,則副本仍然指向相同的變量。以指針作為形參可以使函數處理調用者實參。
int _pointer(int* a) { //return &a; *a += 5; //改變指針指向的地址 return *a; }
3.引用傳遞
給函數傳遞實參的第二種方法:形參其實是引用被傳遞實參的別名,該機制不再復制所提供的實參。允許函數直接訪問調用函數中的實參。
當使用類類型對象時,對函數使用引用形參具有特殊的意義。對象可能會很大,很復雜,此時復制過程中,可能會耗費很多時間。在這樣的情況下,使用引用形參可以大大加快代碼的執行速度。
可以給函數的形參使用const修飾符,以告訴編譯器我們不想以任何方式修改這個形參。
int _reference(int &a, int &b) { a += 5; //改變形參a的值 b += 5; //改變形參b的值 return a + b; }
4.函數返回指針
返回地址的規則:永遠不要從函數中返回局部自動變量的地址。
這樣做很危險的,因為:函數_rpointer(a,b)中的變量num是在該函數開始執行時創建的,並在該函數退出時被銷毀,因此指針ptr指向的內存不在包含原來的變量值。先前分配給num的內存現在可能用於其他目的。
改正:我們的意圖是返回指向某些有用數據的指針,以便最終能夠返回多想數據。一種方法是動態分配內存。使用操作符new,可以在空閒存儲器中創建一個新變量,該變量一直存在,知道最終被delete銷毀,或者知道程序結束。
注意:動態分配內存時,每次調用該函數時都要多分配一些內存。當然不需要內存時,主調程序需要將其刪除,但實踐中
人們很容易忘記這麼做,結果就是空閒存儲器的內存被逐漸消耗,知道某個時刻內存用盡且程序失敗。這類問題稱為內存洩漏。
int* _rpointer(int a, int b) { //int num = a + b; //num += 10; //改正後 int* num{ new int{} }; *num = a + b; *num += 10; return num; }
5.函數中的靜態變量
有些時候用自動變量不能完成的。例如不能計算調用函數的次數,因為無法再多次調用中累積數值。有多重方法可以解決該問題。例如,可以使用引用形參來更新調用程序中的計數器,但如果程序中的許多不同位置都調用該函數,這種方法將無濟於事。還可以使用在函數中遞增的全局變量,但這樣做是有風險的,因為程序中任何位置都可以訪問全局變量,他們非常容易被以外修改。在具有多個訪問全局變量的執行線程的應用程序中,全局變量同樣是危險的,因此必須特別注意管理從不同線程中訪問全局變量的方式。當多個線程都可以訪問某個全局變量時,必須處理的基本問題:一個線程使用全局變量時,另一個線程可以修改該變量的值。在這樣的情況下,最好的解決方案是完全避免使用全局變量。
函數內靜態變量的初始化僅僅發生在第一次調用該函數的時候。事實上,初次調用函數時該變量包含的任何值都可以在下次調用時使用。
關於Static關鍵字
a.靜態變量,分配在靜態存儲區,在數據段中。函數退出之後,變量值不變。
b.作用域,全局的靜態變量、靜態函數只能在本文件中使用。(不同於一般全局變量)
局部的靜態變量同函數的局部變量
注:局部靜態變量占用內存時間較長,並且可讀性差,因此,除非必要,盡量避免使用局部靜態變量。
作用:
a、非靜態全局變量的作用域是整個源程序 ,當一個源程序由多個源文件組成時,非靜態的全局變量在各個源文件中都是有效的。
b、靜態全局變量則限制了其作用域, 即只在定義該變量的源文件 內有效,在同一源程序的其它源文件(即聲明了該變量的CPP文件, 或包含該變量聲明頭文件的CPP文件)中不能使用它。
在C++中,內存分成5個區,他們分別是堆、棧、自由存儲區、全局 / 靜態存儲區和常量存儲區。
int _countNum(int a, int b) { a += 5; b += 5; static int countNum{}; countNum++; cout << "調用次數:"<6.遞歸函數 遞歸函數調用:當函數包含自身的調用時,稱之為遞歸函數。遞歸的函數調用也可以是間接的,即函數fun1 調用函數fun2,後者再調用fun1。 遞歸可以看做實現無窮循環的一種方法,如果我們不小心,就會發生這種情況。無窮循環將鎖住計算機,需要按Ctrl+Alt+Del組合鍵才能終止程序,這永遠是件非常令人討厭的事情。避免無窮循環的前提是函數包含某種使遞歸調用過程停止的方法。 在物理和數學方面,有許多問題可以被視為包含遞歸。整數的階乘就是個簡單的例子。對於給定的整數N來說,其階乘就是乘積1*2*3*。。。*N
注意:除非遇到的問題特別適用於使用遞歸函數,或者沒有明顯的替代方法,否則使用其他方法一般(如循環)會更好。使用循環比使用遞歸的函數調用的效率更高。在深度適中的遞歸調用中,系統開銷也可以大大超過使用循環時的開銷。當然這樣說的意思不是我們永遠不應該使用遞歸。當問題適於用遞歸函數調用來解決時,遞歸就是非常強大的技術可以大大簡化代碼。
int _chengNum(int n) { static int countNum{n}; //聲明一個靜態變量; countNum *= (n - 1); cout << countNum << endl; if (n > 2) return _chengNum(n - 1); else { return countNum; } }