问题描述
有人可以解释一下为什么这段代码给我空列表吗?
typedef struct str_node{
int data;
struct str_node *next;
}node;
void begin(node *head);
void display_list(node *head);
int main(){
node *head;
int i;
head = NULL;
for(i=0;i<5;i++) {
begin(head);
}
display_list(head);
return 0;
}
void begin(node *head){
node *new;
int value;
new = (node*) malloc(sizeof(node));
printf("Insert the element to add at the beginning of the list: ");
scanf("%d",&value);
new->data = value;
new->next = head;
head = new;
}
但是,如果我使用指向指针的指针更改了begin()函数,它会给我正确的列表吗?
void begin(node **head){
node *new;
int value;
new = (node*) malloc(sizeof(node));
printf("Insert the element to add at the beginning of the list: ");
scanf("%d",&value);
new->data = value;
new->next = *head;
*head = new;
}
您是否也可以解释一下为什么当我将主节点传递给函数开始时,我必须将其传递为“&head”?而不再是“头”
解决方法
在此代码段的第一个程序中
head = NULL;
for(i=0;i<5;i++) {
begin(head);
}
指针head
通过值传递给函数begin
。即创建了main中声明的指针head
的值的副本,并将其分配给与函数begin相同名称的参数
void begin(node *head);
因此,在函数内,参数head
最初持有已更改的原始指针head
的副本。其值已分配给该参数的原始指针head
不会更改。
要更改main中声明的原始指针头,您必须像在第二个程序中一样,通过指向该指针头的指针间接地将其传递给函数。
因此该函数应声明为
void begin(node **head);
并且您必须通过指向它的指针间接传递指针头
begin( &head );
在这种情况下,取消引用传递的指针,该函数将直接访问main中声明的原始指针头并可以对其进行更改(而不是它的值的副本,因为它是在第一个函数定义中发生的)
new->next = *head;
*head = new;
为使内容更清楚,请考虑使用此简单的演示程序。
#include <stdio.h>
typedef int T;
void f( T t )
{
t = 10;
}
int main(void)
{
T t = 0;
printf( "Before calling f t is equal to %d\n",t );
f( t );
printf( "After calling f t is equal to %d\n",t );
return 0;
}
其输出为
Before calling f t is equal to 0
After calling f t is equal to 0
当函数f处理传递的参数值的副本时,在main中声明的变量t
的值未更改。
因此,您需要通过像这样的指针通过引用传递原始变量t
#include <stdio.h>
typedef int T;
void f( T *t )
{
*t = 10;
}
int main(void)
{
T t = 0;
printf( "Before calling f t is equal to %d\n",t );
f( &t );
printf( "After calling f t is equal to %d\n",t );
return 0;
}
现在程序输出为
Before calling f t is equal to 0
After calling f t is equal to 10
在这些说明性程序中,名称T
用作类型int
的别名,而主要的对象t
具有这种类型。
现在让我们假设名称T是int *类型的别名。
typedef int * T;
在这种情况下,例如在main中声明
T t = NULL;
表示变量t
的指针类型为int *
。那等于
int * t = NULL;
因此,要将其传递给必须更改原始变量t的函数,我们需要通过引用将其传递给
f( &t );
这意味着相应的函数应具有声明的参数类型,例如
void f( T *t );
但由于T
是int *
的别名,因此这意味着该函数具有类型为int **
的参数。
void f( int * *t );
,
由于head
是(实际上)是局部变量,因此更改*head
不会对函数产生任何影响,而更改head
会更改int
指向的内容,因此也可以。
/ p>
如果您希望函数能够更改x
变量中的值(例如x
),则可以将其传递给指向int*
的指针,该指针将具有键入x
,您将使用&x
获得指向x
的指针。不论class ComplexRadar():
def __init__(self,fig,variables,ranges,n_ordinate_levels=6):
angles = np.arange(0,360,360./len(variables))
axes = [fig.add_axes([0.1,0.1,0.9,0.9],polar=True,label = "axes{}".format(i))
for i in range(len(variables))]
l,text = axes[0].set_thetagrids(angles,labels=variables)
[txt.set_rotation(angle-90) for txt,angle
#in zip(text,angles)]
for ax in axes[1:]:
ax.patch.set_visible(False)
ax.grid("off")
ax.xaxis.set_visible(False)
for i,ax in enumerate(axes):
grid = np.linspace(*ranges[i],#num=n_ordinate_levels)
gridlabel = ["{}".format(round(x,2))
#for x in grid]
if ranges[i][0] > ranges[i][1]:
#grid = grid[::-1] # hack to invert grid
# gridlabels aren't reversed
gridlabel[0] = "" # clean up origin
ax.set_rgrids(grid,labels=gridlabel,# angle=angles[i])
ax.spines["polar"].set_visible(False)
ax.set_ylim(*ranges[i])
# variables for plotting
self.angle = np.deg2rad(np.r_[angles,angles[0]])
self.ranges = ranges
self.ax = axes[0]
def plot(self,data,*args,**kw):
sdata = _scale_data(data,self.ranges)
self.ax.plot(self.angle,np.r_[sdata,sdata[0]],**kw)
def fill(self,self.ranges)
self.ax.fill(self.angle,**kw)
# example data
variables = ('Bench Press',"Vert Leap\xa0(in)",'Broad Jump\xa0(in)','Shuttle','3Cone','60Yd Shuttle','40\xa0Yard')
data_rb = rb_mean_list
data_wr = wr_mean_list
data_qb = qb_mean_list
data_lb = lb_mean_list
data_t = t_mean_list
data_de = de_mean_list
data_dt = dt_mean_list
data_g = g_mean_list
data_te = te_mean_list
data_fb = fb_mean_list
ranges = [(0,50),(15,(0,150),(6,3),(10,6),(14,10),4)]
# plotting
fig,axs = plt.subplots(2,2)
radar = ComplexRadar(fig,ranges)
axs[0,0].radar.plot(data_rb)
axs[0,0].radar.fill(data_rb,alpha=0.2)
axs[0,0].set_title('rb')
axs[0,1].radar.plot(data_wr)
axs[0,1].radar.fill(data_wr,1].set_title('WR')
plt.show()
是什么类型,都一样。
声明可能会引起一些困惑
node *head;
代替
node* head;
您要声明head
。 head
是变量,它是一个指针。它不是节点。还要注意,节点不是链表:链表是节点的集合,可能还有其他的集合,以便获得有用的实现。最后在后面对此进行详细介绍。
事实是您在main()
中声明为head
,仅仅是node*
。该节点本身甚至还不存在。您将begin()
声明为
void begin(node *head);
我想您会更清楚地看到它
void begin(node* parameter);
parameter
是node*
。
在begin()
内,您可以获得指针的副本,并且更改指针不会更改main()
中的原始指针。
对于您而言,它将在main()
中永远指向NULL
。
重要的是,指针就像任何变量一样:指针具有地址。和一个内容。当您按值传递值时,就像您所做的那样,begin()
中的指针以NULL
开头,即main()
的值。但是它们之间的联系在通话中结束了:初始值。
当您将指针传递到begin()
时,使用运算符'address of'并写入&head
,事情发生了变化:您将使用运算符{{1}对其进行更改}表示您将更改其指向的地址,因此它将在'*'
中进行更改。由于main()
是head
的指针将被声明为node*
但是考虑使用以下方法更改链接列表的node**
声明:
begin()
逻辑是插入节点可以更改列表的开头,因此您可以返回新地址,如
node* begin(node* node);
是编写此代码的常用方法。另一种是使用node* _insert_begin(int value,node* pNode)
{
node* new = (node*)malloc(sizeof(node));
new->data = value;
new->next = pNode;
return new;
}
。
我在这里描述的方式,任何可以更改列表头的操作都必须
- 退回新头
- 接收并更新指向头部的指针的指针
再次查看此代码,该代码插入列表的开头:
node**
返回node* _insert_begin(int value,node* pNode)
{ // insert 'value' at the start of the list
node* new = (node*)malloc(sizeof(node));
(*new).data = value;
new->next = pNode;
return new;
}
,即可更新new
。您可以用head
main()
注意行node* another = NULL;
display_list(another);
// inserts 5 to 0 at the beginning
for (int i = 5; i >= 0; i -= 1)
another = _insert_begin(i,another);
printf("inserted 5..0 at the beginning\n");
display_list(another);
,您将看到another = _insert_begin(i,another);
中的指针如何更新。
这是输出
main()
使用empty list
inserted 5..0 at the beginning
0 1 2 3 4
5
list has 6 elements
的此实现,每行打印5个值:
display_list()
另一个例子:在末尾插入
请注意,如果列表为空,则在末尾插入也可以更改头,所以我们仍然需要返回头地址
int display_list(node* p)
{
if (p == NULL)
{
printf("empty list\n");
return 0;
};
int count = 0;
// not empty
do
{
printf("%8d ",p->data);
count++;
if (count % 5 == 0) printf("\n");
p = p->next;
} while (p != NULL);
if (count % 5 != 0) printf("\n");
printf("list has %d elements\n",count);
return count;
};
另一用途:按升序插入
当然,按升序插入也可以改变头,就像
node* _insert_end(int value,node* pNode)
{ // insert value at the end of the list
node* new = (node*)malloc(sizeof(node));
new->data = value;
new->next = NULL;
if (pNode == NULL) return new;
node* p = pNode;
while (p->next != NULL) p = p->next;
p->next = new;
return pNode;
}
删除列表
删除列表还应返回node* _insert_ordered(int value,node* pNode)
{ // insert value at ascending order in the list
node* new = (node*)malloc(sizeof(node));
new->data = value;
new->next = NULL;
if (pNode == NULL) return new;
node* p = pNode;
node* prev = NULL; // previous node: list if forward only
while (p->next != NULL)
{
if (new->data < p->data)
{
// insert before first greater than value
if (prev == NULL)
{
// new head
new->next = p;
return new;
}; // if()
prev->next = new;
new->next = p;
return pNode; // no change in head
};
prev = p; p = p->next; // updates pointers
}; // while()
// we are at the end: new will be the last?
if (new->data < p->data)
{
if (prev == NULL)
pNode = new;
else
prev->next = new;
new->next = p;
}
else
{
p->next = new;
};
return pNode;
} // _insert_ordered()
,以使头部指针无效。这是平常的。当您习惯了它的机制时,这可以确保不会在周围留下无效的指针。
请注意,这种逻辑是协作的:您必须在每次可以更改磁头的调用时都将磁头指针分配回去
node*
正在运行的程序
示例程序的输出
node* delete_list(node* H)
{
if (H == NULL) return NULL;
if (H->next == NULL)
{ // single node
free(H);
return NULL;
};
// more than one node
do
{ node* p = H->next;
free(H);
H = p;
} while (H != NULL);
return NULL;
};
示例C程序
empty list
inserted 5..0 at the beginning
0 1 2 3 4
5
list has 6 elements
inserted 6 to 10 at the end
0 1 2 3 4
5 6 7 8 9
10
list has 11 elements
inserted 0 to 10,ordered
0 0 1 1 2
2 3 3 4 4
5 5 6 6 7
7 8 8 9 9
10 10
list has 22 elements
inserted -1 to -10,ordered
-10 -9 -8 -7 -6
-5 -4 -3 -2 -1
0 0 1 1 2
2 3 3 4 4
5 5 6 6 7
7 8 8 9 9
10 10
list has 32 elements
inserted 11 to 20,ordered
-10 -9 -8 -7 -6
-5 -4 -3 -2 -1
0 0 1 1 2
2 3 3 4 4
5 5 6 6 7
7 8 8 9 9
10 10 11 12 13
14 15 16 17 18
19 20
list has 42 elements
about to delete list
empty list
一种可能更有用的链表结构
请考虑以下内容
#include <stdio.h>
#include <stdlib.h>
typedef struct str_node
{
int data;
struct str_node* next;
} node;
void begin(node* pNode);
node* delete_list(node*);
int display_list(node*);
node* _insert_begin(int,node*);
node* _insert_end(int,node*);
node* _insert_ordered(int,node*);
int main()
{
node* another = NULL;
display_list(another);
// insert 5 to 0 at the beginning
for (int i = 5; i >= 0; i -= 1)
another = _insert_begin(i,another);
printf("inserted 5..0 at the beginning\n");
display_list(another);
// insert 6 to 10 at the end
for (int i = 6; i <= 10; i += 1)
another = _insert_end(i,another);
printf("inserted 6 to 10 at the end\n");
display_list(another);
// insert 0 to 10 ordered
for (int i = 0; i <=10; i += 1)
another = _insert_ordered(i,another);
printf("inserted 0 to 10,ordered\n");
display_list(another);
// insert -1 to -10 ordered
for (int i = -1; i >= -10; i -= 1)
another = _insert_ordered(i,another);
printf("inserted -1 to -10,ordered\n");
display_list(another);
// insert 11 to 20 ordered
for (int i = 11; i <= 20; i += 1)
another = _insert_ordered(i,another);
printf("inserted 11 to 20,ordered\n");
display_list(another);
printf("about to delete list\n");
another = delete_list(another);
display_list(another);
return 0;
}
node* delete_list(node* H)
{
if (H == NULL) return NULL;
if (H->next == NULL)
{ // single node
free(H);
return NULL;
};
// more than one node
do
{ node* p = H->next;
free(H);
H = p;
} while (H != NULL);
return NULL;
};
node* _insert_begin(int value,node* pNode)
{ // insert 'value' at the start of the list
node* new = (node*)malloc(sizeof(node));
(*new).data = value;
new->next = pNode;
return new;
}
node* _insert_end(int value,node* pNode)
{ // insert value at the end of the list
node* new = (node*)malloc(sizeof(node));
new->data = value;
new->next = NULL;
if (pNode == NULL) return new;
node* p = pNode;
while (p->next != NULL) p = p->next;
p->next = new;
return pNode;
}
node* _insert_ordered(int value,node* pNode)
{ // insert value at ascending order in the list
node* new = (node*)malloc(sizeof(node));
new->data = value;
new->next = NULL;
if (pNode == NULL) return new;
node* p = pNode;
node* prev = NULL; // previous node: list if forward only
while (p->next != NULL)
{
if (new->data < p->data)
{
// insert before first greater than value
if (prev == NULL)
{
// new head
new->next = p;
return new;
}; // if()
prev->next = new;
new->next = p;
return pNode; // no change in head
};
prev = p; p = p->next; // updates pointers
}; // while()
// we are at the end: new will be the last?
if (new->data < p->data)
{
if (prev == NULL)
pNode = new;
else
prev->next = new;
new->next = p;
}
else
{
p->next = new;
};
return pNode;
} // _insert_ordered()
int display_list(node* p)
{
if (p == NULL)
{
printf("empty list\n");
return 0;
};
int count = 0;
// not empty
do
{
printf("%8d ",count);
return count;
};
这样,将链表定义为节点的容器。
- 它甚至还有一个可选的
struct no { void* item; struct no* next; struct no* prev; }; // no typedef struct no Node; typedef struct { // example,more flexible char* name; unsigned size; unsigned capacity; Node* head; Node* tail; } Linked_list;
。 -
name
始终可用并且是最新的 - 大小限制可以实现为
size
- 在末尾和插入并不需要您跟随所有其他节点,因为列表封装了指向头尾的指针
- 一个节点具有指向下一个和上一个节点的指针,因此可以更轻松地迭代某些数据,例如播放列表或类似的集合。
- 一个程序可以有任意数量的列表,因为每个列表都封装了所有这些元数据。
- 列表可以包含任何内容,因为数据是指向void
capacity
的指针
- empty()或size()之类的函数可以轻松实现
- 所有函数都使用指向列表的指针
void*
,
关于:
void begin(node *head){
更改head
仅更改调用堆栈的“ head”,所需的是更改调用者函数中“ head”指向的位置。为此,呼叫者必须传递“ head”的地址。 “头”本身就是一个指针这一事实无助于明确说明需要做的事情,