文章目录
一、指针与数组
1. 指针数组
1.1. 概念
指针数组,首先它是一个数组,数组的元素都是指针,数组占多少字节由数组本身决定,它是“储存指针的数组”的简称。
对于指针数组,强调的是数组的概念,只不过,数组所保存的类型是指针罢了,其地位跟普通的数组没有什么区别,都是数组,只不过是大家保存的类型不同而已,因此,我们美名其曰:保存指针的数组就称其为指针数组。
1.2. 形式
指针数组的定义形式一般为:
dataType *arrayName[length];
[ ]的优先级高于*,该定义形式应该理解为:
dataType *(arrayName[length]);
括号里面说明arrayName是一个数组,包含了length个元素,括号外面说明每个元素的类型为dataType *。
1.3. 实例
int *p[10];
char *p[10];
就是定义了一个指向int类型和char类型的指针数组。要注意,这里由于”[]”的优先级高于”*”,因此,数组名p先与”[]”结合,这就构成了一个数组的形式。是一个含有10个元素的一维数组,每个数组元素都是一个一级指针。指针数组名就表示该指针数组的存储首地址,即指针数组名为数组的指针。
2. 数组指针
1.1. 概念
首先它是一个指针,它指向一个数组,在32位系统下永远是占4个字节,至于它所指向的数组占多少字节是不知道的,它是“指向数组的指针”简称。
对于数组指针,强调的是指针的概念,只不过,指针的能力是用来指向数组类型的,并且其方括号中的数字一定,例如:int (*p)[10],p就是指向数组的指针,其中p指针规定了只能指向整形的数组,并且数组大小只能是10个整形空间,不能多也不能少,多之少之都会认为其指针的能力与指向的实体不符。
1.2. 形式
数组指针的定义形式一般为:
dataType (*arrayName) [length];
1.3. 实例
int (*p)[4];
char (*p)[4]; 那int *p[4]和int (*p)[4]有什么区别呢?
区别:由上面可知它们是指针,是指向一个大小为4个整型的数组的数组指针。这里p[4],根据运算优先级,”[]”运算级高于””,而()操作符和[]优先级相等,从左向右进行运算,因此p先和()里的*结合,所以断定这是指针,然后再和[]结合,就成了数组指针。另外,数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关。
3. 理解数组名和指针变量
请看下面的代码:
int i, *pa, a[] = {3,4,5,6,7,3,7,4,4,6};
pa = a;
for (i = 0; i <= 9; i++)
{
printf("%d\n", *pa);
pa++; /*注意这里,指针值被修改*/
}
可以看出,这段代码也是将数组各元素值输出。不过,你把循环体{}中的 pa改成 a 试试。你会发现程序编译出错,不能成功。看来指针和数组名还是不同的。其实上面的指针是指针变量,而数组名只是一个指针常量,这个代码与上面的代码不同的是,指针 pa在整个循环中,其值是不断递增的,即指针值被修改了。数组名是指针常量,其值是不能修改的,因此不能类似这样操作:a++。
4. 理解指针数组名
对于指针数组的数组名,也代表数组的起始地址。由于数组的元素已经是指针了,数组名就是数组首元素的地址,因此数组名是指针的地址,是多级指针了。
比如指针数组int* p[N];数组名p代表&p[0],p[0]是int ,则&p[0]就是int*。若用指针存储数组的起始地址p或&p[0],可以用:int **q=p。
二、指针与函数
1. 指针函数
1.1. 概念
指针函数,首先它是一个函数,只不过,函数所返回的类型是指针类型,它是“返回指针类型的函数”的简称。我们把返回指针类型的函数称其为指针函数,那就意味着只要返回值为指针,无论是什么类型的指针,都有资格称为指针函数。
1.2. 形式
<数据类型> *<函数名称>(<参数说明>) { 语句序列; }
1.3. 实例
//指针函数,返回整型指针
int* fun(int a, int b)
{
return 0;
}
2. 函数指针
1.1. 概念
函数指针,首先它是一个指针,只不过,指针所指向的类型是函数,它是“指向函数的指针”的简称。
1.2. 形式
<数据类型> (*<函数指针名称>) (<参数说明列表>);
1.3. 实例
#include <stdio.h>
int Max(int a, int b)
{
printf("%d\n", a > b ? a : b);
return a > b ? a : b;
}
//函数指针
int (*pfun)(int, int);
void main()
{
//情形1
Max(1, 2);
//情形2
int (*pfun)(int, int);
pfun = &Max;
(*pfun)(1, 2);
//情形3
pfun = Max;
pfun(1, 2);
getchar();
}
三、二级指针
看下面代码:
short int **ppi; /* 这是一个指向指针的指针,注意有两个“*”号*/
*ppi = π 第一句:short int **ppi; —— 声明了一个指针变量 ppi,这个 ppi 是用来存储(或称指向)一个 short int * 类型指针变量的地址。
第二句:&pi 那就是取 pi 的地址,**ppi = &pi就是把 pi 的地址赋给 了 ppi。即将地址值 9 赋值给 ppi。如下图:
从图中看出,指针变量 ppi 的内容就是指针变量 pi 的起始地址。于是…… ppi 的值是多少呢?—— 9。
ppi 的值是多少呢?—— 5,即 pi 的值。
**ppi 的值是多少呢?——50,即 i 的值,也是pi 的值。
四、野指针
1.概念
定义:野指针”不是NULL指针,是未初始化或未清零的指针,他指向的内存地址不是程序员想要的。
2.“野指针”产生的原因
1.1.指针变量没有被初始化
任何指针变量刚被创建时不会自动成为 NULL
指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为 NULL,要么让它指向合法的内存。
例如:
char *p = NULL;
char *str = (char *) malloc(100);
1.2.指针指向的内存被释放了,而指针指向没有置NULL
指针 p 被 free 或者 delete 之后,没有置为 NULL,让人误以为 p 是个合法的指针。别看 free 和 delete的名字恶狠狠的(尤其是 delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。
用调试器跟踪下面的代码,发现指针 p 被 free 以后其地址仍然不变(非 NULL),只是该地址对应的内存是垃圾,p成了“野指针”。如果此时不把 p 设置为 NULL,会让人误以为 p 是个合法的指针。
char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p); // p 所指的内存被释放,但是 p 所指的地址仍然不变
…
if(p != NULL) // 没有起到防错作用
{
strcpy(p, “world”); // 出错
}
1.3. 指针操作超越了变量的作用范围
这种情况让人防不胜防,示例程序如下:
class A
{
public:
void Func(void){ cout << “Func of class A” << endl; }
};
void Test(void)
{
A *p;
{
A a;
p = &a; // 注意 a 的生命期
}
p->Func(); // p 是“野指针”
}
函数 Test 在执行语句 p->Func()时,对象 a 已经消失,而 p 是指向 a 的,所以 p 就成了“野指针”。