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;
}
}