C语言指针(详解)

一、前言

1.指针就是一个变量(指的是指针变量),指针是用来存放地址的,地址唯一标识一块内存空间。(也就是说,内存会划分为小的内存单元,每个内存单元都有一个编号,这个编号就被称为地址,也把地址叫做指针)。

2.指针的大小是固定的,在32位系统下是4个字节,在64位系统下是8个字节。

3.指针是有类型的,指针的类型决定了指针的+-整数的步长(+1或者-1会跳过几个字节),指针解引用操作的时候的权限。

二、指针种类

要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析.下面让我们先从简单的类型开始慢慢分析吧:

2.1 int p 

这是一个普通的整型变量 

2.2 int *p

首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针 

2.3 int p[3]

首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组 

2.4 int *p[3]

首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组 

2.5 int (*p)[3]

首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针 

2.6 int **p

首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针. 

2.7 int p(int)

从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据 

int (*p)(int)

从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针 

2.9 int *(*p(int))[3]

可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.

说到这里也就差不多了,我们的任务也就这么多,理解了这几个类型,其它的类型对我们来说也是小菜了,不过我们一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用,这上面的几种类型已经足够我们用了.

三、指针的使用方法

3.1指针的算术运算

指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以单元为单位。例如:

char a[20];

 int *ptr=(int *)a;  //强制类型转换并不会改变的类型 

ptr++; 

指针ptr 的类型是int*,它指向的类型是int,它被初始化为指向整型变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr 的值加上了sizeof(int),在32 位程序中,是被加上了4,因为在32 位程序中,int 占4 个字节。由于地址是用字节做单位的,故ptr 所指向的地址由原来的变量a 的地址向高地址方向增加了4 个字节。由于char 类型的长度是一个字节,所以,原来ptr 是指向数组a 的第0 号单元开始的四个字节,此时指向了数组a 中从第4 号单元开始的四个字节。我们可以用一个指针和一个循环来遍历一个数组,看例子:

 int array[20]={0}; 

int *ptr=array; 

for(i=0;i<20;i++) 

        (*ptr)++; 

        ptr++;

这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1 个单元,所以每次循环都能访问数组的下一个单元。

3.2 *和&

这里&是取地址运算符,*是间接运算符。
&a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。
*p 的运算结果就五花八门了。总之*p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。

int a=12; int b; int *p; int **ptr; 

p=&a; //&a 的结果是一个指针,类型是int*,指向的类型是 

//int,指向的地址是的地址。 

*p=24; //*p 的结果,在这里它的类型是int,它所占用的地址是 

//p 所指向的地址,显然,*p 就是变量a 

ptr=&p; //&p 的结果是个指针,该指针的类型是的类型加个* 

//在这里是int **。该指针所指向的类型是的类型,这 

//里是int*。该指针所指向的地址就是指针自己的地址。 

*ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针 

//的类型和所指向的类型是一样的,所以用&b 来给*ptr  

//值就是毫无问题的了。 

**ptr=34; //*ptr 的结果是ptr 所指向的东西,在这里是一个指针, 

//对这个指针再做一次*运算,结果是一个int 类型的变量。

3.3 指针表达式

一个表达式的结果如果是一个指针,那么这个表达式就叫指针表式。
下面是一些指针表达式的例子:

int a,b; 

int array[10]; 

int *pa; 

pa=&a; //&a 是一个指针表达式。 

Int **ptr=&pa; //&pa 也是一个指针表达式。 

*ptr=&b; //*ptr &b 都是指针表达式。 

pa=array; 

pa++; //这也是指针表达式。

char *arr[20]; 

char **parr=arr; //如果把arr 看作指针的话,arr 也是指针表达式 

char *str; 

str=*parr; //*parr 是指针表达式 

str=*(parr+1); //*(parr+1)是指针表达式 

str=*(parr+2); //*(parr+2)是指针表达式

由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。
好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。在例七中,&a 不是一个左值,因为它还没有占据明确的内存。*ptr 是一个左值,因为*ptr 这个指针已经占据了内存,其实*ptr 就是指针pa,既然pa 已经在内存中有了自己的位置,那么*ptr 当然也有了自己的位置。

3.4指针和数组

区别:
数组和指针不是一个东西。
数组能够存放一组数,连续的空间,数组的大小取决于元素个数。
指针是一个变量,是存放地址的。
联系:
数组名是地址(指针) 数组名把首元素的地址,交给一个指针变量后,可以通过指针来访问数组

 通过指针和数组的联系,我们可以利用指针的偏移和解引用访问数组中的元素:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}
//输出:1 2 3 4 5 6 7 8 9 10

四、野指针

很多时候我们指针的错误都来自野指针,我们必须重视野指针!!!

  1. 指针未被初始化

  2. 指针越界访问

  3. 指针指向的空间释放

 4.1 未初始化

#include 
int main()
{ 
 int *p;//局部变量指针未初始化,默认为随机值
 *p = 20;
 return 0; 
}

C语言指针(详解)_第1张图片

 

 4.2指针越界访问

C语言指针(详解)_第2张图片

 

 4.3 指针指向的空间释放掉了

C语言指针(详解)_第3张图片

 

4.4 如何避免野指针

1. 指针初始化

C语言指针(详解)_第4张图片

2. 小心指针越界

3. 指针指向空间释放即使置 NULL

4. 避免返回局部变量的地址

5. 指针使用之前检查有效性

至此,今天的内容就结束了。但是指针属于比较难理解的部分,有很多数组的内容和指针是紧密相连的。两者相互掺杂起来会比较难。 大家可以去找一些指针和数组的题目做一做,感受一下。共勉!关于这篇文章有不懂的,或者有什么问题的都可以提出来,加油!

 

你可能感兴趣的:(c语言,开发语言)