適合具備 C 語(yǔ)言基礎(chǔ)的 C++ 教程之四
前言
在上一則教程中,我們講述了重載運(yùn)算符中前 ++和后++的重載函數(shù)的實(shí)現(xiàn),闡述了在 C++中可以將運(yùn)算符進(jìn)行重載的方法,這種方法大大地便利了程序員編寫代碼,在接下來(lái)地?cái)⑹鲋?,我們將著重講述運(yùn)算符重載時(shí)地一些更為細(xì)致地內(nèi)容,其中就包括當(dāng)重載地運(yùn)算符返回值為引用和非引用兩種狀態(tài)時(shí),代碼執(zhí)行效率地高低以及采用在類內(nèi)實(shí)現(xiàn)運(yùn)算符重載函數(shù)的方法。
返回值為引用和非引用的區(qū)別
在上述所示的類當(dāng)中,增加一部分代碼,加入析構(gòu)函數(shù)以及拷貝構(gòu)造函數(shù),代碼如下所示:
- class Point
- {
- private:
- int x;
- int y;
- public:
- Point()
- {
- cout<<"Point()"<<endl;
- }
- Point(int x, int y) : x(x), y(y)
- {
- cout<<"Point(int x, int y)"<<endl;
- }
- Point(const Point& p)
- {
- cout<<"Point(const Point& p)"<<endl;
- x = p.x;
- y = p.y;
- }
- ~Point()
- {
- cout<<"~Point()"<<endl;
- }
- friend Point operator++(Point &p);
- friend Point operator++(Point &p, int a);
- void printInfo()
- {
- cout<<"("<<x<<", "<<y<<")"<<endl;
- }
- };
在上述的代碼中,我們?cè)跇?gòu)造函數(shù)以及拷貝構(gòu)造函數(shù)析構(gòu)函數(shù)都加入了打印信息,其中,運(yùn)算符重載函數(shù)前++和后++函數(shù)沿用之前的一樣,返回值不是引用,與此同時(shí),我們?cè)谇? ++和后 ++函數(shù)中也加入打印信息的代碼,代碼如下所示:
- /* ++p */
- Point operator++(Point &p)
- {
- cout << "++p" << endl;
- p.x += 1;
- p.y += 1;
- return p;
- }
- /* p++ */
- Point operator++(Point &p, int a)
- {
- cout << "p++" << endl;
- Point n;
- n = p;
- p.x += 1;
- p.y += 1;
- return n;
- }
上述便是前 ++和 后 ++的重載函數(shù),緊接著,書寫主函數(shù)的代碼,觀察當(dāng)返回值為非引用的時(shí)候,代碼的運(yùn)行效果,主函數(shù)代碼如下所示:
- int main(int argc, char **argv)
- {
- Point p1(1, 2);
- cout<<"begin"<<endl;
- ++p1;
- cout << "******************"<<endl;
- p1++;
- cout<<"end"<<endl;
- return 0;
- }
上述代碼的運(yùn)行結(jié)果如下所示:
lhp7d3H1crAE9u2
依據(jù)運(yùn)行結(jié)果我們分析一下,第一條輸出信息 Point(int x, int y)是因?yàn)閳?zhí)行了 Point p1(1,2);語(yǔ)句而調(diào)用的構(gòu)造函數(shù),++p這條輸出信息同樣也是因?yàn)閳?zhí)行了 ++p;而調(diào)用的構(gòu)造函數(shù),那緊接著的兩條輸出信息是如何產(chǎn)生的呢,我們回過(guò)頭去看看++p的函數(shù),可以看到 ++p的函數(shù)是一個(gè)返回值為 Point類型的函數(shù),而上述中的輸出語(yǔ)句 Point(const Point& p)和 ~Point()就是在創(chuàng)建這個(gè)返回值對(duì)象時(shí)調(diào)用的構(gòu)造函數(shù)以及當(dāng)返回值返回后調(diào)用的析構(gòu)函數(shù);而緊接著的輸出信息是 p++和 Point()以及~Point(),p++這個(gè)輸出信息自然是因?yàn)檎{(diào)用的后 ++重載運(yùn)算符函數(shù)的構(gòu)造函數(shù)而輸出的打印信息,那緊接著的 Point()和 ~Point()是因?yàn)樵诤?++重載運(yùn)算符函數(shù)中,創(chuàng)建的局部變量 Point n,進(jìn)而調(diào)用了 Point()函數(shù),以及函數(shù)退出之后,局部變量銷毀,調(diào)用了析構(gòu)函數(shù)。
上述詳細(xì)地分析了各個(gè)打印信息輸出的原因,通過(guò)上述的打印信息我們可以清楚知道程序在什么地方調(diào)用了構(gòu)造函數(shù),在什么地方調(diào)用了析構(gòu)函數(shù),再次回顧上述的函數(shù)調(diào)用過(guò)程,可以看出來(lái)其實(shí)調(diào)用的Point(const Point& p)和~Point()是多余的,那要如何改進(jìn)代碼呢,我們只需要將前 ++運(yùn)算符重載函數(shù)的返回值類型改為引用就行,這樣就不會(huì)創(chuàng)建臨時(shí)的變量,同時(shí)也就不會(huì)在調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù),改動(dòng)之后的代碼如下所示:
- Point& operator++(Point &p)
- {
- cout<<"++p"<<endl;
- p.x += 1;
- p.y += 1;
- return p;
- }
那么上述代碼的運(yùn)行結(jié)果是什么呢?在主函數(shù)不變的情況下,輸出結(jié)果如下所示:
M4QzImA1uYxnBK9
可以看到上述結(jié)果中,之前在 ++p后輸出的兩條信息現(xiàn)在因?yàn)閷⒎祷刂翟O(shè)置為引用之后就消失了,說(shuō)明這樣的方法避免了調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù),節(jié)省了程序運(yùn)行的空間,那如果將后++重載函數(shù)設(shè)置為引用可不可行呢,很顯然,如果返回的是 n的引用,那么這在語(yǔ)法中就是錯(cuò)誤的,因?yàn)閚是局部變量,局部變量在函數(shù)調(diào)用結(jié)束就銷毀了,是不能作為引用對(duì)象的。如果返回的是 p呢,那么函數(shù)的運(yùn)行結(jié)果將發(fā)生改變,換句話說(shuō)就是不是實(shí)現(xiàn)的后 ++這個(gè)功能了。
最后,總結(jié)一下,對(duì)于一個(gè)函數(shù)來(lái)說(shuō),函數(shù)的返回結(jié)果如果作為值返回,那么代碼的執(zhí)行效率較低;如果作為引用返回,那么代碼的執(zhí)行效率較高,但是會(huì)存在一個(gè)問(wèn)題,引用返回可能會(huì)導(dǎo)致函數(shù)運(yùn)行出錯(cuò),所以,在保證函數(shù)運(yùn)行沒(méi)有錯(cuò)誤的前提下,為了提高效率應(yīng)該使用的是引用返回。
緊接著,我們知道我們?cè)谑褂?C++進(jìn)行編碼的時(shí)候,基本不會(huì)再采用 C語(yǔ)言中的語(yǔ)法 printf這個(gè)語(yǔ)句,隨之替代的是 cout這個(gè)語(yǔ)句,我們也知道我們使用 cout進(jìn)行輸出的時(shí)候,往往采用的是下面這樣的輸出方式:
- cout << "m=" << m << endl; /* 此時(shí) m 不是一個(gè)實(shí)例化對(duì)象 */
但是如果說(shuō)此時(shí) m 是一個(gè)實(shí)例化的對(duì)象,那么像上述這樣輸出就是存在問(wèn)題的,這個(gè)時(shí)候,就需要對(duì) <<運(yùn)算符進(jìn)行重載,重載的代碼如下所示:
- ostream& operator<<(ostream &o, Point p)
- {
- cout<<"("<<p.x<<", "<<p.y<<")";
- return o;
- }
稍微對(duì)上述代碼進(jìn)行一下解釋, 這里為什么返回值是ostream&呢,是因?yàn)閷?duì)于 cout來(lái)說(shuō),它是ostream類的實(shí)例化對(duì)象,在使用 cout進(jìn)行輸出的時(shí)候,它所遵循的一個(gè)輸出格式是 cout <<,因此,這里的返回值是 ostream。為什么返回值是引用呢,是為了滿足下面所示代碼的運(yùn)行,同時(shí)輸出了 m和 p1,結(jié)合上述代碼,我們來(lái)編寫主函數(shù),主函數(shù)代碼如下所示:
- int main(int argc, char **argv)
- {
- Point p1(1,2);
- Point m;
- m = p1++;
- cout << "m =" << m << "p1 =" << p1 << endl;
- }
上述代碼的運(yùn)行結(jié)果如下所示:
1cGujg7yqZSIfpK
可以看到在重載了運(yùn)算符 <<之后,輸出實(shí)例化的對(duì)象也是可行的。
類內(nèi)實(shí)現(xiàn)運(yùn)算符重載函數(shù)
在上述代碼中我們實(shí)現(xiàn)的 +運(yùn)算符重載函數(shù)以及前 ++運(yùn)算符重載函數(shù)和后++運(yùn)算符重載函數(shù),都是在類外實(shí)現(xiàn)的,那么如果要在類內(nèi)實(shí)現(xiàn)以上幾個(gè)運(yùn)算符重載函數(shù),應(yīng)該如何寫呢,我們先回顧一下,在類外面實(shí)現(xiàn)的+運(yùn)算符重載函數(shù)的函數(shù)聲明如下所示:
- friend Point operator+(Point &p1, Point &p2); /* 因?yàn)樵陬愅庖軌蛟L問(wèn)類里面的數(shù)據(jù)成員,因此這里使用的是友元 */
上述是在類外實(shí)現(xiàn)運(yùn)算符重載函數(shù)時(shí)的函數(shù)原型,那么如果函數(shù)的定義就是在類里面實(shí)現(xiàn)的,函數(shù)又該如何編寫呢?首先,如果是在類里面實(shí)現(xiàn),那么當(dāng)前使用這個(gè)類進(jìn)行實(shí)例化的對(duì)象本身就可以使用 *this來(lái)表征一個(gè)對(duì)象,這個(gè)時(shí)候,如果要重載 +運(yùn)算符函數(shù),那么就只需要一個(gè)Point類的形參就行,代碼如下所示:
- class Point
- {
- private:
- int x;
- int y;
- public:
- /* 省略相關(guān)構(gòu)造函數(shù)的代碼,可以結(jié)合前文補(bǔ)全 */
- Point operator+(Point &p)
- {
- cout<<"operator+"<<endl;
- Point n;
- n.x = this->x + p.x;
- n.y = this->y + p.y;
- return n;
- }
- }
對(duì)比上述在類外面實(shí)現(xiàn)的代碼,對(duì)于重載的運(yùn)算符 +來(lái)說(shuō),只有一個(gè)形參了,而與其相加的另一個(gè)對(duì)象使用的是this來(lái)替代。依據(jù)這樣的一種思路,我們繼續(xù)將前 ++和后 ++重載的運(yùn)算符函數(shù)進(jìn)行改寫,改寫之后的代碼如下所示:
- class Point
- {
- private:
- int x;
- int y;
- public:
- /* Point p(1,2); ++p */
- Point& operator++(void)
- {
- cout<<"operator++(void)"<<endl;
- this->x += 1;
- this->y += 1;
- return *this;
- }
- /* Point p(1,2); p++; */
- Point operator++(int a)
- {
- cout<<"operator++(int a)"<<endl;
- Point n;
- n = *this;
- this->x += 1;
- this->y += 1;
- return n;
- }
- };
結(jié)合上述的代碼,我們?cè)賮?lái)編寫主函數(shù),主函數(shù)的代碼如下所示:
- int main(int argc, char ** argv)
- {
- Point p1(1,2);
- Point p2(2,3);
- Point m;
- Point n;
- cout << "begin" << endl;
- m = ++p1; /* m = p1.operator++(); */
- cout << "m =" << m << "p1 =" << p1 << endl;
- cout << "*********************" << endl;
- n = p2++; /* n = p2.operator++(0); */
- cout << "n =" << n << "p2 =" << p2 << endl;
- return 0;
- }
上述代碼中,注釋掉的代碼和沒(méi)注釋的代碼前后是等價(jià)的,只是說(shuō)注釋掉的代碼看起來(lái)更加直觀,更加容易理解其背后的原理,而注釋前的代碼則更加簡(jiǎn)潔。這里額外說(shuō)一點(diǎn),<<的重載函數(shù)是不能夠放到類內(nèi)實(shí)現(xiàn)的,因?yàn)檫@個(gè)重載函數(shù)的形參不是 Point類的,所以其只能在類外才能實(shí)現(xiàn)。
上述中,敘述了在類內(nèi)實(shí)現(xiàn)的重載運(yùn)算符函數(shù),接下來(lái)敘述一下 =運(yùn)算符在類內(nèi)實(shí)現(xiàn)的重載函數(shù),我們以之前所說(shuō)的 Person類來(lái)實(shí)現(xiàn)這個(gè)功能,Person類的代碼實(shí)現(xiàn)如下所示:
- class Person
- {
- private:
- char *name;
- int age;
- char *work;
- public:
- Person()
- {
- name = NULL;
- work = NULL;
- }
- Person(char *name, int age, char *work)
- {
- this->age = age;
- this->name = new char[strlen(name) + 1];
- strcpy(this->name,name);
- this->work = new char[strlen(work) + 1];
- strcpy(this->work, work);
- }
- /* 拷貝構(gòu)造函數(shù) */
- Person(Person &p)
- {
- this->age = p.age;
- this->name = new char[strlen(p.name) + 1];
- strcpy(this->name,p.name);
- this->work = new char[strlen(p.work) + 1];
- strcpy(this->work, p.work);
- }
- ~Person()
- {
- if (this->name)
- delete this->name;
- if (this->work)
- delete this->work;
- }
- void PrintInfo(void)
- {
- cout << "name =" << name << "age =" << age << "work =" << work << endl;
- }
- }
基于上述的代碼,我們可以書寫如下的主函數(shù)代碼:
- int main(int argc, char **argv)
- {
- Person p1("zhangsan", 18, "doctor");
- Person p2;
- p2 = p1;
- }
上述中,我們還沒(méi)有將 =運(yùn)算符進(jìn)行重載,就使用了 =實(shí)現(xiàn)了實(shí)例化對(duì)象的運(yùn)算,這樣會(huì)存在一個(gè)什么問(wèn)題呢,我們從源頭來(lái)進(jìn)行分析,=運(yùn)算符執(zhí)行的是值拷貝,那么在執(zhí)行了上述語(yǔ)句之后,p2和p1之間的關(guān)系是這樣的:
ywhv3zYKCaRjrXx
通過(guò)上述所示的圖片可以看出,如果不將 =進(jìn)行重載,那么會(huì)讓 p1和 p2的name 和 work指向同一塊內(nèi)存,這會(huì)造成什么問(wèn)題呢,如果此時(shí)已經(jīng)將 p1的內(nèi)存釋放掉了,而這個(gè)時(shí)候又要釋放 p2的內(nèi)存,這種情形就會(huì)出錯(cuò),同一塊內(nèi)存不能夠釋放兩次。
因此,就需要對(duì) =運(yùn)算符進(jìn)行重載,重載的代碼如下所示:
- /* 注意此處的代碼是在類里面實(shí)現(xiàn)的成員函數(shù),這里省略的一部分代碼 */
- Person& operator=(Person &p)
- {
- if (this == &p)
- return *this;
- this->age = p.age;
- if (this->name)
- delete this->name;
- if (this->work)
- delete this->work;
- this->name = new char[strlen(p.name) + 1];
- strcpy(this->name, p.name);
- this->work = new char[strlen(p.work) + 1];
- strcpy(this->work, p.work);
- }
這樣子就會(huì)避免上述情況的出現(xiàn),我們現(xiàn)在繼續(xù)來(lái)書寫主函數(shù):
- int main(int argc, char **argv)
- {
- Person p1("zhangsan", 18, "doctor");
- cout<<"Person p2 = p1" <<endl;
- Person p2 = p1;
- Person p3;
- cout<<"p3=p1"<<endl;
- p3 = p1;
- cout<<"end"<<endl;
- p1.PrintInfo();
- p2.PrintInfo();
- p3.PrintInfo();
- return 0;
- }
上述主函數(shù)運(yùn)行的結(jié)果如下所示:
2kiKb8NEfYynTdo
通過(guò)上述代碼我們看到,實(shí)際上代碼 Person p2 = p1的運(yùn)行并不是調(diào)用的 = 的重載函數(shù),而是調(diào)用的拷貝構(gòu)造函數(shù),只有 p3= p1才是調(diào)用的 =的重載函數(shù)。
在本章節(jié)的最后,額外補(bǔ)充一點(diǎn),剛剛提到了拷貝構(gòu)造函數(shù),實(shí)際上拷貝構(gòu)造函數(shù)的形參大多數(shù)都是加了const修飾符的,也就是像如下所示的這樣子:
- Person& operator=(const Person &p)
而這個(gè)時(shí)候,如果我們定義的 Person p1也是 const的,也就是像這樣:
- const Person p1("zhangsan", 18, "doctor");
那這個(gè)時(shí)候在使用 p1.PrintInfo()的時(shí)候就會(huì)出錯(cuò),因?yàn)榇藭r(shí)必須把該成員函數(shù)也表明為 const的才行,代碼如下所示:
- /* 類內(nèi)成員函數(shù),省略部分代碼 */
- void PrintInfo(void) const
- {
- cout << "name =" << name << "age =" << age << "work =" << work << endl;
- }
總結(jié)一下也就是說(shuō):const對(duì)象只能夠調(diào)用const成員函數(shù),而const表示的是此函數(shù)沒(méi)有對(duì)當(dāng)前對(duì)象進(jìn)行修改
小結(jié)
上述就是本期教程分享的內(nèi)容,到本期教程截至,C++相對(duì)于 C語(yǔ)言不同的一些語(yǔ)法特性就到此結(jié)束了。下期教程將介紹 C++如何實(shí)現(xiàn)面向?qū)ο蟮姆椒ā1酒诮坛趟婕暗降拇a可以通過(guò)百度云鏈接的方式獲取到。
鏈接:https://pan.baidu.com/s/1BC55_QH-iV23-ON0v1OGSA
提取碼:iyf7
本文轉(zhuǎn)載自微信公眾號(hào)「wenzi嵌入式軟件」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系wenzi嵌入式軟件公眾號(hào)。