1. 程式人生 > >C++程式設計學習筆記 複習/拾遺 3

C++程式設計學習筆記 複習/拾遺 3

拷貝建構函式與解構函式

字串函式

例4.1:類中資料成員是字串

#include <iostream>
#include <cstring>//字串函式宣告所在的標頭檔案
using namespace std;
class HelloWorld
{
private:
	char msg[10];//問候資訊
public:
	void Show()
	{cout<<"Hello,"<<msg<<endl;}
	HelloWorld(char s[])//建構函式引數是陣列名
	{	strcpy(msg,s);	}
                HelloWorld()//無參建構函式
                //使用strcpy函式給msg賦值,比較繁瑣
	{	strcpy(msg, "jsj16");	}

};

int main()
{
 char s[10];
 cin>>s;
 HelloWorld a(s),b;
 //需要帶引數建構函式和無參建構函式,或者二合一:帶預設值的建構函式
 a.Show(); 
 b.Show();
 return 0;
}

在這裡插入圖片描述

string類是C++提供的字串類,其主要功能是對字串進行操作。

string類定義的變數稱為字串物件,該物件可以直接用字串常量賦值,也可呼叫string類中定義的成員函式。

例4.2:連線字串例項

#include <iostream>
#include <string>//字串函式宣告所在的標頭檔案,devcpp和moodle平臺都可以省略
using namespace std;
int main()
{
 string m_str1="abc";//用一個字串常量給一個字串物件賦值
 string m_str2=m_str1;//用一個已經有的物件m_str1給新定義的m_str2物件初始化
 string m_str3=m_str1+m_str2;//執行第三行後,m_str3的值應該是“abcabc”。
 //使用+運算子連線2個字串
 if(m_str1==m_str2)//使用==運算子比較2個字串
    cout<<"相等"<<endl;
 else
    cout<<"不等"<<endl;
 cout<<m_str3<<endl;
 m_str3=m_str1; //用m_str1物件給m_str3物件賦值
 cout<<m_str3<<endl;
 return 0;
}
例4.3: string類作函式引數例項

#include<iostream>
#include <string>
using namespace std;
void seta(string s)
{  cout<<s<<" of China"<<endl;}
void setb(const string & s)//在傳遞的"Liuxiang"這樣一個字串常量時,setb函式的形參一定要用const修飾
//如果string t=("Liuxiang"); setb(t);則setb的函式形參可以不用const修飾
{  cout<<s<<" of China"<<endl;}
int main()
{ seta("Wangbo");
  setb("Liuxiang");
  cout<<"See how they run"<<endl;
  return 0;
}

常型別與const修飾符

常型別是指使用型別修飾符const說明的型別,常型別的變數或物件的值是不能被更新的。

C++中,引入const 的目的是為了取代巨集這個預編譯指令,消除它不能進行語法檢查的缺點,同時繼承巨集替換的優點。C++中定義const常量,具有不可變性。const可以保護被修飾的東西,防止意外的修改,增強程式的健壯性。

題4.1: 訪問類中資料成員是字串物件

#include <iostream>
#include <string>//使用string類,宣告所在的標頭檔案
using namespace std;
class HelloWorld
{
private:
	string msg;//訪問資訊
public:
	void Show()
	{cout<<"Hello,"<<msg<<endl;}
	HelloWorld(const string &s)//建構函式引數是物件引用
	{	msg=s;//使用=直接賦值
	}
        HelloWorld()//無參建構函式
	{	msg= "LiMing";//使用=直接賦值
	}

};
int main()
{
 string s;
 cin>>s;
 HelloWorld a(s),b;
 a.Show();
 b.Show();
 return 0;
}

在這裡插入圖片描述

例4.4:日期類中各種建構函式呼叫的例項

#include <iostream>
using namespace std;
class Date
{
private:
	int year;	
	int month;
	int day;
public:
    void Print()//輸出
    {cout<<"日期是"<<year<<"-"<<month<<"-"<<day<<endl;}
	Date(int y,int m,int d)//帶普通引數的建構函式
	{year=y;	month=m;	day=d;
	cout<<"呼叫了帶普通引數的建構函式"<<endl;
	} 		
	Date()//無參建構函式
	{year=0;	month=0;day=0;
	cout<<"呼叫了無參建構函式"<<endl;
	}
};//類的定義結束

