C语言指针的使用

指针是C语言中比较难懂的部分,但是指针又是C语言的精华,所以指针是每个C语言开发人员都必须掌握的知识。在学指针之前先来了解变量在内存中的分布。
C语言指针的使用_第1张图片
在上图中,定义了3个变量a、b、c,这三个变量的地址分别是0x2000,0x2004和0x2008,变量的地址就代表这个变量所在的内存位置。举个例子,在一家酒店里有三个房间,门牌号分别是0x2000,0x2004,0x2008,这里的门牌号就相当于是地址,找到相应的门牌号就能找到相应的房间。那么旅客根据什么东西可以找到相应的房间呢,答案是钥匙,一把钥匙对应一个房间门牌号,通过钥匙找到相应的门牌号进而打开相应的房间。而这里的钥匙就是C语言里面的指针。

  • 指针的使用
    想要定义一个指针变量,只需在变量名字前加一个*号即可,比如定义一个整形指针变量int *a,如果需要把一个普通变量的地址赋值给指针变量需要使用&符号,比如int *a = &b,这段语句意思是把变量b的地址赋值给指针变量a。当想要获取指针变量的值需要在变量名前加*号即可,比如*a就代表取a地址所在内存的值。下面来写一个程序来做个实验。

程序1:

#include 
int main(void)
{
	int *a;       // 定义整形指针变量
	int b = 10;   // 定义整形变量b	
	
	a = &b;       // 将b变量的地址赋值给指针a
	printf("*a = %d\r\n",*a);    // 打印出指针变量a的值
	printf("a  = 0x%x\r\n",a);  // 打印出指针变量a的地址
	printf("&b = 0x%x\r\n",&b); // 打印出变量b的地址
	return 0;
}

在程序1中,定义了指针变量a和普通变量b,然后将b的地址赋值给a,最后打印出a的值,指针a的地址和变量b的地址。
在这里插入图片描述
可以看到,*a的值与变量b的值一样,而且指针变量a的地址与变量b的地址也一样。

  • 未初始化与非法指针
    指针在定义时必须初始化,否则就会成为野指针。
#include 
int main(void)
{
	int *a;       // 定义整形指针变量
	*a = 10;
	printf("a* = %d\r\n",*a);
	
	return 0;
}

在这里插入图片描述
上述程序中定义了指针变量a,但是没有给指针变量初始化,当程序给指针变量a赋值时,系统提示段错误。这其实就是指针变量a的地址没有初始化,导致其指向了内存中的限制地址,这就是常说的越界访问,所以操作系统提示段错误。当我们定义指针但是又不想初始化时,可以使用NULL来初始化指针。

#include 
int main(void)
{
	int *a = NULL;       // 定义整形指针变量并用NULL初始化
	int b = 10;
	a = &b;
	printf("a* = %d\r\n",*a);
	return 0;
}

先用NULL来初始化指针,防止其变成野指针,后续再给指针分配内存地址。

  • 指针的运算
    指针的运算只允许两种方式,一种是指针自加或自减,一种是指针减指针。
    1、 - 指针自加或自减。
#include 
int main(void)
{
	int array[] = {0,1,2,3,4};
	int *a = &array[3];
	
	printf("a* = %d\r\n",*a);
	a++;
	printf("a* = %d\r\n",*a);
	a--;
	printf("a* = %d\r\n",*a);
	return 0;
}

C语言指针的使用_第2张图片
在这里插入图片描述

通过程序和示例图可以了解指针的自加和自减的运算过程。由内存示意图可以知道,数组内的元素地址是挨个排列,其地址都是相差4个字节(这里的4个字节是因为数组的类型为int类型,如果数组是其他类型需要根据实际情况来定)。指针的自加和自减就是指针往前移动n * sizeof(int)个字节或者向后移动n * sizeof(int)个字节。

  • 2、 - 指针的相减
    指针的相减仅限于两个指针指向同一个数组的情况。相减的结果将除以数组元素类型的长度。
