第一個(gè)程序: 這個(gè)程序不是很好的,運(yùn)行的時(shí)候出現(xiàn)的一些問(wèn)題,關(guān)于問(wèn)題的原因注釋有闡述。第二個(gè)程序是第一個(gè)程序的修改版。第一個(gè)程序的代碼如下: strngbad.h文件的代碼:
#include <iostream>
#ifndef STRNGBAD_H_
#define STRNGBAD_H_

 /**//* 這個(gè)StringBad類(lèi)中的問(wèn)題是由自動(dòng)定義的隱式成員函數(shù)引起的,這種函數(shù)的行為與類(lèi)設(shè)計(jì)不符。
* StringBad類(lèi)中的問(wèn)題是由隱式復(fù)制構(gòu)造函數(shù)和隱式復(fù)制操作符引起的。
* 隱式地址操作符返回調(diào)用對(duì)象的地址(即this指針的值)。如果沒(méi)有提供任何構(gòu)造函數(shù),C++將創(chuàng)建
* 默認(rèn)構(gòu)造函數(shù)。也就是說(shuō),編譯器將提供一個(gè)不接受任何參數(shù),也不執(zhí)行任何操作的構(gòu)造函數(shù),這
* 是因?yàn)閯?chuàng)建對(duì)象時(shí)總是會(huì)調(diào)用構(gòu)造函數(shù)。如果定義了構(gòu)造函數(shù),C++將不會(huì)定義默認(rèn)構(gòu)造函數(shù)。如
* 果希望在創(chuàng)建對(duì)象時(shí)顯式地對(duì)它進(jìn)行初始化,或需要?jiǎng)?chuàng)建對(duì)象數(shù)組時(shí),則必須顯示地定義默認(rèn)構(gòu)造
* 函數(shù)。這種構(gòu)造函數(shù)沒(méi)有任何參數(shù),但可以使用它來(lái)設(shè)置特定的值。帶參數(shù)的構(gòu)造函數(shù)也可以是
* 默認(rèn)構(gòu)造函數(shù),只要所有參數(shù)都有默認(rèn)值。
*/
class StringBad
  {
private:
 /**//* 這里使用char指針(而不是char數(shù)組)來(lái)表示姓名。這意味著類(lèi)聲明沒(méi)有為字符串本身分配
* 存儲(chǔ)空間,而是在構(gòu)造函數(shù)中使用new來(lái)為字符串分配空間。這避免了在類(lèi)聲明中預(yù)先定義
* 字符串的長(zhǎng)度。
*/
char * str; //pointer to string
int len; //length of string
 /**//* 這里,將num_strings成員聲明為靜態(tài)存儲(chǔ)類(lèi)。靜態(tài)類(lèi)成員有一個(gè)特點(diǎn):無(wú)論創(chuàng)建了多少對(duì)象,
* 程序都只創(chuàng)建一個(gè)靜態(tài)類(lèi)副本。也就是說(shuō),類(lèi)的所有對(duì)象共享一個(gè)靜態(tài)成員。
*/
static int num_strings; //number of objects
public:
StringBad(const char * s); //constructor
StringBad(); //default constructor
~StringBad(); //destructor
//friend function
friend std::ostream & operator<<(std::ostream & os,const StringBad & st);
};
#endif
strngbad.cpp文件的代碼
#include <cstring>
#include "strngbad.h"
using namespace std;
 /**//* 下面這條語(yǔ)句將鏡頭成員num_strings的值初始化為0。不能在類(lèi)聲明中初始化靜態(tài)成員變量。
* 這是因?yàn)槁暶髅枋隽巳绾畏峙鋬?nèi)存,但并不分配內(nèi)存。你可以使用這種格式來(lái)創(chuàng)建對(duì)象,從
* 而分配和初始化內(nèi)存。對(duì)于靜態(tài)類(lèi)成員,可以在類(lèi)聲明之外使用單獨(dú)的語(yǔ)句來(lái)進(jìn)行初始化。
*/
int StringBad::num_strings=0;

