c语言指针简介
稍后补充
案例一
在c语言程序中,可以使用指向字符型数据的指针(char*)表示字符串。
例子如下:
#include <stdio.h>
int main(int argc, char **argv)
{
char *test = "Hello";
printf("%s\n", test);
printf("%d", sizeof(test));
}
程序输出结果如下:
Hello
8
案例二
在使用c语言实现二叉树的基本操作时,也很考验对于指针的操作熟练程度。
本例子使用二叉链表作为二叉树的数据结构。
#include <stdio.h>
#include <stdlib.h>
typedef char ElemType;
typedef struct BinaryTree
{
ElemType data;
struct BinaryTree *left_child;
struct BinaryTree *right_child;
} BinaryTree;
typedef BinaryTree TreeNode;
下面是测试程序。
int main(int argc, char **argv)
{
BinaryTree *a, *b, *c, *d, *e, *f, *g;
/* 非内置的数据类型需要手动申请内存空间 */
a = (BinaryTree*)malloc(sizeof(BinaryTree));
b = (BinaryTree*)malloc(sizeof(BinaryTree));
c = (BinaryTree*)malloc(sizeof(BinaryTree));
d = (BinaryTree*)malloc(sizeof(BinaryTree));
e = (BinaryTree*)malloc(sizeof(BinaryTree));
f = (BinaryTree*)malloc(sizeof(BinaryTree));
g = (BinaryTree*)malloc(sizeof(BinaryTree));
a->data = 'A';
a->left_child = b; a->right_child = c;
b->data = 'B';
b->left_child = NULL; b->right_child = NULL;
c->data = 'C';
c->left_child = NULL; c->right_child = NULL;
d->data = 'D';
d->left_child = e; d->right_child = f;
e->data = 'E';
e->left_child = NULL; e->right_child = NULL;
f->data = 'F';
f->left_child = NULL; f->right_child = NULL;
g = a;
printf("将节点a赋值给节点g\n");
printf("节点g的值为 %c\n", g->data);
printf("节点g的左孩子的值为 %c\n", g->left_child->data);
printf("节点g的右孩子的值为 %c\n", g->right_child->data);
return 0;
}
上述程序构造了一个简单的二叉树1
A
/ \
B C
以及二叉树2
D
/ \
E F
以及一个空二叉树G
G
/ \
NULL NULL
然后将指针a赋值给指针g。
以上程序的运行结果如下:
将节点a赋值给节点g
节点g的值为 A
节点g的左孩子的值为 B
节点g的右孩子的值为 C
说明:
将a的地址赋值给g后
a指向的左右孩子也是g指向的左右孩子
接着,再在测试案例中添加一小段代码,看看程序运行过程中虚拟内存的变化。
/*
* 在主函数中修改为以下代码
*/
printf("操作前\n");
printf("节点a的虚拟内存地址为 %d\n", a);
printf("节点g的虚拟内存地址为 %d\n", g);
g = a;
printf("将节点a赋值给节点g\n");
printf("节点g的值为 %c\n", g->data);
printf("节点g的左孩子的值为 %c\n", g->left_child->data);
printf("节点g的右孩子的值为 %c\n", g->right_child->data);
/*
* 输出结果显示将a的地址赋值给g后
* a指向的左右孩子也是g指向的左右孩子
*/
printf("节点a的虚拟内存地址为 %d\n", a);
printf("节点g的虚拟内存地址为 %d\n", g);
测试程序运行结果如下:
操作前
节点a的虚拟内存地址为 1394704
节点g的虚拟内存地址为 1395184
将节点a赋值给节点g
节点g的值为 A
节点g的左孩子的值为 B
节点g的右孩子的值为 C
节点a的虚拟内存地址为 1394704
节点g的虚拟内存地址为 1394704
可以看出,将指针a复制给指针g就是将a的内存地址赋值给g。
因此,a所指向的左右孩子也是g所指向的左右孩子。
接着,再尝试使用free()函数释放内存。
再上述代码之后,添加如下代码片段。
free(g);
printf("free节点g\n");
printf("节点g释放内存后的虚拟内存地址为%d\n", g);
printf("节点g的值为 %d\n", g->data);
printf("节点a的虚拟内存地址为 %d\n", a);
printf("节点a的左孩子的地址原来为(即节点b的地址) %d, 现在为%d\n", b, a->left_child);
printf("节点a的右孩子的地址为 %d\n", a->right_child);
运行结果如下:
操作前
节点a的虚拟内存地址为 7817232
节点g的虚拟内存地址为 7817712
将节点a赋值给节点g
节点g的值为 A
节点g的左孩子的值为 B
节点g的右孩子的值为 C
节点a的虚拟内存地址为 7817232
节点g的虚拟内存地址为 7817232
free节点g
节点g释放内存后的虚拟内存地址为7817232
节点g的值为 -16
节点a的虚拟内存地址为 7817232
节点a的左孩子的地址原来为(即节点b的地址) 7817312, 现在为
7799120
节点a的右孩子的地址为 -17891602
可以看出,free(g)但是没有将指针g置为空指针(即令 g = NULL)之后,g的虚拟内存地址不变,但是g的值变化为内存块上的随机值。
并且,对指针g释放内存影响到了原先赋值给其的指针a
指针a的虚拟内存地址不变,(情况和指针g相同)
但是左右孩子的地址跟着被修改。
并且当你试图输出a的左右孩子的值时(如下图)
printf("节点a的左孩子的值为 %c", a->left_child->data);
printf("节点a的右孩子的值为 %c", a->right_child->data);
程序在运行过程中会报错(内存错误)
说明释放指针g的内存时,a的内存也跟着被释放。
原因如下:
指针g和指针a 指向的是同一块地址(同一块堆空间)
所以,在本案例开始的将指针a赋值给g时其实是浅拷贝
如果需要指针g和指针a在拷贝后相互独立,需要使用深拷贝
即将a指向的数据的所有属性逐个赋值给g指向的数据。
另外,若在对指针g进行free()操作后对其赋值为空指针,即
free(g);
g = NULL;
之后的内容与不对其置空基本一致,只是置空之后g的地址为0
由于仍然是浅拷贝,指针a仍然被释放内存,因此结果大部分一致(除了g的地址改变)。
注意:
在对指针使用**free()**的时候,千万要注意free()只能释放之前调用过malloc,calloc,realloc这三个函数返回的指针ptr所指向的内存空间。
也就是说,free和malloc,calloc,realloc需要成对出现。否则会有意外发生。