C语言的指针详解

一、指针的定义及使用

1.指针是什么?

指针是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量。在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。

2.指针的类型及指向类型

从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。而再将*去掉,就是这个指针的指向的类型。 

#include<stdio.h>
int main()
{
    int *ptr;//指针的类型是 int*,指针所指向的类型是 int
    char *ptr;//指针的类型是 char*,指针所指向的的类型是 char
    int **ptr;//指针的类型是 int**,指针所指向的的类型是 int*
    int (*ptr)[3];//指针的类型是 int(*)[3],指针所指向的的类型是 int()[3]
    int *(*ptr)[4];//指针的类型是 int*(*)[4],指针所指向的的类型是 int*()[4]
}

 3.关于指针的的算术运算

指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以指针所指向的类型的字节数为单位。

#include <stdio.h>
int main()
{
	int i = 10;
	char* a1 = (char*)&i;
	int* a2 = &i;
	printf("%p\n", &i);
	printf("%p\n", a1);
	printf("%p\n", a1 + 1);
	printf("%p\n", a2);
	printf("%p\n", a2 + 1);
	return 0;
}

看来答案显而易见,指针变量与整数的运算与指针类型有关,char*类型的指针指向的是char,故跳过一个字节,int*类型的指针指向的是int,故跳过四个字节。

那么我们还有有一个疑问,指针能否可以和指针进行加减?

请看下面的代码。

#include<stdio.h>
int main()
{
	int i = 0;
	int* p = &i;
	int* o = p + 1;
	printf("%d", p - o);
	return 0;
}

 

 指针与指针的相减,是在指针类型相同的前提下成立的。它的结果为什么是-1?这是因为p+1之后,内存地址上向高地址的方向移动了四个字节,而结合指针类型是int*,然后移动的字节数要除以4(int类型占四个字节),所以结果就为-1。

两个指针不能进行加法运算,这是非法操作,因为进行加法后,得到的结果指向一个不知所向的地方,这会有安全隐患。

4.取地址操作符&和解引用操作符*

这里&是取地址操作符,*是解引用操作符。

变量会存放在内存空间,这个时候为了得到变量的地址,就需要&取地址操作符。&a 的运算结果是一个指针,指针的类型是 a 的类型加个 *,指针所指向的类型是 a 的类型,指针所指向的地址,就是 a 的地址。

&a取出的是a所占4个字节中地址较⼩的字节的地址。(int类型占四个字节)

在现实⽣活中,我们使⽤地址要找到⼀个房间,在房间⾥可以拿去或者存放物品。
C语⾔中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象。 *p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是 p 指向的类型,它所占用的地址是 p 所指向的地址。指针的类型决定了,对指针解引用的时候有多大的权限(⼀次能操作几个字节)。

#include<stdio.h>
int main()
{
    int a = 100;
    int* p = &a;
    *p=0;
    printf("%d",a);
    return 0;
}

 

二、指针与其他类型的关系

1.数组与指针的关系

数组名的意义:
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的指针。
3. 除此之外所有的数组名都表示首元素的指针。


数组指针存放的是数组的地址,能够指向数组的指针变量。
 

int (*p)[5];

 p先和*结合,说明p是⼀个指针变量,然后指向的是⼀个大小为5个整型的数组。所以p是⼀个指针,指向⼀个数组,叫数组指针。

2.指针与函数的关系

函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
 

int fun(char *,int);
int (*p)(char *,int);
p=fun;
int a=(*p)("abcdef",6); //通过函数指针调用函数。

除了上文的数组名,函数名也是一个指针。不过它们也有些差异,数组名前加&,是取整个数组的指针,而函数名前加&,还是取的是函数指针。 

3.指针与结构体类型的关系

struct MyStruct
{
    int a;
    int b;
    int c;
};
struct student s={10,20,30};
//结构对象s,并把s的成员初始化为10,20 和30。
struct student *ptr=&s;
//一个指向结构对象s的指针。它的类型是student*,它指向的类型是student。
int *pstr=(int*)&s;
//一个指向结构对象s的指针。但是pstr和它被指向的类型ptr 是不同的。

那么如何访问结构体的成员,结构体成员的直接访问是通过点操作符(.)访问的。而间-接访问是用操作符(->) 访问的。(结构体变量.成员名、结构体指针->成员名)

s.a//直接访问
ptr->a//间接访问
*pstr; //访问了s的成员a。
*(pstr+1); //访问了s的成员b。
*(pstr+2) //访问了s的成员c。

上面还有一个指针pstr,用这样的方式去访问是错误的。为什么是错误的?因为存放结构对象的各个成员的时候,需要遵循对齐规则,它不同于数组在内存中的连续存放,可能会存在空隙,导致非法访问。
 

三、指针的正确使用

1.野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。

产生野指针的原因有哪些?

指针变量未初始化:任何指针变量刚被创建时不会自动成为NULL指针,它的值是随机的。

指针越界访问:当指针指向的范围超出数组的范围时,它很可能会指向随机值。

指针操作超越变量作用域:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

2.如何规避野指针

如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL。NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。

用assert()断言来验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运⾏,否则就会终⽌运⾏,并且会给出报错。

绝不返回局部变量和局部数组的地址,任何使用与内存操作相关的函数必须指定长度信息。

四、多级指针

指针变量也是变量,它同样有地址。所以存放指针变量的指针就是二级指针。

定义指针时,一级指针有一个*,二级指针加两个*,三级指针加三个*,以此类推。

int a =100;
int *p1 = &a;//一级指针
int **p2 = &p1;//二级指针

 想要获取指针指向的数据时,一级指针加一个*,二级指针加两个*,三级指针加三个*,以此类推

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值