期待已久的建模的日子终于来了,可是真的到了这一天有感觉少了点什么,少了点什么呢??对了,今天是我博客访问量突破1000的大日子,哈哈。不过我的目标可是在毕业之前访问量超过10000啊,嘻嘻。
本篇讲的内容不多,主要通过一个例子引出了类的特殊成员函数这个概念。然后主要讲解了在使用析构函数,默认构造函数和默认的复制构造函数遇到的问题,以及它们的主要用法。下篇将是其的姊妹篇,将会补充这篇的内容。
11.1 复习实例和静态类成员
这里我们先给出一个不太完整的类的示例,然后随着这个类的一点点完善来讲解类和动态内存分配
class StringBad
{
private:
char* str;
int len;
static int num_strings;
public:
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()
{
len=4;
str=new char[4];
std::strcpy(str,"C++");
num_strings++;
cout<<num_strings<<":\""<<str<<"\" default object creatd\n";
};
~StringBad()
{
cout<<"\""<<str<<"\"objects deleted,"
--num_strings;
cout<<num_strings<<"left\n";
delete [] str;
};
ostream& operator<<(std::ostream&os,const StringBad& st)
{
os<<st.str;
return os;
};
};
<span style="font-size:18px;">在这里,我们通过讲解这个程序来复习一下之前讲过的知识</span>
(1)首先,我们需要给num_strings初始化,但是我们发现在这个声明中没有,则我们需要这样一句
int StringBad::num_strings=0;
请注意,不能在类声明中初始化静态成员变量,这是因为在类的声明中描述了如何分配内存,单不分配内存。您可以使用这种格式来创建对象,从而分配和初始化内存。对于静态类成员,可以在声明之外使用单独的语句进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分,注意在这里并没有使用static.
(2)对于private部分,它使用char指针来表示姓名。这意味着类声明没有为字符串本身分配存储空间,而是在构造函数中使用new来问字符串分配空间。
(3)在构造函数中使用new来分配内存时,必须在相应的析构函数中使用delete来释放内存。如果使用new【】来分配内存,则应使用delete【】来释放内存。
这里,我再给出一个此类的调试,以及调试所得的输出:
void callme1(StringBad&){cout<<"String passed by refference:\n"; cout<<" \""<<sb<<"\"\n";
void callme2(StringBad) {cout<<"String passed by value:\n"; cout<<" \""<<sb<<"\"\n"; int main()
{
using std::endl;
cout<<"Starting an inner block.\n"<<endl;
StringBad headline1("Celery Stalks at Midnight");
StringBad headline2("Letuece Prey");
StringBad sports("Spanish Leave Bowl for Dollars");
cout<<"headline1:"<<headline1<<endl;
cout<<"headline2:"<<headline2<<endl;
cout<<"sports:"<<sports<<endl;
callme1(headline1);
cout<<"headline1"<<headline1<<endl;
callme1(headline2);//在这之前的结果都没有问题 这里主要看下之后的结果
cout<<"headline2"<<headline2<<endl;//headline:Du。这里标记为1
cout<<"Initiallize one object to another:\n";
StringBad sailor=sports;
cout<<"Assign one object to another:\n";
StringBad knot=headline1;
cout<<"knot:"<<knot<<endl;
cout<<"Exiting the block.\n";
return 0;//在程序结束后由于会调用其析构函数//这里标记为2
//Exiting the block.
//"Celery Stalks at Midnight" objects delted,2 left
//"Spinach Leaves Bowl for Dollars" objects deleted,1 left
//"Spinach Leaves Bowl for Doll8" object deleted,0 left
//"@g" object deleted,-1 left
//"-|" object deleted,-2 left
这里在标记1处,callme2()按值传递headline2,结果表明是个严重的错误。首先,将headline2作为函数参数会调用析构函数(在这里,当callme2()函数调用结束时,会调用析构函数)。为什么引用是就不会调用析构函数呢?此时,原始字符串已经被删除,故无法识别,才会出现上述的结果。
这里再看一下标记2,因为自动存储对象被删除的顺序与创建顺序相反,所以最先删除的是三个对象是knots、sailor、sport。在删除sport时,Dollars变成了Doll8.对于sport,程序只使用它来初始化sailor,单这种操作修改了sport。剩下的两个对象就更不能识别了。
最后在看一下计数异常。因为每个对象呗构造和析构一次,因此调用够早的函数的次数应当与析构函数的调用次数相同。对象技术(num_strings)递减的次数比递增的次数多2,这证明使用了不将其递增的构造函数。下面我们着重来看这条代码。
StringBad sailor=sports;
StringBad sailor=StringBad(sports);//其等价于上式
StringBad(const StringBad &);//其构造函数的原型</span>
当您使用一个对象去初始化另一个对象时,编译器将自动生成上述的构造函数,其被称为复制构造函数。
11.2 特殊的成员函数
StringBad类的问题是有特殊成员函数引起的。这些成员函数是自动定义的,就StringBad而言,这些函数的行为与其类的设计不符。具体的将,C++提供下面了成员函数
- 默认构造函数,如果没有定义构造函数
- 默认析构函数,如果没有定义
- 复制构造函数,如果没有定义
- 赋值运算符,如果没有定义
- 地址运算符,如果没有定义
这里我们主要讲解一下几点:
(1)默认构造函数
如果没有提供任何构造函数,C++将创建默认的构造函数。比如有一个Pig类
Pig::Pig() { }
也就是说,编译器将提供一个不接受任何参数,也不执行任何操作的的构造函数,因为在创建对象时总会遇到构造函数
Pig pig;
如果你定义了构造函数,则C++将不会自动定义默认的构造函数了。如果你仍希望不显式的初始化,则必须显式的调用默认构造函数。其没有参数,但是可以用它来设置特定的值:
Pig::Pig()
{
pig_ct=0;
...
}
(2)复制构造函数
复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中,而不是常规的复制过程中。复制构造函数的原型为
class_name(const Class_name &);
(3)默认的复制构造函数的功能
默认的构造函数逐个复制非讲台成员(成员复制也成为浅复制),复制的是成员的值。下述语句中的两端代码是等效的
StringBad sailor=sports;
StringBad sailor;//与上面的代码等效
sailor.str=sports.str;
sailor.len=sports.len;
(4)何时调用复制的构造函数
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。假设motto为StringBad对象,则在下面的声明中都将调用复制构造函数:
StringBad ditto(motto);
StringBad metoo=motto;
StringBad also=StringBad(motto);
StringBad* pStringBad=new StringBad(motto);
一看表都十二点了,哈哈,果然心情好,干什么事都有干劲啊,大家,晚安