StringBad::StringBad(const char * s)
  {
len=strlen(s);
str=new char[len+1];
strcpy(str,s);
num_strings++;
cout<<num_strings<<": \""<<str<<"\" object created\n";
}
StringBad::StringBad()
  {
len=4;
str=new char[4];
strcpy(str,"C++");
num_strings++;
cout<<num_strings<<": \""<<str<<"\" default object created\n";
}
StringBad::~StringBad()
  {
cout<<"\""<<str<<"\" object deleted, ";
--num_strings;
cout<<num_strings<<" left\n";
 /**//* delete語(yǔ)句是至關(guān)重要的。str成員指向new分配的內(nèi)存。當(dāng)StringBad對(duì)象過(guò)期時(shí),str指針也將過(guò)期。
* 但str指向的內(nèi)存仍被分配,除非使用delete將其釋放。刪除對(duì)象可以釋放對(duì)象本身占用的內(nèi)存,但并
* 不能自動(dòng)釋放屬于對(duì)象成員的指針指向的內(nèi)存。因此,必須使用析構(gòu)函數(shù)。在析構(gòu)函數(shù)中使用delete
* 語(yǔ)句可確保對(duì)象過(guò)期,由構(gòu)造函數(shù)使用new分配的內(nèi)存被釋放。
*/
delete [] str;
}
std::ostream & operator<<(std::ostream & os,const StringBad & st)
  {
os<<st.str;
return os;
}
vegnews.cpp文件的代碼
#include <iostream>
using namespace std;
#include "strngbad.h"

