数据结构 第26课 典型问题分析(Bugfix)-------1----------狄泰软件学院

引用文章: 1

引用文章: 2

问题1 (深入分析 strdup )

#include <iostream>
#include "Exception.h"

using namespace std;
using namespace DTLib;


int main()
{
    
    

    try
    {
    
    
        NullPointerException npe;  // 会调用父类的构造函数

        cout << "throw" << endl;

        throw npe;
    }
    catch(const exception& e)
    {
    
    
        cout << "catch" << endl;
    }


    return 0;
}

/*结果:
    throw
    terminate called after throwing an instance of 'DTLib::NullPointerException'
    至于为啥报错,我也不清楚;;;;;;;;;;;;;;本不应报错
    

    示例程序过于短小,说明不了什么问题,要深入标准C库一探究竟;
    在glibc 中查strdup 函数
    结果:
    glibc 中的 strdup 函数的函数参数是 默认认为字符串不为空的,
    但是我们如果如上图那样定义异常对象,相当于调用strdup(0)。


    在异常类中,不判断message是否为空,是有问题的


    在Exception.h中,一句话经历的改变
    //m_message = message; // 参数指针指向的字符串位置未知,这样无法控制外部字符串的生命周期,这种写法是不安全的, 处理方法: 拷贝一份字符串出来
    //m_message = strdup(message);// 字符串的复制 到堆空间,这时 m_message 指向堆空间中的字符串
    m_message = (message ? strdup(message) : NULL);  // 这样的代码才足够安全

    通过glibc的strdup形成方式,增强代码健壮性的方法就是这样改。

*/

问题2 (链表逻辑,异常安全性)

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace DTLib;

class Test : public Object
{
    
    
    int m_id;
public:
    Test(int id = 0)
    {
    
    
        m_id = id;
    }

    ~Test()
    {
    
    
        if( m_id == 1 )
        {
    
    
            throw m_id;
        }
    }
};

int main()
{
    
    
    LinkList<Test> list;
    Test t0(0), t1(1), t2(2);

    try
    {
    
    
        list.insert(t0);
        list.insert(t1);
        list.insert(t2);

        list.remove(1);
    }
    catch(int e)
    {
    
    
        cout << e << endl;
        cout << list.length() << endl;
    }

    return 0;
}


/*

  出现的问题:

Qt 编译器直接崩溃,VS 编译器也直接崩溃,
因为析构函数中不能抛出异常。但是VS 编译器输出了 1 和 3。
按理说我们移除了一个元素,为什么编译器长输出的长度还是3而不是2呢?

这说明了remove()函数没有考虑异常安全性


再LinkList中,remove函数
        m_length--;
        destroy(toDel);  //应该全部完成再销毁

        clear() 函数改成:
            void clear()
            {
                while(m_header.next != nullptr)
                {
                    Node* toDel = m_header.next;
                    m_header.next = toDel->next;
                    m_length--;
                    destroy(toDel);
                }
            }

*/

问题3 ( LinkList 中遍历操作与删除操作的混合使用)

#include <iostream>
#include "LinkList.h"
using namespace std;
using namespace DTLib;

int main()
{
    
    
    LinkList<int> list;

    for(int i = 0; i < 5; i++)
    {
    
    
        list.insert(i);
    }

    for(list.move(0); !list.end(); list.next())
    {
    
    
        if(list.current() == 3)
        {
    
    
            list.remove(list.current());
            cout << list.current() << endl;
        }
    }

    for(list.move(0); !list.end(); list.next())
    {
    
    
        cout << list.current() << endl;
    }

    return 0;

}

/*
出现的问题:   出现随机值
问题原因:链表被删除后,m_current 依旧指向了原来的内存
解决方案:我们可以把m_current 指向下一个内存。


bool remove(int i)
{
   bool ret = ((0 <= i) && (i <= m_length));


   if(ret)
   {

       Node* current = position(i);
       Node* toDel = current->next;

       if(m_current == toDel)  // 判断m_current是否指向了即将被删除的元素
       {
           m_current = toDel->next;   //m_current 指向即将被删除的下一个元素(因为这一块元素要被删除掉了,没有这一步骤,会出现随机值)
       }


       current->next = toDel->next;  // 链接
        m_length--;
       destroy(toDel);

   }

   return ret;
}

*/

问题4(StaticLinkList 中数据元素删除时的效率问题)

/*
这里的最终结果依然出现了问题,最后仅得到一个 0 值。
*/


