C++ 函數

第五章(循環和關系表達式)和第六章(分支語句和邏輯運算符)直接跳過,所有語言都一樣的,if/else/switch/while/for這種。直接進入第七章(函數)。

1.函數原型

以下是函數原型的例子

void cheers(int);//cheers方法的函數原型

int main()
{
    using namespace std;
    cheers(5);
    return 0;
}

//函數的實際實現
void cheers(int n)
{
    using namespace std;
    for(int i=0;i<n;i++){
        cout << "Cheers! ";
    }
    cout << endl;
}

避免使用函數原型的唯一方法是,在首次使用函數之前定義它。函數原型不要求提供變量名,有類型列表就足夠了。
函數原型的作用:

  • 確保編譯器正確處理函數返回值;
  • 編譯器檢查使用的參數數目是否正確;
  • 編譯器檢查使用的參數類型是否正確。如果不正確,則轉換為正確的類型(如果可能的話)。
2.函數參數和按值傳遞

舉個栗子

double volume = cube(side);

cube的原型如下:

double cube(double x);

被調用時,該函數將創建一個新的名為x的double變量,并將其初始化。這樣cube執行的操作將不會影響原數據,因為cube()使用的是side的副本,而不是原來的數據(C/C+新手的話,一定要注意這塊)。

3.函數和數組

還是先舉個栗子

int sum_arr(int arr[],int n);// n是arr的size

表面上看arr是個數組,但是實際上arr是個指針。在C++中,當且僅當用于函數頭或函數原型中,int *arr 和 int arr[]的含義是相同的。它們都意味著arr是一個int指針。這塊是個知識點,面試題經常會問,數組在函數參數時是退化為指針的。不明白的同學可以嘗試理解下下面的代碼

#include <iostream>

void sizeOfArray(int arr[])
{
    //函數中,arr從數組退化成指針
    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);//輸出的值為指針所占的字節數,64位mac為8
    return 0;
}

將數組地址作為參數的好處是可以節省復制整個數組所需的時間和內存。如果數組很大,使用拷貝的系統開銷將非常大;程序不僅需要更多的計算機內存,還需要花時間復制大塊的數據。壞處是使用原數據增加了破壞數據的風險,可以使用const保護數組,如下:

void show_array(const int arr[],int size);//函數原型

如果嘗試在show_array的實現中嘗試修改arr,編譯器會報錯,如下

AF676F8B-B7F2-421C-8C1D-6CB5579F6063.png

將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++禁止這種情況

如果將指針指向指針,也是類似的規則,C++不允許如下的情況

const int **pp;
int *p;
pp = &p;//不可以,編譯器報錯

結論:如果數據類型本身并不是指針,則可以將const數據或非const數據的地址賦給const的指針,但只能將非const數據的地址賦給const指針。

并且建議盡可能將指針參數聲明為指向常量數據的指針,理由如下:

  • 可以避免由于無意間修改數據而導致的編程錯誤;
  • 使用const使得函數能夠處理const和非const實參,否則將只能接受非const數據。
    再介紹下常量指針,如下
int a = 3;
int b = 4;
int * const pt = &a;//可以
pt = &b;//不可以

pt是一個常量指針,初始化后將不能再修改指向位置。

4.函數和二維數組

考慮如下情況:

int data[3][4];
int total = sum(data,3);

sum的函數原型應該是啥樣?答案是

int sum(int (*a)[4],int size);

這里要注意的是,多維數組的話,是有第一維會退化成指針,后面的維度都是還是數組。并且第一個參數應該是int (*a)[4],而不是int *a[4],因為int *a[4]表示一個由4個指向int的指針組成的數組。sum的另一種可讀性更強的原型是

int sum(int a[][4],int size);

如果對于上面的描述理解不上去的,可以結合下面的代碼感受下

int val = 20;
int valArray[3][4];

int *ptArray[4];//指針數組,也可以這么理解 (int *)ptArray[4],ptArray是一個數組,每個元素 int*
int *pt = &val;
ptArray[0] = pt;

int (*arrArray)[4];//arrArray是個指針,指針指向的每個元素是個int [4]類型
arrArray = valArray;//可以
ptArray = valArray;//不可以, 編譯器報錯,類型不對
5.函數和C-風格字符串

先溫習下C-風格字符串,表示的方式有三種:

  • char數組;
  • 用引號括起的字符串常量(也稱字符串字面值);
  • 被設置為字符串的地址的char指針。
    上述其實說的都是char指針(char*),因此函數原型參數都為如下
void processCStr(char *); 

C風格字符串與常規char數組的一種重要區別是字符串有內置的結束字符。這意味著不必將字符串長度作為參數傳遞給函數,函數可以使用循環檢查字符串中的每個字符直到遇到結尾的空字符。
返回的字符串的方式如下:

char* returnStr(){
    char *s = "string";
    return s;
}

int main() {
    using namespace std;
    cout << "result:" << returnStr() << endl;
    return 0;
}
6.函數和結構體

結構體和普通變量類似,函數都將創建參數對應的副本,函數內操作的其實是結構體變量的副本,所以在結構體變量包含的數據較多時,會有性能問題。有兩種方式可以提高效率,第一種是傳遞結構體的指針,第二種是傳遞結構體的引用(關于引用會在下一章講解),簡單舉個栗子:

#include <iostream>

struct Person {
    int age;
    char *name;
};

//在函數內的操作將不影響原值
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.函數和string對象

雖然C風格字符串和string對象的用途幾乎相同,但與數組相比,string對象與結構體更相似。例如,可以將一個結構體賦給另一個結構體,也可以將一個對象賦給結構體。如果需要多個字符串,可以聲明一個string對象數組,而且不是二維char數組。舉個栗子:

#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轉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.函數與array對象

沒啥好說的,看栗子吧:

#include <iostream>
#include <sstream>
#include <array>
#include <string>


using namespace std;

const int size = 4;

/**
 * 這個比較尷尬,因為C++里int轉string還是比較麻煩,目前先這么寫了
 * @param n
 * @return
 */
string intToString(int n) {
    stringstream stream;
    stream << n;
    return stream.str();
}

//按值傳遞,函數處理的是原始對象的副本
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;
}

打印結果如下

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.函數指針

函數這個話題比較大,這里只是簡單點一下。
與數據項相似,函數也有地址。函數的地址是存儲其機器語言代碼的內存的開始地址。還是看個栗子吧:

void func(string name) {
    cout << "hello " << name << endl;
}

int main() {
    //聲明函數指針
    void (*funcPt)(string);
    
    //獲取函數的地址,就是函數名其實
    funcPt = func;
    
    //使用指針調用函數
    funcPt("mr.lee");
    
    //下面方式也可以,個人傾向于下面這種,雖然寫的對了,但是表示的比較明確
    (*funcPt)("mr.lau");
    return 0;
}

通常,要聲明指向特定類型的函數的指針,可以首先編寫這個函數的原型,然后用形如(*pt)替換函數名即可。

下面解釋一個稍微復雜的栗子:

const double *(*pa[3])(const double *,int) = {f1,f2,f3};

在解釋這個復雜的栗子之前首先回顧一點東西

int *a[3];//a是一個數組,每個元素是int *;
int (*b)[3];//b是一個指針,指針指向的每個元素都是int[3]

f1是什么類型的?
來逐步解釋,首先運算符[]優先級高于,因此pa[3]表示p3是一個數組,這個數組包含三個元素,每個元素是指針類型。那是什么指針類型呢?是一個返回const double *,參數是const double *和int的函數,所以f1的聲明如下

const double * f1(const double *,int)

上面的代碼比較冗長,可以考慮使用typedef簡化,先看下簡單的typedef如何使用

typedef double real;
int main() {
    real a = 5.4;
    return 0;
}

再看如何簡化上面的函數指針

typedef double * (*p_fun)(const double *,int);//聲明別名
const double * f1(const double *,int);//聲明函數原型
p_fun func_pt = f1;//獲取指針
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372