#include 
int main(void)
{
	int array[] = {46,1,452,-893,4};
	int *a = &array[3];
	int *b = &array[0];
	printf("a - b = %d\r\n",a - b);
	
	return 0;
}

在这里插入图片描述

  • 指针与数组的关系
    在C语言中,指针和数组的关系可以说是十分密切,在上一篇讲解数组的文章中,就出现数组和指针的相互引用,下面就来更加深入了解一些指针和数组的关系。
    • 指针与一维数组
#include 

int main(void)
{
	int i = 0;
	int array[] = {0,1,2,3,4};  // 定义数组
	int *a = array;             // 定义指针变量a并指向数组的首地址
	for(i = 0;i < 5;i++)
	{
		printf("*(a + %d) = %d\r\n",i,*(a + i));  // 循环打印出数组的值
	}
	return 0;
}

因为数组在内存中的地址是连续的,而数组名又正好是整个的数组的首地址,所以当用一个指针指针数组名时,就意味着这个指针的地址已经数组的首地址进行关联,所以就可以用指针来直接访问数组元素。
C语言指针的使用_第3张图片

  • 指针数组
    指针数组存放的是指针变量,由多个指针变量结合成一个数组。指针数组的表现形式为char *p[10],根据C语言的优先级,p先跟[]结合,p[10]是一个数组,然后p[10]再跟*结合,变成了*p[10]的指针数组,根据前面的char类型,所以这个是一个存放char* 类型的指针数组。
 #include 
int main(void)
{
	int i = 0;
	char *p[] = {
		"hello world",
		"123456",
		"abcdefg",
	};
	
	for(i = 0;i < 3;i++)
	{
		printf("p[%d] = %s\r\n",i,p[i]);
	}
	
	return 0;
}

C语言指针的使用_第4张图片

  • 数组指针

数组指针的形式是int (*p)[2],根据优先级p先跟星号结合,变成*p,然后再跟[]结合,变成数组指针。数组指针它是一个指针,指向数组。数组指针通常用来访问一个二维数组。

#include 
int main(void)
{
	int i = 0,j = 0;
	int array[3][2] = // 定义一个二维数组
	{
		{0,1},
		{2,3},
		{4,5},
	};
	
	int (*p)[2] = array; // 定义一个数组指针并指针二维数组的首地址
	p++;                 // 数组指针加1,表示指指针向下移动一行。
	printf("(p)[0] = %d\r\n",*(p)[0]);
	
	
	return 0;
}

在这里插入图片描述

  • 指向函数的指针
    在C语言中函数名代表该函数的首地址,既然函数有地址,那么也可以用指针来指向函数,这种指针叫做函数指针。
#include 
typedef void (*pfunc)(void); // 定义一个函数指针,类型为void ()(void)

void printf_fun(void)
{
	printf("hello world\r\n");
}

int main(void)
{
	pfunc pf = printf_fun; // 定义一个函数指针并指向一个函数
	
	printf("0x%x\r\n",printf_fun);
	printf("0x%x\r\n",pf);
	
	pf();  // 通过指针调用函数
	
	return 0;
}

C语言指针的使用_第5张图片
从程序的运行结果中可以看到,函数指针和函数的内存地址是一样的,所以通过指针也可以直接调用函数。函数指针在操作系统中是非常常见的。

  • 使用指针的好处
    • 1、指针可以直接访问内存地址,可以提高效率
      在讲解数组的时候说过,访问数组元素有两种访问形式,一种是下标法,一种是指针法。下标法其实是对指针法的一种封装。当使用下标法访问数组元素的时候,在程序运行的时候最后还是要转换成指针进行访问。所以直接使用指针访问效率会更高。
    • 2、在C语言中一些复杂的数据结构可以通过指针来实现。
      在C语言中的一些复杂结构,比如链表、树二叉树、红黑树等数据结构都是用指针进行构建。
    • 3、在C语言中函数传参是值传递的,函数的形参是不可以修改变量值,但是通过指针却可以。
      函数传参是不可修改变量的值,但是指针就可以,下面可以通过一个经典的数据交换例子来说明。