void callme1(StringBad &);
void callme2(StringBad);

 /**//* 輸出中出現(xiàn)的各種非標(biāo)準(zhǔn)字符隨系統(tǒng)而異,這些字符表明,StringBad類(lèi)名副其實(shí)(是一個(gè)糟糕的類(lèi))。
* 另一種跡象是對(duì)象計(jì)數(shù)為負(fù)。在使用較新的編譯器和操作系統(tǒng)的機(jī)器上運(yùn)行時(shí),該程序通常會(huì)在顯示
* 有關(guān)還有-1個(gè)對(duì)象的信息之前中斷,而有些這樣的機(jī)器將報(bào)告通用保護(hù)錯(cuò)誤(GPF)。GPF表明程序
* 試圖訪問(wèn)禁止它訪問(wèn)的內(nèi)存單元,這是另一種糟糕的信號(hào)。
*/
int main()
  {
StringBad headline1("Celery Stalks at Midnight");
StringBad headline2("Lettuce Prey");
StringBad sports("Spinach Leaves Bowl for Dollars");
cout<<"headline1: "<<headline1<<endl;
cout<<"headline2: "<<headline2<<endl;
cout<<"sports: "<<sports<<endl;
callme1(headline1);
cout<<"headline1: "<<headline1<<endl<<endl;
 /**//* 這里callme2()按值(而不是引用)傳遞headline2,結(jié)果表明這是一個(gè)嚴(yán)重的問(wèn)題!
* 這里,將headline2作為函數(shù)參數(shù)來(lái)傳遞從而導(dǎo)致析構(gòu)函數(shù)被調(diào)用。其次,雖然按
* 值傳遞可以防止原始參數(shù)被修改,但實(shí)際上函數(shù)已使原始字符串無(wú)法識(shí)別,導(dǎo)致
* 顯示一些非標(biāo)準(zhǔn)字符(顯示的具體內(nèi)存取決于內(nèi)存中包含的內(nèi)容)。
*/

 /**//* 出現(xiàn)亂碼的原因在于隱式地復(fù)制構(gòu)造函數(shù)是按值進(jìn)行復(fù)制的。那么復(fù)制str的時(shí)候
* 復(fù)制并不是字符串,而是一個(gè)指向字符串的指針。這通常是內(nèi)存管理不善的表現(xiàn)。
* 解決類(lèi)設(shè)計(jì)中這種問(wèn)題的方法時(shí)進(jìn)行深度復(fù)制(deep copy)。也就是說(shuō),復(fù)制構(gòu)造
* 函數(shù)應(yīng)當(dāng)復(fù)制字符串并將副本的地址賦給str成員,而不僅僅是復(fù)制字符串地址。
* 這樣每個(gè)對(duì)象都有自己的字符串,而不是引用另一個(gè)對(duì)象的字符串,調(diào)用析構(gòu)函數(shù)
* 時(shí)都將釋放不同的字符串,而不會(huì)試圖去釋放已經(jīng)被釋放的字符串。
*/
callme2(headline2);
cout<<"headline2: "<<headline2<<endl<<endl;
cout<<"Initialize one object to another:\n";
 /**//* 這是用的是哪個(gè)構(gòu)造函數(shù)呢?不是默認(rèn)構(gòu)造函數(shù),也不是參數(shù)為const char *的構(gòu)造函數(shù)。記住,
* 這種形式的初始化等效于下面的語(yǔ)句:StringBad sailor=StringBad(sports); //constructor using sports
* 因?yàn)閟ports的類(lèi)型為StringBad,因此相應(yīng)的構(gòu)造函數(shù)原型應(yīng)該如下:StringBad(const StringBad &);
* 當(dāng)使用一個(gè)對(duì)象來(lái)初始化另一個(gè)對(duì)象時(shí),編譯器將自動(dòng)生成上述構(gòu)造函數(shù)(稱(chēng)為復(fù)制構(gòu)造函數(shù),因?yàn)樗鼊?chuàng)建對(duì)象
* 的一個(gè)副本)。自動(dòng)生成的構(gòu)造函數(shù)不知道需要更新靜態(tài)變量num_strings,因此會(huì)將計(jì)數(shù)方案搞亂。實(shí)際上,
* 這個(gè)例子說(shuō)明的所有問(wèn)題都是由編譯器自動(dòng)生成的成員函數(shù)引起的。
*/
StringBad sailor=sports;
cout<<"sailor: "<<sailor<<endl;
cout<<"Assign one object to another: \n";
StringBad knot;
knot=headline1;
cout<<"knot: "<<knot<<endl;
 /**//* 因?yàn)樽詣?dòng)存儲(chǔ)對(duì)象被刪除的順序與創(chuàng)建順序相反。所以最先刪除的3個(gè)對(duì)象是knots、sailor和sport。
* 這里,計(jì)數(shù)變得奇怪。實(shí)際上,計(jì)數(shù)異常是一條線索。因?yàn)槊總€(gè)對(duì)象被構(gòu)造和析構(gòu)一次,因此調(diào)用
* 構(gòu)造函數(shù)的次數(shù)應(yīng)當(dāng)與析構(gòu)函數(shù)的調(diào)用次數(shù)相同。對(duì)象計(jì)數(shù)(num_strings)遞減的次數(shù)比遞增次數(shù)多2,
* 這表明使用了不將num_string遞增的構(gòu)造函數(shù)創(chuàng)建了兩個(gè)對(duì)象。類(lèi)定義聲明并定義了兩個(gè)構(gòu)造函數(shù)
* (這兩個(gè)構(gòu)造函數(shù)都使num_strings遞增),但結(jié)果表明程序使用了3個(gè)構(gòu)造函數(shù)。
*/
cout<<"End of main()\n";
return 0;
}
void callme1(StringBad & rsb)
  {
cout<<"String passed by reference: \n";
cout<<" "<<rsb<<"\"\n";
}
void callme2(StringBad sb)
  {
cout<<"String passed by value: \n";
cout<<" \""<<sb<<"\"\n";
}
第二個(gè)程序: 關(guān)于類(lèi)的new和delete有個(gè)較為清晰的闡述。 string1.h文件的代碼如下:
//string1.h -- fixed and augmented string class definition
#include <iostream>
using std::ostream;
using std::istream;

 /**//* 如果編譯器沒(méi)有實(shí)現(xiàn)布爾類(lèi)型的話,可以使用int代替bool,0代替false,1代替true。
* 如果編譯器不支持靜態(tài)類(lèi)常量,可以用枚舉來(lái)定義CINLIM。enum{CINLIM = 90};
*/

