1.指针是什么
官方解释:
在计算机科学中,指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
自我理解:
内存 | |
一个字节 | 0xFFFFFFF |
一个字节 | 0xFFFFFFE |
内存里的每一个内存单元(一个小的单元为一个字节)都给编了号,而这个编号就是地址,该地址指向了那个内存单元,形象的称为指针
存放地址的变量就称为指针变量
指针在32位平台为4个字节,在64位平台为8个字节
int main()
{
int a = 10;//a占四个字节
int*pa=&a;//拿到a的四个字节中第一个字节的地址
*pa = 20;//指针在32位平台是四个字节,在64位平台是八个字节
return 0;
}
如上图所示,如果int a=10 a为四个字节 int *pa=&a 指针指向的其实是a中第一个字节的地址
总结:指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)
2.指针和指针类型
int main()
{
int* pa;
char* pc;
float* pf;
printf("%d\n", sizeof(pa));
printf("%d\n", sizeof(pc));
printf("%d\n", sizeof(pf));
return 0;
}
在64位平台上大小都为8,
如果都为8,大小相同,那么指针类型有什么意义呢?
指针类型的意义:
int main()
{
// 16进制的组成:0 1 2 3 4 5 6 7 8 9 a b c d e f
// f需要的2进制:1111
// 8421
// 四个2进制位表示一个16进制位
int a = 0x113344;//刚刚好把a填满 44 33 22 11
int a = 0x113344;
int* pa = &a;
*pa = 0;//00 00 00 00
char* pc = &b;
*pc = 0;//00 33 22 11
return 0;
}
如上图,(int*)整型指针修改了四个字节的,(char*)字符指针只修改了一个字节的,因此指针类型是有意义的
int main()
{
int arr[10] = { 0 };
int* p = arr;
char* pc = arr;
printf("%p\n", p);//00000086518FF9D8
printf("%p\n", p+1);//00000086518FF9DC
printf("%p\n", pc);//00000086518FF9D8
printf("%p\n", pc+1);//00000086518FF9D9
return 0;
}
如上图,在64位电脑运行后打印出来地址,整型指针和字符指针相同,整型指针和整型指针加一相差了四个字节,字符指针和字符指针加一相差了一个字节
总结:
(1)指针类型决定了,指针解引用的权限有多大
(2)指针类型决定了,指针走一步能走多远(步长)
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0;i < 10;i++)
{
*(p + i)=1;
}
return 0;
}
p+i其实是下标为i的地址,将数组下标0-9变为1
若要以一个元素一个元素的操作,用int*
int main()
{
int arr[10] = { 0 };
char* p = arr;
int i = 0;
for (i = 0;i < 10;i++)
{
*(p + i) = 1;
}
return 0;
}
若要以一个字节一个字节的操作,用char*
3.野指针
野指针是指指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)
野指针的成因:
(1)指针未初始化
int main()
{
int *p;//p是一个随机的指针变量,局部变量不初始化的话,默认是随机值
*p =20;//非法访问内存
return 0;
}
上面的p就是一个野指针,p本身是一个随机值,然后给*p赋值就是非法访问内存了
(2)指针越界
int main
{
int arr[10]={0};
int *P=arr;
int i = 0;
for (i = 0;i <= 10;i++)
{
*p = i;
p++;
}
return 0;
}
上图代码有问题,当循环到了下标为10的位置,指针指向的已经超出了数组,是越界访问
(3)指针指向的空间释放
test()
{
int a = 10;
return &a;
}
int main()
{
int *p=test();
*p = 20;
return 0;
}
在函数test()调用时int a=10,返回了a的地址,因为函数结束,a的生命周期就结束了,返回回来的地址的空间已经释放,就出现问题了,空间不属于你
还有其他更深入的,目前没有学习
如何规避野指针:
(1)指针要初始化
(2)小心指针越界
(3)指针指向空间释放即使置NULL
(4)指针使用之前检查有效性
int main()
{
//当前不知道p一个初始化为什么地址的时候,直接初始化为NULL
int* p =NULL;
//明确知道初始化的值
int a = 10;
int* ptr = a;
return 0;
}
不知道地址直接初始化为空指针,知道初始化的值,直接是指针指向空间
int main()
{
int *p=NULL;
*p=10;
return 0;
}
上面这个代码是错误的,可以运行,但代码会卡死
C语言本身时不会检查数据的越界行为的
int main()
{
int *p=NULL;
if(p!=NULL)
*P=10;
return 0;
}
可以加上一个if,检查有效性
4.指针运算
指针+-整数:
#define N_VALUES 5
float values[N_VALUES];
float* vp;
for (vp = &values[0];vp < &values[N_VALUES];)//指针的关系运算
{
*vp++=0;
}
定义一个值 N_VALUES 为5, 定义一个float的数组values[5],定义一个float指针vp
for循环 vp等于values[0]的地址 也就是使指针vp指向数组values的下标0
vp小于values[5]的地址时进入循环
使*vp=0也就是vp指向的内容变为0,然后vp+1,也就是vp指向下标加一
循环到vp=&values[5]时停止循环,也就是代码把数组vlues的下标0-5的值变为0
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int* pend = arr + 9;
while (p <= pend)
{
printf("%d\n", *p);
p++;
}
return 0;
}
通过指针相加把数组内容打印出来
总结:指针+-整数,就是使指针移动它本身的类型大小
指针-指针:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[0]);
return 0;
}
如上图,运行结果为9
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
&arr[0]是如表格所示1前面的指针
&arr[9]是如表格所示10前面的指针
两个指针之间有9个元素
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
char c[5];
printf("%d\n", &arr[9] - &c[0]);
//指针和指针的相减,是两个指针指向同一块空间
return 0;
}
如果是不同数组,可以运行,但会显示指针类型不兼容
总结:指针相减就是两个指针之间元素的个数,前提是两个指针指向同一块空间
应用:
#include<string.h>
int main()
{
int len=strlen("abc");
printf("%d\n",len);
return 0;
}
上图是用了一个库函数strlen(),求字符串长度
int my_strlen(char *str)
{
//计数器
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
//strlen();//-求字符串长度
//递归
int len = my_strlen("abc");
printf("%d\n", len);
return;
}
上图是计数器的方法,一个一个找,并用一个计数器记住,一直到找到“\0”
int my_strlen(char *str)
{
assert(str!=NULL);
if(*str=='\0')
return 0;
else
return 1+my_strlen(str+1);
}
int main()
{
int len = my_strlen("abc");
printf("%d\n", len);
return;
}
上图用函数递归的方法,如果str指向的等于\0,则返回0,否则,返回1加上my_strlen(str+1)
#include<string.h>
int my_strlen(char *str)
{
//指针-指针
assert(str!=NULL);
char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
//strlen();//-求字符串长度
int len = my_strlen("abc");
printf("%d\n", len);
return;
}
上图用指针-指针的方法,函数中while循环使找到指向\0的指针,用其减去字符串最前面的指针,就得到字符串大小
指针的关系运算:
#define N_VALUES 5
float values[N_VALUES];
float* vp;
for (vp = &values[N_VALUES];vp > &values[0];)
{
*--vp=0;
}
将数组values4-0的值变为0
#define N_VALUES 5
float values[N_VALUES];
float* vp;
for (vp = &values[N_VALUES-1];vp >= &values[0];vp--)
{
*vp=0;
}
将数组values4-0的值变为0,可以完成任务,但是一般不用,因为C语言标准不确定可行
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
也就是不能在数组第一个元素之前的位置
5.指针和数组
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);//数组名是数组首元素的地址
printf("%p\n", &arr[0]);
return 0;
}
由上图代码运行可知,数组名就是数组首元素的地址
int main()
{
int arr[10] = { 0 };
int *p=arr;
int i = 0;
for (i = 0;i < 10;i++)
{
printf("%p<==>%p\n", &arr[i], p + i);
}
return 0;
}
由上图运行出来 &arr[i]和p+i都相等
int main()
{
int arr[10] = { 0 };
int *p=arr;
int i = 0;
for (i = 0;i < 10;i++)
{
*(p + i) = i;
}
for (i = 0;i < 10;i++)
{
printf("%d\n", * (p + i));
}
return 0;
}
第一个for循环将数组下标0-9的值赋值为0-9,第二个for循环把下标0-9的值打印出来
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;//数组名
//[]是一个操作符,2和arr是两个操作数
//arr[2]-->*(arr+2)-->(2+arr)-->2[arr]
printf("%d\n", 2[arr]);
printf("%d\n", arr[2]);
printf("%d\n", p[2]);
//arr[2]<==>*(arr+2)<==>*(p+2)<==>*(2+p)<==>*(2+arr)==2[arr]
return 0;
}
arr[2]中,本身[ ]是操作符,2和arr是两个操作数
arr[2]编译器会把其变为*(arr+2),arr和2是符合加法交换律的,所以arr和2是可以交换的
5.二级指针
指针变量也是变量,那指针变量的地址存放在哪里?这就是二级指针。
int main()
{
int a = 10;
int* pa = &a;//pa是指针变量,一级指针
int* *ppa=&pa;//pa也是个变量,&pa取出pa在内存中的起始地址
//ppa就是一个二级指针变量
//*ppa=pa *pa==a **ppa==a
int*** pppa = &ppa;
return 0;
}
int* pa=&a int*是类型 pa是整型指针,存放a的地址
int* *ppa= &pa ppa是指针前面要加一个* 指向pa pa的整体类型是int* 所以前面要加一个int*
6.指针数组
int main()
{
int arr[10];//整型数组-存放整型的数组
char ch[5];//字符数组-存放的是字符
//指针数组-存放指针的数组
int * parr[5];//整型指针的数组
char* pch[5];
return 0;
}
指针数组就是存放指针的数组