#include 
void swap(int a,int b)
{
	int c;
	
	printf("swap &a = 0x%x\r\n",&a);
	printf("swap &b = 0x%x\r\n",&b);
	c = a;
	a = b;
	b = c;
}

int main(void)
{
	int a = 5,b = 10;
	
	printf("&a = 0x%x\r\n",&a);
	printf("&b = 0x%x\r\n",&b);
	swap(a,b);
	
	printf("a = %d\r\n",a);
	printf("b = %d\r\n",b);
	
	return 0;
}

函数swap的作用交换两个变量的值,在main函数中定义a和b两个变量,先将两个变量的地址打印出来,然后调用swap函数并将a和b的值作为函数的参数。在swap函数中也打印出两个函数参数的地址并进行数据交换,最后打印出交换后的值。
C语言指针的使用_第6张图片
最后运行的结果是在调用swap函数后,a和b的值并没有被交换,还是原来的值,为什么会这样呢。其实通过两个变量的地址就可以知道,在main函数定义的两个变量的地址跟函数参数的地址是完全不同的地址。虽然在swap函数中有将两个值进行互换,但是地址不同,最后互换的结果自然也就失败。

接下来看一下利用指针作为函数的参数

#include 
void swap(int *a,int *b)
{
	int c;
	
	printf("swap a = 0x%x\r\n",a);
	printf("swap b = 0x%x\r\n",b);
	c = *a;
	*a = *b;
	*b = c;
}

int main(void)
{
	int a = 5,b = 10;
	
	printf("&a = 0x%x\r\n",&a);
	printf("&b = 0x%x\r\n",&b);
	swap(&a,&b);
	
	printf("a = %d\r\n",a);
	printf("b = %d\r\n",b);
	
	return 0;
}

C语言指针的使用_第7张图片

上述程序只是将函数的参数改成了指针,这时swap函数需要传入变量的地址,从运行结果可以看到,在main函数中定义的变量地址和swap参数传入的地址是完全相同。所以在swap函数中修改两个指针的值其实就等于在修改main函数中定义的a和b变量的值。所以最后结果是两个参数的值互换成功。

  • 4、可以利用指针实现面向对象编程。
    众所周知,C语言是一种面向过程的语言,它本身不具备面向对象的功能,但是在很多操作系统中,可以利用C语言的函数指针来实现面向对象的功能。下面来写一个简单的程序来进行说明。
#include 

typedef int (*cal_func)(int,int); // 定义计算器操作函数

int add(int a,int b)  // 加
{
	return a + b;	
}

int sub(int a,int b)  // 减
{
	return a - b;	
}

int mul(int a,int b)  // 乘
{
	return a * b;	
}

int div(int a,int b)  // 除
{
	return a / b;	
}


typedef struct calculator
{
	int a;     // 计数值
	int b;     // 计数值
	cal_func pfun; // 计数器操作指针函数
}calculator_t;


int main(void)
{
	calculator_t cal;
	char c;
	printf("请输入运算类型\r\n");
	scanf("%c",&c);
	
	printf("请输入两个整形变量\r\n");
	scanf("%d",&cal.a);
	scanf("%d",&cal.b);
	
	switch(c)  // 根据用户输入的运行类型进行函数指针的赋值
	{
		case '+':
			cal.pfun = add;
		break;
		case '-':
			cal.pfun = sub;
		break;
		case '*':
			cal.pfun = mul;
		break;
		case '/':
			cal.pfun = div;
		break;
	}
	printf("value = %d\r\n",cal.pfun(cal.a,cal.b)); // 运行函数指针
	
	return 0;
}


上述程序中根据用户输入的运算类型,在switch中选择不同的函数赋值给函数指针,最后调用函数指针得出结果。可以看到结构体中只定义了一个函数指针,但是却可以实现4种不同的运算功能。

你可能感兴趣的:(c语言)