指针是对内存中对象的引用(通常实现为机器地址)。它是C语言提供的一种间接操纵数据的方法(通常C语言通过提供基本数据类型(int,float,char)或构建复合数据类型(struct)直接操纵数据)。
基本用法如下:
- 通过“已有类型+*a”的方式将变量a声明为一个指向某类型的指针(机器地址)。
- e.g. int *a 即声明了变量a是指向int类型的指针,注意a是指针,不是它指向的int类型变量!
- 此外这里的已有类型指的是基本数据类型(int,float,char)或构建的复合数据类型(struct)
- 在声明之后,我们可以使用*a完成对这个类型值的引用。int *a之后的语句,*a的含义就成了这个被指向的int变量自身。特别的,当a指向的是数组时,*a则表示a[0]。
- 一元操作符&表示取一个对象的机器地址。e.g. *&b与b是完全相同的(此处的b指的是某类型变量而非指针)
指针的意义:间接操纵
- 为什么要间接操纵而不直接操纵呢?
- 因为在C语言中,所有函数参数采用按值传递,也就是说,如果函数向参数变量赋一个新的值,该赋值作用只局限于函数内部,在函数中直接操纵并不能在函数外真正改变变量的值。
- 出了这个函数,变量如果想要得到改变,还得写一个专门的return语句使变量得到已经变化的值。但是若有多个变量值进行了改变,那么return不过来了怎么办呢,那么就在函数中用指针间接操纵以达到改变变量值的目的。所以说这就是void函数没有返回值但是也能存在且非常合理地存在的一种底层逻辑。
- 我们把想改变的变量的指针传进函数中,而某些作为常量(不想改变的变量)就传进该变量的“真身”。举个例子:
以下函数把笛卡尔坐标转换为极坐标:
void polar(float x, float y, float *r, float *theta)//此处传入的就是想改变的变量的地址和作为常量传入的x、y的真身
{
*r = sqrt(x*x+y*y);
*theta = atan2(y,x);
}
值得注意的是,函数的参数列表里声明了指针r和theta分别指向一个浮点类型变量,而这个浮点类型的变量名不一定真叫r和theta(一般还真叫r和theta,为了清晰明确地暗示传入的该是哪个变量的地址),传入的仅仅只是地址。
真叫r和theta完全没问题,因为在主函数中的r和theta是浮点变量,而在polar函数列表中的是指向他俩的指针(地址),函数列表中的变量名任意取,与主函数中的变量重名也无所谓,看的主要是真正传入的东西。
所以在定义函数之后,主函数中调用该函数时,是这样的:
polar(x,y,&r,&theta);//这指的就是将主函数中r和theta的地址传入
如果你在主函数中把极坐标r和theta声明成别的名字,叫做r1,theta1,那么调用函数的指令可以变成:
polar(x,y,&r1,&theta1)
- 指针的其他妙用(动态分配内存空间,malloc函数)
- 使用stdlib.h中的malloc函数和sizeof算子为数组或其他数据结构预留恰当的内存空间,并返回一个指向该内存空间的指针。
- 这意味着可以动态改变数组的长度:从命令行获取数组长度N,应用malloc函数和sizeof算子进行内存的申报。示例如下:
#include <stdlib.h>
main (int argc, char *argv[] )//表示从命令行获取信息,argc表示加上调用的指令名之后的变量个数,argv[]是指针数组,每个元素指针指向以char的类型储存调用的指令名(argv[0])和其他输入,在此处“其他输入”是N,此处传入的是指向指针数组argv[]的地址
{ long int N = atol (argv[1]);//atol函数的参数列表中传入的就是指针
int *a = malloc(N*sizeof (int));
......
}
值得注意的是,如果在大型程序中想节省空间,可以对应malloc函数匹配一个free()函数释放该内存块。
- 指针的指针
- 类型*+*变量名,指的是,该变量是指针,指向类型为“类型*”的变量,而类型*的变量又指向类型为“类型”的变量。
- e.g. int* *a
- 该类表示方式主要用于表示指针数组在函数中传参。
- e.g. function(int *a[])相当于function(int* *a)(指针数组的指针被传入,即第一个指针元素的地址)
- e.g. function(int a[])([]中可填可不填数字,但是二维数组的第二个[]一定要填)相当于function(int *a)(传入数组时)
- 类型*+*变量名,指的是,该变量是指针,指向类型为“类型*”的变量,而类型*的变量又指向类型为“类型”的变量。