第五章(循環(huán)和關(guān)系表達(dá)式)和第六章(分支語(yǔ)句和邏輯運(yùn)算符)直接跳過(guò),所有語(yǔ)言都一樣的,if/else/switch/while/for這種。直接進(jìn)入第七章(函數(shù))。
1.函數(shù)原型
以下是函數(shù)原型的例子
void cheers(int);//cheers方法的函數(shù)原型
int main()
{
using namespace std;
cheers(5);
return 0;
}
//函數(shù)的實(shí)際實(shí)現(xiàn)
void cheers(int n)
{
using namespace std;
for(int i=0;i<n;i++){
cout << "Cheers! ";
}
cout << endl;
}
避免使用函數(shù)原型的唯一方法是,在首次使用函數(shù)之前定義它。函數(shù)原型不要求提供變量名,有類(lèi)型列表就足夠了。
函數(shù)原型的作用:
- 確保編譯器正確處理函數(shù)返回值;
- 編譯器檢查使用的參數(shù)數(shù)目是否正確;
- 編譯器檢查使用的參數(shù)類(lèi)型是否正確。如果不正確,則轉(zhuǎn)換為正確的類(lèi)型(如果可能的話(huà))。
2.函數(shù)參數(shù)和按值傳遞
舉個(gè)栗子
double volume = cube(side);
cube的原型如下:
double cube(double x);
被調(diào)用時(shí),該函數(shù)將創(chuàng)建一個(gè)新的名為x的double變量,并將其初始化。這樣cube執(zhí)行的操作將不會(huì)影響原數(shù)據(jù),因?yàn)閏ube()使用的是side的副本,而不是原來(lái)的數(shù)據(jù)(C/C+新手的話(huà),一定要注意這塊)。
3.函數(shù)和數(shù)組
還是先舉個(gè)栗子
int sum_arr(int arr[],int n);// n是arr的size
表面上看arr是個(gè)數(shù)組,但是實(shí)際上arr是個(gè)指針。在C++中,當(dāng)且僅當(dāng)用于函數(shù)頭或函數(shù)原型中,int *arr 和 int arr[]的含義是相同的。它們都意味著arr是一個(gè)int指針。這塊是個(gè)知識(shí)點(diǎn),面試題經(jīng)常會(huì)問(wèn),數(shù)組在函數(shù)參數(shù)時(shí)是退化為指針的。不明白的同學(xué)可以嘗試?yán)斫庀孪旅娴拇a
#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);//輸出的值為指針?biāo)嫉淖止?jié)數(shù),64位mac為8
return 0;
}
將數(shù)組地址作為參數(shù)的好處是可以節(jié)省復(fù)制整個(gè)數(shù)組所需的時(shí)間和內(nèi)存。如果數(shù)組很大,使用拷貝的系統(tǒng)開(kāi)銷(xiāo)將非常大;程序不僅需要更多的計(jì)算機(jī)內(nèi)存,還需要花時(shí)間復(fù)制大塊的數(shù)據(jù)。壞處是使用原數(shù)據(jù)增加了破壞數(shù)據(jù)的風(fēng)險(xiǎn),可以使用const保護(hù)數(shù)組,如下:
void show_array(const int arr[],int size);//函數(shù)原型
如果嘗試在show_array的實(shí)現(xiàn)中嘗試修改arr,編譯器會(huì)報(bào)錯(cuò),如下

