C++类和内存管理【C++ primer 笔记】

程序构建

设计一个badstring类,它将包含一个字符串指针和一个表示字符串长度的值。

badstring.h

#include <iostream>
#ifndef BADSTRING_H_
#define BADSTRING_H_

class badstring
{
private:
    char *str;
    int len;
    static int num_strings;

public:
    badstring(const char *s);
    badstring();
    ~badstring();
    friend std::ostream &operator<<(std::ostream &os, const badstring &st);
};
#endif


  • char *str使用的是字符串指针而不是char数组来表示string的内容,这意味着类申明没有为字符串本身分配内存空间。
  • num_strings确保每创建一个新对象,共享变量的值都+1,从而记录badstring对象的总数。 另外析构函数将共享变量的值-1。

badstring.cpp

#include <cstring>
#include "badstring.h"

using std::cout;
int badstring ::num_strings = 0;

badstring::badstring(const char *s)
{
    int len = std::strlen(s);
    str = new char[len + 1];
    std::strcpy(str, s);
    num_strings++;
    cout << num_strings << ": \"" << str << "\""
         << "  object create\n";
}
badstring::badstring()
{

    int len = 4;
    str = new char[4];
    std::strcpy(str, "c++");
    num_strings++;
    cout << num_strings << ": \"" << str << "\"" << str << " default object create\n";
}
badstring::~badstring()
{
    cout << str << "  object deleted  ,";
    --num_strings;
    cout << num_strings << " words left\n";
    delete[] str;
}

std::ostream &operator<<(std::ostream &os, const badstring &st)
{
    os << st.str;
    return os;
}

上面将静态成员 num_strings 的值初始化为0。
不能在类声明时初始化静态成员变量,因为声明只是描述了如何分配内存,但并没有分配内存。

main.cpp

#include <iostream>

using std::cout;
#include "badstring.h"

void callme(badstring &st1);
void callme2(badstring st2);
int main()
{

    using std::endl;
    {
        cout << "Start an inner block" << endl;
        badstring s1("I am string 1");
        badstring s2("I am strnig 2");
        badstring s3("I am string 3");
        cout << "s1:" << s1 << endl;
        cout << "s2:" << s2 << endl;
        cout << "s3:" << s3 << endl;
        callme(s1);
        cout << "s1:" << s1 << endl;
        //按值传递的对象的函数,将调用复制构造函数。
        callme2(s2);
        cout << "s2:" << s2 << endl;

        cout << "initalize one object to another\n";
        badstring s4 = s3;
        cout << "s4 :  " << s4 << endl;
        cout << "Assign one object to another :" << endl;
        badstring s5;
        s5 = s1;
        cout << "s5: " << s5 << endl;
        cout << "exiting the block..." << endl;
    }
    return 0;
}

void callme(badstring &str1)
{
    cout << "String passed by reference:  ";
    cout << "   \"" << str1 << " \"" << std::endl;
}

void callme2(badstring sb)
{
    cout << "String passed by value:\n";
    cout << "   \"" << sb << " \"" << std::endl;
} 

编译和运行结果:

 ✘ wingchi  ~/Cpp/cppPrimer  g++ -c  vegnews.cpp badstring.cpp 
 wingchi@wingchi01  ~/Cpp/cppPrimer  g++ -c  vegnews.cpp badstring.cpp 
 wingchi@wingchi01  ~/Cpp/cppPrimer  g++ badstring.o vegnews.o -o main 
 wingchi@wingchi01  ~/Cpp/cppPrimer  ./main                            
Start an inner block
1: "I am string 1"  object create
2: "I am strnig 2"  object create
3: "I am string 3"  object create
s1:I am string 1
s2:I am strnig 2
s3:I am string 3
String passed by reference:     "I am string 1 "
s1:I am string 1
String passed by value:
   "I am strnig 2 "
I am strnig 2  object deleted  ,2 words left 
s2:
initalize one object to another
s4 :  I am string 3
Assign one object to another :
3: "c++"c++ default object create
s5: I am string 1
exiting the block...
I am string 1  object deleted  ,2 words left
I am string 3  object deleted  ,1 words left
����U  object deleted  ,0 words left
free(): double free detected in tcache 2
[1]    5742 abort (core dumped)  ./main

可以看到程序执行到最后崩溃了,并且在执行过程中就出现了匪夷所思的结果。

程序说明

从创建s1~s3并打印都是没有问题的,
接着调用了callme和callme2函数,前者按引用传递,后者按值传递。 接下来我们出现意外的第一个结果:

String passed by value:
   "I am strnig 2 "
I am strnig 2  object deleted  ,2 words left 
s2:

string2 调用了callme2函数后,就被撤销了(执行了析构函数)

接下来,我们创建了s4,s5字符串,分别以这样的方式初始化:

        badstring s4 = s3;
        badstring s5;
        s5 = s1;

最后,我们结束函数,各个对象分别调用自己的析构函数,结果如下:

exiting the block...
I am string 1  object deleted  ,2 words left
I am string 3  object deleted  ,1 words left
����U  object deleted  ,0 words left
free(): double free detected in tcache 2

由于自动存储对象的删除和创建顺序相反,所以最先删除的是:s5(s1),s4(s3),s3
可以看到删除s5和s4都是正常的,删除s3时,就出现了异常。

结果分析

让我们从最先出现奇怪结果的代码进行分析:

 callme2(s2);
//String passed by value:
//  "I am strnig 2 "
//I am strnig 2  object deleted  ,2 words left

熟悉C语言的人应该知道,C语言中的参数,无论是传递值,还是传递地址(指针),都使用了拷贝机制。 (就是将实参拷贝一份给传给函数。)

要解释这个现象,我们首先要了解C++的 复制构造函数.

复制构造函数用于将一个对象复制到新创建的对象中。如果类中没有定义这个函数,将由编译器自动生成. 就是默认复制构造函数. 它用于初始化的过程。 原型如下:
Class_name (const Class_name &)
badstring (const badstring &)
对于复制构造函数,要知道两点:什么时候调用和有什么作用:

何时调用:
新建一个对象并将其初始化成一个同类型的现有对象时,复制构造函数都会被调用:

假设已经有一个对象:badstring s0(“I am the original string”); 下面四种声明都有可能调用复制构造函数:

badstring s1(s0);
badstring s2 = s0 ;
badstring s3 = badstring(s0) ;
badstring *ps4 = new badstring(s0); 

中间两种声明可能会使用复制构造函数创建s2和s3,也可能使用一个临时对象,然后将临时对象的内容赋值给s2和s3
最后一种声明使用s0初始化一个匿名对象,并将新对象的地址赋值给pstring指针。

每当程序产生了对象副本时,编译器都将使用复制构造函数。 具体地说,当函数按值传递对象或函数返回对象时,都使用复制构造函数。

默认复制构造函数的功能
默认的复制构造函数逐个复制非静态成员,复制的是成员的值(又称浅复制),静态变量不受影响,因为它们属于整个类。


到这里,我们就知道上面调用callme2函数会导致新创建一个对象。函数退出时,这个临时对象会被销毁,也就触发了析构函数。
同时,因为默认复制构造函数是浅拷贝,它在进行复制s2的str变量时,假设有这样一个过程:

temp.str = s2.str ;

这里复制的不是字符串,而是复制指向该字符串的指针,也就是说,临时对象的str成员和s2的str成员保存了指向同一个地址的指针。 当析构函数被调用时,它释放了临时变量的内存,也就释放了s2.str的内存.

说明:默认复制构造函数不说明其行为,因此它不打印创建语句,也不增加技术器strings_num的值。 但析构函数更新了计数,因为任何对象都会被调用。


cout << "initalize one object to another\n";
    badstring s4 = s3;

注意上面的赋值运算,也在我们提及的4种声明之一,也就是说:这里也使用默认复制构造函数创建了一个s4对象.
记住,这种初始化语句相当于下面的语句:

badstring s4 = badstring(s3);

    cout << "Assign one object to another :" << endl;
        badstring s5;
        s5 = s1;
//Assign one object to another :
//3: "c++"c++ default object create

可以看到上面的赋值语句导致默认的构造函数被调用了,这里又是什么原因呢? 我们接下来学习另一个概念: 赋值运算符.

赋值运算符

C++允许类对象赋值,这是通过自动为类重载复制运算符实现的,这种运算符的原型是:

Class_name &Class_name::operator=(const Class_name &)

它接受并返回一个指向类对象的引用,例如,badstring类的赋值运算符如下:

badstring& badstring::operator=(const badstring &)

赋值运算符的使用:

badstring s1 ("haha I am a string") ;

badstring s2 ;

 s2 = s1 ; // assignment operator invoke 

初始化对象时,不一定会使用赋值运算符

badstring s3 =s1 ; // 使用复制构造函数,也可能赋值运算符

和默认的复制构造函数一样,逐个复制非静态成员,(成员复制也称浅复制),复制的是成员的值。静态成员则不受影响,因为他们属于类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值