内存
计算机把内存分为一个个单元,每一个单元大小为一个字节,每个单元都有单独的地址编号,就像酒店里的地址一样,可以方便我们通过 ,地址快速访问内存中的元素。
一块内存的地址是连续的不间断的,地址由低到高。
指针
C语言在创建变量就是在内存中申请空间,不同类型的变量就申请不同大小的内存空间。
(比如int 4个字节 short 2个字节 long 4个字节float 4个字节等等)
如何得到地址
为了得到变量的地址,就会使用&取地址操作符,& 就是取出变量的较小字节的地址。
如图 int a=1,a占四个字节 &a取出的地址就是0X00000000
指针的类型
int*pa=&a;取出a的地址放到变量pa中,pa的类型是int*。‘ * ’说明pa是指针变量,int说明pa地址所指向的内存存放的是int类型的数据。 比较特殊的是void类型的指针,也叫泛型指针,他可以接受任何类型的地址,但是不能直接进行解引用操作和加减运算。
例子
char*pc=&ch; int(*parr)[4]=&arr; void(*fun)(int,int)=function;上述指针的类型分别是char*
int(*)[4], void(*)(fun,fun)。
指针的解引用
指针类型的解引用能决定访问多少个字节。可通过强制类型转换来改变变量指针的类型,解应用访问内存字节的大小。
int a=0; int*p=&a; char *pa=(char*)&a; 解应用a可以访问4个字节,解应用pa可以访问1个字节
const对指针的影响
const如果在*的左边那么, 该指针不能解引用改变指向变量的值,但是可以改变指针本身所存放的地址。
const如果在*的右边那么,那么不可以改变指针所存放的地址,但是可以解引用改变所指向内存存放的值。
指针的大小
指针变量的大小在不同的平台是不一样的,32位机器假设有32根地址总线,8个比特位为一个字节,那么指针大小是4个字节,同理在64位平台是8个字节。
指针的加减运算
不同类型的指针加减运算跳过的字节大小是不一样的,int*类型的加1跳过四个字节,char*是1个,int(*)[4]类型的整形数组 ,数组取地址加1跳过16个字节。
两个指针都指向同一个数组里面(同一块连续的空间),两个指针相减是的绝对值,是 求出两个指针之间的元素的个数。
野指针
野指针的原因
1 没有初始化 2指针的越界访问 比如数组用指针越界访问 3 使用过的指针 不再使用时候没有释放
如何规避野指针
1 指针初始化,如果不知道指针指向哪里的时候附上null
2避免指针越界访问
3对于不使用的指针 要及时释放地址,使用free();
函数传值和传址
在使用函数的时候, 函数的形参是 在内存临时开辟新的空间,在函数调用结束的时候空间就会销毁,由于形参和实参是两块不相同的空间,改变函数的形参,并不会影响实参数。
为了能改遍实参,所以我们可以通过传地址的方式,使用指针形参来接收,在函数内部解引用来改变实参的值。
数组指针 (一维数组 二维数组)
int arr[10]={1,2,3,4,5,6,7,8,9,0}
数组名就是首元素的地址,我们通过打印数组名 arr的地址 和数组第一个元素的地址 &arr[0]发现是同一个地址。
通常数组名就是代表首元素地址,但是有两个例外比如 sizeof(arr) arr就是整个数组,这时候是计算出整个数组的大小。另外一个就是取地址数组名 &arr 这里是取出这个数组的地址,这里和arr数组名打印出来的地址是一样的,但是对这两个地址进行加1减1的操作时,一个是跳过40个字节,就是一个数组,另外一个是跳过4个字节,就是一个元素,原因是这两个指针类型是不同的。
int*pa=arr; int(*pa2)[10]=&arr;
同时把这个两个地址用两个指针变量来接收,就可以发现一个指针的类型是 int*,另外一个类型是int(*)[10],所以进行加1减1的操作时,跳过的元素个素也不一样。
所以我们对于数组内元素的访问,可以通过地址来解应用来进行。访问第几个元素就可以通过给首元素地址加几解引用来进行。注意数组的元素下标是从0开始的。
*(arr+i)=arr[ i ] ;
这两种方式是等价的
一位维数组传参的本质就是就是传地址,数组名是数组⾸元素的地址;那么在数组传参
的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组⾸元素的地址。
所以函数形参上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写sizeof(arr)
计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函
数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。所以一维
数组传参 ,形参数本质上可以写成数组的形式和地址的形式。
二级指针
二级指针本质上也是指针,因为一级指针也是个变量,存放一级指针变量的地址的变量就是二级指针。
int a=10; int*pa=&a; int** paa=&pa;
右边的*代表paa是个指针变量,int*是paa变量存放的地址,指向的内存中存放的数据是int*类型
指针数组
数组就是相同类型元素的集合,指针数组就是一个数组,他和整形数组 ,字符数组,浮点型数组都是数组,只是数组内部存放元素的类型是不同的。指针数组存放元素都是指针类型的。
int*pa[3]; pa因为优先级的关系首先与[]相结合 说明pa是数组,存放元素的类型是int*。
int(*pa)[3];数组指针。因为有括号与*相结合所以是个指针。
指针数组和数组指针平时写的时候要注意区分
所以我们可以通过指针数组来模拟出一个二位数组,
也可以直接写成 arrs[i][j],两种方式是等价的
arrs[i]是访问arrs数组的元素,arrs[i]找到的数组元素指向了整型⼀维数组,arrs[i][j]就是整型⼀维数组中的元素。
要注意二位数组在内存上的空间是连续的,所以指针数组模拟出的二维数组是不一样的。
二维数组传参
我的理解 二维数组就是一堆一维数组的集合,里面的每个元素都是一维数组,而且一维数组的大小是相同的,一维数组所存放元素的类型也是相同的,在内存上的空间是连续的。
int parr[3][5]={{1,2,3,4,5,},{2,3,4,5,6,},{3,4,5,6,7}};
&parr是取出整个二维数组的地址,和一维数组是一样的,地址进行加1的操作是跳过整个二维数组。例如parr数组是跳过60个字节。
地址名是首元素地址,parr就是这个二维数的对个元素的地址,就是第一个一位数组的地址,所以用指针变量接收要写成 int(*parr1)[5]=parr;对于parr加i就是跳过一个元素,也就是跳过一个一维数组,在解引用得到一维数组的数组名。一维数组名就是一维数组首元素的地址,通过加j就是访问一维数组中的元素。
二位数组的传参和一位数组一样 ,就是就是传数组名,也就是二维数组的首原素地址,行参就是写成一位数组指针的形式 或者二维数组的形式。
例子:void*function(int(*)p[5]);void*function(int p[3][5]);
行可以省去 列不能省去
函数指针
函数也是有地址的,可以通过函数指针来存放函数的地址。和数组一样 函数名也是代表函数的地址。因为取地址函数名和函数名地址打印出来都是一样的。
函数指针的 写法是。 返回类型 (*指针变量名)(形参的类型)
例子,int(*func)(int ,int);这个函数指针的类型是 int(*)(int ,int),指向的是返回值是int类型,形参是两个int类型的函数。
通过函数指针写函数指针数组,也可以通过函数指针实现回调函数。
例子,通过回调函数来实现简单的计算器,因为这几个函数的类型都相同
通过数组下标得到函数名,函数名就是函数的地址,地址解引用得到函数。但是函数名可以不用解引用直接使用。
*(pfarr+1)(a,b)==pfarr[1](a,b)==add(a,b)