合并两个有序单链表,对象析构这一着我实在没想到。

简介: 合并两个有序单链表,对象析构这一着我实在没想到。

最初代码中把b链表合并到a链表中后,b链表与a链表还没有断开。


两个链表的合并是调用函数进行的,函数的参数是一个链表类型(传值)。注意,虽然传值是在函数中建立了一个b链表的副本,但是,链表类的成员其实就只有头尾两个指针,作为链表实体的结点从来都只有一份。


那么问题来了,当函数调用介绍后,副本b这个实例就要析构了!从b链表的头结点开始往后面把内存释放得干干净净。好不容易合并出来的新a链表就跟着寄了。


a链表寄了之后,再到主函数里输出a链表,a链表并不会简单地就输出前面一段然后让你看着你的程序正常结束。(图3)虽然从2结点到后面都释放了,但是,1结点里面还存着2结点的地址。内存是释放了,但是程序交出去的只是访问权限,地址还是原来的地址没有变,而结构体的成员变量标识符已经对该地址没有指导作用。(比如结点node的data变量,如果你对一个失去权限的地址使用node->data访问,说实话,我也不知道它会访问那个地址处的哪一段字节)。


程序运行可能结果:


1)你的编译器有可能不会限制这种非法访问权限,于是,在访问1结点之后,继续访问已经不存在的2结点,输出一个莫名其妙的数字。令我感到奇怪的是,访问2之后程序并不停下来(可能因为我设置的循环退出条件是指针到NULL,而随便取一段字节大概率不是0),而是像个死循环一直输出莫名其妙的数字。


2)有时你的编译器又会阻止你,比如我的dev-c++5.11后来程序可以停下来,只是不能在主函数里输出a链表,然后return一个非0数字。


3)有时你的编译器里运行代码甚至可能没有任何异常,它可以正常给你输出a链表,然后正常给你return 0;


不管编译器怎么做,总之代码确实是有问题的。


后来我一个解决尝试是,在函数最后将b2指向b链表的头结点,将头节点的next置空,断开和a链表的连接。但是问题没有解决,因为b链表的头结点会在b的副本析构时释放掉。释放掉又怎么样呢?反正这个结点没有用了。但是,在main函数运行结束后,b链表作为一个实例也是要析构的,实例b的头指针里依然存着原来头结点的地址,而这个头结点其实已经被释放掉了。于是,在析构b时,非法访问又发生了。


于是我尝试将b2也置为空,后来我意识到没有用,因为b2只是被我指向了b的头结点,将b2置空只是不指向b的头结点了而已。从始至终,b的副本的头指针都没有变。


后来我在单链表类加了个返回头指针的引用的函数,用(node*)型的b2来接收它,其实数据类型没对上,然而我的dev-c++并不报错,结果也很自然地什么用也没有。最后我改用了一个(node *&)类型的变量来接受,问题解决了。


总结


      如果仅仅是想让程序正常跑起来,其实有这样两个解决方案:


      方案一:


      给合并链表的函数传参数时,改用传指针或引用,反正别传值,不在函数结束时发生析构,就不会有那么多幺蛾子了。


      方案二:


      合并两个链表也不一定要把另一个链表的结点原封不动地拆过来,比如可以给a链表新建结点,这样a链表根本就不会和b链表发生连接。但是,如果你的函数采用传值在函数中建立副本,请小心析构。


       代码:

//单链表 
#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;
struct node {
  int data;
  node* next;
  node() {
  next = NULL;
  }
};
//--------------------------------------------------
class list {
private:
  node* head, * rear;
public:
  list() { head = rear = NULL; }
  void create();  //构建链表,从键盘输入数据 
  void create_2();  //构建链表,自动生成数据,有序 
  void print();  //输出链表到屏幕 
  ~list();    //释放内存 
  void sort(list b);  //排序 
  node* get_head();
  node*& get_phead();
};
//--------------------------------------------------
node*& list::get_phead() {
  return head;
}
//--------------------------------------------------
node* list::get_head() {
  return head;
}
//--------------------------------------------------
list::~list() {
  node* t;
  while (head) {
  cout << "~析构~" << endl;
  t = head;
  head = head->next;
  delete t;
  }
  cout << endl;
}
//--------------------------------------------------
void list::print() {
  node* t = head->next;
  while (t != NULL) {
  cout << t->data << " ";
  t = t->next;
  }
  cout << endl;
}
//--------------------------------------------------
void list::create() {
  head = new node;
  rear = head;
  node* t;
  int n(0), t_data(0);
  cin >> n; //数据的数量
  for (int i(0); i < n; ++i) {
  t = new node; //建立新结点 
  cin >> t_data;
  t->data = t_data;
  rear->next = t;
  rear = rear->next;
  }
}
//--------------------------------------------------
void list::create_2() {
  srand(time(0));
  int data(0);
  head = new node;
  rear = head;
  node* t;
  int n(0), t_data(0);
  cin >> n; //数据的数量
  for (int i(0); i < n; ++i) {
  t = new node; //建立新结点 
  data += rand() % 6;
  t_data = data;
  t->data = t_data;
  rear->next = t;
  rear = rear->next;
  }
}
//--------------------------------------------------
void list::sort(list b) {
  node* a1(head), * b1(b.get_head());
  node* t(NULL);
  b1 = b1->next;
  while (b1 != NULL && a1->next != NULL) {
  if (b1->data <= a1->next->data) {
    t = b1->next;
    b1->next = a1->next;
    a1->next = b1;
    b1 = t;
  }
  else {
    a1 = a1->next;
  }
  print();
  }
  if (b1 != NULL) {
  a1->next = b1;
  }
  cout << "last b:" << endl;
  b.print();
  node*& b2 = b.get_phead();
  b2->next = NULL;
  b2 = NULL;    //不能去掉 
}
int main() {
  list a, b;
  cout << "输入两个数字,代表两个序列的长度:";
  a.create_2();
  b.create_2();
  a.print();
  b.print();
  a.sort(b);
  cout << "合并后a:" << endl;
  a.print();
  return 0;
}
相关文章
|
人工智能 编译器 测试技术
一次性搞清数组指针、指针数组---从多维数组的本质上刨析(让你顿悟版)
一次性搞清数组指针、指针数组---从多维数组的本质上刨析(让你顿悟版)
37 0
|
存储 C++
剑指Offer - 面试题25:合并俩个排序的链表
剑指Offer - 面试题25:合并俩个排序的链表
57 0
|
Java API
公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!(2)
公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!
194 0
公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!(2)
|
Java 编译器
公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!(1)
公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!
143 0
公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!(1)
|
存储 算法
数据结构之单链表一生的历程(创建一个线性表,动态分布空间,单链表创建的思路,单链表的增、删、改、毁)
数据结构之单链表一生的历程(创建一个线性表,动态分布空间,单链表创建的思路,单链表的增、删、改、毁)
|
算法
数据结构上机实践第四周项目6- 循环双链表应用
数据结构上机实践第四周项目6- 循环双链表应用
数据结构上机实践第四周项目6- 循环双链表应用
|
存储
合并两个排序的链表(面试常考,非常重要)
合并两个排序的链表(面试常考,非常重要)
93 0
合并两个排序的链表(面试常考,非常重要)
俩个有序顺序表的合并(好好学习)
今天上课的时候老师提到了这题,上课的时候脑子卡了,居然没做出来,在路上才想起来怎么操作 对于这道题首先考虑的是LA LB为空的三种情况,
俩个有序顺序表的合并(好好学习)
|
存储 算法 数据可视化
我明白了,通过链表来思考递归
上篇文章已经从底层完整实现了一个单链表这样的数据结构,并且也依托链表这样的数据结构实现了栈和队列,在实现队列的时候对链表进行了一些改进。 递归不光用于树这样的结构中还可以用在链表这样的结构中,链表本身就天然的具有递归结构性质,只不过链表太简单了,它是一个线性结构,所以可以使用非递归的方式,如使用循环的方式就可以非常容易的解决链表的问题,从链表开始就要打好递归的基础,对深入学习树结构包括更加深刻的理解递归算法都是非常有好处的。
265 0
我明白了,通过链表来思考递归