#include <iostream>
#include "StaticLinkList.h"
using namespace std;
using namespace DTLib;

int main()
{
    
    
    StaticLinkList<int, 10> list;

    for(int i=0; i<5; i++)
    {
    
    
        list.insert(i);
    }

    list.remove(3);  // 会调用到 destroy()里去;

    for(int i=0; i<list.length(); i++)
    {
    
    
        cout << list.get(i) << endl;
    }


}

/*
最后for循环加一个break即可;

    void destroy(Node *pn)
    {
        SNode* space = reinterpret_cast<SNode*>(m_space);  // 要做指针运算,所以要做强制类型转换
        SNode* pst = dynamic_cast<SNode*>(pn);             // 从父类指针转换到子类指针
        for(int i=0; i<N; i++)      // 查看需要归还的空间是哪一个
        {
            if(pst == (space + i))  // 当pst 等于(space + i) 的地址时, i 空间需要归还
            {
                m_used[i] = 0;      // 标记当前内存单元可用
                pst->~SNode();      // 析构
                break;  // 归还了就可以跳出循环了,没有必要继续循环下去,虽然在循环下去月不可能走进if语句了,数据结构为了高效


*/

            }
        }
    }

问题5 (非常经典的问题)

#include <iostream>
#include "LinkList.h"
#include "StaticLinkList.h"
using namespace std;
using namespace DTLib;

int main()
{
    
    
    StaticLinkList<int, 5> list;

    for(int i = 0; i < 5; i++)
    {
    
    
        list.insert(i);
    }

    for(int i = 0; i < 5; i++)
    {
    
    
        cout << list.get(i) << endl;
    }
}

// 这个子类对象被析构的时候,会调用父类的析构函数,,父类delete的内存空间来自子类所创建的空间,
// 最终delete的就不是堆空间了,就会造成系统不稳定,程序什么时间会崩溃是无法预测的



/*

是否提供析构函数由资源来决定,申请了资源就要归还(析构)
StaticLinkList.h 在构造函数中没有申请系统资源, 从资源的角度,就没有必要提供析构函数了;
(有一个前提条件,所实现的类是一个单独的类,没有任何继承关系,)

但,是否提供唯一的析构函数,不能以唯一的准则来判断

打开所继承的LinkList类, LinkList提供了析构函数,并且在析构函数中调用了虚函数,但是,多态是不会发生的
构造函数和析构函数中是不会发生多态的,,,

所以,不管在哪里调用,都是唯一的版本,

仔细分析,clear()中又调用了 destory()虚函数,
而 destory()在父类中一个版本,在子类中又实现一个版本

意味着,父类的析构函数被调用的时候,始终调用的destory() 都是父类中的destory(),
子类的destory()是没有办法在析构的时候被调用到的,
构造函数和析构函数中是不会发生多态的,,
不管你是直接调用的虚函数还是间接调用的虚函数,都是当前类中所实现的那个虚函数



解决方案:
在子类中添加自己的析构版本,
~StaticLinkList()
{
    this->clear();
}

调用的是父类的clear函数,clear函数调用destory(),就是当前类中的实现


构造函数和析构函数是不会发生多态的,所调用的虚函数,都是当前类中的实现版本,不管是直接调用还是间接调用;

*/

问题6(DTLib 是否有必要增加多维数组类?)

#include <iostream>
#include "StaticLinkList.h"
#include "DynamicArray.h"
using namespace std;
using namespace DTLib;


/*
— DTLib 是否有必要增加多维数组类?
答:没必要,因为多维数组的本质:数组的数组

如果你想得到一个二维数组对象,可以这样做:

来构造不规则的二维数组;;;

*/


int main()
{
    
    
    DynamicArray< DynamicArray<int> > d;  // d 是一个对象,具体类型是一个数组,保存的元素是另一个数组   《二维》

    d.resize(3);   // 第一维数组   3行
    for(int i = 0; i < d.length();i++)  // 确定每一行的元素
    {
    
    
        d[i].resize(i + 1);  // 第0数组 有一个元素, 第2数组,又两个元素~~
    }

    for(int i = 0; i < d.length();i++)
    {
    
    
        for(int j = 0; j < d[i].length(); j++)
        {
    
    
            d[i][j] = i * j;
        }
    }
    for(int i = 0; i < d.length();i++)
    {
    
    
        for(int j = 0; j < d[i].length(); j++)
        {
    
    
            cout << d[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/dashuu/article/details/115137959