溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

【C++探索之旅】第一部分第八課:傳值引用,文件源頭

發(fā)布時(shí)間:2020-06-29 15:01:08 來(lái)源:網(wǎng)絡(luò) 閱讀:455 作者:frogoscar 欄目:移動(dòng)開(kāi)發(fā)

【C++探索之旅】第一部分第八課:傳值引用,文件源頭


內(nèi)容簡(jiǎn)介

1、第一部分第八課傳值引用,文件源頭

2、第一部分第九課預(yù)告:數(shù)組威武,動(dòng)靜合一



傳值引用,文件源頭


這一課的標(biāo)題有點(diǎn)怪。其實(shí)是由這一課的幾個(gè)重點(diǎn)內(nèi)容結(jié)合起來(lái)取的名,慢慢學(xué)習(xí)就知道啦。


上一課《【C++探索之旅】第一部分第七課:函數(shù)效應(yīng),分而治之》中,我們初步認(rèn)識(shí)了函數(shù)。


不過(guò)不要高興得太早,你以為函數(shù)就這樣離你遠(yuǎn)去了嘛?怎么可能,函數(shù)將伴隨一生好嗎,只要你繼續(xù)編程的話。哈哈,所以你是跑不掉了~


【小編,都跟你簽了協(xié)議了,沒(méi)吃藥不要隨便出來(lái)溜達(dá)】


這一課我們就繼續(xù)深入學(xué)習(xí)與函數(shù)相關(guān)的幾個(gè)知識(shí)點(diǎn)。不過(guò)函數(shù)我們會(huì)一直深入學(xué)習(xí)的,以后有了類(lèi),對(duì)象等面向?qū)ο蟮闹R(shí),到時(shí)函數(shù)還會(huì)換一個(gè)稱呼。



值傳遞和引用傳遞


值傳遞


我們首先來(lái)學(xué)習(xí)在函數(shù)范圍內(nèi)操作系統(tǒng)是怎樣管理內(nèi)存的。


再拿我們之前的addTwo函數(shù)舉例。這個(gè)函數(shù)很簡(jiǎn)單,就是在參數(shù)的基礎(chǔ)上加上2,然后返回其值。如下:


int addTwo(int a)
{
    a += 2;
    return a;
}


是不是覺(jué)得 a+= 2; 這一句有點(diǎn)多余呢?完全可以直接 return a + 2; 啊。接下來(lái)會(huì)知道這里為什么添加這一句。


寫(xiě)個(gè)小程序測(cè)試一下此函數(shù):


#include <iostream>
using namespace std;

int addTwo(int a)
{
    a+=2;
    return a;
}

int main()
{
    int number(4), result;
    result = addTwo(number);
    
    cout << "number的值是 : " << number << endl;
    cout << "調(diào)用函數(shù)后結(jié)果是 : " << result << endl;
    return 0;
}


運(yùn)行這個(gè)程序,輸出:


number的值是 : 4

調(diào)用函數(shù)后結(jié)果是 : 6


程序中最關(guān)鍵的一句當(dāng)然就是


result = addTwo(number);


當(dāng)addTwo函數(shù)被調(diào)用時(shí),其實(shí)發(fā)生了很多事情:


  1. 程序獲取number的值,發(fā)現(xiàn)是4。

  2. 申請(qǐng)了內(nèi)存中的一塊地址(好像一個(gè)抽屜),抽屜上的標(biāo)簽是a(名字),類(lèi)型是int,抽屜里存放的值等于number的值,為4。

  3. 程序進(jìn)入到函數(shù)體中。

  4. 將變量a加上2,a變?yōu)?。

  5. a的值被作為函數(shù)的返回值賦給變量result。result的值也變?yōu)?了。

  6. 跳出函數(shù)。


重要的一點(diǎn)是:變量number被拷貝到了內(nèi)存的一個(gè)新的地址上(新的抽屜),這個(gè)新的抽屜的標(biāo)簽是a。我們說(shuō)參數(shù)a是通過(guò)值來(lái)傳遞的(number的值拷貝給了a),叫做值傳遞。當(dāng)我們的程序在addTwo函數(shù)里時(shí),內(nèi)存中的情況大致如下圖所示:


【C++探索之旅】第一部分第八課:傳值引用,文件源頭