#ifndef STRING1_H_
#define STRING1_H_
class String
  {
private:
char * str; //pointer to string
int len; //length of string
static int num_strings; //number of objects
 /**//* 原語(yǔ)句是static const int CINLIM=80;但是我的vc6編譯不通過(guò),所以就改成這樣的形式
* class A
* {
* static const int a;
* };
* const int A::a = 10;
* 只有基本數(shù)字類(lèi)型(書(shū)上說(shuō)的是整型,但有些編譯器支持float等類(lèi)型)的static const變量
* 才可以在類(lèi)體內(nèi)初始化,關(guān)于static const成員變量,不同的編譯器在具體實(shí)現(xiàn)上也有些差異
* 定義常量只有兩種方式,一是上述的,二是enum。
*/
static const int CINLIM; // cin input limit
public:
// constructors and other methods
String(const char * s); //constructor
String(); //default constructor
String(const String &); //copy constructor
~String();
 int length()const {return len;}
// overloaded operator methods
String & operator=(const String &);
String & operator=(const char * s);
char & operator[](int i);
const char & operator[](int i)const;
// overloaded operator friends
// vc6對(duì)友元支持不是很好,上網(wǎng)查了一些資料,解決辦法可查看如下網(wǎng)址
// http://m.shnenglu.com/kangnixi/archive/2010/02/15/107852.html
friend bool operator< (const String &st1,const String &st2);
friend bool operator> (const String &st1,const String &st2);
friend bool operator== (const String &st1,const String &st2);
friend ostream & operator<< (ostream & os,const String & st);
friend istream & operator>> (istream & is,String & st);
// static function
static int HowMany();
};
#endif
string1.cpp文件的代碼如下:
//string1.cpp -- String class methods
#include <cstring>
#include "string1.h" //includes<iostream>
using std::cin;
using std::cout;

// initializing static class member
int String::num_strings = 0;
const int String::CINLIM=80;
// static method
int String::HowMany()
  {
return num_strings;
}

// class methods
// 程序使用構(gòu)造函數(shù)String(const char *)來(lái)創(chuàng)建一個(gè)臨時(shí)String對(duì)象,其中包含temp中的字符串拷貝
String::String(const char * s) //construct String from C string
  {
len = strlen(s); //set size
str = new char[len+1]; //allot storage
strcpy(str,s); //initialize pointer
num_strings++; //set object count
}

 /**//* 這是新的默認(rèn)構(gòu)造函數(shù),讀者可能會(huì)問(wèn),為什么代碼為:str = new char[1];而不是:str = new char;
* 上面兩種方式分配的內(nèi)存量相同,區(qū)別在于前者與類(lèi)析構(gòu)函數(shù)兼容,而后者不兼容。析構(gòu)函數(shù)中包含如下
* 代碼:delete [] str;delete[]與使用new []初始化的指針和空指針都兼容。因此對(duì)于下述代碼:
* str = new char[1];
* str[0]='\0';
* 可修改為:str = 0; // sets str to the null pointer
* 對(duì)于以其他方式初始化的指針,使用delete[]時(shí),結(jié)果將是不確定的。
*/
String::String() //default constructor
  {
len = 4;
str = new char[1];
str[0]='\0'; //default string
num_strings++;
}

String::String(const String & st)
  {
num_strings++; //handle static member update
len = st.len; //same length
str = new char [len+1]; //allot space
strcpy(str,st.str); //copy string to new location
}

String::~String() //necessary destructor
  {
--num_strings; //required
delete [] str; //required
}

// overloaded operator methods
// assign a String to a String
String & String::operator=(const String & st)
  {
if(this == &st)
return *this;
delete [] str;
len=st.len;
str=new char[len+1];
strcpy(str,st.str);
return *this;
}

// assign a C string to a String
// 下面函數(shù)為提高處理效率,這樣就能夠直接使用常規(guī)字符串,不用創(chuàng)建和刪除臨時(shí)對(duì)象了。
String & String::operator=(const char * s)
  {
// 一般來(lái)說(shuō),必須釋放str指向的內(nèi)存,并為新字符串分配足夠的內(nèi)存。
delete [] str;
len =strlen(s);
str=new char[len+1];
strcpy(str,s);
return *this;
}

// read-write char access for non-const String
char & String::operator[](int i)
  {
return str[i];
}
//read-only char access for const String
const char & String::operator[](int i)const
  {
return str[i];
}