int main()
{ Date a1(2012,2,27);//呼叫帶普通引數的建構函式
  a1.Print();    
  Date a2;//呼叫無參建構函式
  a2.Print();    
  Date a3(a1);
  a3.Print();    	
  return 0;
}//呼叫預設拷貝建構函式


執行結果:
呼叫了帶普通引數的建構函式
日期是2012-2-27
呼叫了無參建構函式
日期是0-0-0
日期是2012-2-27

在這裡插入圖片描述

拷貝建構函式

用一個物件值建立並初始化另一個物件。

日期類的設計與實現中
          Date a1(2012,2,27);//呼叫帶普通引數的建構函式
          Date a3(a1);//呼叫預設拷貝建構函式  此句與Date a3=a1;等價
          就是用a1 的值初始化新建立的物件a3

拷貝建構函式的特點:
1.拷貝建構函式名字與類同名,沒有返回型別;//不是必須的,通常預設拷貝建構函式即可;
2.拷貝建構函式只有一個形引數,該引數是該類的物件的引用;
如果一個類中沒有定義拷貝建構函式,則系統自動生成一個預設拷貝建構函式,其功能是將已知物件的所有資料成員的值拷貝給對應物件的資料成員。

定義

拷貝建構函式的格式如下:
      <類名>::<拷貝建構函式名>(<類名>&<引用名>)
       {<函式體>}
      其中, <拷貝建構函式名>與該類名相同;

拷貝建構函式的用處

  1. 拷貝建構函式用於使用已知物件的值建立一個同類的新物件
  2. 把物件作為實引數進行函式呼叫時,系統自動呼叫拷貝建構函式實現把物件值傳遞給形參物件;
  3. 函式的返回值為物件時,系統自動呼叫拷貝建構函式對返回物件值建立一個臨時物件,然後再將這個臨時物件值賦給接收函式返回值的物件。
  4. 拷貝建構函式需要定義的情況是資料成員是指標。

學生類拷貝建構函式使用例項

#include <iostream>
#include <cstring>
using namespace std;
class CStuScore 
{
public:// 公有型別宣告
       void Show() 
        {cout<<strName<<"的平均成績為:"<<GetAverage()<<endl;}
        CStuScore(char *pName,int no,float s0, float s1,float s2)
       {strName=pName;//strName=pName;使資料成員指向外部(例如:main函式)地址,帶來不可知結果
        iStuNO=no;
        fScore[0] = s0;
        fScore[1] = s1;
       fScore[2] = s2;
       }				
   private:	  // 私有型別宣告
       float fScore[3];	// 三門課程成績 
       char *strName;// 姓名
       int iStuNO;// 學號
       float GetAverage();//計算平均成績 
  };
float CStuScore::GetAverage()//計算均值
  {
      return (float)((fScore[0] + fScore[1] + fScore[2])/3.0);
  }

int main()
{
CStuScore oOne("LiMing",21020501,80,90,65),b(oOne); 
  oOne.Show();  
  b.Show();
  return 0;
}
//CStuScore b(oOne);
//呼叫預設拷貝建構函式,只是指標值的複製(淺拷貝),即存在oOne和b物件的指標成員指向同一個記憶體空間的問題

在這裡插入圖片描述

分析

1. strName=pName;使資料成員指向外部c陣列(例如:main函式)地址,也會被改,無法封裝


int main()
{
CStuScore oOne("LiMing",21020501,80,90,65),b(oOne); 
  oOne.Show();  
  b.Show();
  return 0;
}

int main()
{
  char c[10]="LiMing";
  CStuScore oOne(c,21020501,80,90,65),b(oOne); 
  oOne.Show();  
  b.Show();
  strcpy(c,"wangbo"); //修改陣列c的內容 
  oOne.Show();    
  return 0;
}



2. strName=pName;使資料成員指向外部c陣列(例如:main函式)地址,c被釋放,strName成為野指標

int main()
{
  char *c=new char[10];
  strcpy(c,"LiMing");
  CStuScore oOne(c,21020501,80,90,65),b(oOne); 
  oOne.Show();  
  b.Show();
  delete []c; //釋放動態陣列c 
  oOne.Show(); //發生記憶體錯誤  
  return 0;
}

解構函式

不是必須的,通常預設解構函式即可(無參,函式體為空)

必須定義解構函式的情況:
1.建構函式開啟一個檔案,使用完檔案時,需要關閉檔案。
2.從堆中分配了動態記憶體區,在物件消失之前必須釋放。

特點

  1. 無返回型別,但是不要加void。
  2. 無引數,因此不存在解構函式過載,只有1個解構函式。
    在物件釋放時由系統自動呼叫。
  3. 如果程式中未宣告,則系統自動產生出一個預設形式的解構函式。
  4. 解構函式與建構函式的功能相對應,所以解構函式名是建構函式名前加一個邏輯反運算子“~”
  5. 解構函式以呼叫建構函式相反的順序被呼叫。

學生類中解構函式定義的例項

該類定義的建構函式在物件之外分配一段堆記憶體空間,撤銷時,由解構函式收回堆記憶體。通過動態陣列申請和釋放,解決了指標成員指向外部(main函式中)地址的問題。

#include <iostream>
#include <cstring>
using namespace std;
class CStuScore 
{
public:// 公有型別宣告
       void Show() 
        {cout<<strName<<"的平均成績為:"<<GetAverage()<<endl;}
        CStuScore(char *pName,int no,float s0, float s1,float s2)
       {         strName= new char[12];//動態陣列申請
	strcpy(strName,pName);
       	iStuNO=no;
       	fScore[0] = s0;
	fScore[1] = s1;
	fScore[2] = s2;
        }
       ~CStuScore()
      {delete []strName;//釋放動態申請的記憶體 
      }		//動態陣列釋放
   private:	  // 私有型別宣告
       float    fScore[3];	// 三門課程成績 
       char *strName;// 姓名
       int      iStuNO;// 學號
       float GetAverage();//計算平均成績 
  };
float CStuScore::GetAverage()//計算均值 
  {     return (float)((fScore[0] + fScore[1] + fScore[2])/3.0);  }

int main()
{ CStuScore oOne("LiMing",21020501,80,90,65);//,b(oOne); 
  oOne.Show(); 
  //b.Show();   
  return 0;
}

在這裡插入圖片描述

拷貝建構函式的深淺拷貝問題

strName=r.strName;
//淺拷貝:對逐個成員的指標值的依次複製
//存在2個或2個以上物件姓名成員指向同一個記憶體空間的問題(即2個或2個以上物件共用一個姓名),如果釋放其中一個物件,將導致另外的物件可能沒有地方存姓名。這就發生記憶體錯誤。

CStuScore(CStuScore &r){strName= new char[12];
strcpy(strName,r.strName);}
//深拷貝
//重新定義拷貝建構函式。學生類中姓名是一個指標,指向各自由new建立存放姓名字串的記憶體空間首地址。

建構函式與解構函式例項

#include <iostream>
using namespace std;
class point
{
public:
    point(int xp,int yp)
	{ x=xp; y=yp; }
    point(point& p); 
    ~point() {cout<<"解構函式被呼叫"<<endl;}
    int getx() {return x;}
    int gety() {return y;}
private:
        int x,y;
};
point::point(point& p)
{   x=p.x;   y=p.y;
    cout<<"拷貝建構函式被呼叫"<<endl;
}
point fun(point q);//函式宣告
//不要在返回指向區域性變數或者臨時物件的引用。
函式執行完畢之後,區域性變數和臨時物件將消失,
引用將指向不存在的資料。

int main()
{
    point M(12,20),P(0,0),S(0,0);//呼叫3次帶引數的建構函式建立物件M、P、S
    point N(M);//呼叫拷貝建構函式建立物件N
    P=fun(N);//函式呼叫完成,呼叫解構函式2次,釋放區域性物件R以及形參物件q
    S=M;
    cout<<"P="<<P.getx()<<","<<P.gety()<<endl; 
    cout<<"S="<<S.getx()<<","<<S.gety()<<endl;
    return 0;
}//程式結束前呼叫4次解構函式,釋放物件N、S、P、M
point fun(point q)//形參是物件,引數傳遞時呼叫拷貝建構函式建立物件q
{
    cout<<"OK"<<endl;
    int x=q.getx()+10;
    int y=q.gety()+15;
    point R(x,y);//建立物件R,呼叫帶引數的建構函式
    return R;
}


執行結果:
拷貝建構函式被呼叫
拷貝建構函式被呼叫
OK
解構函式被呼叫
解構函式被呼叫
P=22,35
S=12,20
解構函式被呼叫
解構函式被呼叫
解構函式被呼叫
解構函式被呼叫

注意,解構函式以呼叫建構函式相反的順序被呼叫。