目录
一、指针是什么?
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量:
可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个
变量就是指针变量
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
return 0;
}
//a变量占用4个字节的空间,这里是将a的4个字节的
第一个字节的地址存放在p变量中,p就是一个之指针变量。
总结:
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
那这里的问题是:
一个小的单元到底是多大?(1个字节)
如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于
32
位的机器,假设有
32
根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者
0
);
那么
32
根地址线产生的地址就会是
00000000 00000000 00000000 0000000000000000 00000000 00000000 00000001...11111111 11111111 11111111 11111111
这里就有
2
的
32
次方个地址。
每个地址标识一个字节,那我们就可以给
(
2^32Byte==2^32/1024KB==2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB)
4G
的空闲进行编址。
同样的方法,那
64
位机器,如果给
64
根地址线,那能编址多大空间,自己计算。
这里我们就明白:
在
32
位的机器上,地址是
32
个
0
或者
1
组成二进制序列,那地址就得用
4
个字节的空间来存储,所以
一个指针变量的大小就应该是
4
个字节。
那如果在
64
位机器上,如果有
64
个地址线,那一个指针变量的大小是
8
个字节,才能存放一个地
址。
总结:
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在
32
位平台是
4
个字节,在
64
位平台是
8
个字节
#include<stdio.h>
int main()
{
printf("%d\n",sizeof(char*));
printf("%d\n",sizeof(short*));
printf("%d\n",sizeof(int*));
printf("%d\n",sizeof(double*));
return 0;
}
//在32位平台输出都是4,在64位平台输出都是8
二、指针和指针类型
指针类型决定了指针进行解引用操作的时候,能够访问空间的大小
int*p:*p能够访问4个字节
char*p:*p能够访问1个字节
double*p:*p能够访问8个字节
指针类型决定了:指针走一步走多远(指针的步长)
int*p:p+1 -> 4
char*p:p+1 -> 1
double*p:p+1 -> 8
三、野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
指针未初始化
#include <stdio.h>
int main()
{
int *p; //局部变量指针未初始化,默认为随机值
*p = 20; //局部的指针变量,就被初始化随机值
return 0;
}
指针越界
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
//*p = i;
//p++;
*(p++) = i;
}
return 0;
}
指针指向的空间释放
如何规避野指针
1.
指针初始化
#include<stdio.h>
int main()
{
int a = 10;
int*pa = &a; //指针初始化
int* p=NULL; //如果不知道初始化什么就设置为NULL
}
2.
小心指针越界
3.
指针指向空间释放即使置
NULL
4.
避免返回局部变量的地址
5.
指针使用之前检查有效性
#include <stdio.h>
int main()
{
int *p = NULL;
//....
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}
四、指针运算
指针+-整数
+
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
int* p = arr; //数组名就是首元素地址
//把首元素地址交给p,p的类型是int*
for(i=0;i<sz;i++)
{
printf("%d ",*p);
p = p+1; //p++;
}
return 0;
}
-
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
int* p = &arr[9]; //arr[9]是第10个元素的地址
for(i=0;i<sz;i++)
{
printf("%d ",*p);
p = p-1; //p--;
}
return 0;
}
指针-指针
#include<stdio.h>
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%d\n",&arr[9]-&arr[0]);//大地址-小地址=中间元素个数
return 0;
}
//一般指针减指针是用在两个指针指向同一块空间
#include<stdio.h>
int my_strlen(char* str)
{
char* start = str;
char* end = str;
while(*end != '\0')
{
end++;
}
return end-start;
}
int main()
{
//递归-模拟实现strlen的计数器方式
//strlen-求字符串长度
char arr[] = "bit";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
指针的关系运算
#include<stdio.h>
#define N_VALUES 5
float values[N_VALUES];
float *vp;
int main()
{
①
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
②
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
return 0;
}
//实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与
指向第一个元素之前的那个内存位置的指针进行比较。
五、指针和数组
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr); 数组名=首元素地址
printf("%p\n", &arr[0]);
return 0;
}
数组名≠首元素地址:
1.&数组名 例如:&arr时
数组名不是首元素地址,是表示整个数组,取出的是整个数组的地址
2.sizeof(数组名) 例如sizeof(arr)
数组名表示的是整个数组,sizeof计算的是整个数组的大小
#include <stdio.h>
int main()
{
int arr[] = {0};
int *p = arr; //指针存放数组首元素的地址
int i = 0;
for(i=0;i<10;i++)
{
printf("%p ====== %p\n",p+i,&arr[i]);
}
return 0;
}
#include <stdio.h>
int main()
{
int arr[] = {0};
int *p = arr; //指针存放数组首元素的地址
int i = 0;
for(i=0;i<10;i++)
{
*(p+i)=i;
}
for(i=0;i<10;i++)
{
printf("%d ",arr[i]);
printf("%d ",*(p+i));
}
return 0;
}
六、二级指针
七、指针数组
int arr1[5];
char arr2[6];
int* arr3[5]; //arr3是一个数组,有五个元素,每个元素是一个整形指针