C++學(xué)習(xí)筆記:(二)函數(shù)重載&&常量與引用

本文章分為知識點(diǎn)、例子和心得,交流群728483370,一起學(xué)習(xí)加油!

3.函數(shù)重載

3.1非成員函數(shù)重載

3.2成員函數(shù)重載

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

3.4內(nèi)聯(lián)函數(shù)

4.常量與引用

4.1 const的最初動機(jī)

4.2 const與指針

4.3 const與函數(shù)

4.4 const與類

4.5引用(&)

4.6復(fù)制構(gòu)造函數(shù)

3.函數(shù)重載

函數(shù)名可以看做是一個(gè)操作的名字。通過這些名字,可以寫出易于人們理解和修改的程序。但是有的編程語言規(guī)定每個(gè)函數(shù)只能有唯一的標(biāo)識符。如果想打印三種不同類型的數(shù)據(jù):

整型、字符型和實(shí)型,則不得不用三個(gè)不同的函數(shù)名,如Print_int()、Print_char()和Print_float(),這樣顯然增加了編程的工作量。針對這類問題,也就是當(dāng)函數(shù)實(shí)現(xiàn)的是同一類功能,只是部分細(xì)節(jié)不同(如參數(shù)的個(gè)數(shù)或參數(shù)類型不同)時(shí),C++提供了函數(shù)重載機(jī)制,即將這些函數(shù)取成相同的名字,從而使程序易于閱讀和理解,方便記憶和使用。

函數(shù)重載是指兩個(gè)或兩個(gè)以上的函數(shù)具有相同的函數(shù)名,但參數(shù)類型不一致或參數(shù)個(gè)數(shù)不同。編譯器根據(jù)實(shí)參和形參的類型及個(gè)數(shù)進(jìn)行相應(yīng)地匹配,自動確定調(diào)用哪一個(gè)函數(shù)。使得重載的函數(shù)雖然函數(shù)名相同,但功能卻不完成相同。函數(shù)重載是C++對C語言的擴(kuò)展,包括非成員函數(shù)的重載和成員函數(shù)重載。


3.1非成員函數(shù)重載

非成員函數(shù)重載是指對用戶所編寫的那些功能相同或類似、參數(shù)個(gè)數(shù)或類型不同的用戶自定義函數(shù),在C語言中必須采用不同的函數(shù)名加以區(qū)分,而在C++中可以采用相同的函數(shù)名,從而提高程序的可讀性。

#include <iostream>

using namespace std;

class Complex

{

private:

double real;

double imag;

public:

Complex(double r, double i);

Complex(double r);

Complex();

~Complex(){

cout << "destructor" <<endl;

}

void Print();

Complex add(Complex a);

};

Complex::Complex(double r, double i)

{

real = r;

imag = i;

cout << "Constructor:Two" <<endl;

}

Complex::Complex(double r)

{

real = r;

imag = 0;

cout << "Constructor:One" <<endl;

}

Complex::Complex()

{

real = 0;

imag = 0;

cout << "Constructor:Zero" <<endl;

}

Complex Complex::add(Complex a)

{

Complex temp;

temp.real = this->real + a.real;

temp.imag = this->imag + a.imag;

return temp;

}

void Complex::Print()

{

cout <<real;

if(imag > 0)

cout << "+";

if(imag != 0)

cout << imag << "i" <<endl;

}

int main()

{

Complex com1(1.1, 2.2),com2(3.3, 4.4),com3(4.4),total;

total = com1.add(com2);

total.Print();

total = com1.add(com3);

total.Print();

return 0;

}

注意:(1)重載函數(shù)必須具有不同的參數(shù)個(gè)數(shù)或不同的參數(shù)類型,若只是返回值的類型不同或形參名不同是不行的。

(2)重載函數(shù)應(yīng)滿足:函數(shù)名相同,函數(shù)的返回值類型可以相同也可以不同,但各函數(shù)的參數(shù)表中的參數(shù)個(gè)數(shù)或類型必須有所不同。這樣才能進(jìn)行區(qū)分,從而正確的調(diào)用函數(shù)。

(3)不要將不同功能的函數(shù)定義為重載函數(shù),以免產(chǎn)生誤解。

如:int f(int a, int b)

{

return a+b;

}

double f(double a, double b)

{

return a*b;

}

創(chuàng)建重載函數(shù)時(shí),必須讓編譯器能區(qū)分兩個(gè)(或更多)的重載函數(shù),當(dāng)創(chuàng)建的多個(gè)重載函數(shù),編譯器不能區(qū)分時(shí),編譯器就認(rèn)為這些函數(shù)具有多義性,這些函數(shù)調(diào)用是錯(cuò)誤,編譯器不會編譯該程序。

#include <iostream>

using namespace std;

float mul(float x)

{

??return 2*x;

}

double mul(double x);

{

??return 2*x;

}

int main()

{

??cout << mul(10.4) <<endl;

cout << mul(10) <

??return 0;

}


3.2成員函數(shù)重載

成員函數(shù)的重載主要是為了適應(yīng)相同成員函數(shù)的參數(shù)多樣性。成員函數(shù)重載的一個(gè)很重要的應(yīng)用就是重載構(gòu)造函數(shù)。創(chuàng)建一個(gè)對象時(shí),有可能需要帶參數(shù),也有可能不需要帶參數(shù),或是帶的參數(shù)的個(gè)數(shù)不一樣。通過對構(gòu)造函數(shù)進(jìn)行重載,可以實(shí)現(xiàn)定義對象時(shí)初始化賦值的多樣性。

