目录
一.内存和地址
1.初识指针
2.如何理解编址
二. 指针变量
三.指针的解引用操作符
1.指针变量的大小
四.指针变量类型的意义
五.指针的运算
1.指针加减整数
2.指针减指针
3.野指针
3.1指针未初始化
3.2指针越界访问
3.3指针指向的空间被提前释放
3.4如何规避野指针
六.void* 指针和const修饰指针
6.1void
6.2const
七.传值调用和传址调用
八.指针比较和二级指针
8.1指针比较
8.2二级指针
九.字符指针
十.指针数组
10.1指针数组模拟⼆维数组
十一.数组指针
11.1定义
11.2再次讨论数组名
11.3⼆维数组传参的本质
十二.函数指针
12.1函数指针定义
12.2函数指针变量的使用
12.3怪题
12.4回调函数
十三.函数指针数组
十四.指向函数指针数组的指
十五.qsort的实现
一.内存和地址
1.初识指针
在学习指针之前我们先要明白指针到底是什么,指针就是用来访问内存的,每一个单位内存都会占一个字节,也就是八个比特位,每一个内存单号有一个编号,就是地址,这样可以让CPU快速地找到他,我们可以把内存比做成房子,内存单号就是每一个住户,那么地址也就是你的门牌号了。所以我们可以理解为:内存单元的编号 == 地址 == 指针。在C语言中地址的新名字就可以把它看作成指针。
2.如何理解编址
CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节 很多,所以需要给内存进行编址,那么到底是如何编址的呢?
首先必须理解计算机内是有很多硬件单元,这些硬件单元他们是相互工作的,他们会进行数据传递相互的,我们可以来看一下这个图片图片中,一共有三个线地址,总线数据总线和控制总线。比如说,我要从内存中读取一个信息,这个读的指令就是通过控制总线内存向CPU传递的,然后CPU通过地址总线向找到内存所开辟的空间,然后内存再用数据总线传给CPU。不过,我们今天只关心组线,叫做地址总线。其实所谓硬件编制还跟你是几位机器有关,如果你是32位机器,那么你就有32位地址线,64位机器则64位地址总线,一根机器含有两个态0.1,所以说32根地址线就可以表示2的32次方含义,每种含义都可以表示一个地址.
二. 指针变量
所以说指针到底是什么呢?他就是地址,那么指针变量,他就是存放指针的变量,比如说整形变量int a=4;它可以用来存放一个整型,反之指针变量也是可以用来存放指针也就是地址。我们先来看一个最简单的指针变量。
这就是最简单的一个指针的创建,然后我们把它拆分开来看就可以了。注意指针变量是用来存放地址的,也就是存放变量a的
所以是指针他也是分类型的,如果有⼀个char类型的变量ch,ch的地址,要放在char*的指针变量种去。
三.指针的解引用操作符
那么这个存起来的地址到底有什么用呢?肯定我们将地址保存起来,未来是要使用的,那怎么使用呢? 在现实生活中,我们使用地址要找到⼀个房间,在房间里可以拿去或者存放物品。 C语言中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这里必须学习⼀个操作符叫解引用操作符(*)。
*pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0.其实可以这样看*可以与&操作抵消。因为pa=&a所以*pa=*&a=a'。
1.指针变量的大小
所以说指针变量的大小到底是什么呢?我们可以这样来推理,指针变量是用来存放地址的那么地址是怎么产生的呢?地址肯定是由地址线产生的,以32位机器为模板32位机器就是32根地址线,就是32个比特位那么32个比特位置四个字节,所以说指针变量大小就是地址的大小那么地址大小就和机器的这个位数是有关系的,你是64位,那你就是八个字节,注意类型是无关的。
四.指针变量类型的意义
现在有一个令人疑惑的问题,就是这个指针变量它的存在到底有什么意义?指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是⼀样的,为什么还要有各 种各样的指针类型呢?我一个指针变量让他有这么多类型是干什么的?或者是他到底有什么用?
调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。所以说,指针类型决定了指针进行节,引用操作的时候能够访问空间的大小。
五.指针的运算
1.指针加减整数
用数组来举例子,因为数组在内存中是连续存放的,只到第一个元素的地址就能找到后面的所有,所以说数组的打印也可以用指针加减整数来去实现。
注意:这里有一个重要的思想,*(p+i)其实就等于arr[i],又因为p=arr所以arr[i]=p[i] , *(p+i)=p[i]
只是一个很重要的思想,很多题目都会用到。
2.指针减指针
这里就是运用了指针减指针,注意不同类型的指针变量是不可以相减的,其实相同类型的指针相减可以看作这两个数组下标之间包含了几个元素,一般都是用大减小。
3.野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。野指针是非常危险的,就比如说野狗,如果没有人管的话,你怕不怕。所以说常见的野指针类型,我们是必须要知道的.
3.1指针未初始化
局部变量指针未初始化,默认为随机值,不知道初始化什么的时候,我们可以把它定做成一个空指针。
3.2指针越界访问
当指针指向的范围超出数组arr的范围时,p就是野指针。
3.3指针指向的空间被提前释放
因为出了test函数结束的时候,a的空间就会销毁,所以说访问的空间不再是当前程序的。所以地址找不到a,就会形成野指针。
3.4如何规避野指针
1.如果不知道指针应该指向哪⾥,可以给指针赋值NULL.
2.⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
3.避免返回局部变量的地址
六.void* 指针和const修饰指针
6.1void
在指针类型中有⼀种特殊的类型是 void* 类型的,泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,void* 类型的指针不能直接进行指针的+-整数和解引用的运算。
6.2const
代表他有常属性是无法修改的,他修饰指针变量时,分为在指针变量的左侧和在指针变量的右侧这两种情况。
const在修饰指针变量的时候放在*右边,const限制的是指针变量本身,不能再指向别的变量,但是可以通过指针变量修改指向的内容。
放在右边的时候,限制的是指针指向的内容,不能通过指针来修改指向的内容可以修改指针变量本身的,只也就是修改指针变量的指向。
七.传值调用和传址调用
存在传值调用和传址调用形式,也就是在有些问题中非用指针不可,所以我们才会来学习指针。比如说,写一个函数,交换两个整型变量的值。
void Swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
当Swap1函数调用结束后回到main函数,a和b的没法交换。Swap1函数在使用的时候,是把变量本身直接传递给了函数,这种调用函数的方式我们之前在函数的时候就知道了,这种就叫传值调用。
void Swap2(int* px, int* py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
我们可以看到实现成Swap2的方式,顺利完成了任务,这里调用Swap2函数的时候是将变量的地址传递给了函数,这种函数调用方式叫:传址调用。
所以未来函数中只是需要主调函数中的变量值来实现计,就需要传值调用
如果函数内部要修改 主调函数中的变量的值,就需要传址调用
八.指针比较和二级指针
8.1指针比较
注意:C语言语法规定,只允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
8.2二级指针
指针变量也是变量是变量就地址,那么指针变量的地址存放在哪儿呢?其实二级指针就是用来存放指针变量的地址的。所以说,三级指针四级指针也很容易解释了,三级指针就是存放二级指针地址的四级指针就是存放三级指针地址的。
总之还可以这么理解
九.字符指针
在指针的类型中,我们知道一种指针类型叫做字符指针char*,我们知道他的使用方式就可以了。
这里是把一个字符串全部放进去了,还是只放了首字母呢?注意,他是一个常量字符串,答案是他放的是首字母把常量字符串的首字母放到了变量中。
十.指针数组
指针数组,首先他是一个数组,是用来存指针的,后面还会出现数组指针,函数指针,函数指针数组,那这些东西该如何去判断呢,我们可以这样去看找主语,比如说好大儿好大儿,他是儿子,我们只要看后面的那个主语是谁就可以判断他是谁了。
10.1指针数组模拟⼆维数组
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数 组中的元素。注意这里的parr[i][j]可以写成*(parr[i]+j)==*(*(pi)+j)。由直接的思想可以得出。
十一.数组指针
11.1定义
整形指针是用来存放整形地址的指针,字符指针是用来存放字符地址的指针,所以说数组指针就是用来存放数组地址的指针。
int (p指向的数组的元素类型)(*p1)(变量名)[10] (指向数组的元素个数) =&arr;
11.2再次讨论数组名
数组名通常表示的都是首元素的地址,但是有两个意外,1.sizeof(数组名)这里数组名表示整个数组,计算的是整个数组大小。2.&数组名,这里的数组名表示的依然是整个数字,所以取地址取出的是整个数字的地址。注意:数组传参的本质是首元素的地址,所以形参访问的数组和实参的数组是同一个数组。
11.3⼆维数组传参的本质
有了数组指针的理解,我们就能够讲⼀下⼆维数组传参的本质了。二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址。前面也讲过怎么实现二维数组这里就不展示代码了,主要还是讲解一个传递参数方式,我们用函数来打印二位数组的话肯定就需要传递参数。有两种传参的形式
1.void test(int (*p)[5], int r, int c)
2.void test(int a[3][5], int r, int c)
为什么这样可以呢?因为二维数组的首元素是他第一行,第一行的地址就是一个意位数组的地址。所以说我需要他的一个首元素,那么首元素就很好说了,我要么就直接打印他的数组名,要么就用数组指针,所以就会有这两种方法。所以说⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。
十二.函数指针
12.1函数指针定义
就是指向函数的一个指针,对于函数来说,取地址函数名和函数名都是函数的地址。他的定义方法是(返回类型) (*p) (参数)。
定义: int (*pf3) (int x, int y)
12.2函数指针变量的使用
可以直接通过指针来进入函数,为什么两种写法都可以,因为前面讲过了对于函数来说,取地址函数名和函数名都是函数的地址。
12.3怪题
1 (* (void (*) () ) 0) () ;是什么意思?
题目来自于《c语言陷阱与缺陷》这句代码表达了什么意思直接看的话,是有点复杂,所以我们把他拆分下来,先看里面的void(*) ()这是一个函数指针类型,拿出去,还剩一个(*0)这是一个指针变量,表示0处的地址,所以
以上代码是一次函数的调用调用的是0作为地址处的函数,把0强制类型转化为一个没有参数返回类型,是void的函数地址,在调用0地址处的这个函数。
2 void (*signal (int , void (*) (int) )) (int)
比较第一题要更加复杂了,我们还是可以拆分来看,先看里面的signal(int,void(*(int)这是一个函数指针类型,拿去后,还剩一个void(*)(int)这也是一个函数指针类型,
以上代码是一次函数的声明,声明的signal函数的第一个数参数的类型是int,第二个参数的类型是函数指针,该指针指向的函数参数是int,返回类型是void,signal函数的返回类型,也是一个函数指针的函数,该指针指向的函数参数是int,返回类型是void。
12.4回调函数
就是一个通过函数指针调用的函数,如果你把函数指针(地址)作为参数传给了另一个函数,当这个指针被用来调用其所指向的函数的时候就叫做回调函数。
十三.函数指针数组
数组是一个存放相同类型数据的存储空间,那么如果我要把函数的地址存到一个数组中,这些数字该如何定义呢?这里就要用到函数指针数组了,首先他肯定是一个数组,我们用函数指针的结构再去套上数组就行了。
int (*arr[4]) (int,int) ={add,sub,mul,div};
运用了就是可以用它来定义一个计算机把他的计算机方法的函数全部定义在这个数组里
十四.指向函数指针数组的指针
因为我的能力也有限,在此只能补充一个定义,首先他肯定是指针,然后他是指向函数指针数组的
int (*(*arr[4]) )(int,int)=&arr;
十五.qsort的实现
以前写过一篇就直接放在这里了用c语言自己实现qsort和冒泡排序-CSDN博客
完