日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++学习笔记:(二)函数重载 常量与引用

發布時間:2024/2/28 c/c++ 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++学习笔记:(二)函数重载 常量与引用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

3.函數重載

3.1 非成員函數重載

3.2 成員函數重載

3.3 函數的默認參數

3.4 內聯函數

4.常量與引用

4.1 const的最初動機

4.2 const與指針

4.3 const與函數

4.4 const與類

4.5 引用(&)

4.6 復制構造函數


3.函數重載

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

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

函數重載是指兩個或兩個以上的函數具有相同的函數名,但參數類型不一致或參數個數不同。編譯器根據實參和形參的類型及個數進行相應地匹配,自動確定調用哪一個函數。使得重載的函數雖然函數名相同,但功能卻不完成相同。函數重載是C++對C語言的擴展,包括非成員函數的重載和成員函數重載。

?

3.1 非成員函數重載

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

#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; }

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

(2)重載函數應滿足:函數名相同,函數的返回值類型可以相同也可以不同,但各函數的參數表中的參數個數或類型必須有所不同。這樣才能進行區分,從而正確的調用函數。

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

如:int f(int a, int b) {return a+b; } double f(double a, double b) {return a*b; }

創建重載函數時,必須讓編譯器能區分兩個(或更多)的重載函數,當創建的多個重載函數,編譯器不能區分時,編譯器就認為這些函數具有多義性,這些函數調用是錯誤,編譯器不會編譯該程序。

#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) <<endl; ???//產生二義性,編譯器不知道該怎么處理10return 0; }

?

3.2 成員函數重載

成員函數的重載主要是為了適應相同成員函數的參數多樣性。成員函數重載的一個很重要的應用就是重載構造函數。創建一個對象時,有可能需要帶參數,也有可能不需要帶參數,或是帶的參數的個數不一樣。通過對構造函數進行重載,可以實現定義對象時初始化賦值的多樣性。

#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; }

?

3.3 函數的默認參數

在C++中,提供了默認參數的做法,也就是允許在函數的聲明或定義時給一個或多個參數指定默認值。這樣在函數調用時,如果不給出實際參數,則可以按指定的默認值進行工作。

如:Complex(double r = 0, double i = 0)當進行調用時,編譯器會按從左到右順序將實參與形參結合,若未指定足夠的實參,則編譯器按順序用函數原型中的默認值來補足所缺少的實參。

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)當函數既有原型聲明又有定義時,默認參數只能在原型聲明中指定,而不能在函數定義中指定。例如:

Complex (double r = 0, double i = 0); Complex (double r = 0, double i = 0) ??//錯誤情況 {... }

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

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

(3)在函數調用時,若某個參數省略,則其后的參數皆應該省略而采用默認值。不允許某個參數省略后,在給其后的參數指定參數值。如:Complex( , 9.6) ?//錯誤形式

(4)當函數的重載帶有默認參數時,要注意避免二義性。

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

是錯誤的。因為如果函數調用Complex(3.5),編譯器將無法確定調用哪一個函數。函數的帶默認參數值的功能可以在一定程度上簡化程序的編寫。

?

3.4 內聯函數

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

內聯函數的定義格式: inline 返回值類型 ?函數名(形參表) {//函數體 } #include <iostream> using namespace std;inline char trans(char ch) {if(ch >='a' && ch <='z')return ch-32;elsereturn ch+32; }int main() {char ch;while((ch = getchar()) != '\n')cout <<trans(ch);cout <<endl;return 0; }

(1)內聯函數代碼不宜太長,而且不能含有復雜的分支或循環等語句。

(2)在類內定義的成員函數默認為內聯函數。

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

(4)遞歸調用的函數不能定義為內聯函數。

?

4.常量與引用

4.1 const的最初動機

for(int i = 0; i <= 100; i++) {... }

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

?

4.1.1 由define引發的問題

首先看define會引發的錯誤:

#define fun(a) a*5 ... int s = fun(3+5)

設計的目的是要得到結果(3+5)*5=40,然而結果是28!

在define預處理機制中,編譯器僅把fun(a)當做是一個名字來對待,它替代的是數字(或公式)a*5。預處理過程中,編譯器既不對其做類型檢查,也不對其分配存儲空間,當調用fun(a)時,僅僅對fun(a)中的符號做簡單的替換處理,轉換成:fun(a) = 3+5*5。編譯器永遠也看不到fun(a)這個符號,因為在預處理過程中被替換掉了。

?

4.1.2 const使用方法

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

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

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

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

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

extern const int bufsize;

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

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]]; ?????//錯誤 float fList[sList[0].i]; ???????//錯誤

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

?

4.2 const與指針

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

指向常量的指針定義格式: const 類型名 *指針變量名; const int *p;

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

常指針定義格式:

類型名 *const 指針名; int i = 4; int *const q = &i;

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

i= 5; *q = 6;