因此我們?cè)趦?nèi)存中使用了三個(gè)"抽屜"。注意:number變量并沒(méi)有被改變。


所以在程序中操作變量a的時(shí)候,已經(jīng)與number沒(méi)什么關(guān)系了,只是操作number的一份值的拷貝。


引用傳遞


我們之前的課程初步介紹了引用(reference)的概念。其實(shí)引用這個(gè)名詞太抽象,一般第一次接觸引用的朋友都覺(jué)得:哇,好高深的感覺(jué)。其實(shí)一點(diǎn)也不高深,引用應(yīng)該更確切地被稱為"別名"。


比如小編叫謝恩銘,有的人可能會(huì)戲稱為小銘。那小銘就是我的別名啦,這兩個(gè)名字是不是指同一個(gè)人呢?是,都是指向略微頑皮,但在編程技術(shù)上絕不馬虎的小編。


除了前面說(shuō)的值傳遞的方式,也就是將變量number的值拷貝到變量a中。


除了值傳遞,我們也有其他的方式??梢越o內(nèi)存中名為number的抽屜再貼一個(gè)標(biāo)簽,叫做a。等于給number變量取了一個(gè)別名,稱為a。此時(shí)函數(shù)的參數(shù)就要使用引用了。如下:


int addTwo(int& a)  // 注意 & 這個(gè)表示引用的符號(hào)
{
    a+=2;
    return a;
}


當(dāng)我們調(diào)用函數(shù)時(shí),就沒(méi)有之前那樣的值拷貝了。程序只是給了number變量一個(gè)別名而已。當(dāng)我們的程序在addTwo函數(shù)里時(shí),內(nèi)存中的情況大致如下圖所示:


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


這次,變量a和變量number是指向同一塊內(nèi)存地址(同一個(gè)抽屜),抽屜里存放的值是4,a和number只是這個(gè)抽屜的兩個(gè)不同標(biāo)簽而已。我們說(shuō)變量a是通過(guò)引用傳遞的,稱為引用傳遞。


引用很特別,從上圖中我們看到,我們?cè)趦?nèi)存中并沒(méi)有給a這個(gè)引用變量分配新的內(nèi)存地址,它是指向它所引用的number這個(gè)變量的內(nèi)存地址。所以引用變量和它所指向的變量的內(nèi)存地址是一樣的。我們可以來(lái)測(cè)試一下:


#include <iostream>
using namespace std;

int main()
{
    int number(4);
    int &refNumber = number;
    cout << "number的內(nèi)存地址是 : " << &number << endl;
    cout << "refNumber的內(nèi)存地址是 : " << &refNumber << endl;
    return 0;
}


運(yùn)行,輸出:


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


如上圖中所示,變量number和引用變量refNumber的內(nèi)存地址是完全一樣的。


既然值傳遞可以幫我們解決問(wèn)題,為什么要用引用傳遞呢?既生瑜何生亮呢?引用有什么好處呢?


首先,從上例中我們知道了:引用不需要在內(nèi)存中新開(kāi)辟一塊地址,減少開(kāi)銷(xiāo)。


C語(yǔ)言沒(méi)有引用的概念,但是C++有。引用傳遞可以讓我們的函數(shù)addTwo直接修改參數(shù)。繼續(xù)使用之前的測(cè)試程序,只不過(guò)這次在函數(shù)參數(shù)中的是一個(gè)引用:


#include <iostream>
using namespace std;

int addTwo(int &a)
{
    a+=2;
    return a;
}

int main()
{
    int number(4), result;
    result = addTwo(number);
    
    cout << "number的值是 : " << number << endl;
    cout << "調(diào)用函數(shù)后結(jié)果是 : " << result << endl;
    return 0;
}


運(yùn)行這個(gè)程序,輸出:


number的值是 : 6

調(diào)用函數(shù)后結(jié)果是 : 6


為什么number的值變成了6呢?之前在值傳遞的例子中,number的值是不變的。


很簡(jiǎn)單,我們之前說(shuō)了,引用其實(shí)就是別名。a就是number的一個(gè)別名。那么其實(shí)它們指向的是同一個(gè)內(nèi)存地址,因此對(duì)a做加2操作,也就是對(duì)number做加2操作,當(dāng)然會(huì)改變number的值。


