本文章分為知識點(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;
}