將const用于指針有一些很微妙的地方。可以用兩種不同的方式將const用于指針。第一種方法是讓指針指向一個(gè)常量對(duì)象,這樣可以防止使用該指針來(lái)修改所指向的值,第二種方法是將指針本身聲明為常量,這樣可以防止改變指針指向的位置。舉個(gè)栗子:
int age = 39;
const int *pt = &age;//一個(gè)指針,指向的是const int
*pt = 1;//不可以,因?yàn)橹羔樦赶虻氖莄onst int
age = 20;//可以,因?yàn)閍ge本身只是int,是可變的
const float g_earth = 9.80;
const float *pe = &g_earth;//可以,常量
const float g_moon = 9.99;
float * pm = &g_moon;//不可以,C++禁止這種情況
如果將指針指向指針,也是類(lèi)似的規(guī)則,C++不允許如下的情況
const int **pp;
int *p;
pp = &p;//不可以,編譯器報(bào)錯(cuò)
結(jié)論:如果數(shù)據(jù)類(lèi)型本身并不是指針,則可以將const數(shù)據(jù)或非const數(shù)據(jù)的地址賦給const的指針,但只能將非const數(shù)據(jù)的地址賦給const指針。
并且建議盡可能將指針參數(shù)聲明為指向常量數(shù)據(jù)的指針,理由如下:
- 可以避免由于無(wú)意間修改數(shù)據(jù)而導(dǎo)致的編程錯(cuò)誤;
- 使用const使得函數(shù)能夠處理const和非const實(shí)參,否則將只能接受非const數(shù)據(jù)。
再介紹下常量指針,如下
int a = 3;
int b = 4;
int * const pt = &a;//可以
pt = &b;//不可以
pt是一個(gè)常量指針,初始化后將不能再修改指向位置。
4.函數(shù)和二維數(shù)組
考慮如下情況:
int data[3][4];
int total = sum(data,3);
sum的函數(shù)原型應(yīng)該是啥樣?答案是
int sum(int (*a)[4],int size);
這里要注意的是,多維數(shù)組的話(huà),是有第一維會(huì)退化成指針,后面的維度都是還是數(shù)組。并且第一個(gè)參數(shù)應(yīng)該是int (*a)[4],而不是int *a[4],因?yàn)閕nt *a[4]表示一個(gè)由4個(gè)指向int的指針組成的數(shù)組。sum的另一種可讀性更強(qiáng)的原型是
int sum(int a[][4],int size);
如果對(duì)于上面的描述理解不上去的,可以結(jié)合下面的代碼感受下
int val = 20;
int valArray[3][4];
int *ptArray[4];//指針數(shù)組,也可以這么理解 (int *)ptArray[4],ptArray是一個(gè)數(shù)組,每個(gè)元素 int*
int *pt = &val;
ptArray[0] = pt;
int (*arrArray)[4];//arrArray是個(gè)指針,指針指向的每個(gè)元素是個(gè)int [4]類(lèi)型
arrArray = valArray;//可以
ptArray = valArray;//不可以, 編譯器報(bào)錯(cuò),類(lèi)型不對(duì)
5.函數(shù)和C-風(fēng)格字符串
先溫習(xí)下C-風(fēng)格字符串,表示的方式有三種:
- char數(shù)組;
- 用引號(hào)括起的字符串常量(也稱(chēng)字符串字面值);
- 被設(shè)置為字符串的地址的char指針。
上述其實(shí)說(shuō)的都是char指針(char*),因此函數(shù)原型參數(shù)都為如下
void processCStr(char *);
C風(fēng)格字符串與常規(guī)char數(shù)組的一種重要區(qū)別是字符串有內(nèi)置的結(jié)束字符。這意味著不必將字符串長(zhǎng)度作為參數(shù)傳遞給函數(shù),函數(shù)可以使用循環(huán)檢查字符串中的每個(gè)字符直到遇到結(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)體和普通變量類(lèi)似,函數(shù)都將創(chuàng)建參數(shù)對(duì)應(yīng)的副本,函數(shù)內(nèi)操作的其實(shí)是結(jié)構(gòu)體變量的副本,所以在結(jié)構(gòu)體變量包含的數(shù)據(jù)較多時(shí),會(huì)有性能問(wèn)題。有兩種方式可以提高效率,第一種是傳遞結(jié)構(gòu)體的指針,第二種是傳遞結(jié)構(gòu)體的引用(關(guān)于引用會(huì)在下一章講解),簡(jiǎn)單舉個(gè)栗子:
#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);//實(shí)際打印age:18,name:mrlee,沒(méi)有變化
//指針傳遞
processStruct2(&originPerson);
printPerson(originPerson);//實(shí)際打印age:21,name:mrlee2
//引用傳遞
processStruct3(originPerson);
printPerson(originPerson);//實(shí)際打印age:22,name:mrlee3
return 0;
}
7.函數(shù)和string對(duì)象
雖然C風(fēng)格字符串和string對(duì)象的用途幾乎相同,但與數(shù)組相比,string對(duì)象與結(jié)構(gòu)體更相似。例如,可以將一個(gè)結(jié)構(gòu)體賦給另一個(gè)結(jié)構(gòu)體,也可以將一個(gè)對(duì)象賦給結(jié)構(gòu)體。如果需要多個(gè)字符串,可以聲明一個(gè)string對(duì)象數(shù)組,而且不是二維char數(shù)組。舉個(gè)栗子:
#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;
}
}
/**
* 這個(gè)比較尷尬,因?yàn)镃++里int轉(zhuǎn)string還是比較麻煩,目前先這么寫(xiě)了
* @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對(duì)象
沒(méi)啥好說(shuō)的,看栗子吧:
#include <iostream>
#include <sstream>
#include <array>
#include <string>
using namespace std;
const int size = 4;
/**
* 這個(gè)比較尷尬,因?yàn)镃++里int轉(zhuǎn)string還是比較麻煩,目前先這么寫(xiě)了
* @param n
* @return
*/
string intToString(int n) {
stringstream stream;
stream << n;
return stream.str();
}
//按值傳遞,函數(shù)處理的是原始對(duì)象的副本
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ù)這個(gè)話(huà)題比較大,這里只是簡(jiǎn)單點(diǎn)一下。
與數(shù)據(jù)項(xiàng)相似,函數(shù)也有地址。函數(shù)的地址是存儲(chǔ)其機(jī)器語(yǔ)言代碼的內(nèi)存的開(kāi)始地址。還是看個(gè)栗子吧:
void func(string name) {
cout << "hello " << name << endl;
}
int main() {
//聲明函數(shù)指針
void (*funcPt)(string);
//獲取函數(shù)的地址,就是函數(shù)名其實(shí)
funcPt = func;
//使用指針調(diào)用函數(shù)
funcPt("mr.lee");
//下面方式也可以,個(gè)人傾向于下面這種,雖然寫(xiě)的對(duì)了,但是表示的比較明確
(*funcPt)("mr.lau");
return 0;
}
通常,要聲明指向特定類(lèi)型的函數(shù)的指針,可以首先編寫(xiě)這個(gè)函數(shù)的原型,然后用形如(*pt)替換函數(shù)名即可。
下面解釋一個(gè)稍微復(fù)雜的栗子:
const double *(*pa[3])(const double *,int) = {f1,f2,f3};
在解釋這個(gè)復(fù)雜的栗子之前首先回顧一點(diǎn)東西
int *a[3];//a是一個(gè)數(shù)組,每個(gè)元素是int *;
int (*b)[3];//b是一個(gè)指針,指針指向的每個(gè)元素都是int[3]
f1是什么類(lèi)型的?
來(lái)逐步解釋?zhuān)紫冗\(yùn)算符[]優(yōu)先級(jí)高于,因此pa[3]表示p3是一個(gè)數(shù)組,這個(gè)數(shù)組包含三個(gè)元素,每個(gè)元素是指針類(lèi)型。那是什么指針類(lèi)型呢?是一個(gè)返回const double *,參數(shù)是const double *和int的函數(shù),所以f1的聲明如下
const double * f1(const double *,int)
上面的代碼比較冗長(zhǎng),可以考慮使用typedef簡(jiǎn)化,先看下簡(jiǎn)單的typedef如何使用
typedef double real;
int main() {
real a = 5.4;
return 0;
}
再看如何簡(jiǎn)化上面的函數(shù)指針
typedef double * (*p_fun)(const double *,int);//聲明別名
const double * f1(const double *,int);//聲明函數(shù)原型
p_fun func_pt = f1;//獲取指針