因此,引用的使用要謹(jǐn)慎,因?yàn)樗鼤?huì)改變所引用的那個(gè)對(duì)象。


對(duì)于引用的使用,經(jīng)典的例子也就是swap函數(shù)了,用于交換兩個(gè)參數(shù)的值。


#include <iostream>
using namespace std;

void swap(double& a, double& b)
{    
    double temporary(a);  // 將變量a的值保存到變量temporary中    
    a = b;           // 用b的值替換a的值    
    b = temporary;   // 將temporary的值賦給b,就是用a的舊值替換b的值
}

int main()
{    
    double a(2.3), b(5.4);    
    cout << "a的值是 " << a << "  b的值是 " << b << endl;    
    swap(a,b);   // 使用swap函數(shù)    
    cout << "a的值是 " << a << " b的值是 " << b << endl;    
    return 0;
}


運(yùn)行程序,輸出:


a的值是 2.3 b的值是 5.4

a的值是 5.4 b的值是 2.3


可以看到a和b這兩個(gè)變量的值被交換了。


假如我們不用引用傳遞,而是用傳統(tǒng)的值傳遞,那么我們交換的就只是變量的拷貝而已,并不是變量本身。


暫時(shí),引用的概念對(duì)大家可能還是有些抽象,但是不要擔(dān)心,我們之后的課程會(huì)經(jīng)常用到引用的,關(guān)于引用還有不少要說(shuō)的呢,慢慢來(lái),熟能就生巧了嘛。


之后,學(xué)到指針那章,還會(huì)講解引用和指針的異同。


不可改寫(xiě)的引用傳遞


既然說(shuō)到了引用,就需要介紹一個(gè)引用的慣常用法。這個(gè)用法在接下來(lái)的課程中會(huì)很有用,我們先來(lái)一窺堂奧。


我們說(shuō)引用傳遞相比值傳遞有一個(gè)優(yōu)勢(shì):不進(jìn)行任何拷貝。


設(shè)想一下,如果我有一個(gè)函數(shù),其中一個(gè)參數(shù)是string類(lèi)型的字符串。假如你的這個(gè)字符串變量里面的內(nèi)容是很長(zhǎng)的一串字符串,例如有一本小書(shū)那么多的字 符。那么拷貝這么長(zhǎng)的一個(gè)字符串的開(kāi)銷(xiāo)可是很大很費(fèi)時(shí)的,即使拷貝是在內(nèi)存中進(jìn)行。這樣的拷貝完全沒(méi)什么意義,因此我們就要避免使用值傳遞的方式。


當(dāng)然,你會(huì)自豪地對(duì)我說(shuō):我們可以用引用傳遞啊。是的,好主意。使用引用傳遞的話,就不用拷貝了。但是,引用傳遞有一個(gè)小缺陷:可以修改所指向的對(duì)象。不過(guò)這也不能說(shuō)是缺陷吧,畢竟正是因此引用也才顯得有用啊。


void f1(string text);  // 進(jìn)行耗時(shí)的拷貝
{
}
void f2(string& text);  // 不進(jìn)行拷貝,函數(shù)可以直接修改string變量的值
{
}


解決辦法就是使用:不可改寫(xiě)的引用傳遞。


之前的課程我們介紹過(guò)const這個(gè)關(guān)鍵字,被它修飾的變量就變成不可改變的變量。


我們用引用來(lái)避免了拷貝,再用const變量修飾引用就可以使引用不能被修改了。


void f1(string const& text); // 不進(jìn)行拷貝,函數(shù)也不能修改string變量的值
{
}


目前來(lái)說(shuō)這個(gè)用法對(duì)我們貌似沒(méi)太大用處,但是本課程的第二部分會(huì)學(xué)習(xí)面向?qū)ο缶幊?,到時(shí)會(huì)經(jīng)常用到這個(gè)技術(shù)。



頭文件和源文件,合理安排


在上一課介紹函數(shù)時(shí),我們已經(jīng)說(shuō)過(guò)函數(shù)是為了可以重用(重復(fù)使用)已經(jīng)創(chuàng)建的"磚塊"(代碼塊)。


