指针
指针应该是c语言中的核心了。。但是自己不管怎么学都始终云里雾里,在做完后大作业后才发现,如果单独去理解指针确实是有困难,但是如果将指针与其他的数据结构结合在一起,或许可以理解的更加深刻一点点吧(大概╮(╯▽╰)╭)
指针的定义
(点击代码框的text可以放大代码)
先看下面几行行语句
int a;
int *p;
p=&a;
指针的作用是存储一个地址,更确切的说是存储一个内存空间的地址,在上面的代码中,指针p利用&运算符取得了一个变量a的地址 那么内存空间该怎么理解呢?看下面这段代码
int var1;
char var2[10];
printf("var1 变量的地址: %p\n", &var1 );
printf("var2 变量的地址: %p\n", &var2 );
return 0;
下列的代码输出结果为
var1 变量的地址: 0x7fff5cc109d4
var2 变量的地址: 0x7fff5cc109de
每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址
指针的作用
那么,为什么访问一个变量要用指针呢,在我刚开始学习时感到非常的不解,但是现在,我当时的想法是有问题的,指针并没有访问一个变量 ,它访问的永远是变量的地址,而不是一个单纯的值,所以在模块化设计中,指针的作用便体现出来了,首先我们必须要明确一个概念,在函数的传递值是单向的,不可逆的,如果没有return的话,你在函数对数做的任何操作,对主函数是没有影响的,所以有两句话
1,以数组元素做实参,向形式参数传递的是数组的值
2、以数组名做实参,向形式参数传递的是数组的地址
值和地址可不一样,值只是第一个单一的数,而地址是整个数组 ,所以这句话的数组名可以理解成数组名的首地址,
所以看下面的代码
int balance[5] = {1000, 2, 3, 17, 50};
avg = getAverage( balance, 5 ) ;
记住 ,数组名为该数组首元素的地址,访问到首个地址就可以访问所有数组的地址。
double getAverage(int *arr, int size)
{
int i, sum = 0;
double avg;
for (i = 0; i < size; ++i)
{
sum += arr[i]; //注意 arr[i]和*(arr+i)等价 记住规则即可,本质是利用指针从头部开始遍历。
}
avg = (double)sum / size;
return avg;}
这段代码,以数组名做实参后,既然这是一个地址,自然需要一个指针去访问,所以在形式参数中,定义了一个*arr的指针。
这便是指针的基本性质 ,通过指针作形式参数,可以直接对原函数的数组进行修改,完成模块化设计中各种数据的交接。后文会提到
链表
在了解了指针的基本性质后,我们便可以对链表进行一个基本的了解了,那么,什么是链表呢?请参考这幅图有一个基本的了解。
哈 ,通俗的说,链表就是由数个由结构体节点组成的一个数据结构,而每个结构体分为两个部分,第一个我们需要存放的数值,第二个是则是指向下个节点的该结构体类型的指针,
就像这样
struct node
{
int data;
struct node *next;
}
在上面的代码中,我们定义了一个名字为node结构体类型,他由两部分组成,一个数据data,一个是指针,用来指向下一个节点的地址,因为下个节点的类型为struct node,所以指针类型自然为struct *node啦。
那么现在让我们开始建立一个链表,首先,任何数据都要有一个开始访问的路,所以我们需要建立一个头指针。
struct node *head 去指向链表的第一个结点。 一开始为空head=null。
接下来,我们来创建第一个节点,并用临时指针p去指向他们。
struct node*p;
p=(struct node *)malloc(sizeof(struct node));//动态申请一个空间去存放节点,
这时,我们就可以利用指针p来操作这个空间勒,
接下来,设置这个结点的左半部分和右半部分。就是存放数据啦。
scanf("%d",&a);
p->data=a;
p->next=null;
其中,->叫做结构体运算符,用来访问结构体的内部成员的,因为p是一个指针,所以不能用结构体符号.来访问
接下来我们可以设置头指针并设置新新创建的节点指向空,头指针的作用是遍历整个链表。
同时,因为后续需要利用for循环去创建多个结点。所以需要去判断结点的插入情况。是头部的结点?还是中间,还是尾部
if(head==null)
head=p; //如果这是头一个结点,则把p指向的地址传入head;
else
q->next=p //如果不是第一个结点,则把上一个结点的next指针指向当前结点,完成两个链表的拼接
最后,也是我忽略的一步,q=p;需要将指针q指向当前的p结点,因为指针p只是临时的,待会还要用来指向其他的数据,这时候,p的地址传入q,q就正式获取了我们的数据啦
先看完整的代码。
#include<stdio.h>
#include<stdlib.h>
struct node
{
int data;
struct node *next;
} ;
int main()
{ struct node *head ,*p,*q,*t;
int i,n,a;
scanf("%d",&n);
head=NULL;
for(i=1;i<=n;i++)
{
scanf("%d",&a);
p=(struct node*)malloc(sizeof(struct node)); //
p->data=a;
p->next=NULL;
if(head==NULL)
head=p;
else
q->next=p;
q=p;
}
接下来,就是进行链表的遍历,这一块就比较方便了,只需要一个指针访问头指针然后从头开始遍历
t=head;
while(t!=null)
{
printf("%d",t->data);
t=t->next;
}
getchar();
return 0;
}
这时候,我们又要强调一次指针的作用了,指向地址,看,我们用for循环输入了这么多数据,但是最终用一个指针就实现了对他们的操纵,在看上文提到的,指针访问的始终是一地 址,所以我们可以用指针去访问不同的地址,我们在for循环中利用for循环中的malloc分配了不同的内存空间,而指针q指向的最终的便是他们
这就是一个最基本的链表勒。0_o.