1.靜態類成員:
private:
char * str;
int length;
static int objectNum; //靜態數據成員,爲所有的對象所共享
上例中的objectNum就是靜態類成員,它是所有對象所共享的,如下圖:
在上例中,創建了三個對象,內存會給每個對象都分配數據單元用來存儲Str和Len,但是不會爲每個對象都創建num_strings,在內存中只會創建一個num_strings,供所有對象共享。靜態類數據成員的初始化不是在類的聲明文件中進行的,而是在類的函數定義文件中進行的。如在StringBad.cpp文件中初始化,而不是在StringBad.h文件中初始化。
2.特殊成員函數:
●默認構造函數:如果沒有定義構造函數,就會自動生成。
●默認析構函數:如果沒有定義析構函數,就會自動生成。
●複製構造函數:當用一個對象初始化另外一個對象時,如果我們沒有專門定義複製構造函數,則會自動生成複製構造函數,詳解見下面。
●賦值運算符:當用一個對象初始化另外一個對象時,如果我們沒有專門定義賦值運算符,則會自動生成賦值運算符,詳解見下面。
●地址運算符
3.複製構造函數:
class StringBad
{
private:
char * str;
int length;
static int objectNum; //靜態數據成員,爲所有的對象所共享
public:
StringBad(); //默認構造函數
StringBad(const char * s); //構造函數
~StringBad(); //析構函數
friend ostream & operator<<(ostream & os, const StringBad & sb);
};
int StringBad::objectNum = 0; //靜態數據成員
StringBad::StringBad() //默認構造函數
{
length = 4;
str = new char[4];
strcpy(str, "c++");
objectNum++;
cout << "Object" << objectNum << ": " << str << " is created." << endl;
}
StringBad::StringBad(const char * s) //構造函數
{
length = strlen(s);
str = new char[length + 1];
strcpy(str, s);
objectNum++;
cout << "Object" << objectNum << ": " << str << " is created." << endl;
}
StringBad::~StringBad() //析構函數
{
objectNum--;
cout << str << " is deleted." << endl;
delete [] str;
cout << objectNum << " objects left." << endl;
}
ostream & operator<<(ostream & os, const StringBad & sb) //重載<<運算符
{
os << sb.str << endl;
return os;
}
int main()
{
StringBad sb1("abcde"), sb2 = sb1;
cout << sb1;
cout << sb2;
cout << endl;
return 0;
}
在上例中,定義了一個類StringBad,在類中定義了一些成員函數,然後使用sb2 = sb1,用sb1來初始化sb2,當用一個對象初始化另外一個對象時,就會調用複製構造函數,此時就調用了複製構造函數。
默認的複製構造函數逐個複製非靜態成員(成員複製也成爲淺拷貝),複製的是成員的值。如在上例中,其實是將sb1.length賦給sb2.length,sb1.str賦給sb2.str,而對象的str成員存儲的是字符串的地址,而不是字符串本身,所以這種淺拷貝是將sb1.str和sb2.str指向同一個地方,所以這種淺拷貝容易出問題,當將sb1.str指向的內容刪除時,其實也將sb2.str指向的內容也刪除了,再刪除sb2.str所指向的內容時就會出錯。
所以,此時我們就需要自己定義一個複製構造函數來解決這種問題,將sb1.str和sb2.str指向不同的地方,但這兩個地方存儲的字符串內容是一樣的(也就是深拷貝)。
淺拷貝和深拷貝的區別:淺拷貝是僅僅只拷貝字符串的地址,使兩個指針指向同一個地方。深拷貝是拷貝內容到另外一個地方,使另一個指針指向拷貝內容的地方,通過深拷貝兩個指針指向的地址就不同,但字符串內容仍是相同的。
淺拷貝示意圖:
深拷貝示意圖:
此例中我們自己定義一個複製構造函數,使得通過對象初始化,也能使兩個對象的str指針指向不同的地址:
StringBad::StringBad(const StringBad & st) //複製構造函數,用於將一個對象用於初始化另外一個對象,如object1 = object2
{
objectNum++;
length = st.length;
str = new char[length + 1];
strcpy(str, st.str);
cout << "Object" << objectNum << ": " << str << " is created." << endl;
}
警告:如果類中包含了使用new初始化的指針成員,則我們自己應定義一個複製構造函數,以複製指向的數據,而不是複製指針,這被成爲深拷貝。淺拷貝僅僅只是複製指針的值。
4.賦值運算符:
object2 = object1;
當我們使用以上語句用object1對object2進行初始化時,實際是分兩步來處理:先使用複製構造函數創建一個臨時對象,然後通過複製運算符將臨時對象複製到新對象中。在第一步中,我們使用我們自己定義的複製構造函數來進行深拷貝;在第二步中,如果我們不自己定義賦值運算符時,我們進行的會是跟默認的複製構造函數一樣的淺拷貝,所以我們在這裏需要自己定義賦值運算符進行深拷貝。
這裏定義的賦值運算符如下:
StringBad & StringBad::operator=(const StringBad & sb)
{
if(this == &sb)
return *this;
delete [] str;
length = sb.length;
str = new char[length + 1];
strcpy(str, sb.str);
return *this;
}
5.整個項目實例代碼:
文件結構:
din.h代碼:
#ifndef DIN_H_INCLUDED
#define DIN_H_INCLUDED
#include <iostream>
using namespace std;
class StringBad
{
private:
char * str;
int length;
static int objectNum; //靜態數據成員,爲所有的對象所共享
public:
StringBad(); //默認構造函數
StringBad(const char * s); //構造函數
~StringBad(); //析構函數
StringBad(const StringBad & st); //複製構造函數,用於將一個對象用令一個對象來初始化,如object1 = object2
StringBad & operator=(const StringBad & sb);
friend ostream & operator<<(ostream & os, const StringBad & sb);
};
#endif // DIN_H_INCLUDED
din.cpp代碼:
#include <iostream>
#include <cstring>
#include "din.h"
using namespace std;
int StringBad::objectNum = 0; //靜態數據成員
StringBad::StringBad() //默認構造函數
{
length = 4;
str = new char[4];
strcpy(str, "c++");
objectNum++;
cout << "Object" << objectNum << ": " << str << " is created." << endl;
}
StringBad::StringBad(const StringBad & st) //複製構造函數,用於將一個對象用於初始化另外一個對象,如object1 = object2
{
objectNum++;
length = st.length;
str = new char[length + 1];
strcpy(str, st.str);
cout << "Object" << objectNum << ": " << str << " is created." << endl;
}
StringBad::StringBad(const char * s) //構造函數
{
length = strlen(s);
str = new char[length + 1];
strcpy(str, s);
objectNum++;
cout << "Object" << objectNum << ": " << str << " is created." << endl;
}
StringBad & StringBad::operator=(const StringBad & sb)
{
if(this == &sb)
return *this;
delete [] str;
length = sb.length;
str = new char[length + 1];
strcpy(str, sb.str);
return *this;
}
StringBad::~StringBad() //析構函數
{
objectNum--;
cout << str << " is deleted." << endl;
delete [] str;
cout << objectNum << " objects left." << endl;
}
ostream & operator<<(ostream & os, const StringBad & sb) //重載<<運算符
{
os << sb.str << endl;
return os;
}
main.cpp代碼:
#include <iostream>
#include "din.h"
using namespace std;
int main()
{
StringBad sb1, sb2("abcde"), sb4;
StringBad sb3 = sb2;
sb4 = sb2;
cout << sb1;
cout << sb2;
cout << sb3;
cout << sb4;
cout << endl;
return 0;
}
運行結果: