带头结点的链表初始化 和不带头结点的链表初始化
2014-01-09 00:48
495 查看
我一般常用的不带头结点的链表初始化:
第一、 在createList方法中,list l = p语句之后,修改p会导致指针 l 的修改,调试发现不会有这样的问题。原因在于,l 是一个指针,实际上是有独立的内存空间,汇编为证。
以后对p的修改 自然而然不会影响到 l ,l 只是和p 指向了同一个node节点的内存空间,修改p,只是修改p的指向。不会影响 l 的值。
在这段代码里的第二个容易混淆的地方。 是 createList()方法中 声明的变量 l 作用域为 createList 方法内部。 返回时会不会释放内存,导致l 的值变化。事实上 return语句返回是放在 寄存器中返回的,所有的在栈内空间 申请的局部变量内存都会被销毁,除非通过new 在堆上申请出来的内存空间。返回的指针值 l 只是通过寄存器保存了一份返回。
汇编为证。多次验证,发现 都是用eax 保存返回值。 google 了下,/article/1935353.html 相关内容参考这里。
其次,在输出链表的时候需要注意:遍历链表的结束条件,是NULL != p,不是 NULL != p->next;这个地方很容易犯错,导致最后一个结点遍历不到或者出现内存溢出情况。
其实,最上面的代码可以不那么啰嗦。
差别不大,但是没那么啰嗦。
下一个议题,第二种方法,
同时 lea 与mov 指令的差别: mov 是将 右边的地址表达式里面的值取出来,然后传到左边寄存器或内存地址(不能内存地址直接传到内存地址,必须用寄存器充当中介), 而lea 是 直接将计算出来的地址,(不取值)传到左边。 注意区别。
看汇编码, 不管是 * 还是 &,赋值表达式最终都是 修改内存地址, 只是* 是将地址 取到寄存器中,通过 [ 寄存器 + 偏移地址 ] 寻址修改内存,而 &的操作是将内存 取到寄存器里, 从而也可以 理解&为什么不能包含在 充当左值的表达式中。
第三种:
以上整理自这里: /article/10790794.html 感谢。
#include <iostream> using namespace std; typedef struct node { int data; struct node* next; }lnode, *list; list createList() { int data = 0; cout<<"please Enter Data: (end of 0)"<<endl; cin>>data; node *p = NULL,*q; list l = p; if( 0 != data ){ p = new node; p->data = data; p->next = NULL; }else { return NULL; } l = p; cout<<"please Enter Data: (end of 0)"<<endl; cin>>data; while ( 0 != data ){ q = new node; q->data = data; q->next = NULL; p->next = q; p = q; cout<<"please Enter Data: (end of 0)"<<endl; cin>>data; } return l; } void printList(list l) { node *p = l; while( NULL != p ) { cout<<'\t'<<p->data; p = p->next; } } int main() { printList( createList() ); return 0; }一直存在一个知识盲点:
第一、 在createList方法中,list l = p语句之后,修改p会导致指针 l 的修改,调试发现不会有这样的问题。原因在于,l 是一个指针,实际上是有独立的内存空间,汇编为证。
以后对p的修改 自然而然不会影响到 l ,l 只是和p 指向了同一个node节点的内存空间,修改p,只是修改p的指向。不会影响 l 的值。
在这段代码里的第二个容易混淆的地方。 是 createList()方法中 声明的变量 l 作用域为 createList 方法内部。 返回时会不会释放内存,导致l 的值变化。事实上 return语句返回是放在 寄存器中返回的,所有的在栈内空间 申请的局部变量内存都会被销毁,除非通过new 在堆上申请出来的内存空间。返回的指针值 l 只是通过寄存器保存了一份返回。
汇编为证。多次验证,发现 都是用eax 保存返回值。 google 了下,/article/1935353.html 相关内容参考这里。
其次,在输出链表的时候需要注意:遍历链表的结束条件,是NULL != p,不是 NULL != p->next;这个地方很容易犯错,导致最后一个结点遍历不到或者出现内存溢出情况。
其实,最上面的代码可以不那么啰嗦。
#include <iostream> using namespace std; typedef struct node { int data; struct node* next; }lnode, *list; list createList(void) { int data; node *temp = NULL,*tail = NULL; list l = temp; while ( 1 ){ cout<<"please Enter Data: (end of 0)"<<endl; cin>>data; if( 0 == data){ break; } temp = new node; temp->data = data; temp->next = NULL; if( NULL == l ){ l = temp; temp = l; }else { tail->next = temp; tail = temp; } } return l; } void printList(list l) { node *p = l; while( NULL != p ) { cout<<'\t'<<p->data; p = p->next; } } int main() { printList( createList() ); return 0; }
差别不大,但是没那么啰嗦。
下一个议题,第二种方法,
list createList(void) { int data; list l = NULL; node *temp = NULL,**tail = &l; while ( 1 ){ cout<<"please Enter Data: (end of 0)"<<endl; cin>>data; if( 0 == data){ break; } temp = new node; temp->data = data; temp->next = NULL; *tail = temp; tail = &(temp->next); } return l;
}其实就是用二维指针。 关键是 理解 二维指针, 是指向指针的指针变量 这个概念。 通过二维指针 去操作temp节点,就不用每次都去 判断 temp 是否为空,而二维指针 在汇编层,仅仅也是把他当做一个 内存单元来处理, 相比第一个方法 是节省了一个临时变量,在操作上 每次循环节省了一个判空的操作的。
同时 lea 与mov 指令的差别: mov 是将 右边的地址表达式里面的值取出来,然后传到左边寄存器或内存地址(不能内存地址直接传到内存地址,必须用寄存器充当中介), 而lea 是 直接将计算出来的地址,(不取值)传到左边。 注意区别。
看汇编码, 不管是 * 还是 &,赋值表达式最终都是 修改内存地址, 只是* 是将地址 取到寄存器中,通过 [ 寄存器 + 偏移地址 ] 寻址修改内存,而 &的操作是将内存 取到寄存器里, 从而也可以 理解&为什么不能包含在 充当左值的表达式中。
第三种:
list createList(void) { int data; list l= new node; l->next = NULL; l->data = (int)l; node *temp = NULL; while ( 1 ){ cout<<"please Enter Data: (end of 0)"<<endl; cin>>data; if( 0 == data){ break; } temp = new node; temp->data = data; temp->next = NULL; (( node* )( l->data ))->next = temp; l->data = (int)temp; } return l; }添加头结点,用数据域来保存当前节点的地址值 方法比较新,有学习意义。 但是会有头结点。 个人感觉,只是做法比较新颖,实际意义不大。
以上整理自这里: /article/10790794.html 感谢。
相关文章推荐
- 带头结点与不带头结点的单链表初始化
- 假设以带头结点的循环链表表示队列, 并且只设一个指针指向队尾元素结点(注意不设头指针) 试编写相应的队列初始化,入队列和出队列的算法
- 带头结点的循环链表表示队列的初始化、入队列和出队列的算法
- 编写算法实现建立一个带头结点的含n个元素的双向循环链表H,并在链表H中的第i个位置插入一个元素e
- 带头结点和不带头结点的尾插法建立双链表
- 带头结点的单链表的基本操作
- 带头结点的单链表就地逆置
- 带头结点的双向循环链表
- 带头结点的单链表就地逆置(10 分)
- 不带头结点的单链表代码实现
- C++实现带头结点的单链表(友元类)
- 数据结构 P38 算法实现 在带头结点的单链表的第i个元素插入元素e
- 删除不带头结点的单链表的非尾结点&&逆序打印单链表
- 对一个不带头结点的单链表进行逆置
- 带头结点的单链表就地逆置
- 不带头结点的单链表
- 对于带头结点的单链表的相关操作
- 不带头结点的单链表的基本操作
- 单向不带头结点不带环的链表实现
- 单链表反转问题(带头结点 和 不带头结点的 创建链表过程等)