目录
1指针是什么?
指针是什么?
指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
那我们就可以这样理解
内存是电脑上的储存设备(4/8/16G),程序在运行的时候会加载到内存中,也会使用内存空间,我们把内存分为一个一个格子,把它叫做内存单元,大小为一个字节,对内存进行编号(如:0x00000000),也就是说我们如果知道编号就能找到对应的内存空间,如果说内存单元就是一个房间,编号就相当于房间地址,在c语言中叫做指针。我们在找房间的时候就可以通过地址去找。
我们定义一个整型a,对a取地址,及&a,整形占4个字节,&a取得是首字节的地址(及较小的那个地址),也就是说&a==0x0012ff40;取出第一个字节的地址,也就能知道后面的地址。
&a是一个16进制的数字,我们可以把&a存起来,存到pa里面去,pa=&a//pa就是一个指针变量,他的类型是int*,及int*pa=&a,指针变量是一种变量用来专门存放地址的。也就是说我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量。
用代码来表示,&a和pa打印出来的结果都是一样的,我们是可以通过怕找到a的,*——解引用操作,*pa就是a,通过更改*pa的值还能更改a的值
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
printf("%p\n", &a);
printf("%p\n", pa);
*pa=20;
printf("d\n",a)
return 0;
}
把内存化为一个单远格,对单元个进行编号,编号是怎么来的了?
对于32位的机器,假设有32根地址线(就是一根一根的物理电线),那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);32根地址线产生的信号就可以用来作为内存单元的编号。注意这些编号不存在,他只是告诉你这个空间能通过这个地址来访问,,每一次都是通过地址线产生电信号,产生一个2进制序列,这个2进制序列作为一个编号来寻找内存单元。
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
32根地址线能产生2^32个二进制序列,能产生2^32个编号,管理2^32个字节。2^32个字节就等于4G。
这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结:
指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在64位平台是8个字节
2指针和指针类型
char*p1 int*p2 float*p3,在这里我们会发现我们区分了指针类型,没有通用的指针类型
2.1 指针的解引用
指针类型的第一个意义:
#include<stdio.h>
int main()
{
int a = 0x11223344;
/*int* pa = &a;
*pa = 0;*/
char* pc = &a;
*pc = 0;
return 0;
}
在这里你会发现指针类型决定了指针进行解引用操作的时候一次性访问几个字节。
如果是int*的指针,解引用访问4个字节
如果是char*的指针,解引用访问1个字节
如果是float*的指针,解引用访问4个字节
…………
2.12指针+-整数
指针类型的第二个意义:
废话不多说先上代码
#include<stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
char* pc = &a;
*pc = 0;
printf("%p\n", pa);
printf("%p\n", pa+1);
printf("%p\n", pc);
printf("%p\n", pc+1);
return 0;
}
在这个地方我们可以看到pa的值等于pc,pa+1从28变成了2c,从28变到2c,值改变了4
当我是一个整形指针的时候+1,跳过了4个字节
而pc的值是从28变位29,从28变到29,值改变了1
当我是一个char*指针的时候+1,跳过了1个字节
也就是说指针类型决定指针的步长(加1跳过几个字节)
字符指针+1,跳过1个字节
整型指针+1,跳过4个字节
你想怎么访问字节,就用什么类型的指针
3. 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1 野指针成因
1指针未初始化
int main()
{
int*p;
*p=20;
return 0;
}
这个代码问题在:int*p
p是一个局部变量,没有初始化,里面是随机值
在*p里面放随机值,去找内存的相应空间把20放进去,是不行的,因为他没有指向明确空间
所以我们在访问空间的时候,要有明确的指向。
2指针的越界访问
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%d", *p)
p++;
}
return 0;
}
我们来看这个代码,再循环里面我们故意写道i<=10,在循环里面,每循环一次p就往后移一位,一直到10指向的那个位置(如图),而最后一次访问的内存空间是后面10后面的空间,也就是图上的绿色区域,而这块区域他是不确定的。你可以看到程序运行的效果。打印出来后面有一串值,这就是越界访问。
3. 指针指向的空间释放
int*test()
{
int a=10;
return &a;
int main()
{
test();
printf("%d\n",*p);
return 0;
}
a首先我们从main函数进入,这个时候进入test函数,这个时候,我们,我们为a申请了一块空间,使用完后,会把空间还给操作系统,*p就不能在找到a的地址(这涉及到函数栈帧,后期会写一篇专门的博客),那就举个例子来理解吧。 我在酒店定了个房间,晚上我给张三打电话说这个酒店好舒服,我在这里定了房子。你快来住,第二天张三过来了而我却把房退了,张三就不能住了,。当函数中这片空间被调用,值也会随着改变。就好像,张三过去这个房间没有人定,那他自己订,重新住进去了,但是如果有人订走了,那他就只能走了,而这个这时候这个房间的入住信息就变了人。
3.2 如何规避野指针
1. 指针初始化
如果指针有明确的指向,就比如我明确的把a的值给*pa。
int a=10;
int*pa=&a;
如果说我们没有明确指向,那我们就置NULL,NULL实际上就是(void*0),NULL-空指针,专门用来初始化指针。
int*p=NULL;
当我们要用的时候,只需要用if语句判断一下它等于空
if(p!=NULL)
{
}
2. 小心指针越界
3. 指针指向空间释放,及时置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
4指针运算
指针+- 整数
指针-指针
指针的关系运算
4.1 指针+-整数
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
假设[N_VALUES]=5
*vp++=0;就相当于*vp=0;vp++,在循环里面将元素变成0
4.2 指针-指针
运算前提两个指针要指向同一块空间
int main()
{
int arr[10]={0};
printf("%d\n",&arr[9]-&arr[0]);
return 0;
}
运行结果等于9 ,如果将printf("%d\n",&arr[9]-&arr[0])改成printf("%d\n",&arr[0]-&arr[9]),运算结果为-9,也就是说指针-指针的绝对值就等于两个指针之间的元素个数。
练习:写一个函数求字符串长度的函数
my_strlen(char* str)
{
char* start = str;
while (*str != '0')
{
str++;
}
return str - start;
}
int main()
{
char arr[] = "abcdef";
int len =my_strlen(arr);
return 0;
}
运算结果为6,大家可以用编译器去运算一下。
4.3指针的关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
代码简化, 这将代码修改如下
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
这个时候你会发现这两个代码一个是向后比较,一个是行前比较
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证 它可行
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较
5. 指针和数组
1指针和数组是不同的对象
指针是一种变量,存放地址的,大小4/8个字节
数组是一组相同类型的元素的集合,是可以存放多个元素的,大小是取决于元素个数和元素的类型的
2数组的组名是数组首元素的地址,地址是可以放在指针变量中,可以通过指针访问数组
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
*p=i+1;
p++;
}
for (i = 0; i < sz; i++)
{
printf("%d", *p);
p++;
}
return 0;
}
首先我们来看这样一个代码,我们把首元素地址给*p,求出长度,再用循环给数组每个元素赋值然后打印。这个时候你会发现数组赋值打印都是用指针来赋值打印
在这个地方你要知道
arr[i]==*(arr+i)
i[arr]=arr[i] 也就是说[]是一个操作符,i和arr是[]这个操作符的操作数,支持交换率
6.二级指针
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
return 0;
}
就如上1图,这是a与pa之间的关系,接下来我们来理解一下 int**ppa=&pa这句代码,
为了方便,我写成这样 int * * ppa=&pa,在这里我们来看,*是在告诉你我这ppa是一个指针,int *是指我ppa访问的pa的类型是int *,ppa就是一个二级指针,int***pppa=&ppa,pppa就是一个三级指针,但这个一般不常见。
7. 指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
数组我们已经知道整形数组,字符数组
int arr1[5];
char arr2[6];
int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int e = 40;
int* arr3[5]= { &a,&b,&c,&d,&e };
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d", *(arr[i]));
}
return 0;
}
int* arr3[5]
arr3是一个数组,有五个元素,每个元素是一个整形指针
上面举得例子只是为了让你了解认知指针数组,接下来我举一个比较实用地例子
用一维数组模拟二维数组。(假设我们想打印一个3行4的数组)
他的原理图就是这样
int main()
{
int a[] = { 1,2,3,4 };
int b[] = { 2,3,4,5 };
int c[] = { 3,4,5,6 };
int* arr[3] = { a,b,c };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("%d", arr[i][j]);
}printf("\n");
}
return 0;
}
在这个地方如何去理解arr[i][j]了?
实际上arr[i],假设i=0,那么,arr[i]就相当于于a,,我们去a中找下标为j中的元素,arr[i][j]就可以理解为a[j]
这才是未来指针数组可能会运用到的场景。