目前,我們已經(jīng)學(xué)習(xí)了如何創(chuàng)建自定義的函數(shù),但是這些函數(shù)暫時(shí)還是和main函數(shù)位于同一個(gè)文件中。我們還沒(méi)有真正很好地重用它們。


和C語(yǔ)言一樣,C++也允許我們將程序分割成不同的源文件。在這些源文件中我們可以定義自己的函數(shù)。


在要用到這些函數(shù)時(shí),引入文件的內(nèi)容,也就引入了這些函數(shù)。這樣我們的函數(shù)就可以為不同的項(xiàng)目所使用了。借此我們就可以真正意義上地重用這些被分割開(kāi)的磚塊來(lái)建造房屋了。


必要的文件


但是為了更好地組織程序,計(jì)算機(jī)先驅(qū)們用了兩種文件,而不是一種文件(為何"多此一舉",學(xué)下去就知道了):


  • 源文件(source file):以.cpp結(jié)尾(也可以由.cc,.cxx,.C結(jié)尾),包含函數(shù)的具體源代碼。

  • 頭文件(header file):以.h結(jié)尾(也可以由.hxx,.hpp結(jié)尾),包含函數(shù)的描述,術(shù)語(yǔ)稱為函數(shù)的原型(prototype)。


這下知道為什么今天這課的標(biāo)題里有"文件源頭"了吧?就是指頭文件和源文件。


那么我們還是以addTwo函數(shù)為例,來(lái)分別創(chuàng)建源文件和頭文件吧。


int addTwo(int number)
{
    int value(number + 2);
    return value;
}


我們會(huì)用CodeBlocks這個(gè)IDE(集成開(kāi)發(fā)環(huán)境)來(lái)演示。如果你是用文本編輯器(比如Vim,Emacs,Sublime,等)加gcc(編譯器)來(lái)編寫(xiě)和編譯代碼的,那么直接創(chuàng)建文件就好了。


而且,我們默認(rèn)你的Codeblocks的C++項(xiàng)目已經(jīng)創(chuàng)建好了,也就是說(shuō)已經(jīng)有了一個(gè)自帶main.cpp文件的項(xiàng)目,名稱隨便取。


如下圖:


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


我們只是演示如何創(chuàng)建函數(shù)的源文件和頭文件。


源文件


首先,我們按照以下順序打開(kāi)菜單欄:File > New > File。


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


然后在以下窗口中選擇C/C++ source:


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


然后,點(diǎn)擊Go這個(gè)按鈕。進(jìn)入下圖的窗口:


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


選擇C++,點(diǎn)擊Next按鈕。進(jìn)入下圖所示窗口:


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


填寫(xiě)要?jiǎng)?chuàng)建的源文件的完整路徑(選擇目錄,然后填寫(xiě)文件名)。我們把文件的目錄選擇為和main.cpp一樣的目錄。


文件名字盡量做到見(jiàn)名知意,不要來(lái)個(gè) 1.cpp,以后都不知道這文件干什么的。


我們的演示中使用了math.cpp,因?yàn)槲覀兊腶ddTwo函數(shù)進(jìn)行的是數(shù)學(xué)運(yùn)算,就用math(mathematics的縮寫(xiě))來(lái)命名。


然后可以把Debug和Release都選上。


點(diǎn)擊Finish。你的源文件就創(chuàng)建好了。我們接著來(lái)創(chuàng)建頭文件。


頭文件


開(kāi)頭和之前創(chuàng)建源文件是一樣的。依次打開(kāi)菜單:File > New > File,然后在下圖窗口中選擇C/C++ header:


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


點(diǎn)擊Go進(jìn)入下一步:


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


建議頭文件的名字和源文件的名字一樣,只是后綴名不同。


頭文件的也放在和main.cpp一樣的目錄。那個(gè)MATH_H_INCLUDED是自動(dòng)生成的,不需要改動(dòng)。


點(diǎn)擊Finish。你的頭文件就創(chuàng)建好了。


一旦源文件和頭文件都創(chuàng)建好之后,你的項(xiàng)目應(yīng)該是類(lèi)似下圖這樣的:


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


現(xiàn)在文件有了,我們就要往里面填寫(xiě)內(nèi)容了,就是來(lái)定義我們的函數(shù)。


完成源文件


