C语言基础——教你如何玩转指针(1)

目录

什么是指针

指针和指针类型

指针分类的意义

野指针

指针运算

指针和数组的关系

用指针来访问数组

二级指针

指针数组


什么是指针

计算机中,将内存分为一个个小的单元,每个单元为一个字节,内存中最小单元的编号(32or64位的二进制编号)就是指针(地址),口头说的指针,通常指的是指针变量。

指针变量:通过&(取地址操作符)取出变量的内存真实地址,把地址存放到一个变量中,这个变量就是指针变量,如下面的pa。

#include<stdio.h>
int main()
{
	int a=10;
	int* pa=&a;
return 0;
}

指针用来存放地址的,地址唯一标示一块地址空间,指针大小在32位平台是4个字节,在64位平台是8个字节。对于 32 位的计算机,有32根地址线,地址编号就有 2^32 个,可以管理2^32个字节即4 GB的空间。而对于 64 位的计算机,有64根地址线,就能够访问 2^64 个字节的空间,允许访问的内存还是很大的。

指针和指针类型

如下面常见的数据类型:

char a;

short b;

int c;

flaot d;

double e

对这些不同类型的数据进行取地址操作,存放它们地址的指针变量的类型就是在其对应数据类型后加上*:

char* pa = &a;

short* pb = &b;

int* pc = &c;

flaot* pd = &d;

double* pe = &e;

指针分类的意义

指针类型发生变化后,指针在解引用访问的时候,能操作的字节也改变了,即指针类型决定了指针在被解引用的时候,能访问的权限,整型指针解引用能访问操作4个字节,字符指针解引用能访问操作1个字节,可以看下面两个代码:

#include<stdio.h>
int main()
{
	int a=0x11223344;
    //从内存窗口观察a(小端字节序):44 33 22 11

	int* pa=&a;
	*pa=0;		
    //从内存窗口观察a(小端字节序):00 00 00 00

	int b=0x11223344;
    //从内存窗口观察b(小端字节序):44 33 22 11
	
    char* pb=&b;
    *pb=0;
    //从内存窗口观察b(小端字节序):00 33 22 11
return 0;
}
#include<stdio.h>
int main()
{
	int a=10;
	int* pa=&a;
	char* pc=&a;

	printf("%p\n",pa);//00AFF738
	printf("%p\n",pc);//00AFF738

	printf("%p\n",pa+1);//00AFF73C --- 整型指针加一跨了4个字节
	printf("%p\n",pc+1);//00AFF739 --- 字符型指针加一跨了1个字节
return 0;
}

总之,指针类型决定了我们看内存的视角,指针向前或者向后走一步,走了多大的距离,所以内存放的是什么类型的数据,就要用什么类型的指针。

野指针

如果一个指针指向的位置是不可知的、随机的、没有明确限制的,那么这个指针就是野指针。

造成野指针的主要原因有:

1、指针未初始化

#include <stdio.h>
int main()
{
	int* p;//局部变量没有被初始化时为随机值
	*p=20;
return 0;
}

2、指针越界访问

#include <stdio.h>
int main()
{
	int arr[5]={1,2,3,4,5};
	int i=0;
	int *p=arr;
	for(i=0;i<10;i++)
	{
		printf("%d ",*p);//1 2 3 4 5	i=5后就是越界访问了,这时的指针就是野指针
		p++;
	}
return 0;
}

 3、指针指向的空间被释放

#include <stdio.h>
int* test()
{
	int a=10;
	printf("%d\n",a);
	return &a;
}
//a出了函数被销毁
int main()
{
	int* p=test();
	*p=100;//这里就相当于野指针了
return 0;
}

根据以上几个现象,可以归纳出避免出现野指针的几个方法: 

1、指针初始化

2、小心指针越界

3、避免返回局部变量的地址

4、指针使用之前检查有效性

 另外,在不使用某些指针的时候,可以置NULL,使其变得安全,避免成为野指针,如下面代码:

#include <stdio.h>
int main()
{
	int a=10;
	int* p=&a;

	int* q=NULL;//赋予空值	
	if(q!=NULL)
	{
		//...
	}
	p=NULL;
return 0;
}

指针运算

1、指针加减整数,跳过这个指针所指数据类型长度的字节。

