我的改错日常-
指针使用守则以及建议
守则1:每一个指针调用前都要有所指向的空间(即地址);
守则2:要修改指针时需要传入更高一级指针;
建议:不要使用指针。
法则及真理详解
守则1:每一个指针调用前都要有所指向的空间(即地址);
当你新建一个指针的时候,都要记住需要为其分配空间,最常见使用malloc函数分配空间。没有分配空间之前不要使用它。指针与地址空间是对应的。没有分配地址的指针指向的空间是不安全的,可能出现问题。只有指针指向的空间是确定的、自己分配的这时候指针才是安全的。
守则2:要修改指针时需要传入更高一级指针;
当使用函数调用修改指针时,一定要记住,参数一定是更高一级的指针,否者不会修改其值。类比变量的修改,假若有一个局部变量int a = 10;调用函数修改a时,函数传入的参数必须时a的地址,才可以修改a的值。否则函数执行完不会修改其值。以此类推,要修改一重指针,函数需要传入二级指针。需要修改二重指针,函数需要传入三重指针。等等。切记,不要看见是指针就直接传入参数调用。一定要分清。
在函数内部赋值时,需要解引用传入的更高一级指针,使其成为要修改的指针类型,再对齐进行修改。当然可以声明变量为p级别指针,而传入&p级指针。
小例子:
// 例1
int a = 0;
void changeA(int *pa)// 传入更高一级指针
{
*pa = 10; // 采用解引用修改值
}
changeA(a);
// 例2
int *b = NULL;
void changeB(int **pb) // 传入更高一级指针
{
*pb = (int*)malloc(sizeof(int)); // 采用解引用修改值
}
changeB(&b);
// 等于例2
int *b = NULL;
b = (int*)malloc(sizeof(int));
建议:不要使用指针。
在你没有完全搞明白指针的时候,建议避免使用指针。可以使用全局变量、通过返回值修改原来的值、C++中函数传入引用等等。如果无法避免,不要使用过高级的指针。
实例
昨天心血来潮敲一下二叉树的建立,没想到直接卡死。
下面是源程序
// 自定义数据类型
typedef char DataType;
// 二叉树的定义
typedef struct Node
{
DataType data; // 数据
struct Node* LChild; // 左子树
struct Node* RChild; // 右子树
}BiTNode,*BiTree; //BiTNode:二叉树类型 BiTree:指针类型
/*
* 建立二叉树
* 采用类似先序遍历的方式建立二叉树。
* 首先读入的是当前根节点的数据,如果是“.”,则将当前树的根置为空,否则申请一个新节点,
* 存入当前的数据,分别用用当前节点的左子域和右子域进行递归调用创建左子树右子树。
*/
void CreateBiTree(BiTree* bt)
{
char ch;
ch = getchar();
if (ch == ".") *bt = NULL;
else {
(*bt) = (BiTree)malloc(sizeof(BiTNode));
if ((*bt) != NULL) { // C6011警告 C++ malloc申请的节点使用前先判空
(*bt)->data = ch;
CreateBiTree(&((*bt)->LChild));
CreateBiTree(&((*bt)->RChild));
}
}
}
然而我是这样调用的:
// 初始化二叉树
void InitBiTree(BiTree* bt)
{
bt = (BiTree*)malloc(sizeof(BiTree));
}
int main()
{
BiTree* tree = NULL;
InitBiTree(tree);
CreateBiTree(tree);
}
出现如下报错
出错原因:
当然CreateBiTree(BiTree* bt)函数是没有任何问题的,问题就出在InitBiTree(tree)上,但是确实使用InitBiTree给tree初始化分配空间了,而且传入的是指针。但是为什么tree是nullptr呢?这时候请看回头看守则1与守则2。为什么会报错?因为tree没有指向的空间,守则1不满足。为什么tree没有指向分配的空间?因为tree的值没有修改,而修改指针的值,请参照守则2,必须传入更高一级的指针。InitBiTree(tree)不满足守则2。
正确调用如下:
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
// 自定义数据类型
typedef char DataType;
// 二叉树的定义
typedef struct Node
{
DataType data; // 数据
struct Node* LChild; // 左子树
struct Node* RChild; // 右子树
}BiTNode,*BiTree; //BiTNode:二叉树类型 BiTree:指针类型
/*
* 建立二叉树
* 采用类似先序遍历的方式建立二叉树。
* 首先读入的是当前根节点的数据,如果是“.”,则将当前树的根置为空,否则申请一个新节点,
* 存入当前的数据,分别用用当前节点的左子域和右子域进行递归调用创建左子树右子树。
*/
void CreateBiTree(BiTree* bt)
{
char ch;
ch = getchar();
if (ch == ".") *bt = NULL;
else {
(*bt) = (BiTree)malloc(sizeof(BiTNode));
if ((*bt) != NULL) { // C6011警告 C++ malloc申请的节点使用前先判空
(*bt)->data = ch;
CreateBiTree(&((*bt)->LChild));
CreateBiTree(&((*bt)->RChild));
}
}
}
void InitBiTree(BiTree **bt)
{
// 分配空间
*bt = (BiTree*)malloc(sizeof(BiTree));
}
int main()
{
BiTree* tree = NULL;
InitBiTree(&tree);
CreateBiTree(tree);
return 0;
}
解释:
声明BiTree* tree = NULL;后需要对其进行修改,即分配内存,如使用函数调用的方式参考守则2,必须传入更高一级指针。而且赋值时采用解引用的方式,即*变量名的方式。
或者如下:
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
// 自定义数据类型
typedef char DataType;
// 二叉树的定义
typedef struct Node
{
DataType data; // 数据
struct Node* LChild; // 左子树
struct Node* RChild; // 右子树
}BiTNode,*BiTree; //BiTNode:二叉树类型 BiTree:指针类型
/*
* 建立二叉树
* 采用类似先序遍历的方式建立二叉树。
* 首先读入的是当前根节点的数据,如果是“.”,则将当前树的根置为空,否则申请一个新节点,
* 存入当前的数据,分别用用当前节点的左子域和右子域进行递归调用创建左子树右子树。
*/
void CreateBiTree(BiTree* bt)
{
char ch;
ch = getchar();
if (ch == ".") *bt = NULL;
else {
(*bt) = (BiTree)malloc(sizeof(BiTNode));
if ((*bt) != NULL) { // C6011警告 C++ malloc申请的节点使用前先判空
(*bt)->data = ch;
CreateBiTree(&((*bt)->LChild));
CreateBiTree(&((*bt)->RChild));
}
}
}
int main()
{
BiTree tree = NULL;
CreateBiTree(&tree);
return 0;
}
解释:
声明BiTree tree = NULL;这时候tree本身有地址,但是没有指向的地址。CreateBiTree(&tree);传入tree的地址,使用解引用“ *bt = NULL; ” ,“ (*bt) = (BiTree)malloc(sizeof(BiTNode)); ”等来修改tree符合守则2。所以正确。
两个程序最重要的区别就是tree的类型不同。
当然也可以使用如下程序调用
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
// 自定义数据类型
typedef char DataType;
// 二叉树的定义
typedef struct Node
{
DataType data; // 数据
struct Node* LChild; // 左子树
struct Node* RChild; // 右子树
}BiTNode,*BiTree; //BiTNode:二叉树类型 BiTree:指针类型
/*
* 建立二叉树
* 采用类似先序遍历的方式建立二叉树。
* 首先读入的是当前根节点的数据,如果是“.”,则将当前树的根置为空,否则申请一个新节点,
* 存入当前的数据,分别用用当前节点的左子域和右子域进行递归调用创建左子树右子树。
*/
void CreateBiTree(BiTree* bt)
{
char ch;
ch = getchar();
if (ch == ".") *bt = NULL;
else {
(*bt) = (BiTree)malloc(sizeof(BiTNode));
if ((*bt) != NULL) { // C6011警告 C++ malloc申请的节点使用前先判空
(*bt)->data = ch;
CreateBiTree(&((*bt)->LChild));
CreateBiTree(&((*bt)->RChild));
}
}
}
int main()
{
BiTree *tree = NULL;
tree = (BiTree*)malloc(sizeof(BiTree)); // 直接分配,不使用初始化函数。
CreateBiTree(tree);
return 0;
}
总结
C/C++语言十分灵活,使用指针的时候可以采取多种调用以及声明的方式,根据自己日常习惯选择就行。但是在使用的时候一定要分清指针是否有所指向的地址空间以及调用函数的时候传入参数的类型。
每一个指针都要有所指向的空间(即地址);
要修改指针时需要传入更高一级指针;
如果对指针的使用还是不够熟练建议避免使用指针。