之前說(shuō)過(guò),源文件包含了函數(shù)的具體定義。這是一點(diǎn)。


另外還有一點(diǎn)理解起來(lái)略為復(fù)雜:編譯器需要獲知源文件和頭文件之間存在聯(lián)系。


目前,我們的源文件是空的,我們首先需要往里面添加這樣的一行:


#include "math.h"


你應(yīng)該對(duì)這行代碼的格式不陌生,我們之前說(shuō)過(guò)。


#include <iostream>


這一行我們熟悉的代碼,是為了引入C++的標(biāo)準(zhǔn)iostream(輸入輸出流)庫(kù)。


那么,#include "math.h" 就是為了引入math.h 這個(gè)文件中定義的內(nèi)容。


但是,為什么C++標(biāo)準(zhǔn)庫(kù)的頭文件是包括在尖括號(hào)中,而我們自己定義的math.h是包括在雙引號(hào)中呢?


C++有一些編寫(xiě)好的頭文件(比如標(biāo)準(zhǔn)函數(shù)庫(kù)等等),它們存放在系統(tǒng)的include文件夾里。當(dāng)我們使用#include <文件名> 命令時(shí),編譯器就到這個(gè)文件夾里去找對(duì)應(yīng)的文件。


顯然,用這種寫(xiě)法去包含一個(gè)我們自己編寫(xiě)的頭文件(不在系統(tǒng)include文件夾里)就會(huì)出錯(cuò)了。


所以包含C++提供的頭文件時(shí),應(yīng)該使用尖括號(hào)。


相反地,#include "文件名" 命令則是先在當(dāng)前文件所在的目錄搜索是否有符合的文件,如果沒(méi)有再到系統(tǒng)include文件夾里去找對(duì)應(yīng)的文件。


現(xiàn)在我們就來(lái)完成math.cpp的內(nèi)容,其實(shí)很簡(jiǎn)單,如下:


#include "math.h"

int addTwo(int number)
{
    int value(number + 2);
    return value;
}


完成頭文件


我們可以看到,math.h這個(gè)頭文件的初始內(nèi)容不是空的。而是:


#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED

#endif // MATH_H_INCLUDED

這幾句指令又是什么意思呢?


它們是為了防止編譯器多次引入這個(gè)頭文件。編譯器并不總是那么智能的,有可能會(huì)循環(huán)引入同樣的頭文件。


前面說(shuō)過(guò),頭文件包含函數(shù)的原型。我們要把原型寫(xiě)在上面第二句話和第三句話之間,如下所示:


#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED

int addTwo(int number);

#endif // MATH_H_INCLUDED


上面的三個(gè)以#開(kāi)頭的語(yǔ)句是如何防止重復(fù)引入頭文件內(nèi)容的呢?


這幾句語(yǔ)句被稱為條件編譯語(yǔ)句。


首先,如果是第一次引入頭文件,那么,就會(huì)讀到第一句命令:


#ifndef MATH_H_INCLUDED


ifndef是if not defined的縮寫(xiě),表示"如果沒(méi)有定義"。整句的意思就是:假如MATH_H_INCLUDED沒(méi)有被定義。


那如果是第一次引入此頭文件,MATH_H_INCLUDED確實(shí)還沒(méi)被定義。#ifndef MATH_H_INCLUDED 成立。


所以就進(jìn)入此條件編譯語(yǔ)句中,執(zhí)行 #define MATH_H_INCLUDED,定義(define是英語(yǔ)"定義"的意思)MATH_H_INCLUDED


然后引入


int addTwo(int number);


最后,來(lái)到第三句條件編譯指令:


#endif // MATH_H_INCLUDED


endif是end if的縮寫(xiě),表示"結(jié)束if條件編譯",后面的 // MATH_H_INCLUDED 是注釋?zhuān)瑫?huì)被編譯器忽略。


然后,假如之后我們又引入此頭文件,編譯器讀到第一句條件編譯指令,


#ifndef MATH_H_INCLUDED


發(fā)現(xiàn)之前已經(jīng)定義過(guò)了 MATH_H_INCLUDED,所以#ifndef MATH_H_INCLUDED不成立。因此不進(jìn)入條件編譯的語(yǔ)句中。直接跳出。就不會(huì)再引入


int addTwo(int number);


了。很妙吧。


