指针是C语言中比较难懂的部分,但是指针又是C语言的精华,所以指针是每个C语言开发人员都必须掌握的知识。在学指针之前先来了解变量在内存中的分布。
在上图中,定义了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来初始化指针,防止其变成野指针,后续再给指针分配内存地址。
#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;
}
通过程序和示例图可以了解指针的自加和自减的运算过程。由内存示意图可以知道,数组内的元素地址是挨个排列,其地址都是相差4个字节(这里的4个字节是因为数组的类型为int类型,如果数组是其他类型需要根据实际情况来定)。指针的自加和自减就是指针往前移动n * sizeof(int)
个字节或者向后移动n * sizeof(int)
个字节。
#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;
}
#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;
}
因为数组在内存中的地址是连续的,而数组名又正好是整个的数组的首地址,所以当用一个指针指针数组名时,就意味着这个指针的地址已经数组的首地址进行关联,所以就可以用指针来直接访问数组元素。
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;
}
数组指针的形式是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;
}
#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;
}
从程序的运行结果中可以看到,函数指针和函数的内存地址是一样的,所以通过指针也可以直接调用函数。函数指针在操作系统中是非常常见的。
#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函数中也打印出两个函数参数的地址并进行数据交换,最后打印出交换后的值。
最后运行的结果是在调用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;
}
上述程序只是将函数的参数改成了指针,这时swap函数需要传入变量的地址,从运行结果可以看到,在main函数中定义的变量地址和swap参数传入的地址是完全相同。所以在swap函数中修改两个指针的值其实就等于在修改main函数中定义的a和b变量的值。所以最后结果是两个参数的值互换成功。
#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种不同的运算功能。