我一般常用的不带头结点的链表初始化:
#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 了下,http://blog.csdn.net/misskissc/article/details/16822349 相关内容参考这里。
其次,在输出链表的时候需要注意:遍历链表的结束条件,是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;
}
添加头结点,用数据域来保存当前节点的地址值 方法比较新,有学习意义。 但是会有头结点。 个人感觉,只是做法比较新颖,实际意义不大。
以上整理自这里: http://blog.csdn.net/chiichen/article/details/6656595 感谢。