1.复习new和delete以及学习静态类成员
// stringbad.h 表示一个正确完成了显而易见的工作,但是有一些有益的功能被省略了的类
#include
#ifndef STRINGBAD_H_
#define STRINGBAD_H_
class StringBad
{
private:
char * str;
int len;
static int num_strings; //静态存储类
public:
StringBad(const char * s);
StringBad();
~StringBad();
friend std::ostream & operator<<(std::ostream & os,const StringBad & st);
};
#endif
num_strings是静态存储类,这种静态存储类的特点是:无论创建了多少对象,程序都只创建一个静态类变量副本。也就是说,类的所有对象都共享同一个静态类成员。本程序中的num_strings表示的是所创建的对象数目。
// stringbad.cpp 类方法实现
#include
#include "stringbad.h"
using std::cout;
//初始化静态类成员
int StringBad::num_strings = 0;
StringBad::StringBad(const char *s)
{
len = std::strlen(s);
str = new char[len+1];
std::strcpy(str,s);
num_strings++;
cout << num_strings << ":\"" << str << "\" object created\n";
}
StringBad::StringBad()
{
len = 4;
str = new char[4];
std::strcpy(str,"C++");
num_strings++;
cout << num_strings << ":\"" << str << "\" object created\n";
}
StringBad::~StringBad()
{
cout << "\"" << str << "\" object deleted, ";
--num_strings;
cout << num_strings << " left\n";
delete [] str;
}
friend std::ostream & operator<<(std::ostream & os,const StringBad & st)
{
os << st.str;
return os;
}
int StringBad::num_strings = 0;
这条语句将静态成员num_strings的值初始化为零,注意,不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。初始化之所以在方法文件中进行,而不再头文件中进行,具体原因是因为如果在头文件中进行,可能会产生很多初始化副本,导致程序运行错误。
注意:const整数类型的和枚举型的静态数据成员可以在类中声明。
注意:字符串并不保存在对象中,而是单独保存在堆内存里,对象仅保存了指出到哪里去查找字符串的信息。
// vegnews.cpp
#include
using std::cout;
#include"stringbad.h"
void callme1(StringBad &);
void callme2(StringBad);
int main()
{
using std::endl;
{
cout << "Starting an inner block!\n";
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;
callme2(headline2);
cout << "headline2: " << headline2 << endl;
cout << "Initialize one object to another: \n";
StringBad sailor = sports;
cout << "sailor : " << sailor << endl;
cout << "Assign one object to another: \n";
StringBad knot;
knot = headline1;
cout << "Knot: " << knot << endl;
cout << "Exiting the block.\n";
}
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";
}
注意:这个程序的输出结果是糟糕的,首先是调用析构函数之后,最后的剩余字符串数目结果不正确,其次是字符串的内容也遭到了破坏。字符串数目不正确的原因是系统默认提供了一个构造函数----复制构造函数,这也是*StringBad sailor = sports;*这条语句能够使用的原因,系统提供一个构造函数,格式为:*StringBad(const StringBad &);*当我们使用一个对象初始化另一个对象的时候就会调用这个默认构造函数,而在整个默认构造函数中,会导致new_strings不自增。字符串内容不正确的原因是,在按值传递内容的时候,系统会调用析构函数。
2.特殊成员函数
C++中会自动提供下列成员函数:
注意:更加准确的说法是,编译器将生产上述最后三个函数的定义,如果程序使用对象的方式要求这样做。也就是如果您需要用一个对象去初始化另一个对象,则系统会提供复制构造函数。如果您要将一个对象的值赋值给另一个对象,系统会默认提供赋值运算符的定义。
3.StringBad类的问题出在哪里
两个异常之处
StringBad::StringBad(const String & s)
{
num_strings++;
...//其他内容
}
//能够解决问题的复制构造函数
StringBad::StringBad(const StringBad & st)
{
num_strings++;
len = st.len;
str = new char[len+1];
std::strcpy(str,st.str);
cout << num_strings << ":\"" << str << "\" object created\n";
}
注意:这种复制方法称为深复制,也就是说它是新创建了一个字符串,而非与原对象使用同一个地址指向的字符串,新对象和原对象所指向的字符串内容相同,地址不相同,也就是修改原字符串并不会对新字符串产生影响,反之亦然。
4.StringBad的其他问题:赋值运算符
C++所允许的类对象赋值,这是通过自动为类重载赋值运算符来实现的,这种运算符的原型如下:
StringBad & StringBad::operator=(const StringBad &);
将已有的对象赋值给另一个对象,将使用重载的赋值运算符:
//使用重载的赋值运算符
StringBad headline1("Celery Stalks at Midnight");
StringBad knot;
knot = headline1;
与复制构造函数一样,赋值运算符的隐式实现也是对成员进行逐个复制。如果成员本身就是类对象,则程序将使用为这个类定义的赋值运算符来复制该成员,但静态数据成员不受影响。
赋值出现的问题:与隐式复制构造函数相同,出现的问题都是会导致内存被释放两次,原因也是一样的,赋值运算符也属于浅复制,原对象和新对象共用同一个地址所指向的内容,所以当knot被释放之后,headline1所指向的内容就已经被销毁,再次释放headline1的时候就会出错。
解决方法如下: