这个部分只要就是说实例了,我看的书是c++ primer plus,第十二章里面开始的时候就有好大的篇幅讲解复制构造函数以及赋值操作符。
我这里就从程序最初的错误的地方一个个的讲解,我的前一篇文章里面已经说了:
使用引用的时机。流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
这个程序里面到处都是&,也就是引用,引用当然可以不用了,我会一一改变程序来说明这一点,但是使用引用就可以带来很多方便,或者说可以解决很多意想不到的问题。
程序最初给出的含错程序就是这样的,很长了:
头文件strngbad.h:
// strngbad.h -- flawed string class definition
#include <iostream>
#ifndef STRNGBAD_H_
#define STRNGBAD_H_
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 function
friend std::ostream & operator<<(std::ostream & os,
const StringBad & st);
};
#endif
源文件strngbad.cpp :
// strngbad.cpp -- StringBad class methods
#include <cstring> // string.h for some
#include "strngbad.h"
using std::cout;
// initializing static class member
int StringBad::num_strings = 0;
// class methods
// construct StringBad from C string
StringBad::StringBad(const char * s)
{
len = std::strlen(s); // set size
str = new char[len + 1]; // allot storage
std::strcpy(str, s); // initialize pointer
num_strings++; // set object count
cout << num_strings << ": /"" << str
<< "/" object created/n"; // For Your Information
}
StringBad::StringBad() // default constructor
{
len = 4;
str = new char[4];
std::strcpy(str, "C++"); // default string
num_strings++;
cout << num_strings << ": /"" << str
<< "/" default object created/n"; // FYI
}
StringBad::~StringBad() // necessary destructor
{
cout << "/"" << str << "/" object deleted, "; // FYI
--num_strings; // required
cout << num_strings << " left/n"; // FYI
delete [] str; // required
}
std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
os << st.str;
return os;
}
包含main 的源文件vegnews.cpp:
// vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include <iostream>
using std::cout;
#include "strngbad.h"
void callme1(StringBad &); // pass by reference
void callme2(StringBad); // pass by value
int main()
{
using std::endl;
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 << "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";
}
void callme1(StringBad & rsb)
这个函数在调用的时候不会再调用别的函数,因为他在调用的时候,进行的是地址的传递,main中的调用语句是:callme1(headline1);其实就是把headline1的地址传递给rsb这个变量,使用的是引用,这样就使得,两者的地址和值都是一样的,就是c/c++中的很简单的一个引用级别的地址传递,不调用其他函数。
再看函数里面的cout这个东西,这个东西在其他的地方也看到过,但是这个地方跟其他的地方的变量有点区别的,他在这里是用的是类StringBad里面的一个友员函数,而不是简单的标准的io流,具体的看StringBad里面的定义。
下面的这个调用就有点问题了,这个就有一个传值和赋值的问题了。如果是一个简单的内置变量类型的传值和赋值就是可以直接的进行也不要调用其他的函数,或者说即使调用也不会出现我们无法控制的问题,这里就不同了,他会再调用一个隐式复制函数,如果作者没有给定显式复制函数的话
void callme2(StringBad sb); // pass by value
这个函数在程序里面使用过一次,问题在这里就开始产生headline2传递给sb之后进行赋值,就是StringBad sb=headline2,注意了,StringBad sb=headline2和StringBad sb;sb =headline2;这样的写法是不一样的,后面讲。
上面已经说过了,如果没有显式的复制函数就会自动调用或者说系统会帮你构造一个隐式复制函数,呵呵系统对我们真的好,有的时候这个好就出了问题了,这个好也就是程序的运行结果里面为什么会出现乱码,以及出现析构时的-1,-2 的缘故。
看看构造函数:
// construct StringBad from C string
StringBad::StringBad(const char * s)
{
len = std::strlen(s); // set size
str = new char[len + 1]; // allot storage
std::strcpy(str, s); // initialize pointer
num_strings++; // set object count
cout << num_strings << ": /"" << str
<< "/" object created/n"; // For Your Information
}
StringBad::StringBad() // default constructor
{
len = 4;
str = new char[4];
std::strcpy(str, "C++"); // default string
num_strings++;
cout << num_strings << ": /"" << str
<< "/" default object created/n"; // FYI
}
每次构造一个类对象的时候就会把num_strings++执行,增加计数。
在新建一个对象并将其用另一个同类类对象初始化的时候都会调用复制构造函数StringBad::StringBad(const StringBad &st),就是看时隐式还是显式的了。
隐式的没有显示的那么符合人的意思了,他就是复制而且给值,我们要求num_strings++但是他能做的就是最基本的给值,不会帮我们++这么一下。这个时一个方面,另一个方面才是最恐怖的,就是系统自己给我们的隐式复制函数是临时的!临时的是什么意思?就是说,当一个变量活到头了就要消失了:
StringBad sb=headline2
这个语句会执行,把headline2的成员的量都给sb以后,就要执行析构函数,把sb给析构了,但是headline和sb的str字符串指针都是指向的一个位置,那么当把sb给析构的同时,headline也被析构了,再要用std::ostream & operator<<(std::ostream & os, const StringBad & st)这个函数来显示str量就不对了,因为要对一个已经不存在的地址进行读或者写都是会导致异常的。这个就是后来的乱码的来由!
怎么办?
给出显示的复制构造函数:可以用指针的也可以用引用的:
由于StringBad sb=headline2相当于:StringBad sb=StringBad::StringBad(headline2),那么这么构造:
StringBad::StringBad(const StringBad &st)
{
num_strings++;
len = st.len; // set size
str = new char[len + 1]; // allot storage
strcpy(str, st.str); // initialize pointer
// set object count
cout << num_strings << ": /"" << str
<< "/" object created/n"; // For Your Information
}
这个是引用的,指针的就后来再看。
错误已经解决的差不多了,下面还有一个问题, StringBad knot; knot = headline1;这两条语句就跟上面的StringBad sb=headline2不一样了,起码过程不一样,分为两步,第一步就是初始化knot,这个由默认构造函数执行,第二步就是knot = headline1赋值,这个也没有给出函数原型,系统又来帮忙了,帮我们构造隐式的一些构造函数,这个也是我们看不见的,所以我们要自己来给出一个成员函数或者友员函数。
成员函数:
StringBad & StringBad::operator=(const StringBad &st)
{
if (this == &st)
return *this;
delete [] str;
len = st.len;
str = new char[len + 1];
strcpy(str, st.str);
return *this;
}
也是用了引用的形式,使用的是重载赋值符号。到这里差不多可以给出完整的正确的程序了:
源文件strngbad.cpp :
// strngbad.cpp -- StringBad class methods
#include <string.h> // string.h for some
#include "stringbad.h"
//using std::cout;
using namespace std;
// initializing static class member
int StringBad::num_strings = 0;
// class methods
// construct StringBad from C string
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"; // For Your Information
}
StringBad::StringBad() // default constructor
{
len = 4;
str = new char[4];
strcpy(str, "C++"); // default string
num_strings++;
cout << num_strings << ": /"" << str
<< "/" default object created/n"; // FYI
}
StringBad::~StringBad() // necessary destructor
{
cout << "/"" << str << "/" object deleted, "; // FYI
--num_strings; // required
cout << num_strings << " left/n"; // FYI
delete [] str; // required
}
std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
os << st.str;
return os;
}
StringBad::StringBad(const StringBad &st)
{
num_strings++;
len = st.len; // set size
str = new char[len + 1]; // allot storage
strcpy(str, st.str); // initialize pointer
// set object count
cout << num_strings << ": /"" << str
<< "/" object created/n"; // For Your Information
}
StringBad & StringBad::operator=(const StringBad st)
{
if (this == &st)
return *this;
delete [] str;
len = st.len;
str = new char[len + 1];
strcpy(str, st.str);
return *this;
}
main文件:vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include <iostream>
using std::cout;
#include "stringbad.h"
void callme1(StringBad &); // pass by reference
void callme2(StringBad &); // pass by value
int main()
{
using std::endl;
StringBad headline1=StringBad::StringBad("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 =StringBad::StringBad(sports);
cout << "sailor: " << sailor << endl;
cout << "Assign one object to another:/n";
StringBad knot;
knot = headline1;
cout << "knot: " << knot << endl;
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.h
// strngbad.h -- flawed string class definition
#ifndef STRNGBAD_H_
#define STRNGBAD_H_
#include <iostream>
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
StringBad::StringBad(const StringBad &st);
StringBad & StringBad::operator=(const StringBad st);
// friend function
friend std::ostream & operator<<(std::ostream & os,
const StringBad & st);
};
#endif