C++ 函數(shù)

第五章(循環(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ò),如下

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

將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;//獲取指針
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容