| 
			
	
	
		第一个程序:      这个程序不是很好的,运行的时候出现的一些问题,关于问题的原因注释有阐述。第二个程序是第一个程序的修改版。第一个程序的代码如下:strngbad.h文件的代码: strngbad.cpp文件的代码 #include <iostream> 
  #ifndef STRNGBAD_H_ 
  #define STRNGBAD_H_ 
  
   /**//* 这个StringBad类中的问题是由自动定义的隐式成员函数引起的,这种函数的行为与类设计不符。 
  * StringBad类中的问题是由隐式复制构造函数和隐式复制操作符引起的。 
  * 隐式地址操作符返回调用对象的地址(即this指针的值)。如果没有提供任何构造函数,C++将创建 
  * 默认构造函数。也就是说,编译器将提供一个不接受任何参数,也不执行任何操作的构造函数,这 
  * 是因为创建对象时总是会调用构造函数。如果定义了构造函数,C++将不会定义默认构造函数。如 
  * 果希望在创建对象时显式地对它进行初始化,或需要创建对象数组时,则必须显示地定义默认构造 
  * 函数。这种构造函数没有任何参数,但可以使用它来设置特定的值。带参数的构造函数也可以是 
  * 默认构造函数,只要所有参数都有默认值。 
  */ 
  class StringBad 
    { 
  private: 
   /**//* 这里使用char指针(而不是char数组)来表示姓名。这意味着类声明没有为字符串本身分配 
  * 存储空间,而是在构造函数中使用new来为字符串分配空间。这避免了在类声明中预先定义 
  * 字符串的长度。 
  */ 
  char * str;    //pointer to string 
  int len;    //length of string 
   /**//* 这里,将num_strings成员声明为静态存储类。静态类成员有一个特点:无论创建了多少对象, 
  * 程序都只创建一个静态类副本。也就是说,类的所有对象共享一个静态成员。 
  */ 
  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); 
  }; 
  #endifvegnews.cpp文件的代码 #include <cstring> 
  #include "strngbad.h" 
  using namespace std; 
   /**//* 下面这条语句将镜头成员num_strings的值初始化为0。不能在类声明中初始化静态成员变量。 
  * 这是因为声明描述了如何分配内存,但并不分配内存。你可以使用这种格式来创建对象,从 
  * 而分配和初始化内存。对于静态类成员,可以在类声明之外使用单独的语句来进行初始化。 
  */ 
  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语句是至关重要的。str成员指向new分配的内存。当StringBad对象过期时,str指针也将过期。 
  * 但str指向的内存仍被分配,除非使用delete将其释放。删除对象可以释放对象本身占用的内存,但并 
  * 不能自动释放属于对象成员的指针指向的内存。因此,必须使用析构函数。在析构函数中使用delete 
  * 语句可确保对象过期,由构造函数使用new分配的内存被释放。 
  */ 
  delete [] str; 
  } 
  std::ostream & operator<<(std::ostream & os,const StringBad & st) 
    { 
  os<<st.str; 
  return os; 
  }第二个程序: #include <iostream> 
  using namespace std; 
  #include "strngbad.h" 
  
  void callme1(StringBad &); 
  void callme2(StringBad); 
  
   /**//* 输出中出现的各种非标准字符随系统而异,这些字符表明,StringBad类名副其实(是一个糟糕的类)。 
  * 另一种迹象是对象计数为负。在使用较新的编译器和操作系统的机器上运行时,该程序通常会在显示 
  * 有关还有-1个对象的信息之前中断,而有些这样的机器将报告通用保护错误(GPF)。GPF表明程序 
  * 试图访问禁止它访问的内存单元,这是另一种糟糕的信号。 
  */ 
  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,结果表明这是一个严重的问题! 
  * 这里,将headline2作为函数参数来传递从而导致析构函数被调用。其次,虽然按 
  * 值传递可以防止原始参数被修改,但实际上函数已使原始字符串无法识别,导致 
  * 显示一些非标准字符(显示的具体内存取决于内存中包含的内容)。 
  */ 
  
   /**//* 出现乱码的原因在于隐式地复制构造函数是按值进行复制的。那么复制str的时候 
  * 复制并不是字符串,而是一个指向字符串的指针。这通常是内存管理不善的表现。 
  * 解决类设计中这种问题的方法时进行深度复制(deep copy)。也就是说,复制构造 
  * 函数应当复制字符串并将副本的地址赋给str成员,而不仅仅是复制字符串地址。 
  * 这样每个对象都有自己的字符串,而不是引用另一个对象的字符串,调用析构函数 
  * 时都将释放不同的字符串,而不会试图去释放已经被释放的字符串。 
  */ 
  callme2(headline2); 
  cout<<"headline2: "<<headline2<<endl<<endl; 
   
  cout<<"Initialize one object to another:\n"; 
   /**//* 这是用的是哪个构造函数呢?不是默认构造函数,也不是参数为const char *的构造函数。记住, 
  * 这种形式的初始化等效于下面的语句:StringBad sailor=StringBad(sports); //constructor using sports 
  * 因为sports的类型为StringBad,因此相应的构造函数原型应该如下:StringBad(const StringBad &); 
  * 当使用一个对象来初始化另一个对象时,编译器将自动生成上述构造函数(称为复制构造函数,因为它创建对象 
  * 的一个副本)。自动生成的构造函数不知道需要更新静态变量num_strings,因此会将计数方案搞乱。实际上, 
  * 这个例子说明的所有问题都是由编译器自动生成的成员函数引起的。 
  */ 
  StringBad sailor=sports; 
   
  cout<<"sailor: "<<sailor<<endl; 
  cout<<"Assign one object to another: \n"; 
  StringBad knot; 
  knot=headline1; 
  cout<<"knot: "<<knot<<endl; 
   
   /**//* 因为自动存储对象被删除的顺序与创建顺序相反。所以最先删除的3个对象是knots、sailor和sport。 
  * 这里,计数变得奇怪。实际上,计数异常是一条线索。因为每个对象被构造和析构一次,因此调用 
  * 构造函数的次数应当与析构函数的调用次数相同。对象计数(num_strings)递减的次数比递增次数多2, 
  * 这表明使用了不将num_string递增的构造函数创建了两个对象。类定义声明并定义了两个构造函数 
  * (这两个构造函数都使num_strings递增),但结果表明程序使用了3个构造函数。 
  */ 
  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"; 
  }      关于类的new和delete有个较为清晰的阐述。string1.h文件的代码如下: 
  //string1.h -- fixed and augmented string class definition 
  #include <iostream> 
  using std::ostream; 
  using std::istream; 
  
   /**//* 如果编译器没有实现布尔类型的话,可以使用int代替bool,0代替false,1代替true。 
  * 如果编译器不支持静态类常量,可以用枚举来定义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 
   /**//* 原语句是static const int CINLIM=80;但是我的vc6编译不通过,所以就改成这样的形式 
  * class A 
  * { 
  *        static const int a; 
  * }; 
  * const int A::a = 10; 
  * 只有基本数字类型(书上说的是整型,但有些编译器支持float等类型)的static const变量 
  * 才可以在类体内初始化,关于static const成员变量,不同的编译器在具体实现上也有些差异 
  * 定义常量只有两种方式,一是上述的,二是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对友元支持不是很好,上网查了一些资料,解决办法可查看如下网址 
  // http://www.cppblog.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(); 
  }; 
  #endifstring1.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 
  // 程序使用构造函数String(const char *)来创建一个临时String对象,其中包含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 
  } 
  
   /**//* 这是新的默认构造函数,读者可能会问,为什么代码为:str = new char[1];而不是:str = new char; 
  * 上面两种方式分配的内存量相同,区别在于前者与类析构函数兼容,而后者不兼容。析构函数中包含如下 
  * 代码:delete [] str;delete[]与使用new []初始化的指针和空指针都兼容。因此对于下述代码: 
  * str = new char[1]; 
  * str[0]='\0'; 
  * 可修改为:str = 0; // sets str to the null pointer 
  * 对于以其他方式初始化的指针,使用delete[]时,结果将是不确定的。 
  */ 
  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 
  // 下面函数为提高处理效率,这样就能够直接使用常规字符串,不用创建和删除临时对象了。 
  String & String::operator=(const char * s) 
    { 
  // 一般来说,必须释放str指向的内存,并为新字符串分配足够的内存。 
  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 
   /**//* 要实现字符串比较函数,最简单的方法是使用标准的strcmp()函数。如果按照字母顺序, 
  * 第一个参数位于第二个参数之前,则该函数返回一个负值;如果两个字符串相同,则返回 
  * 0;如果第一个参数位于第二个参数之后,则返回一个正值 
  */ 
  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 
   /**//* 重载>>操作符提供了一种将键盘输入行读入到String对象中的简单方法。它假定输入的字符数不多于 
  * String::CINLIM的字符数,并丢弃多余的字符。在if条件下,如果由于某种原因(如到达文件尾,或 
  * get(char *,int)读取的是一个空行)导致输入失败,istream对象的值将置为false。 
  */ 
  istream & operator>> (istream & is,String & st) 
    { 
  char temp[String::CINLIM]; 
  is.get(temp,String::CINLIM); 
  if(is) 
  st=temp; 
  // 上面的式子在vc6编译下,发生如下错误代码 
  // error C2678: binary '=' : no operator defined which takes a left-hand operand 
  // of type 'const class String' (or there is no acceptable conversion) 
  // 原来是我不小心看错,将st声明为const String &类型了 
  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。不过,对于这些版本来说 
  * 如果读取了一个空行,则字符串中第一个字符将是一个空字符。这个范例使用了下述代码: 
  * if(!cin||temp[0]=='\0')    //empty line? 
  *        break;                //i not incremented 
  * 如果实现遵循了最新的C++标准,则if语句中的第一个条件将检测到空行,第二个条件用于 
  * 旧版本实现中检测空行。 
  */ 
  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; 
  }
   
	    
    
 |