#include <iostream>

using namespace std;

int mul(int x, int y)

{

return x*y;

}

double mul(double x, double y)

{

return x*y;

}

int main()

{

int x,y;

double a,b;

cout << "Input x,y:" <<endl;

cin >> x >> y;

cout << "x*y = " << mul(x,y) <<endl;

cout << "Input a,b:" <<endl;

cin >> a >> b;

cout << "a*b = " << mul(a,b) <<endl;

return 0;

}


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

在C++中,提供了默認(rèn)參數(shù)的做法,也就是允許在函數(shù)的聲明或定義時(shí)給一個(gè)或多個(gè)參數(shù)指定默認(rèn)值。這樣在函數(shù)調(diào)用時(shí),如果不給出實(shí)際參數(shù),則可以按指定的默認(rèn)值進(jìn)行工作。

如:Complex(double r = 0, double i = 0)當(dāng)進(jìn)行調(diào)用時(shí),編譯器會按從左到右順序?qū)?shí)參與形參結(jié)合,若未指定足夠的實(shí)參,則編譯器按順序用函數(shù)原型中的默認(rèn)值來補(bǔ)足所缺少的實(shí)參。

Complex(3.5, 9.6); // ?r = 3.5 ?i = 9.6

Complex(3.5); ??// ?r = 3.5 ??i = 0

Complex(); ????// ???r = 0 ??i = 0

(1)當(dāng)函數(shù)既有原型聲明又有定義時(shí),默認(rèn)參數(shù)只能在原型聲明中指定,而不能在函數(shù)定義中指定。例如:

Complex (double r = 0, double i = 0);

Complex (double r = 0, double i = 0) ??//錯(cuò)誤情況

{

??...

}

(2)在函數(shù)原型中,所有取默認(rèn)值的參數(shù)都必須出現(xiàn)在不取默認(rèn)值的參數(shù)的右邊。也就是一旦開始定義默認(rèn)值得參數(shù),在其后面就不能再說明不取默認(rèn)值得參數(shù)了。

void fun(int i, int j = 5, int k); ?//錯(cuò)誤

void fun(int i, int k, int j = 5);

(3)在函數(shù)調(diào)用時(shí),若某個(gè)參數(shù)省略,則其后的參數(shù)皆應(yīng)該省略而采用默認(rèn)值。不允許某個(gè)參數(shù)省略后,在給其后的參數(shù)指定參數(shù)值。如:Complex( , 9.6) ?//錯(cuò)誤形式

(4)當(dāng)函數(shù)的重載帶有默認(rèn)參數(shù)時(shí),要注意避免二義性。

如:Complex(double r, double i = 0);

Complex(double r); ???

是錯(cuò)誤的。因?yàn)槿绻瘮?shù)調(diào)用Complex(3.5),編譯器將無法確定調(diào)用哪一個(gè)函數(shù)。函數(shù)的帶默認(rèn)參數(shù)值的功能可以在一定程度上簡化程序的編寫。


3.4內(nèi)聯(lián)函數(shù)

在程序設(shè)計(jì)中,效率是一個(gè)重要的指標(biāo)。在C中,提高效率的一個(gè)方法是使用宏。宏可以不用函數(shù)調(diào)用,但看起來像函數(shù)調(diào)用。宏的實(shí)現(xiàn)是用預(yù)處理器。預(yù)處理器直接用宏代碼代替宏調(diào)用,因此就不需要函數(shù)調(diào)用所需的保存調(diào)用時(shí)的現(xiàn)場狀態(tài)和返回地址、進(jìn)行參數(shù)傳遞等時(shí)間花費(fèi)。然而,C++的預(yù)處理器不允許存取私有數(shù)據(jù)。這意味著預(yù)處理器宏在用作成員函數(shù)時(shí)變得非常無用。為了既保證預(yù)處理器宏的效率有增加安全性,而且還能像一般成員函數(shù)一樣可以在類里訪問自如,因此C++引入了內(nèi)聯(lián)函數(shù)。內(nèi)聯(lián)函數(shù)是一個(gè)函數(shù),它與一般函數(shù)的區(qū)別是在使用時(shí)可以像宏一樣展開,所以沒有函數(shù)調(diào)用的開銷。通過內(nèi)聯(lián)函數(shù),它把函數(shù)體的代碼直接插入到調(diào)用處,將調(diào)用函數(shù)的方式改為順序執(zhí)行直接插入的程序代碼,減少了程序的執(zhí)行時(shí)間(但也增加了代碼的實(shí)際長度)。因此,使用內(nèi)聯(lián)函數(shù)可以提高系統(tǒng)的執(zhí)行效率。但在內(nèi)聯(lián)函數(shù)體中,不能含有復(fù)雜的結(jié)構(gòu)控制語句,如switch和while語句等。內(nèi)聯(lián)函數(shù)實(shí)際上是一種空間換時(shí)間的方案,因此其缺點(diǎn)是增大了系統(tǒng)空間方面的開銷。在類內(nèi)給出函數(shù)體定義的成員函數(shù)被默認(rèn)為內(nèi)聯(lián)函數(shù)。