可以使用一個常指針指向一個變量,也可以把非const對象變為const對象。

int i = 4; int *const p = &i; ??????????//可以用const指針指向一個非const對象 const int *const q = &i; ?????//可以把非const對象地址賦給const對象指針

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

char *p = “hello!”;

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

const char *q = “hello!”; ?????????//非const指針,const數據 const char *const p = “hello!”; ????//const指針,const數據

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

int i = 5; const int j = 3; int *p = &i; //int *q = &i; ?????//錯誤,把const對象的地址賦給指向非const對象的指針 int *s = (int *)&j; ??//強制轉換,合法

?

4.3 const與函數

4.3.1 const類型參數

定義格式: 返回值類型 ?函數名稱(const 類型 參數名, ...) void f(const int j) {i++; ???????//錯誤 }void f(const int *p) {(*p)++; ????//錯誤 }

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

?

4.3.2 const類型返回值

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

int res() {return 5; } const int conres() {return 5; } int main() {int j = res();j++;const int i = res(); ????//正確,函數返回值賦值給常量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函數返回一個Tcons對象,并把對象Tcons(10)的值賦給它test1().Seti(20);//正確,調用test1(),得到一個返回對象,并調用此對象的成員函數//test2() = Tcons(10); //錯誤,常對象不能被修改//test2().Seti(20); //錯誤,常對象內容不能被修改return 0; }

?

4.3.3 const在傳遞地址中的應用

在函數的實參與形參結合時的傳遞地址的過程中,對于在被調用的函數中不需要修改的指針或對象,用const修飾是合適的。

#include <iostream> using namespace std; void test(int *p){} void testpointer(const int *p) {//(*p)++; //錯誤,不允許修改常量內容int i = *p; //正確,常量賦值給變量//int *q = p; //錯誤,不能把一個指向常量的指針賦值給一個指向非常量的指針 }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; //正確,可以把非常量的地址賦給一個指向常量的指針test(im); //test(cim); //錯誤,不能把指向常對象的指針賦值給指向非常對象的指針testpointer(im); testpointer(cim);//char *p = teststring();//錯誤,不能把指向常對象的指針賦值給指向非>常對象的指針const char *q = teststring();cout << *q <<endl;//int *ip = testint(); //錯誤,不能把指向常對象的指針賦值給指向非>常對象的指針const int *const ipm = testint();cout << *ipm <<endl;const int *iqm = testint();cout << *iqm <<endl;//*testint() = 10; //錯誤,因為testint()返回值指向常量的指針,其內容不能被修改return 0; }

Test()是一個有普通指針參數的函數,而testpointer()是一個帶有指向常量指針參數的函數,因此testpointer()函數中試圖修改const內容時會發生錯誤。

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

而函數testint()返回的指針不僅是常量,而且指向的空間為靜態的,函數不隨著調用的結束而釋放指針所指向的空間,調用結束后仍然有效。需要注意的是,函數testint()的返回值類型為const int*const,它可以賦值給const int* const類型的,也可以賦值給const int*類型的(編譯器不報錯,因為返回值是拷貝方式),第二個const的含義僅當返回量出現在賦值號左邊時(*testint()=10),const才顯示它的含義,編譯器才會報錯。

?

4.4 const與類

const在類里有兩種應用:一是在類里建立類內局部常量,可用在常量表達式中,而常量表達式在編譯期間被求值;二是const和類成員函數的結合使用。

4.4.1 類內const局部常量

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

在類里建立一個const成員時不能賦初值,只能在構造函數里對其賦初值,而且要放在構造函數特殊的地方。因為const必須在創建它的地方被初始化,所以在構造函數的主體里,const成員必須已被初始化。

如: class conClass {const int NUM; ?????//不能賦初值 public:conClass(); }; conClass::conClass():NUM(100){}

常用的一個場合就是在類內聲明一個常量,用這個常量來定義數組的大小,從而把數組的大小隱藏在類里。

錯誤示例:

class conClass {const int NUM = 100;int iData[NUM]; public:conClass(); };

以上為錯誤情形。因為在類中進行存儲空間分配,編譯器不能知道const的內容是什么,所以不能把它用做編譯期間的常量。在類里const的意思是“在這個特定對象的壽命期內,而不是對于整個類來說,這個值是不變的(const)”。

兩種解決辦法

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

class Student {static const int NUM = 30;int iScorelist[NUM];... };

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

const int Student::NUM;

老版本的編輯器不會接受上面的語法,因為它認為類的靜態成員在聲明時定義初始值是非法的;而且類內只允許初始化整數類型(如int、bool、char等),且只能是常量。

enum(枚舉) class Student {enum{NUM = 30};int iData[NUM]; public:conClass(); };

?

4.4.2 常對象與常成員函數

像聲明一個普通的常量一樣,可以聲明一個復雜的對象為常量。

const int i = 10; const conClass cTest(10);

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

const成員函數定義格式: class 類名 {...返回值類型 成員函數名稱(參數列表)const;... };

如在函數的前面加上const,表明函數返回值為const,為了防止混淆,把const放在函數的后面。在一個const成員函數里,試圖改變任何數據成員或調用非const成員函數,編譯器都將給出出錯信息。

#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(); //錯誤,常對象調用非const成員函數s2.Print();return 0; }

如果真的想改變常對象的某些數據成員怎么辦?有兩種方法:一是強制轉換;二是使用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; //錯誤,在常成員函數中修改類成員((Test*)this)->j = 5;//強制轉換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; //錯誤,在常成員函數中修改類成員j = 5; cout << "I:" << i << " J:" << j <<endl; }int main() {const Test t;t.f();return 0; }

?

4.5 引用(&)

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

4.5.1 引用的概念

引用被認為是某個變量或對象的別名,引用定義格式:

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

引用就像給原來的對象起了一個“綽號”,訪問引用時,實際訪問的就是被引用的變量或對象的存儲單元。

#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; }

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

(1)當引用被創建時,它必須被初始化(指針則可以在任何時候被初始化)。

(2)沒有NULL引用。必須確保引用是和一個合法的存儲單元關聯。

(3)一旦一個引用被初始化為指向一個對象,它就不能被改變為對另一個對象的引用(指針可以在任何時候指向另一個對象)。

#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; }

當我看到這個代碼的時候,我以為r = two;是改變為對另一個對象的引用,其實不是這樣的,r = two;只是賦值而已,可以看到&One和&R一直沒變。

當定義一個引用時,必須被初始化指向一個存在的對象。

int n; int &m = n; //int &j; ????//錯誤,沒有初始化 int x= 5; int &y = x; int &z = y;

使用引用的時候要注意:

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

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

int iData[5]; //int &icData[5] = iData; ?//錯誤 int i; //int &&j = i;

?

4.5.2 引用與指針

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

#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 引用與函數

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

#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; }

當函數的返回值為引用方式時,需要特別注意的是,不要返回一個不存在的或已經銷毀的變量的引用。

int& tcite2() {int m = 2;//return m; //錯誤,調用完函數tcite2()后,臨時對象m將被釋放,返回值為一個空引用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); ??????//錯誤,在函數t1()中,可以修改參數內容,而1為常量 t2(1); ???????//正確,在函數t2()中,參數聲明為常量 }

C語言中,如果設計者想改變指針本身,而不是改變指針指向的內容,則使用指向指針的指針;而在C++中可以使用簡潔的引用來實現。

?

4.6 復制構造函數

復制構造函數:是一種特殊的構造函數,其形參是本類的對象的引用,其作用是使用一個已經存在的對象,去初始化一個新的同類對象,在以下三種情況下會被調用:①當用一個已經存在的對象,去初始化該類的另一個對象時。②如果函數的形參是類對象,調用函數進行形參和實參結合時。③如果函數的返回值是類對象,函數調用完成返回時。

在編程過程中可以根據情況定義復制構造函數,以實現同類對象之間數據成員的傳遞。如果沒有定義類的復制構造函數,系統會在必要時自動生成一個隱含的復制構造函數。這個隱含的復制構造函數的功能是,把初始值對象的每個數據成員值都復制到新建的對象中。

#include <iostream> using namespace std;/* class 類名 { public:類名(形參表);類名(類名&對象名);... };類名::類名(類名&對象名); {函數體 }*/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); //第一個對象aPoint b = a; //情況1,用a初始化bcout << b.getx() <<endl;fun1(b); //情況2,對象b作為fun1的實參b = fun2(); //情況3,函數返回值是類對象cout << b.getx() <<endl;return 0; }

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

統計類聲明對象個數

#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;//靜態數據成員賦值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; }

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

在這種情況下,C++需要真正的初始化操作,這項工作是由復制構造函數完成。當使用復制構造函數時,編譯器將不再使用位拷貝。

復制構造函數定義格式: 構造函數名(const 類名&); class A {... public:A();A(const A&); }

上個例子修改后的程序:

#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;//靜態數據成員賦值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; }

構造函數與復制構造函數使用情況

#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; }

跟蹤程序,得到當前Point類對象的個數。

#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; }

使用復制構造函數時要注意:

(1)并不是所有的類聲明中都需要復制構造函數。僅當準備用傳值的方式傳遞類對象時,才需要復制構造函數。

(2)為了防止一個對象不被通過傳值方式傳遞,需要聲明一個私有復制構造函數。因為復制構造函數設置為私有,已顯示的聲明接管了這項工作,所以編譯器不再創建默認的復制構造函數。

#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); //錯誤,拷貝構造函數為私有,不能被調用//fun1(A); //錯誤,同上return 0;}

?

總結

以上是生活随笔為你收集整理的C++学习笔记:(二)函数重载 常量与引用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。