//overloaded operator friends
 /**//* 要實(shí)現(xiàn)字符串比較函數(shù),最簡(jiǎn)單的方法是使用標(biāo)準(zhǔn)的strcmp()函數(shù)。如果按照字母順序,
* 第一個(gè)參數(shù)位于第二個(gè)參數(shù)之前,則該函數(shù)返回一個(gè)負(fù)值;如果兩個(gè)字符串相同,則返回
* 0;如果第一個(gè)參數(shù)位于第二個(gè)參數(shù)之后,則返回一個(gè)正值
*/
bool operator<(const String &st1,const String &st2)
  {
return(strcmp(st1.str,st2.str)<0);
}
bool operator>(const String &st1,const String &st2)
  {
return st2.str<st1.str;
}
bool operator==(const String &st1,const String &st2)
  {
return(strcmp(st1.str,st2.str)==0);
}
// simple String output
ostream & operator<<(ostream & os,const String & st)
  {
os<<st.str;
return os;
}
// quick and dirty String input
 /**//* 重載>>操作符提供了一種將鍵盤(pán)輸入行讀入到String對(duì)象中的簡(jiǎn)單方法。它假定輸入的字符數(shù)不多于
* String::CINLIM的字符數(shù),并丟棄多余的字符。在if條件下,如果由于某種原因(如到達(dá)文件尾,或
* get(char *,int)讀取的是一個(gè)空行)導(dǎo)致輸入失敗,istream對(duì)象的值將置為false。
*/
istream & operator>> (istream & is,String & st)
  {
char temp[String::CINLIM];
is.get(temp,String::CINLIM);
if(is)
st=temp;
// 上面的式子在vc6編譯下,發(fā)生如下錯(cuò)誤代碼
// error C2678: binary '=' : no operator defined which takes a left-hand operand
// of type 'const class String' (or there is no acceptable conversion)
// 原來(lái)是我不小心看錯(cuò),將st聲明為const String &類(lèi)型了
while(is && is.get()!= '\n')
continue;
return is;
}
saying1.cpp文件的代碼如下:
// saying1.cpp -- using expanded String class
// compile with string1.cpp
#include <iostream>
#include "string1.h"
using namespace std;
const int ArSize = 10;
const int MaxLen = 81;
int main()
  {
String name;
cout<<"Hi, what's your name?\n>> ";
cin>>name;

cout<<name<<", please enter up to "<<ArSize<<" short sayings <empty line to quit>:\n";
String sayings[ArSize]; //array of objects
char temp[MaxLen]; //temporary string storage
int i;
for(i=0;i<ArSize;i++)
 {
 /**//* 較早的get(char *,int)版本在讀取空行后,返回的值不為false。不過(guò),對(duì)于這些版本來(lái)說(shuō)
* 如果讀取了一個(gè)空行,則字符串中第一個(gè)字符將是一個(gè)空字符。這個(gè)范例使用了下述代碼:
* if(!cin||temp[0]=='\0') //empty line?
* break; //i not incremented
* 如果實(shí)現(xiàn)遵循了最新的C++標(biāo)準(zhǔn),則if語(yǔ)句中的第一個(gè)條件將檢測(cè)到空行,第二個(gè)條件用于
* 舊版本實(shí)現(xiàn)中檢測(cè)空行。
*/
cout<<i+1<<": ";
cin.get(temp,MaxLen);
while(cin && cin.get()!='\n')
continue;
if(!cin || temp[0] == '\0') // empty line
break; //i not incremented
else
sayings[i]=temp; //overloaded assignment
}
int total=i; //total # of lines read

cout<<"Here are your sayings:\n";
for(i=0;i<total;i++)
cout<<sayings[i][0]<<": "<<sayings[i]<<endl;

int shortest=0;
int first=0;
for(i=1;i<total;i++)
 {
if(sayings[i].length()<sayings[shortest].length())
shortest=i;
if(sayings[i]<sayings[first])
first=i;
}
cout<<"Shortest saying:\n"<<sayings[shortest]<<endl;
cout<<"First alphabetically:\n"<<sayings[first]<<endl;
cout<<"This program used "<<String::HowMany()<<" String objects. Bye.\n";

return 0;
}
|