內(nèi)聯(lián)函數(shù)的定義格式:

inline返回值類型 ?函數(shù)名(形參表)

{

//函數(shù)體

}

#include <iostream>

using namespace std;

inline char trans(char ch)

{

if(ch >='a' && ch <='z')

return ch-32;

else

return ch+32;

}

int main()

{

char ch;

while((ch = getchar()) != '\n')

cout <<trans(ch);

cout <<endl;

return 0;

}

(1)內(nèi)聯(lián)函數(shù)代碼不宜太長,而且不能含有復(fù)雜的分支或循環(huán)等語句。

(2)在類內(nèi)定義的成員函數(shù)默認(rèn)為內(nèi)聯(lián)函數(shù)。

(3)在類外定義時(shí),則必須加上關(guān)鍵字inlin。否則,編譯器會將它作為普通成員函數(shù)對待。

(4)遞歸調(diào)用的函數(shù)不能定義為內(nèi)聯(lián)函數(shù)。


4.常量與引用

4.1 const的最初動機(jī)

for(int i = 0; i <= 100; i++)

{

...

}

這段代碼存在一個(gè)可讀性問題。100作為循環(huán)范圍的目的是什么?另一個(gè)問題是維護(hù)性問題,假設(shè)程序代碼中有很多地方要用到100這個(gè)數(shù)字,如果發(fā)生改動,則要明白每個(gè)100的具體作用,哪些需要修改,哪些不需要修改。解決的辦法是幫它取一個(gè)名字,即值替代的方式,做到見名知義。可以使用C語言中的:#define MAX 100。


4.1.1由define引發(fā)的問題

首先看define會引發(fā)的錯(cuò)誤:

#define fun(a) a*5

...

int s = fun(3+5)

設(shè)計(jì)的目的是要得到結(jié)果(3+5)*5=40,然而結(jié)果是28!

在define預(yù)處理機(jī)制中,編譯器僅把fun(a)當(dāng)做是一個(gè)名字來對待,它替代的是數(shù)字(或公式)a*5。預(yù)處理過程中,編譯器既不對其做類型檢查,也不對其分配存儲空間,當(dāng)調(diào)用fun(a)時(shí),僅僅對fun(a)中的符號做簡單的替換處理,轉(zhuǎn)換成:fun(a) = 3+5*5。編譯器永遠(yuǎn)也看不到fun(a)這個(gè)符號,因?yàn)樵陬A(yù)處理過程中被替換掉了。


4.1.2 const使用方法

使用const的好處是它允許指定一種語意上的約束:某種對象不能被修改,而由編譯器具體來實(shí)施這種約束。通過const,可以通知編譯器和其他程序員某個(gè)值要保持不變。只要是這種情況,應(yīng)明確的使用const,因?yàn)檫@樣做可以借助編譯器的幫助確保這種約束不被破壞。

聲明格式:const類型名 對象名; ??如:const int MAX = 100;

表明可以在編譯器知道的任何地方使用MAX。

注意:(1)盡量把const定義放進(jìn)頭文件里,由此通過包含頭文件,把const定義放在一個(gè)需要放置的地方,并由編譯器分配給它一個(gè)編譯單元。C++中const為內(nèi)部連接,即由const定義的常量僅在被定義的文件才能看到,而不會被其他文件看到,除非使用extern!一般情況下,編譯器不為const分配空間(而extern則強(qiáng)制分配空間)。

(2)當(dāng)定義一個(gè)const常量時(shí),必須初始化,除非用extern做了清楚的說明:

extern const int bufsize;

常量的使用一是消除不安全因素,二是消除存儲和讀操作,使代碼的執(zhí)行效率更高。

const int Datalist[] = {5,8,11,14};

Struct Mystruct {int i; int j;}

const struct Mystruct sList[] = {{1,2},{3,4}};

char cList[Datalist[1]]; ?????//錯(cuò)誤

float fList[sList[0].i]; ???????//錯(cuò)誤

錯(cuò)誤的原因在于,在編譯時(shí)編譯器必須為數(shù)組分配固定大小的內(nèi)存空間。而使用const修飾的數(shù)組意味著“不能被改變”的一塊存儲區(qū),其值在編譯期間不能被使用。


4.2 const與指針

const與指針的結(jié)合使用,有兩種情況:一是用const修飾指針,即修飾存儲在指針里的地址;二是修飾指針指向的對象。為了防止混淆使用,采用“靠近”原則,即const離哪個(gè)量近則修飾哪個(gè)量。如果const修飾符離變量近,則表達(dá)的意思為指向常量的指針;如果離指針近,則表示指向變量的常指針。

指向常量的指針定義格式:

const類型名 *指針變量名;

const int *p;

表明p是一個(gè)指針,是一個(gè)指向const int的指針。即p指向一個(gè)整型常量,這個(gè)常量當(dāng)然不能被改變,但是p可以“被改變”。

常指針定義格式:

類型名*const指針名;

int i = 4;

int *const q = &i;

表明q是一個(gè)常指針,一個(gè)指向int類型的變量i的const指針,q必須有一個(gè)初始值,它只能指向這個(gè)初始值對象i,不能“被改變”而指向其他對象,但所指向的對象的值可以被改變。

i= 5;

*q = 6;

可以使用一個(gè)常指針指向一個(gè)變量,也可以把非const對象變?yōu)閏onst對象。

