指针的知识梳理

一、指针的导论

如果我们要了解指针,一定要了解电脑中内存的划分。内存划分为一个个内存单元,每个内存单元为一个字节,每个内存单元一个编号。(编号=地址=指针)
地址在内存中的样子
内存编号可以快速的让我们找到内存单元。

二、指针

(一)、取地址操作符(&)

当我们要使用地址时一定要是用取地址操作符。
区别:&a,他是单目操作符,而a&b他是双目操作符 与。

(二)、指针的类型

#include<stdio.h>
int main()
{
          int a=10;
          int * p=&a;
           return 0;
}

其中,int * 是指针类型,也就是p的类型。
int表示指针指向的对象类型—> a
*(星号)说明p是指针变量。

存放在指针变量中的值,都会被当成地址来使用。指针变量就是用来存放地址的。

(三)、解引用操作符/间接访问操作符

解引用操作符是一个*(星号),

 int main()
 {
             int a=0;
         int *p=&a;
             *p=10;  
             return 0;
 }

p的意思就是通过p存放的地址,找到地址所指向的空间,也就是p就是a。

(四)、指针的大小

指针的大小到底是多少呢?可以思考一下以下代码

int main()
{
//在X86环境下
              
int a = 10;
char b = 'a';
int* ch = &b;
int* p = &a;
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(ch));

return 0;
}

在这里插入图片描述
你会发现不管是char类型还是int类型,都是4,同理在X64环境下都是8

结论:指针变量的大小与类型无关,与环境有关。

(五)、指针类型的意义

指针类型决定了指针在解引用操作的权限,也就是一次解引用访问几个字节。
指针类型决定了指针进行+1/-1操作时,一次跳过几个字节(指针的步长)

int main()
{
    int arr[] = {1,2,3,4,5};
    char* p = arr;
    printf("%d\n", *p);
    printf("%d\n", *(p+1));

    return 0;
}

在这里插入图片描述
在这里插入图片描述

(六)、const修饰指针

const修饰变量,使得变量不可更改。const有两种使用方法,一种是放在星号左边,一个是放在星号右边。
(1).const放在星号左边,限制指针指向内容,但是能修改变量本身
(2).const放在星号右边,限制指针本身不能修改。

举一个例子,比如我有十块钱n=10,但是我朋友A有100块a=100,这个时候我想吃一碗凉面十块钱,但是这十块钱有一部分早饭的钱,我的钱不够,这个时候我就用const 锁住我的钱包,即:const int p=&n;这个时候我就不能把这十块钱花出去了,即:不能执行p=0这个操作;那么这个时候朋友A说你可以借我的钱,然后有空再还我,那么我就可以从A朋友的钱包中拿出,即:p=&a;在假设,我觉得借钱还钱太麻烦了,那么我就直接用我的十块钱买了,不从朋友A的钱包中借了,即: int * const p=10;*p=0;但是p=&a是不行的,我就直接花了我的钱,买了碗凉面。希望通过这个例子可以让你区分const在左右的用处。

(七)、指针的运算

指针的运算有两种

1.指针 + 或者- 整数

指针加减整数指指针跳过某类型大小的位置。

#include<stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5 };
	int* ptr = arr;
	printf("%d\n", *(ptr + 1));
	printf("%d\n", *(ptr + 3));

	return 0;
}

在这里插入图片描述
解析图

2.指针 — 指针

指针减去指针得到的绝对值是指针域指针之间的元素个数。(前提:两个指针必须是同一块空间)

int main()
{
	int arr1[] = { 1,2,3,4 };
	char arr2[] = "abcd";
	//printf("%d ", (arr1[0] - arr2[0]));//大错特错,
	//在内存中地址都是随机存储,你怎么知道这两块地址是否挨着?
	//所以前提两个指针必须在同一块空间!!
	printf("%d ", arr1[0] - arr1[3]);
	return 0;
}

在这里插入图片描述

3.指针的运算关系(地址比较大小)

int main()
{
	//指针大小的比较
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;//如果写成 int*p=&arr[0];也可以
	while (p < arr + sz)
	{
		printf("%d " ,* p);
		p++;
	}
	return 0;
}

在这里插入图片描述

三、野指针

什么事野指针?野指针是指针指向的位置不可知的(随机的,不正确的,没有明确限制的)。那么什么情况下可能会出现野指针呢?

1.指针未初始化

int main()
{
	int a = 0;
	int* p;//局部未初始化,得到随机值
	return 0;
}

野指针未初始化,就像是你走在大街上,随便一个房子你就要进去住,还不给钱。

2.指针的越界访问

在这里插入图片描述
像上面情况就是越界访问,最后输出会出现6个0,但是VS已经发生报错行为。

3.指针指向的空间已经释放

在这里插入图片描述
像上面就是我需要放回数组里2的地址,但是当我的函数已经返回了的时候,空间释放。

4.避免野指针

既然这些情况会出现野指针,那么我们如何避免野指针的出现呢?

①初始化

可以用NULL(空指针)来初始化,即: int *ptr=NULL;(注意使用NULL一定要包括头文件: #intclude<stdio.h>),等到使用时在赋值或者先进行有效性判断。
注:NULL不能直接访问,有NULL大概率是指针。

当我们使用完指针不用的时候,也可以先置空,然后需要时在从新取地址。

②防止越界,谨防已释放空间

在书写时多认真注意界限,和空间是否合理

四、assert断言与传值调用和传址调用

(一)、assert

断言的主要作用是判断指针,你也许觉得if判断也可以,但是使用if效率低。
使用assert断言时要包括头文件:#include<assert.h>
在这里插入图片描述
断言为什么高效,就是因为他会给我们爆出错误的位置,并且提示在哪。
如果assert()括号中语句为真什么都不发生;如果语句为假,报错。
断言也是可以关闭的,用 #define NDEBUG 即可。
注意:assert在release版本下是不报错的。

(二)、传值调用和传址调用

传值调用和传址调用的含义

1.传值调用:实参传给函数的形参,只是一份临时拷贝,形参会开辟新的空间,所以形参的修改不能影响实参。
2.传址调用:当需要通过函数进行交换,修改,需要函数与主函数建立联系是,就可以使用传址调用。

扩展:鲁棒性(健壮性):
鲁棒性(robustness)就是系统的健壮性。它是指一个程序中对可能导致程序崩溃的各种情况都充分考虑到,并且作相应的处理,在程序遇到异常情况时还能正常工作,而不至于死机。比如说,计算机软件在输入错误、磁盘故障、网络过载或有意攻击情况下,能否不死机、不崩溃,就是该软件的鲁棒性。

五、数组名的理解

(一)、数组名的理解

我们一般说数组名表示首元素的地址,但是有两个例外,第一个是,sizeof(数组名),计算整个数组大小,单位字节;第二个是,&arr,表示取整个数组的地址,但是如果打印的话,显示首元素的地址。

区别:&arr和arr
&arr和arr打印出来都是首元素的地址,但是他们还是有本质上区别。
在这里插入图片描述

(二)、使用指针访问数组

在这里插入图片描述

六、二级指针与指针数组

(一)、二级指针

int main()
{
	int a = 10;
	int* p = &a;//我们将p叫做指针
	//那么如果我们要存放p指针的地址又该怎么写呢?
	int* * pp = &p;//我们就把pp叫做二级指针
	return 0;
}

在这里插入图片描述
大致可以这么理解,p中存放的是a的地址,而pp里面存放了p的地址,当我第一次*pp;时,拿到了p的内容也就是a的地址,当我对第二次**pp;时,我就拿到了a的内容。
三级指针与上面相差不大,也不常用这里我们不再过多提及。

(二)、指针数组

指针数组顾名思义就是存放指针的数组。所以这个数组里面存放的都是指针。例如:int * p[10]={&a,&b};
l梁梦楠制作,如果取图求赞,谢谢QWQ

如图例子:


int main()
{
	int arr1[] = {1,2,3};
	int arr2[] = { 2,3,4, };
	int * parr[] = { arr1, arr2 };//既然是指针数组,要注意数组元素的类型!!!别粗心!
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d ", (*(*(parr+i)+j)));
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述

七、字符指针、数组指针和二维数组传参本质

(一)、字符指针

字符指针顾名思义是存放字符的指针。例如:char * p=“abcdef”;(注意:并不是将abcdef\0放入p字符指针中,而是将第一个字符a的地址放入p中。并且abcdef属于常量字符串并不可以修改!!

(二)、数组指针与二维数组传参的本质

数组指针是存放数组的地址。例如:int (*p) [10]=&arr;
在这里插入图片描述

void test(int (*arr)[3],int r,int w)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < w; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[2][3] = { {1,2,3}, {2,3,4,} };
	test(arr,2,3);

	return 0;
}

在这里插入图片描述
二维数组每一行是一个一维数组,这个一维数组可以看做二维数组一个元素,所以二维数组名就表示第一行的地址。在形参部分我们可以写成指向第一行的数组指针。

八、函数指针

函数指针是指向函数的指针,是存放函数地址的指针。
书写: int(*pf)(int ,int)=&Add;
在这里插入图片描述

int Add(int x,int y )
{
	return (x + y);
}
int main()
{
	int ret = Add(3,5);
	printf("%d\n", ret);
	printf("====================\n");
	int (*pf)(int, int) = Add;
	printf("%d\n", pf(3, 5));
	printf("%d\n", (*pf)(3, 5));

	return 0;
}

在这里插入图片描述
以上就是指针的总体大类,还有一些小细节需要你我共同思考,希望能给大家带来一定的解惑,谢谢观看~!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值