归纳点:
- 静态成员变量初始化以及注意点
- 特殊成员函数
对于静态数据成员,特别注意的是静态类成员是单独存储的,并不是对象的组成部分,因此,不论创建了多少个对象,程序都只创建一个静态类变量副本,所有的对象共享同一个静态成员,可以参考下面的图:
#ifndef TIME_H_
#define TIME_H_
#include
using namespace std;
class StringBad{
private:
char* str; // pointer to string
int len; // length of string
static int num_strings; // number of objects
public:
StringBad(const char* s); // constructor
StringBad(); // default constructor
~StringBad(); // destructor
friend ostream& operator<<(ostream& os, const StringBad& st);
};
#endif
#include
#include "time.h"
// initializing static class member
int StringBad::num_strings = 0;
StringBad::StringBad(const char* s){
len = strlen(s); // set size
str = new char[len + 1]; // allot storage
strcpy(str, s); // initialize pointer
num_strings++; // set object count
cout << num_strings << ": \"" << str
<< "\" object created\n";
}
StringBad::StringBad(){ // default constructor
len = 4;
str = new char[len];
strcpy(str, "C++"); // default string
num_strings++;
cout << num_strings << ": \"" << str
<< "\" default object created\n";
}
StringBad::~StringBad(){ // necessary destructor
cout << "\"" << str << "\" object deleted, ";
--num_strings; // required
cout << num_strings << " left\n";
delete[] str; // required
}
ostream & operator<<(ostream& os, const StringBad& st){
os << st.str;
return os;
}
// vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include
#include "time.h"
void callme1(StringBad&); // pass by reference
void callme2(StringBad); // pass by value
int main(){
{
cout << "Starting an inner block.\n";
StringBad headline1("Celery Stalks at Midnight");
StringBad headline2("Lettuce Prey");
StringBad sports("Spinach Leaves Bowl for Dollars");
cout << endl;
cout << "headline1: " << headline1 << endl;
cout << "headline2: " << headline2 << endl;
cout << "sports: " << sports << endl << endl;
callme1(headline1);
cout << "headline1: " << headline1 << endl;
callme2(headline2);
cout << "headline2: " << headline2 << endl;
cout << "\nInitialize 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& rstr){
cout << "String passed by reference:\n";
cout << " \"" << rstr << "\"\n";
}
void callme2(StringBad str){
cout << "String passed by value:\n";
cout << " \"" << str << "\"\n";
}
输出:
Starting an inner block.
1: "Celery Stalks at Midnight" object created
2: "Lettuce Prey" object created
3: "Spinach Leaves Bowl for Dollars" object created
headline1: Celery Stalks at Midnight
headline2: Lettuce Prey
sports: Spinach Leaves Bowl for Dollars
String passed by reference:
"Celery Stalks at Midnight"
headline1: Celery Stalks at Midnight
String passed by value:
"Lettuce Prey"
"Lettuce Prey" object deleted, 2 left
headline2: 葺葺葺葺葺葺葺葺葺葺葺葺QjD?
Initialize one object to another:
sailor: Spinach Leaves Bowl for Dollars
Assign one object to another:
3: "C++" default object created
knot: Celery Stalks at Midnight
Exiting the block.
"Celery Stalks at Midnight" object deleted, 2 left
"Spinach Leaves Bowl for Dollars" object deleted, 1 left
"Spinach Leaves Bowl for Doll8" object deleted, 0 left
"@C" object deleted, -1 left
"-|" object deleted, -2 left
首先输出的第2-4行可以看出静态成员变量
num_strings
是所有创建的object一起共享,如果不是共享的,那2-4行前面的数字就应该都是1;
然后就是理清楚对象创建的顺序,即:headline1 -> headline2 -> sports -> sailor -> knot
,那么最后析构的时候就是反着来。那就看输出的导数5行,先删除knot,然后sailor,这两个删除是正常的,然后删除sport的时候发现这个并不是sport完整的内容,Dollars变成了Doll8,到底是哪里导致改变的呢?回到程序发现就执行了StringBad sailor = sports;
,那就是这种操作修改了sport,最后被删除的两个对象headline1
、headline2
也是面目全非;
最后计数异常也是个问题,按逻辑最后应该是0 left
,分析过来就只能是调用了不将num_string
递增的构造函数两次,但程序里定义的两个构造函数都有使num_string
递增,那就只能说明该程序使用了第三个构造函数。
StringBad sailor = sports;
调用的是哪个构造函数呢?应该是StringBad(const StringBad&);
这个就是 复制构造函数。
关于内容为什么出现乱码,我放到最后来讲。
特殊成员函数:
默认构造函数 Stack::Stack(){ }
带参数的构造函数也可以是默认构造函数,只要所有参数都有默认值,例如Stack() {num = 0;}
或者 Stack(int n = 0) {num = n;}
,这两者只能选其一,不然会有二义性。
默认析构函数;
复制构造函数 StringBad(const StringBad&);
赋值运算符;
地址运算符。
归纳点:
- 何时调用复制构造函数;
- 显式复制构造函数
①当我们新建一个对象并将其初始化为同类现有对象时被调用;
假设motto是StringBad对象,那下面四个声明都将调用复制构造函数:
StringBad ditto(motto);
StringBad metoo = motto;
StringBad also = StringBad(motto);
StringBad* pStringBad = new StringBad(motto);
默认复制构造函数逐个复制非静态成员,静态成员是保持不变的。
②每当程序生成对象副本时也将调用复制构造函数;
例如 按值传递 以及 函数返回对象 时也将调用复制构造函数。因为按值传递需要创建一个原始变量的副本,若该原始变量是对象的话,那程序将会调用复制构造函数生成临时对象,引用传递就不会。
对于上面程序callme2(headline2);
就是按值传递。此时用的默认复制构造函数初始化callme2()
的形参,除了该处,StringBad sailor = sports;
也用到复制构造函数,没有增加num_strings
的值,析构却会更新,才导致了 “-2” 的出现。怎么解决这样的问题呢?
采用下面的显式复制构造函数就可以解决:
StringBad::StringBad(const StringBad& s) {
num_strings++;
len = strlen(s.str);
str = new char [len+1]; // allot space
strcpy(str, s.str);
}
输出内容异常那一定就是内存被释放导致的,什么时候被释放的呢?先回顾下释放的顺序knot -> sailor -> sports -> headline2 -> headline1
,发现sports
、headline2
、headline1
这三者内容都有问题,逐个分析:
①callme2(headline2);
导致headline2
内存释放;
按值传递按道理不应该改变原值,这里改变了是怎么回事?首先调用复制构造函数为
callme2()
生成一个形参,即headline2
的副本,此时将headline2
的内容传给副本,包括char* str
,这是个指针,复制的不是字符串,是个地址,这就危险了,此时副本以及headline2
的str
指针都指向一个地方,那么当这部分程序运行完,副本释放后,headline2
的str
所指向的内存也没了,才导致自身的内容发生改变。
②StringBad sailor = sports;
导致sports
内存释放;
同理,
sports
的str
指针和sailor
的都指向同一个字符串指针,所以释放完sailor
的内存后,sports
的str
指向的内存也没了,因此导致乱码。
③knot = headline1;
导致headline1
内存释放。
与②一样,就不多赘述了。