int i = 4;

int *const p = &i; ??????????//可以用const指針指向一個(gè)非const對象

const int *const q = &i; ?????//可以把非const對象地址賦給const對象指針

也可以用指向字符的指針來指向字符串,例如:

char *p =“hello!”;

此處,p為非const指針,指向非const數(shù)據(jù)(雖然”hello”為常量),但編譯器把它當(dāng)成非常量來處理,指針指向它在內(nèi)存中的首地址。雖然沒有語法錯(cuò)誤,但是不建議這樣使用。如果想用指針指向字符串常量,可以這樣:

const char *q =“hello!”; ?????????//非const指針,const數(shù)據(jù)

const char *const p =“hello!”; ????//const指針,const數(shù)據(jù)

注意:可以把非const數(shù)據(jù)對象地址賦給const指針,但是不能把const對象的地址賦給指向非const對象的指針。

int i = 5;

const int j = 3;

int *p = &i;

//int *q = &i; ?????//錯(cuò)誤,把const對象的地址賦給指向非const對象的指針

int *s = (int *)&j; ??//強(qiáng)制轉(zhuǎn)換,合法


4.3 const與函數(shù)

4.3.1 const類型參數(shù)

定義格式:返回值類型函數(shù)名稱(const 類型 參數(shù)名, ...)

void f(const int j)

{

i++; ???????//錯(cuò)誤

}

void f(const int *p)

{

(*p)++; ????//錯(cuò)誤

}

表示參數(shù)i的初始值在函數(shù)f()中不能被改變。對于傳遞地址類型的參數(shù),而又不想讓使用者在函數(shù)中改變參數(shù)值時(shí),要用const修飾。


4.3.2 const類型返回值

可以用const修飾符修飾函數(shù)的返回值,即函數(shù)返回一個(gè)常量,此常量既可以賦給常量,也可以賦給變量。

int res()

{

return 5;

}

const int conres()

{

return 5;

}

int main()

{

int j = res();

j++;

const int i = res(); ????//正確,函數(shù)返回值賦值給常量

int k = conres(); ??????//正確,把常量的值賦給變量

k--; ????????????????//正確,變量變化

const int f = conres(); ??//正確,常量賦值給常量

return 0;

}

常對象的使用

#include <iostream>

using namespace std;

class Tcons

{

int iData;

public:

Tcons(int i = 0):iData(i){}

void Seti(int i)

{

iData = i;

cout << "iData:" << iData <<endl;

}

};

Tcons test1()//返回普通對象

{

return Tcons();

}

const Tcons test2()//返回常對象

{

return Tcons();

}

int main()

{

test1() = Tcons(10);

//正確,test1函數(shù)返回一個(gè)Tcons對象,并把對象Tcons(10)的值賦給它

test1().Seti(20);

//正確,調(diào)用test1(),得到一個(gè)返回對象,并調(diào)用此對象的成員函數(shù)

//test2() = Tcons(10); ??//錯(cuò)誤,常對象不能被修改

//test2().Seti(20); ?????//錯(cuò)誤,常對象內(nèi)容不能被修改

return 0;

}


4.3.3 const在傳遞地址中的應(yīng)用

在函數(shù)的實(shí)參與形參結(jié)合時(shí)的傳遞地址的過程中,對于在被調(diào)用的函數(shù)中不需要修改的指針或?qū)ο螅胏onst修飾是合適的。

#include <iostream>

using namespace std;

void test(int *p){}

void testpointer(const int *p)

{

//(*p)++; ?????????//錯(cuò)誤,不允許修改常量內(nèi)容

int i = *p; ???????//正確,常量賦值給變量

//int *q = p; ?????//錯(cuò)誤,不能把一個(gè)指向常量的指針賦值給一個(gè)指向非常量的指針

}

const char *teststring()

{

return "hello!";

}

const int *const testint()

{

static int i = 100;

return &i;

}

int main()

{

int m = 0;

int *im = &m;

const int *cim = &m; ????//正確,可以把非常量的地址賦給一個(gè)指向常量的指針

test(im); ?????????????????

//test(cim); ????????????//錯(cuò)誤,不能把指向常對象的指針賦值給指向非常對象的指針

testpointer(im); ??????

testpointer(cim);

//char *p = teststring();//錯(cuò)誤,不能把指向常對象的指針賦值給指向非>常對象的指針

const char *q = teststring();

cout << *q <<endl;

//int *ip = testint(); ??//錯(cuò)誤,不能把指向常對象的指針賦值給指向非>常對象的指針

const int *const ipm = testint();

cout << *ipm <<endl;

const int *iqm = testint();

cout << *iqm <<endl;

//*testint() = 10; ??????//錯(cuò)誤,因?yàn)閠estint()返回值指向常量的指針,其內(nèi)容不能被修改

return 0;

}

Test()是一個(gè)有普通指針參數(shù)的函數(shù),而testpointer()是一個(gè)帶有指向常量指針參數(shù)的函數(shù),因此testpointer()函數(shù)中試圖修改const內(nèi)容時(shí)會發(fā)生錯(cuò)誤。

函數(shù)teststring()返回值為地址,表明編譯器為字符串常量分配的地址。此時(shí)const修飾顯得很重要;否則,如果允許執(zhí)行語句char *p = teststring();,將會導(dǎo)致通過指針p來修改“常量”的內(nèi)容而發(fā)生錯(cuò)誤。