當(dāng)然了,我們因?yàn)橛玫氖荂odeBlocks這樣的IDE,它幫你自動(dòng)生成了這三句條件編譯指令。假如我們用的是文本編輯器,那是沒(méi)有這三句自動(dòng)生成的語(yǔ)句的。就需要我們自己手工寫(xiě)了。


MATH_H_INCLUDED可以改成其他任何文本,只需要保證這三句條件指令中這個(gè)文本是一樣的就行,不過(guò)也要保證沒(méi)有兩個(gè)頭文件是使用同樣的文本。


注意。我們以上的函數(shù)原型其實(shí)就是源文件中函數(shù)定義的頭一行,只不過(guò)沒(méi)有了大括號(hào)包括起來(lái)的函數(shù)體,而且在參數(shù)列表的之后多了一個(gè)分號(hào)。


以上演示的是簡(jiǎn)單的情況,是在函數(shù)的參數(shù)是普通變量類(lèi)型時(shí)。假如我們要使用諸如string這樣的類(lèi)型。那么頭文件有些許改動(dòng)哦,如下:


#ifndef MESSAGE_H_INCLUDED
#define MESSAGE_H_INCLUDED

#include <string>

void displayMessage(std::string message);

#endif // MESSAGE_H_INCLUDED


可以看到,需要在函數(shù)原型之前加 #include <string>這句指令,表示要引入string這個(gè)C++標(biāo)準(zhǔn)庫(kù)的頭文件。而且,在參數(shù)列表中,string類(lèi)型前也要加上std::


為什么呢?


還記得我們之前在使用cout,cin時(shí),并沒(méi)有再前面加上std:: 嗎?


那是因?yàn)槲覀冊(cè)谑褂胏out,cin前,已經(jīng)用了


using namespace std;


表示使用std命名空間(關(guān)于命名空間,之后的課程會(huì)學(xué)習(xí))。


因?yàn)閏out和cin是位于命名空間std中,因此就可以省略了std::


但是,這里的頭文件中并沒(méi)有 using namespace std; 這句指令(絕對(duì)不建議把using namespace std; 放在頭文件中,要放到源文件中),因此我們需要在string前加上std::,因?yàn)閟tring也位于std命名空間。


現(xiàn)在,我們來(lái)測(cè)試一下addTwo函數(shù)吧。


我 們只需要在main.cpp中加入一行 #include "math.h",表示引入math.h頭文件,而其定義是在math.cpp中,但是因?yàn)閙ath.cpp中已經(jīng)有了#include "math.h",因此我們?cè)趍ain.cpp中就不要指定math.cpp來(lái)引入了。(是否已經(jīng)暈了...)


#include <iostream>
#include "math.h"
using namespace std;

int main()
{
    int a(2), b(3);
    cout << "a的值是 : " << a << endl;
    cout << "b的值是 : " << b << endl;
    b = addTwo(a);        // 函數(shù)調(diào)用
    cout << "a的值是 : " << a << endl;
    cout << "b的值是 : " << b << endl;
    return 0;
}


運(yùn)行,顯示:


a的值是 : 2

b的值是 : 3

a的值是 : 2

b的值是 : 4


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


好了,現(xiàn)在我們真正做到了將可重用的磚塊分開(kāi)存放了。如果之后你想要在另一個(gè)項(xiàng)目中使用addTwo函數(shù),只需要拷貝math.h和math.cpp這兩個(gè)文件過(guò)去就可以了。然后在要使用的源文件中寫(xiě)上 #include "math.h文件所在的路徑/math.h"


我們也可以在同一個(gè)文件中定義多個(gè)函數(shù)。一般來(lái)說(shuō),我們都會(huì)把同一類(lèi)函數(shù)放到同樣的文件中,比如那些數(shù)學(xué)運(yùn)算的函數(shù)放到一個(gè)文件中,那些用于顯示菜單的函數(shù)放到另一個(gè)文件中,等等。


編程,就要有條理,有規(guī)劃。


好的注釋是代碼的一半


最煩看到大型項(xiàng)目中代碼不怎么寫(xiě)注釋的了,特別是那種許多人合作開(kāi)發(fā)的項(xiàng)目,大家的命名規(guī)范和編程風(fēng)格不盡相同,如果沒(méi)有注釋?zhuān)雌饋?lái)相當(dāng)累。


