彻底搞懂指针、数组与函数

一、指针与数组

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 就成了“野指针”。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哈喽朝龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值