指针初阶

指针是什么

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思hi是通过它能找到以它为内存的内存单元。

1
2
3
4
5
6
7
int main()
{
int a=10;//a占4个字节
int *pa = &a;//拿到的是4个字节中的第一个字节
*pa = 20;
return 0;
}

存放地址的变量被叫做指针变量,地址就是指针。

指针和指针类型

1
2
3
4
5
6
7
8
9
10
int main()
{
int* pa;
char* pc;
float* pf;

printf("%d\n",sizeof(pa));
printf("%d\n",sizeof(pc));
printf("%d\n",sizeof(pf));
}

结果是一样的,虽然所占空间是一致的,但是没有万用指针类型,因此指针类型还是有实际意义的。

指针类型的意义

1
2
3
4
5
6
7
8
9
10
int main()
{
//一个十六进制位表示4个二进制位//f=1111=15
int a = 0x11223344;//两两为一个字节,正好4个人字节
int*pa =&a;
*pa=0;//改变了4个字节
char*pc =&a;
*pc=0;//只改变了1个字节
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
int main()
{
int arr[10]={0};
int *p=arr;
char*pc=arr;//无论什么类型的指针都能存放地址、
printf("%p\n",p);
printf("%p\n",p+1);
printf("%p\n",pc);
printf("%p\n",pc+1);
return 0;
}

结果发现,字符指针+1相当于跳过一个字符,因此地址增加一个字节,而整形指针+1相当于跳过一个整型,因此地址增加4个字节

总结

1.指针类型决定了:指针解引用有的权限有多大

2.指针类型决定了:指针走一步,能走多远(步长)

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
int arr[10]={0};
int *p=arr;
int i=0;
for(i=0;i<10;i++)
{
//p+i 其实是下标为i的地址
*(p+i)=1;
}
return 0;
}

野指针

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

野指针的成因

指针未初始化

1
2
3
4
5
6
int main()
{
int *p;//p是一个局部的指针变量,局部变量不初始化的话,默认是随机值
*p =20;//非法访问内存
return 0;
}

指针越界访问

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
int a[10]={0};
int *p=a;
int i=0;
for(i=0;i<=10;i++)
{
*p=i;//第十次的指针p变成了野指针
p++;
}
return 0;
}

指针指向的空间释放

1
2
3
4
5
6
7
8
9
10
11
int *test()
{
int a=10;
return &a
}
int main()
{
int *p=test();
8p=20;
return 0;
}

如何规避野指针

1.指针初始化

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
//当不知道p应该初始化为,什么地址的时候,直接初始化为NULL
int *p = NULL;
//当明确知道初始化的值时
int a=10;
int *ptr =&a;
return 0;
}

2.避免指针越界(C语言本身是不会检查数数据的越界行为的)

3.指针指向的空间释放之后及时指向为空指针(NULL)

4.使用指针之前,要检查其有效性

1
2
3
4
5
int *p =NULL;
if(p!=NUll)
{
*p=10;
}

一个指针变量当你不知道会指向什么地方的时候,初始化为空指针,当它指向的空间被释放掉之后,也指向到空指针,当它指向一个有效空间时,就给它一个有效的地址。因此指针就要么是一个空指针,要么就是一个有效的指针。

指针运算

  • 指针+-整数
  • 指针-指针
  • 指针的关系运算

指针+-整数

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#define N_VALUES 5
int main()
{
float values [N_VALUES];
float*p;
for(vp=&values[0];vp<&values[N_VALUES];)//指针的关系运算
{
*vp++;//指针+整数
}//地址由低到高变化
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
int *p =a;
int *pend =a+9;
while(p<=pend)
{
printf("%d\n",*p);
p++;
}
return 0;
}

指针-指针

1
2
3
4
5
Int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%d\n",arr[9]-arr[10])
}

答案是9。

因此我们发现指针-指针得到的是两个指针中间相差的元素个数

但是指针相减有个前提是两个指针指向同一块空间

strlen()求字符串

1
2
3
4
5
6
7
#include <stdio.h>
int main()
{
int len = strlen("abc");
printf("%d",len);
return 0;
}

手搓strlen()的三种形式

指针-指针

1
2
3
4
5
6
7
8
9
int my_strlen(char*str)
{
int *start = str;
while(str!=0)
{
str++;
}
return start-str;
}

建立临时变量

1
2
3
4
5
6
7
8
9
10
int my_strlrn(char*str)
{
int cont=0;
while(*str!-'\0')
{
cont++;
str++;
}
return cont;
}

函数递归

1
2
3
4
5
6
7
8
9
10
11
int my_strlen(char* str)
{
if(*str != '\0')
{
return 1+(my_strlen{str+1})
}
else
{
return 0;
}
}

指针的关系运算

1
2
3
4
5
#define N_VALUES 5
for(vp = &values[N_VALUES];vp> &values[0];)
{
*--vp=0;
}
1
2
3
4
for(vp=&values[N_VALUES-1];vp>&values;vp--)
{
*vp=0;
}

这两种关系运算,虽然都可以达到目的,但还是要尽可能的避免使用后一种写法,因为标准并不保证其的可行性。

标准规定

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

指针和数组

数组名是什么?

1
2
3
4
5
int main()
{
int arr[10]={0}
printf("%p",arr);//数组名是数组首元素地址
}
1
2
3
4
5
6
7
int main()
{
int arr[10]={0};
int* p = arr;//数组名
//arr[2] <==> *(p+2) <==> *(2+p) <==> *(2+arr) <==>*(arr+2) == 2[arr]
return 0;
}

能写成2[arr]是因为[]是一个操作符,而2和arr是两个操作数,而当计算机识别时

1
2
arr[2] --> *(arr+2)
2[arr] --> *(2+arr)

二级指针

1
2
3
4
5
6
7
8
9
10
int main()
{
int a=10;
int* pa =&a;//pa是指针变量,一级指针
pa;//pa有变量,&pa取出pa再内存中的地址
//ppa就是一个二级指针变量
int* *ppa =&pa;//*ppa是告诉你ppa是个指针,int*是说明 *ppa的字符类型
//*ppa == pa,**ppa == a
return 0;
}

指针数组

存放指针的数组

1
2
3
4
5
6
int main()
{
int arr[10];//整型数组 = 存放整形的数组
char ch[10];//字符数组 = 存放字符的数组
int* parr[10];//整型指针数组 = 存放整型类型指针的数组
}