一般不寫(xiě)注釋的程序員不是好程序員,只顧自己寫(xiě)代碼寫(xiě)得爽,完全不關(guān)心后來(lái)人是不是被他的代碼拍死在沙灘上。應(yīng)該被拖出去高速揉臉五分鐘(這是什么呆萌的刑罰,那畫(huà)面太美我不敢看):


【C++探索之旅】第一部分第八課:傳值引用,文件源頭


寫(xiě)注釋對(duì)于函數(shù)特別有用。因?yàn)槟愫芸赡軙?huì)用到別的程序員寫(xiě)的函數(shù)。那么如果已經(jīng)有了對(duì)函數(shù)的作用等的注釋?zhuān)阋话憔筒恍枰x函數(shù)的所有代碼了(程序員是會(huì)"偷懶"的)。


對(duì)于一個(gè)函數(shù),一般我們的注釋要包含三方面:

  1. 函數(shù)的功用

  2. 參數(shù)列表

  3. 返回值


我們就來(lái)給addTwo函數(shù)寫(xiě)個(gè)注釋吧:


#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED

/*
 * 對(duì)參數(shù)進(jìn)行加2操作
 * - number : 要進(jìn)行加2操作的數(shù)
 * 返回值 : number + 2
 */
int addTwo(int number);

#endif // MATH_H_INCLUDED


寫(xiě)注釋的格式隨個(gè)人愛(ài)好不盡相同。不顧一般常用的注釋格式是這樣(doxygen的格式):


/** 
 * \brief 對(duì)參數(shù)進(jìn)行加2操作
 * \param number 要進(jìn)行加2操作的數(shù)
 * \return number + 2
 */
 int addTwo(int number);



參數(shù)的默認(rèn)值


函數(shù)的參數(shù),我們已經(jīng)學(xué)習(xí)過(guò)了。如果一個(gè)函數(shù)有三個(gè)參數(shù),那么我們需要向函數(shù)提供這三個(gè)參數(shù)的值,才能讓函數(shù)被正常調(diào)用。


但是,也并不一定。我們用以下函數(shù)來(lái)學(xué)習(xí)一下默認(rèn)函數(shù)參數(shù)的用法:


int secondNumber(int hours, int minutes, int seconds)
{
    int total = 0;
    total = hours * 60 * 60;
    total += minutes * 60;
    total += seconds;
    return total;
}


這個(gè)函數(shù)的作用就是根據(jù)給出的小時(shí)數(shù),分鐘數(shù),秒數(shù),計(jì)算出一共有多少秒。非常好理解。


因此,我們說(shuō)hours, minutes, seconds是函數(shù)secondNumber的三個(gè)參數(shù)。在函數(shù)被調(diào)用時(shí),我們須要給它這三個(gè)參數(shù)一定的值。



這個(gè)我們之前早就學(xué)過(guò)了。


參數(shù)默認(rèn)值


我們要學(xué)習(xí)新知識(shí)點(diǎn),就是我們其實(shí)可以給函數(shù)的參數(shù)指定默認(rèn)值。假如函數(shù)在被調(diào)用時(shí)這些參數(shù)沒(méi)有被指定值,那么就用默認(rèn)的值。


首先來(lái)看沒(méi)有指定默認(rèn)值的情況:


#include <iostream>
using namespace std;

// 函數(shù)原型
int secondNumber(int hours, int minutes, int seconds);

// 主函數(shù)
int main()
{
    cout << secondNumber(1, 10, 25) << endl;
    return 0;
}

// 函數(shù)定義
int secondNumber(int hours, int minutes, int seconds)
{
    int total = 0;
    total = hours * 60 * 60;
    total += minutes * 60;
    total += seconds;
    return total;
}


運(yùn)行,顯示:


4225


因?yàn)?1小時(shí)等于3600秒,10分鐘等于600秒,25秒等于... 25秒。所以 3600 + 600 + 25 = 4225


現(xiàn)在,假如我們要將secondNumber函數(shù)的部分參數(shù)設(shè)為有默認(rèn)值的參數(shù)。例如,我指定分鐘數(shù)minutes和seconds默認(rèn)都為0,因?yàn)橐话阄覀兌加谜c(diǎn)比較多,就只有小時(shí)數(shù)。