而函數(shù)testint()返回的指針不僅是常量,而且指向的空間為靜態(tài)的,函數(shù)不隨著調(diào)用的結(jié)束而釋放指針?biāo)赶虻目臻g,調(diào)用結(jié)束后仍然有效。需要注意的是,函數(shù)testint()的返回值類型為const int*const,它可以賦值給const int* const類型的,也可以賦值給const int*類型的(編譯器不報(bào)錯(cuò),因?yàn)榉祷刂凳强截惙绞?,第二個(gè)const的含義僅當(dāng)返回量出現(xiàn)在賦值號左邊時(shí)(*testint()=10),const才顯示它的含義,編譯器才會報(bào)錯(cuò)。


4.4 const與類

const在類里有兩種應(yīng)用:一是在類里建立類內(nèi)局部常量,可用在常量表達(dá)式中,而常量表達(dá)式在編譯期間被求值;二是const和類成員函數(shù)的結(jié)合使用。

4.4.1類內(nèi)const局部常量

在一個(gè)類里使用const修飾的意思是“在這個(gè)對象壽命期內(nèi),這是一個(gè)常量”。然而,對這個(gè)常量來說,每個(gè)不同的對象可以含一個(gè)不同的值。

在類里建立一個(gè)const成員時(shí)不能賦初值,只能在構(gòu)造函數(shù)里對其賦初值,而且要放在構(gòu)造函數(shù)特殊的地方。因?yàn)閏onst必須在創(chuàng)建它的地方被初始化,所以在構(gòu)造函數(shù)的主體里,const成員必須已被初始化。

如:

class conClass

{

const int NUM; ?????//不能賦初值

public:

conClass();

};

conClass::conClass():NUM(100){}

常用的一個(gè)場合就是在類內(nèi)聲明一個(gè)常量,用這個(gè)常量來定義數(shù)組的大小,從而把數(shù)組的大小隱藏在類里。

錯(cuò)誤示例:

class conClass

{

const int NUM = 100;

int iData[NUM];

public:

conClass();

};

以上為錯(cuò)誤情形。因?yàn)樵陬愔羞M(jìn)行存儲空間分配,編譯器不能知道const的內(nèi)容是什么,所以不能把它用做編譯期間的常量。在類里const的意思是“在這個(gè)特定對象的壽命期內(nèi),而不是對于整個(gè)類來說,這個(gè)值是不變的(const)”。

兩種解決辦法

靜態(tài)常量。為了提高效率,保證所有的類對象最多只有一份拷貝值,通常需要聲明為靜態(tài)的。

class Student

{

static const int NUM = 30;

int iScorelist[NUM];

...

};

程序中的NUM,不是定義,而是一個(gè)聲明,所以在類外還需要加上定義:

const int Student::NUM;

老版本的編輯器不會接受上面的語法,因?yàn)樗J(rèn)為類的靜態(tài)成員在聲明時(shí)定義初始值是非法的;而且類內(nèi)只允許初始化整數(shù)類型(如int、bool、char等),且只能是常量。

enum(枚舉)

class Student

{

enum{NUM = 30};

int iData[NUM];

public:

conClass();

};


4.4.2常對象與常成員函數(shù)

像聲明一個(gè)普通的常量一樣,可以聲明一個(gè)復(fù)雜的對象為常量。

const int i = 10;

const conClass cTest(10);

因?yàn)槁暶鱟Test為const類型,所以必須要保證在對象cTest的整個(gè)生命周期內(nèi)不能被改變。對于公有數(shù)據(jù)很容易做到,然而對于私有數(shù)據(jù),如何保證每個(gè)成員函數(shù)的調(diào)用也不改變?需要聲明成員函數(shù)為const類型,等同于告訴編譯器此類的一個(gè)const對象可以調(diào)用這個(gè)成員函數(shù),而const對象調(diào)用非const成員函數(shù)則不行。

const成員函數(shù)定義格式:

class類名

{

...

返回值類型成員函數(shù)名稱(參數(shù)列表)const;

...

};

如在函數(shù)的前面加上const,表明函數(shù)返回值為const,為了防止混淆,把const放在函數(shù)的后面。在一個(gè)const成員函數(shù)里,試圖改變?nèi)魏螖?shù)據(jù)成員或調(diào)用非const成員函數(shù),編譯器都將給出出錯(cuò)信息。

#include <iostream>

#include <string.h>

using namespace std;

class Student

{

int no;

char name[20];

public:

Student();

int Getno()const;

const char* Getname();

void Print()const;

};

Student::Student()

{

no = 1;

strcpy(name, "wang");

}

int Student::Getno()const

{

return no;

}

const char* Student::Getname()

{

return name;

}

void Student::Print()const

{

cout << "No:" << no << " Name:" << name <<endl;

}

int main()

{

Student s1;

s1.Getno();

s1.Getname();

const Student s2;

s2.Getno();

//s2.Getname(); ?//錯(cuò)誤,常對象調(diào)用非const成員函數(shù)

s2.Print();

return 0;

}

如果真的想改變常對象的某些數(shù)據(jù)成員怎么辦?有兩種方法:一是強(qiáng)制轉(zhuǎn)換;二是使用mutable。

#include <iostream>

using namespace std;

class Test

{

int i,j;

public:

Test():i(0),j(0){}

void f()const;

};

void Test::f()const

{

//i = 1; ???//錯(cuò)誤,在常成員函數(shù)中修改類成員

((Test*)this)->j = 5;//強(qiáng)制轉(zhuǎn)換

cout << "I:" << i << " J:" << j <<endl;

}

int main()

{

const Test t;

t.f();

return 0;

}

#include <iostream>

using namespace std;

class Test

{

int i;

mutable int j;

public:

Test():i(0),j(0){}

void f()const;

};

void Test::f()const

{

//i = 1; ???//錯(cuò)誤,在常成員函數(shù)中修改類成員

j = 5; ?????

cout << "I:" << i << " J:" << j <<endl;

}

int main()

{

const Test t;

t.f();

return 0;

}


4.5引用(&)

在C++中,當(dāng)函數(shù)參數(shù)采用傳值方式傳送時(shí),除非明確指定,否則函數(shù)的形參總是通過對“實(shí)參的拷貝”來初始化的,函數(shù)的調(diào)用者得到的也是函數(shù)返回值的拷貝。傳值方式采用位拷貝方式,使得運(yùn)行效率低下。雖然通過指針傳遞地址的方式提高了運(yùn)行效率,但是相比引用而言,代碼不夠簡潔明了。引用是C++的一大特點(diǎn),是支持C++運(yùn)算符重載的語法基礎(chǔ),也為函數(shù)參數(shù)的傳入與傳出提供了便利。如果不想改變參數(shù),則可通過常量引用傳遞。(位拷貝拷貝的是地址,而值拷貝則拷貝的是內(nèi)容。)

4.5.1引用的概念

引用被認(rèn)為是某個(gè)變量或?qū)ο蟮膭e名,引用定義格式:

類型名&引用名 = 被引用的對象名稱;

引用就像給原來的對象起了一個(gè)“綽號”,訪問引用時(shí),實(shí)際訪問的就是被引用的變量或?qū)ο蟮拇鎯卧?/p>

#include <iostream>

using namespace std;

int main()

{

int i = 0;

int &j = i;

cout << "i=" << i << ",j=" << j <<endl;

i++;

cout <<"i=" << i << ",j=" << j <<endl;

j++;

cout <<"i=" << i << ",j=" << j <<endl;

return 0;

}

引用就像一個(gè)自動能被編譯器逆向引用的常量型指針。它通常用于修飾函數(shù)的參數(shù)和函數(shù)的返回值,但也可以獨(dú)立使用。

(1)當(dāng)引用被創(chuàng)建時(shí),它必須被初始化(指針則可以在任何時(shí)候被初始化)。

(2)沒有NULL引用。必須確保引用是和一個(gè)合法的存儲單元關(guān)聯(lián)。

(3)一旦一個(gè)引用被初始化為指向一個(gè)對象,它就不能被改變?yōu)閷α硪粋€(gè)對象的引用(指針可以在任何時(shí)候指向另一個(gè)對象)。

#include <iostream>

using namespace std;

int main()

{

int one;

int &r = one;

one = 5;

cout << "One:\t" << one <<endl;

cout << "R:\t" << r <<endl;

cout << "&One:\t" << &one <<endl;

cout << "&R:\t" << &r <<endl;

int two = 64;

r = two;

cout << "\nOne:\t" << one <<endl;

cout << "Two:\t" << two <<endl;

cout << "R:\t" << r <<endl;

cout << "&One:\t" << &one <<endl;

cout << "&R:\t" << &r <<endl;

cout << "&Two:\t" << &two <<endl;

return 0;

}

當(dāng)我看到這個(gè)代碼的時(shí)候,我以為r = two;是改變?yōu)閷α硪粋€(gè)對象的引用,其實(shí)不是這樣的,r = two;只是賦值而已,可以看到&One和&R一直沒變。

當(dāng)定義一個(gè)引用時(shí),必須被初始化指向一個(gè)存在的對象。

int n;

int &m = n;

//int &j; ????//錯(cuò)誤,沒有初始化

int x= 5;

int &y = x;

int &z = y;

使用引用的時(shí)候要注意:

(1)不能建立引用數(shù)組。

(2)不能建立引用的引用。

int iData[5];

//int &icData[5] = iData; ?//錯(cuò)誤

int i;

//int &&j = i;


4.5.2引用與指針

引用與指針有著本質(zhì)的區(qū)別,指針通過變量的地址來間接訪問變量,而引用通過變量的別名來直接訪問變量。

#include <iostream>

using namespace std;

int main()

{

int i = 0;

int *p = &i;

int &c = i;

cout << "i=" << i << ",*p=" << *p << ",c=" << c <<endl;

(*p)++;

cout << "i=" << i << ",*p=" << *p << ",c=" << c <<endl;

c++;

cout << "i=" << i << ",*p=" << *p << ",c=" << c <<endl;

return 0;

}

#include <iostream>

using namespace std;

void changpointer1(int **x)

{

(*x)++;

**x = 1;

}

void changpointer2(int*& x)

{

x++;

*x = 2;

}

int main()

{

int idata[3] = {0};

int *p = idata;

int i;

for(i = 0; i < 3; i++)

cout << "idata [" << i << "]=" << idata[i] << " ";

cout <<endl;

changpointer1(&p);

for(i = 0; i < 3; i++)

cout << "idata [" << i << "]=" << idata[i] << " ";

cout <<endl;

p = idata;

changpointer2(p);

for(i = 0; i < 3; i++)

cout << "idata [" << i << "]=" << idata[i] << " ";

cout <<endl;

return 0;

}


4.5.3引用與函數(shù)

采用引用的主要用途之一就是做函數(shù)的參數(shù)使用。

#include <iostream>

using namespace std;

void swappointer(int *x, int *y)

{

int z;

z = *x;

*x = *y;

*y = z;

}

void swapcite(int &x, int &y)

{

int z;

z = x;

x = y;

y = z;

}

int main()

{

int i = 10,j = 20;

int m = 10,n = 20;

swappointer(&i, &j);

swapcite(m, n);

cout << "i=" << i << ",j=" << j <<endl;

cout << "m=" << i << ",n=" << j <<endl;

return 0;

}

當(dāng)函數(shù)的返回值為引用方式時(shí),需要特別注意的是,不要返回一個(gè)不存在的或已經(jīng)銷毀的變量的引用。

int& tcite2()

{

int m = 2;

//return m; ?????//錯(cuò)誤,調(diào)用完函數(shù)tcite2()后,臨時(shí)對象m將被釋放,返回值為一個(gè)空引用

static int x = 5;

return x;

}

int* tpointer(int *p)

{

(*p)++;

return p;

}

int& tcite(int &c)

{

c++;

return c;

}

int main()

{

int i;

tpointer(&i);

tcite(i);

return 0;

}


對常量引用的例子

void t1(int &){}

void t2(const int&){}

int main()

{

//t1(1); ??????//錯(cuò)誤,在函數(shù)t1()中,可以修改參數(shù)內(nèi)容,而1為常量

t2(1); ???????//正確,在函數(shù)t2()中,參數(shù)聲明為常量

}

C語言中,如果設(shè)計(jì)者想改變指針本身,而不是改變指針指向的內(nèi)容,則使用指向指針的指針;而在C++中可以使用簡潔的引用來實(shí)現(xiàn)。


4.6復(fù)制構(gòu)造函數(shù)

復(fù)制構(gòu)造函數(shù):是一種特殊的構(gòu)造函數(shù),其形參是本類的對象的引用,其作用是使用一個(gè)已經(jīng)存在的對象,去初始化一個(gè)新的同類對象,在以下三種情況下會被調(diào)用:①當(dāng)用一個(gè)已經(jīng)存在的對象,去初始化該類的另一個(gè)對象時(shí)。②如果函數(shù)的形參是類對象,調(diào)用函數(shù)進(jìn)行形參和實(shí)參結(jié)合時(shí)。③如果函數(shù)的返回值是類對象,函數(shù)調(diào)用完成返回時(shí)。

在編程過程中可以根據(jù)情況定義復(fù)制構(gòu)造函數(shù),以實(shí)現(xiàn)同類對象之間數(shù)據(jù)成員的傳遞。如果沒有定義類的復(fù)制構(gòu)造函數(shù),系統(tǒng)會在必要時(shí)自動生成一個(gè)隱含的復(fù)制構(gòu)造函數(shù)。這個(gè)隱含的復(fù)制構(gòu)造函數(shù)的功能是,把初始值對象的每個(gè)數(shù)據(jù)成員值都復(fù)制到新建的對象中。

#include <iostream>

using namespace std;

/*

class類名

{

public:

類名(形參表);

類名(類名&對象名);

...

};

類名::類名(類名&對象名);

{

函數(shù)體

}

*/

class Point

{

public:

Point(int xx = 0, int yy = 0)

{

x = xx;

y = yy;

}

Point(Point &p);

int getx()

{

return x;

}

int gety()

{

return y;

}

private:

int x, y;

};

Point::Point(Point &p)

{

x = p.x;

y = p.y;

cout << "Calling the copy constructor"<<endl;

}

void fun1(Point p)

{

cout << p.getx() <<endl;

}

Point fun2()

{

Point a(1,2);

return a;

}

int main()

{

Point a(4,5); ?//第一個(gè)對象a

Point b = a; ??//情況1,用a初始化b

cout << b.getx() <<endl;

fun1(b); ??????//情況2,對象b作為fun1的實(shí)參

b = fun2(); ???//情況3,函數(shù)返回值是類對象

cout << b.getx() <<endl;

return 0;

}

怎么少了一次?什么鬼?查閱資料后發(fā)現(xiàn)原因是:RVO(return value optimization),被C++進(jìn)行值返回的優(yōu)化了。我們可以將RVO優(yōu)化關(guān)閉,可以對g++增加選項(xiàng)-fno-elide-constructors,重新編繹之后,執(zhí)行結(jié)果如下:

統(tǒng)計(jì)類聲明對象個(gè)數(shù)

#include <iostream>

using namespace std;

class Student

{

private:

static int number;

public:

Student()

{

number++;

show("Student");

}

~Student()

{

number--;

show("Student");

}

static void show(const char* str = NULL)//指向常量的指針

{

if(str)

{

cout << str << ":";

}

cout << "number=" << number <<endl;

}

};

int Student::number = 0;//靜態(tài)數(shù)據(jù)成員賦值

Student f(Student x)

{

x.show("x inside f()");

return x;

}

int main()

