之所以称为C指针基础,因为这些关于指针的操作是基础,但是我们又经常出错,在做数据结构的实验时,又遇到不少,我在这里做下总结,希望能帮到大家。另:这个不像之前的算法,也可能仍会有很多认识不足的地方,希望大家指正!
变量的地址即指针,存储地址值的内存单元称为指针单元。指针的类型由其类型决定,基类型是指指针变量所指的数据类型。所以指向整型变量的指针我们称之为整型指针;指向字符变量的指针称为字符指针等等
一、下面说一点最最基本的
1.指针声明:int *p, *q; //整型指针
char *ch; //字符指针
void *n; //这个指针也是对的,可以指向任何数据,要规定长度,这个到进阶的时候再说吧
2.指针使用:a.取地址单目运算符:int sum, *psum; psum = ∑即取sum的地址作为指针psum
b.指针的赋值:上面的psum = ∑ 是一个赋值语句;同样类型的指针 p = q; 这样赋值也是对的
c.间访单目运算符 * :sum = *q; 就可以将q所指单元的值赋给sum。
3.针对结构体指针,访问数据项的操作,方式有两种:
a. p->name (如果p是双重指针,则应该为 (*p)->name )
b. (*p).name
Ps:假设p是指向一个结构的指针,该结构内部有一个名为name的数据项,对它的访问可以使用上面两种方式
这里就有一个必须要理解的问题,指针的所指,赋值操作p = q;他们的指针单元是不一样的,存储单元的地址是不一样的,所以有时候在一些链表操作中,会犯这个错误,虽然你认真考虑了,但是你会认为 q = s; p =q; 那么 p 的指向就变成了s, 这个本身是没有太大问题,但是如果你要修改的是p指针的存储单元,那就绝对错了!下面给出例子说明。
二、指针操作常见错误
引用我自己写的基于链表的顺序表的插入操作。
下面是链表的头结点和数据项结构定义:
typedef int status;
typedef struct{
int length;
struct LNode *FirstNode;
}HNode;
typedef struct LNode{
char name[16];
int age;
char phone[12];
char mail[64];
struct LNode *next;
}LNode;
下面是ListInsert函数实现代码:
status ListInsert(HNode *L, int i, LNode *e){
LNode *p, *q, *N;
p = L->FirstNode; //指向首元结点
q = NULL; //临时指针
if((i > (L->length + 1)) || (i <= 0)) //考察参数合法性
{
printf("i的值非法!\n");
return ERROR;
}
if((N = (LNode *)malloc(sizeof(LNode))) == NULL)
exit(-1);
printf("请输入新联系人的名字:");
//gets(N->name);
scanf("%s",N->name);
printf("\n请输入新联系人的年龄: ");
scanf("%d",&(N->age));
printf("\n请输入新联系人的电话:");
scanf("%s",N->phone);
printf("\n请输入新联系人的邮箱:");
scanf("%s",N->mail);
printf("录入完成!\n");
if(i>1)
{
i--;
while( --i )
{
p = p->next;
}
N->next=p->next;
p->next=N;
}
else
{
L->FirstNode=N; //致命错误:前面有语句p=L->FirstNode,如果写成p=N,就是错的
N->next=p; //即{p=L->FirstNode;p=N;} 不等价于 L->FirstNode=N;
}
++(L->length);
(*e) = (*N);
return OK;
}//ListInsert
第二个容易犯的错误:就是我们在参数列表中定义了一个指向数据元素的结构指针,我们用它返回我们前面插入的数据元素,那么如何返回?
错误写法: e = N;
错误好像还不是很明显哈,很多人就会问,为什么在函数体内可以用e指针访问所指的结构体,返回到主函数就不行了(我觉得这和汇编里面的子程序调用差不多,传的是指针,我们明显会对它进行压栈保护,也就是说在子函数e改变了指向,指向N所指的结构(某块存储空间),但是返回时,e所指的结构体(另一块存储空间)恢复了)。那么当然就没有预期效果了。
正确的写法:如上面代码的38行 (*e) = (*N); 这是两个同类型的结构体直接赋值,你也可以一个个的数据项赋值,但是明显那么做不明智。
关于结构体的赋值,也会有很多的小细节,一并说一下,因为这些细节有时候也很容易被忽略:
第三个容易犯的错误:就像前面的(*e) = (*N), e在主函数里定义,如果你只是定义了,没有分配存储空间给它,如果悬挂了,那么显然,前面这个语句就是错误的,因为e悬挂,没有所指,没有实体存储单元,就会出错!
另外就是前面说的结构体赋值的两种方式,一定要清楚等式两端的数据类型;如果结构体定义了字符串,用->来访问数据项或者赋值会更加麻烦且容易出错!
小结:前面三个错误可简述为:1.对指针所指地址和本身的存储地址理解不透彻;2.对指针参数传值理解不透彻;3.对指针赋值混淆,不清楚数据类型。所以大家要多练习,加深理解就好啦!
此外,再添加一点,定义在不同的头文件下的结构体,在main函数下include后,是可以引用另一个结构声明变量的,可以构成嵌套的结构:
//-------二叉树的二叉链表存储表示-------
typedef struct BiTNode
{
char ch;
struct BiTNode *lchild, *rchild; //左右孩子指针
}BiTNode, *BinTree;
以上是在usr_bintree.h的二叉树节点定义,下面是usr_stack.h的堆栈数据元素节点定义:
//堆栈的链栈表示--栈结点
typedef struct SNode
{
char ch; //堆栈结点保存的数据
struct SNode *next; //指向下一个链栈结点的指针
struct BiTNode *pbtnode; //指向二叉树结点的指针,这个结构定义在usr_bintree.h中
}SNode;
//堆栈的链栈表示--栈元素
typedef struct
{
SNode *top; //栈顶指针
SNode *base; //栈底指针
int stacksize; //栈当前大小
}SqStack;
在上面在栈结点我就采用了另一个头文件的二叉树定义来定义了一个数据项: struct BiTree *pbtnode; 这样是没有任何问题的,实际可行,这是我的实验,通过测试的!