我們須要改寫(xiě)函數(shù)的原型,如下所示:


int secondNumber(int hours, int minutes = 0, int seconds = 0);


看到了嗎?在minutes和seconds后面,都多了


= 0


這個(gè)就是函數(shù)的默認(rèn)參數(shù)值。此處都設(shè)為0。測(cè)試如下:


#include <iostream>
using namespace std;

// 函數(shù)原型
int secondNumber(int hours, int minutes = 0, int seconds = 0);

// 主函數(shù)
int main()
{
    cout << secondNumber(1) << endl;
    return 0;
}

// 函數(shù)定義,沒(méi)有寫(xiě)參數(shù)默認(rèn)值
int secondNumber(int hours, int minutes, int seconds)
{
    int total = 0;
    total = hours * 60 * 60;
    total += minutes * 60;
    total += seconds;
    return total;
}


運(yùn)行,顯示:


3600


這是因?yàn)閔ours為1,而minutes和seconds沒(méi)指定,默認(rèn)為0。1小時(shí)等于3600秒。


請(qǐng)注意:函數(shù)參數(shù)的默認(rèn)值只能寫(xiě)在函數(shù)的原型中,假如寫(xiě)在函數(shù)的定義中,編譯器會(huì)報(bào)錯(cuò)。


假如改為:


cout << secondNumber(1,10) << endl;


則輸出:


4200


因?yàn)閔ours為1;minutes為10;seconds沒(méi)指定,默認(rèn)為0


所以3600 + 600 = 4200


特殊情況


函數(shù)的默認(rèn)參數(shù)的使用中,會(huì)出現(xiàn)多種情況,有些比較特殊,我們舉例如下(還是以secondNumber函數(shù)為例):


1.假如我指定了hours和seconds的值,但是minutes的值沒(méi)指定,那么如何做呢?


你不可以使用這樣的形式:


cout << secondNumber(1,,25) << endl;


這樣是會(huì)出錯(cuò)的。在C++中,我們不能跳過(guò)參數(shù),即使它有默認(rèn)值。如果我們給開(kāi)頭和結(jié)尾的參數(shù)賦了值,那么中間的參數(shù)也需要賦值。


還是得這么寫(xiě):


cout << secondNumber(1, 0, 25) << endl;


2.這樣寫(xiě)可以嗎?


int secondNumber(int hours = 0, int minutes, int seconds);


不可以,會(huì)出錯(cuò)。因?yàn)閰?shù)的默認(rèn)值必須要靠右寫(xiě)。也就是說(shuō),假如只有hours這個(gè)參數(shù)有默認(rèn)值,那么必須要把hours參數(shù)寫(xiě)到最右邊,如下所示:


int secondNumber(int minutes, int seconds, int hours = 0);


這樣就沒(méi)有錯(cuò)誤了。


3.我能否將所有參數(shù)都設(shè)為有默認(rèn)值?


可以。舉例如下:


int secondNumber(int hours = 0, int minutes = 0, int seconds = 0);
cout << secondNumber() << endl;


輸出就是0了。


設(shè)置默認(rèn)參數(shù)的兩條主要規(guī)則


  1. 只有函數(shù)原型才能包含參數(shù)的默認(rèn)值。

  2. 參數(shù)的默認(rèn)值是寫(xiě)在參數(shù)列表之后。而且是靠右邊寫(xiě)。



總結(jié)


  1. 一個(gè)函數(shù)可以接收數(shù)據(jù)(通過(guò)參數(shù)),也可以返回?cái)?shù)據(jù)(通過(guò)retur函數(shù)可以接收引用作為參數(shù),以直接修改內(nèi)存中的信息。

  2. 當(dāng)你的程序漸漸多起來(lái)時(shí),建議將其分為不同的文件。每個(gè)文件中存放特定的函數(shù),而且文件是成對(duì)組織的:.cpp文件存放函數(shù)的定義,.h文件存放函數(shù)的原型。

  3. 一定要寫(xiě)好注釋。



第一部分第九課預(yù)告


今天的課就到這里,一起加油吧!

下一課我們學(xué)習(xí):數(shù)組威武,動(dòng)靜合一

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI