C++ Primer Plus的若干收获--(十二)

期待已久的建模的日子终于来了,可是真的到了这一天有感觉少了点什么,少了点什么呢??对了,今天是我博客访问量突破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);


 

一看表都十二点了,哈哈,果然心情好,干什么事都有干劲啊,大家,晚安

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值