第五章(循環(huán)和關(guān)系表達式)和第六章(分支語句和邏輯運算符)直接跳過浇冰,所有語言都一樣的浆西,if/else/switch/while/for這種曾掂。直接進入第七章(函數(shù))。
1.函數(shù)原型
以下是函數(shù)原型的例子
void cheers(int);//cheers方法的函數(shù)原型
int main()
{
using namespace std;
cheers(5);
return 0;
}
//函數(shù)的實際實現(xiàn)
void cheers(int n)
{
using namespace std;
for(int i=0;i<n;i++){
cout << "Cheers! ";
}
cout << endl;
}
避免使用函數(shù)原型的唯一方法是壁顶,在首次使用函數(shù)之前定義它珠洗。函數(shù)原型不要求提供變量名,有類型列表就足夠了若专。
函數(shù)原型的作用:
- 確保編譯器正確處理函數(shù)返回值许蓖;
- 編譯器檢查使用的參數(shù)數(shù)目是否正確;
- 編譯器檢查使用的參數(shù)類型是否正確调衰。如果不正確膊爪,則轉(zhuǎn)換為正確的類型(如果可能的話)。
2.函數(shù)參數(shù)和按值傳遞
舉個栗子
double volume = cube(side);
cube的原型如下:
double cube(double x);
被調(diào)用時嚎莉,該函數(shù)將創(chuàng)建一個新的名為x的double變量米酬,并將其初始化。這樣cube執(zhí)行的操作將不會影響原數(shù)據(jù)趋箩,因為cube()使用的是side的副本赃额,而不是原來的數(shù)據(jù)(C/C+新手的話,一定要注意這塊)阁簸。
3.函數(shù)和數(shù)組
還是先舉個栗子
int sum_arr(int arr[],int n);// n是arr的size
表面上看arr是個數(shù)組爬早,但是實際上arr是個指針哼丈。在C++中启妹,當(dāng)且僅當(dāng)用于函數(shù)頭或函數(shù)原型中,int *arr 和 int arr[]的含義是相同的醉旦。它們都意味著arr是一個int指針饶米。這塊是個知識點,面試題經(jīng)常會問车胡,數(shù)組在函數(shù)參數(shù)時是退化為指針的檬输。不明白的同學(xué)可以嘗試理解下下面的代碼
#include <iostream>
void sizeOfArray(int arr[])
{
//函數(shù)中,arr從數(shù)組退化成指針
using namespace std;
cout << "in func arr size:" << sizeof(arr) << endl;
}
int main() {
using namespace std;
int arr[10];
cout << "arr size:" << sizeof(arr) << endl;//輸出的值為sizeof(int)*10
sizeOfArray(arr);//輸出的值為指針所占的字節(jié)數(shù)匈棘,64位mac為8
return 0;
}
將數(shù)組地址作為參數(shù)的好處是可以節(jié)省復(fù)制整個數(shù)組所需的時間和內(nèi)存丧慈。如果數(shù)組很大,使用拷貝的系統(tǒng)開銷將非常大主卫;程序不僅需要更多的計算機內(nèi)存逃默,還需要花時間復(fù)制大塊的數(shù)據(jù)。壞處是使用原數(shù)據(jù)增加了破壞數(shù)據(jù)的風(fēng)險簇搅,可以使用const保護數(shù)組完域,如下:
void show_array(const int arr[],int size);//函數(shù)原型
如果嘗試在show_array的實現(xiàn)中嘗試修改arr,編譯器會報錯瘩将,如下
將const用于指針有一些很微妙的地方吟税“及遥可以用兩種不同的方式將const用于指針。第一種方法是讓指針指向一個常量對象肠仪,這樣可以防止使用該指針來修改所指向的值肖抱,第二種方法是將指針本身聲明為常量,這樣可以防止改變指針指向的位置藤韵。舉個栗子:
int age = 39;
const int *pt = &age;//一個指針虐沥,指向的是const int
*pt = 1;//不可以,因為指針指向的是const int
age = 20;//可以泽艘,因為age本身只是int欲险,是可變的
const float g_earth = 9.80;
const float *pe = &g_earth;//可以,常量
const float g_moon = 9.99;
float * pm = &g_moon;//不可以匹涮,C++禁止這種情況
如果將指針指向指針天试,也是類似的規(guī)則,C++不允許如下的情況
const int **pp;
int *p;
pp = &p;//不可以然低,編譯器報錯
結(jié)論:如果數(shù)據(jù)類型本身并不是指針喜每,則可以將const數(shù)據(jù)或非const數(shù)據(jù)的地址賦給const的指針,但只能將非const數(shù)據(jù)的地址賦給const指針雳攘。
并且建議盡可能將指針參數(shù)聲明為指向常量數(shù)據(jù)的指針带兜,理由如下:
- 可以避免由于無意間修改數(shù)據(jù)而導(dǎo)致的編程錯誤;
- 使用const使得函數(shù)能夠處理const和非const實參吨灭,否則將只能接受非const數(shù)據(jù)刚照。
再介紹下常量指針,如下
int a = 3;
int b = 4;
int * const pt = &a;//可以
pt = &b;//不可以
pt是一個常量指針喧兄,初始化后將不能再修改指向位置无畔。
4.函數(shù)和二維數(shù)組
考慮如下情況:
int data[3][4];
int total = sum(data,3);
sum的函數(shù)原型應(yīng)該是啥樣?答案是
int sum(int (*a)[4],int size);
這里要注意的是吠冤,多維數(shù)組的話浑彰,是有第一維會退化成指針,后面的維度都是還是數(shù)組拯辙。并且第一個參數(shù)應(yīng)該是int (*a)[4]郭变,而不是int *a[4],因為int *a[4]表示一個由4個指向int的指針組成的數(shù)組。sum的另一種可讀性更強的原型是
int sum(int a[][4],int size);
如果對于上面的描述理解不上去的涯保,可以結(jié)合下面的代碼感受下
int val = 20;
int valArray[3][4];
int *ptArray[4];//指針數(shù)組诉濒,也可以這么理解 (int *)ptArray[4],ptArray是一個數(shù)組,每個元素 int*
int *pt = &val;
ptArray[0] = pt;
int (*arrArray)[4];//arrArray是個指針遭赂,指針指向的每個元素是個int [4]類型
arrArray = valArray;//可以
ptArray = valArray;//不可以循诉, 編譯器報錯,類型不對
5.函數(shù)和C-風(fēng)格字符串
先溫習(xí)下C-風(fēng)格字符串撇他,表示的方式有三種:
- char數(shù)組茄猫;
- 用引號括起的字符串常量(也稱字符串字面值)狈蚤;
- 被設(shè)置為字符串的地址的char指針。
上述其實說的都是char指針(char*)划纽,因此函數(shù)原型參數(shù)都為如下
void processCStr(char *);
C風(fēng)格字符串與常規(guī)char數(shù)組的一種重要區(qū)別是字符串有內(nèi)置的結(jié)束字符脆侮。這意味著不必將字符串長度作為參數(shù)傳遞給函數(shù),函數(shù)可以使用循環(huán)檢查字符串中的每個字符直到遇到結(jié)尾的空字符勇劣。
返回的字符串的方式如下:
char* returnStr(){
char *s = "string";
return s;
}
int main() {
using namespace std;
cout << "result:" << returnStr() << endl;
return 0;
}
6.函數(shù)和結(jié)構(gòu)體
結(jié)構(gòu)體和普通變量類似靖避,函數(shù)都將創(chuàng)建參數(shù)對應(yīng)的副本,函數(shù)內(nèi)操作的其實是結(jié)構(gòu)體變量的副本比默,所以在結(jié)構(gòu)體變量包含的數(shù)據(jù)較多時幻捏,會有性能問題。有兩種方式可以提高效率命咐,第一種是傳遞結(jié)構(gòu)體的指針篡九,第二種是傳遞結(jié)構(gòu)體的引用(關(guān)于引用會在下一章講解),簡單舉個栗子:
#include <iostream>
struct Person {
int age;
char *name;
};
//在函數(shù)內(nèi)的操作將不影響原值
void processStruct1(Person p) {
p.name = "mrlee1";
p.age = 20;
}
void processStruct2(Person *p) {
p->name = "mrlee2";
p->age = 21;
}
void processStruct3(Person &p) {
p.name = "mrlee3";
p.age = 22;
}
void printPerson(Person p) {
using namespace std;
cout << "age:" << p.age << ",name:" << p.name << endl;
}
int main() {
using namespace std;
Person originPerson = {18, "mrlee"};
//按值傳遞
processStruct1(originPerson);
printPerson(originPerson);//實際打印age:18,name:mrlee,沒有變化
//指針傳遞
processStruct2(&originPerson);
printPerson(originPerson);//實際打印age:21,name:mrlee2
//引用傳遞
processStruct3(originPerson);
printPerson(originPerson);//實際打印age:22,name:mrlee3
return 0;
}
7.函數(shù)和string對象
雖然C風(fēng)格字符串和string對象的用途幾乎相同醋奠,但與數(shù)組相比榛臼,string對象與結(jié)構(gòu)體更相似。例如窜司,可以將一個結(jié)構(gòu)體賦給另一個結(jié)構(gòu)體沛善,也可以將一個對象賦給結(jié)構(gòu)體。如果需要多個字符串塞祈,可以聲明一個string對象數(shù)組金刁,而且不是二維char數(shù)組。舉個栗子:
#include <iostream>
#include <sstream>
using namespace std;
void processSting(const std::string strings[], int size) {
for (int i = 0; i < size; i++) {
cout << "string[" << i << "]:" << strings[i] << endl;
}
}
/**
* 這個比較尷尬织咧,因為C++里int轉(zhuǎn)string還是比較麻煩胀葱,目前先這么寫了
* @param n
* @return
*/
string intToString(int n) {
stringstream stream;
stream << n;
return stream.str();
}
int main() {
const int size = 5;
string strings[size];
for (int i = 0; i < size; i++) {
strings[i] = "mrlee" + intToString(i + 1);
}
processSting(strings, size);
return 0;
}
8.函數(shù)與array對象
沒啥好說的漠秋,看栗子吧:
#include <iostream>
#include <sstream>
#include <array>
#include <string>
using namespace std;
const int size = 4;
/**
* 這個比較尷尬笙蒙,因為C++里int轉(zhuǎn)string還是比較麻煩,目前先這么寫了
* @param n
* @return
*/
string intToString(int n) {
stringstream stream;
stream << n;
return stream.str();
}
//按值傳遞庆锦,函數(shù)處理的是原始對象的副本
void wrongModifyArray(array<string, size> stringArray) {
for (int i = 0; i < stringArray.size(); i++) {
stringArray[i] = "modified1";
}
}
//按指針傳遞
void rightModifyArray(array<string, size> *stringArray) {
for (int i = 0; i < (*stringArray).size(); i++) {
(*stringArray)[i] = "modified" + intToString(i);
}
}
//按引用傳遞
void rightModifyArray2(array<string, size> &stringArray) {
for (int i = 0; i < stringArray.size(); i++) {
stringArray[i] = "modified2" + intToString(i);
}
}
void printArray(array<string, size> stringArray) {
for (int i = 0; i < stringArray.size(); i++) {
cout << "string" << i << ":" << stringArray[i] << endl;
}
cout << endl;
}
int main() {
array<string, size> originStringArray = {
"string1", "string2", "string3", "string4"
};
printArray(originStringArray);
wrongModifyArray(originStringArray);
printArray(originStringArray);
rightModifyArray(&originStringArray);
printArray(originStringArray);
rightModifyArray2(originStringArray);
printArray(originStringArray);
return 0;
}
打印結(jié)果如下
string0:string1
string1:string2
string2:string3
string3:string4
string0:string1
string1:string2
string2:string3
string3:string4
string0:modified0
string1:modified1
string2:modified2
string3:modified3
string0:modified20
string1:modified21
string2:modified22
string3:modified23
9.函數(shù)指針
函數(shù)這個話題比較大捅位,這里只是簡單點一下。
與數(shù)據(jù)項相似搂抒,函數(shù)也有地址艇搀。函數(shù)的地址是存儲其機器語言代碼的內(nèi)存的開始地址。還是看個栗子吧:
void func(string name) {
cout << "hello " << name << endl;
}
int main() {
//聲明函數(shù)指針
void (*funcPt)(string);
//獲取函數(shù)的地址,就是函數(shù)名其實
funcPt = func;
//使用指針調(diào)用函數(shù)
funcPt("mr.lee");
//下面方式也可以求晶,個人傾向于下面這種焰雕,雖然寫的對了,但是表示的比較明確
(*funcPt)("mr.lau");
return 0;
}
通常芳杏,要聲明指向特定類型的函數(shù)的指針矩屁,可以首先編寫這個函數(shù)的原型辟宗,然后用形如(*pt)替換函數(shù)名即可。
下面解釋一個稍微復(fù)雜的栗子:
const double *(*pa[3])(const double *,int) = {f1,f2,f3};
在解釋這個復(fù)雜的栗子之前首先回顧一點東西
int *a[3];//a是一個數(shù)組吝秕,每個元素是int *;
int (*b)[3];//b是一個指針泊脐,指針指向的每個元素都是int[3]
f1是什么類型的?
來逐步解釋烁峭,首先運算符[]優(yōu)先級高于,因此pa[3]表示p3是一個數(shù)組容客,這個數(shù)組包含三個元素,每個元素是指針類型约郁。那是什么指針類型呢?是一個返回const double *,參數(shù)是const double *和int的函數(shù)缩挑,所以f1的聲明如下
const double * f1(const double *,int)
上面的代碼比較冗長,可以考慮使用typedef簡化鬓梅,先看下簡單的typedef如何使用
typedef double real;
int main() {
real a = 5.4;
return 0;
}
再看如何簡化上面的函數(shù)指針
typedef double * (*p_fun)(const double *,int);//聲明別名
const double * f1(const double *,int);//聲明函數(shù)原型
p_fun func_pt = f1;//獲取指針