#define N_VALUES 5
#include<stdio.h>
int main()
{
    float value[N_VALUES];
    float *vp;
    for(vp=&values[0];vp<&values[N_VALUES];)    //指针的关系运算
    {
	    *vp++=0;    //指针 +- 整数								
    }
    return 0;
}

2、两个指针指向同一块空间时,指针减指针得到两个指针之间元素的个数。

#include <stdio.h>
int main()
{
	int arr[10]={0};
	printf("%d\n",&arr[9]-&arr[0]);//9	 高地址减低地址
	printf("%d\n",&arr[0]-&arr[9]);//-9	 低地址减高地址

    char ch[5]
    int arr[6]
    &arr[4]-&ch[3];//error    两指针必须指向同一块空间

return 0;
}

3、指针的关系运算

注意避免出现下面代码的情况:

#define N_VALUES 5
#include<stdio.h>
int main()
{
    float value[N_VALUES];								
    float *vp;
    for(vp=&values[N_VALUES-1];vp>=&values[0];vp--)
    {
	    *vp=0;			//				0   0   0   0   0			
					    //values		0	1	2	3	4	5
    }
    return 0;
}

可以看到,结束循环后指针此时指向第一个元素0前面一格处。标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。即使上面这个代码在许多编译器上是可以顺利完成任务的,但要尽量避免上面这种写法。

指针和数组的关系

用指针来访问数组

#include<stdio.h>
int main()
{
	int arr[10]={1,2,3,4,5,6,7,8,9,10};
	int* p=arr;
	int i=0;
	int sz=sizeof(arr)/sizeof(arr[0]);
	for(i=0;i<sz;i++)
	{
		printf("%d ",*(p+i));    //用指针访问整个数组
	}
return 0;
}

数组名一般指数组首元素的地址,但是有以下两个例外:

1. sizeof(数组名),此时数组名表示整个数组,计算的是整个数组的大小。

2. &数组名,此时数组名表示整个数组,取出的是整个数组的地址。

 同时要注意的是,数组和指针并不是一回事,数组是一块连续的空间,指针是存放地址的空间,可以通过指针来访问数组,再看下面这个例子:

#include<stdio.h>
int main()
{
	int arr[10]={0};

	printf("%p\n",arr);//010FFCD4
	printf("%p\n",arr+1);//010FFCD8		
    //数组首元素地址加一,跳过一个元素加4

	printf("%p\n",&arr[0]);//010FFCD4
	printf("%p\n",&arr[0]+1);//010FFCD8		
    //数组首元素地址加一,跳过一个元素加4

	printf("%p\n",&arr);//010FFCD4
	printf("%p\n",&arr+1);//010FFCFC		
    //数组的地址加一,加了一个整型数组的大小40

return 0;
}

二级指针

int a = 10;
int* pa = &a;
pa 是一个一级指针,* 告诉我们 pa 是指针,int 说明 pa 指向的是 int 类型的数据

int** ppa = &pa;      
ppa 是一个二级指针,这里后一个 * 告诉我们 ppa 是指针,而前面的 int* 说明 ppa 指向的对象是 int* 的

*ppa --> pa       *pa --> a      即* *ppa -->a

int*** pppa = &ppa;

同理,pppa 是一个三级指针

指针数组

指针数组,顾名思义是一个数组,只不过是存放指针的数组,如:
int arr[5];//整型数组---存放整型的数组

char ch[6];//字符数组---存放字符的数组

int* arr2[5];//指针数组---存放指针(地址)的数组

代码一: 

#include<stdio.h>
int main()
{
	int a=10;
	int b=11;
	int c=12;
	int d=13;
	int e=14;
	int* arr2[5]={&a,&b,&c,&d,&e};    //指针数组-每个元素都是地址
	int i=0;
	for(i=0;i<5;i++)
	{
		printf("%d ",*(arr2[i]));
	}	 
return 0;
}

代码二: 

#include<stdio.h>
int main()
{
	int i=0;
	int j=0;
	int data1[]={1,2,3,4,5};
	int data2[]={6,7,8,9,10};
	int data3[]={11,12,13,14,15};

	int* arr[3]={data1,data2,data3};    //arr是个指针数组
	for(i=0;i<3;i++)
	{
		for(j=0;j<5;j++)
		{
			//printf("%d ",arr[i][j]);    //像打印出了个二维数组
			printf("%d ",*(arr[i]+j));
		}
		printf("\n");
	}
return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值