本章重点
1.指针是什么?
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单位,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
指针是个变量,存放内存单元的地址(编号)。
那对应的代码:
int main()
{
int a=10; //在内存中开辟一块空间
int *p=&a;//指针变量 //这里我们对变量a,取出它的地址,可以使用&操作符
//将a的地址存放在p变量中,p就是一个指针变量。
return 0;
}
//总结:指针就变量,用来哦存放地址的变量。(存放在指针中的值都被当成地址处理)。
那这里的问题是:
一个小的单元到底是多大?(1个字节)
如何编址?
经过仔细地集散和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设没跟地址线在寻址的是产生一个电信号正点/负电(1或者0)
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
....
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个字节。
2.指针和指针的类型
这里我们讨论一下:指针的类型我们知道,变量有不同的类型,整型,浮点型等。那只真有没有类型呢?准确的说:有的。
int main(){
printf("%d\n",sizeof(char*));
printf("%d\n",sizeof(short*));
printf("%d\n",sizeof(int*));
printf("%d\n",sizeof(double*));
return 0;
}
结果是4 4 4 4
int a=0x11223344;
int *pa=&a;
char *pc=&a;
printf("%p\n",pa);
printf("%p\n",pc);
int a=0x11223344;
char *pc=&a;
*pc=0;
指针类型决定了指针进行解引用操作的时候,能够访问空间的大小。
int *p; *p能够访问4个字节
char *p;*p能够访问1个字节
double *p;*p能够访问8个字节
int main(){
// printf("%d\n",sizeof(char*));
// printf("%d\n",sizeof(short*));
// printf("%d\n",sizeof(int*));
// printf("%d\n",sizeof(double*));
int a=0x11223344;
int *pa=&a;
char*pc =&a;
// char *pc=&a;
printf("%p\n",pa);
printf("%p\n",pa+1);
printf("%p\n",pc);
printf("%p\n",pc+1);
return 0;
}
指针类型决定了:指针走一步走多远(指针的步长)
int *p;p+1-->4
char *p;p+1-->1
double *p;p+1-->8;
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。
int main(){
int arr[10]={0};
int *p=arr;//数组名-首元素的地址
int i=0;
for(i=0;i<10;i++)
{
*(p+i)=1;
}
return 0;
}
总结:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节),比如:char *的指针解引用就只能访问一个自己而,而int *的指针的解引用就能访问四个字节。
3.野指针
概念:野指针就是指针指向的位置是不可知的(随机的,不正确的没有明确限制的)
野指针成因
1.指针未初始化
int main(){
int *p;//局部的指针变量,就被初始化随机值
*p=20;//局部的指针变量,就被初始化随机值
return 0;
}
2.指针越界访问
int main(){
int arr[10]={0};
int *p=arr;
int i=0;
for(i=0;i<12;i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
//*(p++)=i;
*p=i;
p++;
}
return 0;
}
如何规避指针
1.指针初始化
2.小心指针越界
3.指针指向空间释放及时置为NULL
4.指针使用之前检查有效性
int *test()
{
int a=10;
return &a;
}
int main()
{
int *p=test();
printf("%d\n",*p);
return 0;
}
int *test()
{
// int a=10;
// return &a;
int arr[10]={0};
return arr;
}
int main()
{
int *p=test();
printf("%d\n",*p);
return 0;
}
int main(){
int b=0;
int a=10;
int *pa=&a;//初始化
int *p=NULL;//NULL-用来初始化指针的,给指针变量初始化
}
int main(){
int a=10;
int*pa=&a;
*pa=20;
pa=NULL;
if(pa!=NULL){
}
return 0;
}
4.指针运算
- 指针+-整数
- 指针-指针
- 指针的关系运算
指针+-整数
#define N_VALUES 5
float value[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for(vp=&values[0];vp<&values[N_VALUES];)
{
*vp++=0;
}
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;
for(i=0;i<10;i++){
printf("%d ",*p);
p++;
}
return 0;
}
p+2;
跳两个地址
1 3 5 7 9
int main(){
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int i=0;
int *p=&arr[9];
for(i=0;i<5;i++){
printf("%d ",*p);
p-=2;;
}
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
int my_strlen(char *str)
{
char *start=str;
char*end=str;
while(*end!='\0')
{
end++;
}
return end-start;
}
int main(){
//strlen -求字符串长度
//递归 -模拟实现了strlen -计数器的方式1,递归方式2
//
char arr[]="bit";
int len=my_strlen(arr);
printf("%d\n",len);
return 0;
}
指针比较大小
#define N_VALUES 5
flaot values[N_VALUES]
float *vp;
for(vp =&values[N_VALUES];vp>&values[0];){
*--vp=0;
}
for(vp =&values[N_VALUES-1];vp>=&values[0];vp--){
*vp=0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存存在位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5.指针和数组
int main()
{
int arr[10]={0};
printf("%p\n",arr);//地址-首元素地址
printf("%p\n",&arr[0]);
printf("%p\n",&arr);
//1.&arr-&数组名-数组名不是首元素地址-数组名表示整个数组-&数组名-取出的是整个数组的地址
//2.sizeof(arr)-sizeof(数组名)-数组名表示的是整个数组-sizeof(数组名)计算的是整个数组名的大小
return 0;
}
int main()
{
int arr[10]={0};
printf("%p\n",arr);//地址-首元素地址
printf("%p\n",arr+1);//地址-首元素地址
printf("%p\n",&arr[0]);
printf("%p\n",&arr[0]+1);
printf("%p\n",&arr);
printf("%p\n",&arr+1);
//1.&arr-&数组名-数组名不是首元素地址-数组名表示整个数组-&数组名-取出的是整个数组的地址
//2.sizeof(arr)-sizeof(数组名)-数组名表示的是整个数组-sizeof(数组名)计算的是整个数组名的大小
return 0;
}
int main(){
int arr[10]={0};
int *p=arr;
int i=0;
for(i=0;i<10;i++){
printf("%p ====== %p\n",p+i,&arr[i]);
}
return 0;
}
写法一:
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));
}
写法二:
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",arr[i]);
}
6.二级指针
int main(){
int a=10;
int *pa=&a;
int **ppa=&pa;//二级指针
//int ***pppa=&ppa;//三级指针
printf("%d\n",**ppa);
return 0;
}
int main(){
int a=10;
int *pa=&a;
int **ppa=&pa;//二级指针
//int ***pppa=20;//三级指针
printf("%d\n",**ppa);
printf("%d\n",a);
return 0;
}
结果都是20
7.指针数组
好孩子--孩子
指针数组---数组--存放指针的数组
//数组指针--指针
int main()
{
int a=10;
int b=20;
int c=30;
// int*pa=&a;
//int *pb=&b;
//int *pc=&c;
//整形数组-存放整型
//字符数组-存放字符
//指针数组-存放指针
//int arr[10];
int*arr2[3]={&a,&b,&c};//指针数组
int i=0;
for(i=0;i<3;i++){
printf("%d\n",*(arr2[i]));
}
return 0;
}