{

Student h1;

Student h2 = f(h1);

Student::show("after call f()");

return 0;

}

結(jié)果可能不是預(yù)期的效果。在函數(shù)f()調(diào)用時(shí),原來的對象h1在函數(shù)之外,函數(shù)內(nèi)要增加一個(gè)新對象,參數(shù)x采用的值是原來對象h1的拷貝。而參數(shù)傳遞采用的是“位拷貝”方式,所以達(dá)不到預(yù)期效果。當(dāng)局部對象在函數(shù)f()調(diào)用結(jié)束時(shí),析構(gòu)函數(shù)被調(diào)用,從而number減少。同理,h2的值也是采用位拷貝方式傳遞,構(gòu)造函數(shù)也沒有被調(diào)用。所以結(jié)果是主函數(shù)運(yùn)行結(jié)束后,對象數(shù)目為負(fù)值。

在這種情況下,C++需要真正的初始化操作,這項(xiàng)工作是由復(fù)制構(gòu)造函數(shù)完成。當(dāng)使用復(fù)制構(gòu)造函數(shù)時(shí),編譯器將不再使用位拷貝。

復(fù)制構(gòu)造函數(shù)定義格式:

構(gòu)造函數(shù)名(const類名&);

class A

{

...

public:

A();

A(const A&);

}

上個(gè)例子修改后的程序:

#include <iostream>

using namespace std;

class Student

{

private:

static int number;

public:

Student()

{

number++;

show("Student");

}

Student(const Student&)

{

number++;

show("Student");

}

~Student()

{

number--;

show("Student");

}

static void show(const char* str = NULL)//指向常量的指針

{

if(str)

{

cout << str << ":";

}

cout << "number=" << number <<endl;

}

};

int Student::number = 0;//靜態(tài)數(shù)據(jù)成員賦值

Student f(Student x)

{

x.show("x inside f()");

return x;

}

int main()

{

Student h1;

Student h2 = f(h1);

Student::show("after call f()");

return 0;

}

構(gòu)造函數(shù)與復(fù)制構(gòu)造函數(shù)使用情況

#include <iostream>

#include <math.h>

using namespace std;

class Point

{

private:

double x,y;

public:

Point(double a, double b);

Point()

{

cout << "NO.2 constructor..." <<endl;

}

Point(Point &p)

{

cout << "\nNO.3 constructor..." <<endl;

}

~Point();

double Distance(Point p);

};

Point::Point(double a, double b)

{

cout << "NO.1 constructor..." <<endl;

x = a;

y = b;

}

Point::~Point()

{

cout << "destructor..." <<endl;

}

double Point::Distance(Point p)

{

double d;

d = sqrt((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y));

return d;

}

int main()

{

Point p1(3,4), p2;

cout << "The distance is " << p1.Distance(p2) <<endl;

return 0;

}

跟蹤程序,得到當(dāng)前Point類對象的個(gè)數(shù)。

#include <iostream>

#include <iomanip>

using namespace std;

class Point

{

private:

static int number;

int x,y;

public:

Point(int xx = 0, int yy = 0)

{

x = xx;

y = yy;

number++;

show("normal construction");

}

Point(const Point &p);

~Point()

{

number--;

show("~Point");

}

void show(const char* p = NULL);

};

int Point::number = 0;

Point::Point(const Point &p)

{

x = p.x;

y = p.y;

number++;

show("copy construction");

}

void Point::show(const char* p)

{

if(p)

cout << p <<":";

cout << number <<endl;

}

void fun1(Point p)

{

p.show("inside fun1()");

}

int main()

{

Point A(1,2);

Point B(A);

fun1(A);

return 0;

}

使用復(fù)制構(gòu)造函數(shù)時(shí)要注意:

(1)并不是所有的類聲明中都需要復(fù)制構(gòu)造函數(shù)。僅當(dāng)準(zhǔn)備用傳值的方式傳遞類對象時(shí),才需要復(fù)制構(gòu)造函數(shù)。

(2)為了防止一個(gè)對象不被通過傳值方式傳遞,需要聲明一個(gè)私有復(fù)制構(gòu)造函數(shù)。因?yàn)閺?fù)制構(gòu)造函數(shù)設(shè)置為私有,已顯示的聲明接管了這項(xiàng)工作,所以編譯器不再創(chuàng)建默認(rèn)的復(fù)制構(gòu)造函數(shù)。

#include <iostream>

#include <iomanip>

using namespace std;

class Point

{

private:

static int number;

int x,y;

Point(const Point& p);

public:

Point(int xx = 0, int yy = 0)

{

x = xx;

y = yy;

number++;

show("normal construction");

}

~Point()

{

number--;

show("~Point");

}

void show(const char* p = NULL);

};

int Point::number = 0;

Point::Point(const Point &p)

{

x = p.x;

y = p.y;

number++;

show("copy construction");

}

void Point::show(const char* p)

{

if(p)

cout << p <<":";

cout << number <<endl;

}

void fun1(Point p)

{

p.show("inside fun1()");

}

int main()

{

Point A(1,2);

//Point B(A); ???//錯(cuò)誤,拷貝構(gòu)造函數(shù)為私有,不能被調(diào)用

//fun1(A); ??????//錯(cuò)誤,同上

return 0;

}

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

相關(guān)閱讀更多精彩內(nèi)容

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