指针是C/C++中的难点之一,本文列举一些复杂的指针内容。
指针与数组
这一点应该已经是常识了,一个数组的名字实际上就是一个指向该数组的起始位置的指针,这使得我们可以通过指针对数组进行操作:
int a[] = {1,2,3,4};
int *p;
p = a;
//此时指针p就指向数组a的起始元素。
另外,在数组作为函数参数时,
void func(int *p){}
和
void func(int a[]){}
是等价的,这两个函数是完全等价的函数,不属于重载。
而且要注意到的是,在实际函数调用中,即使采用的是第二种方式,传入的实参依然是指针,这意味着在函数中不能调用sizeof()来计算数组所占的内存,因为结果会是本地编译器中指针类型的内存。
如果想要在函数中计算实参数组的长度,可以利用模板函数,通过传递对数组的引用。
void指针
void *p;
以上语句声明了一个类型为void的指针。
void指针需要注意的点是,在C++中,不同类型的指针不存在隐式转换,但是void指针是一个特例,void指针不能隐式转换为其他类型的指针(在另外一篇博客中有解释,这也是C++中NULL的定义和C中不同的原因)但是其他类型的指针可以隐式转换为void类型的指针
另外,void类型指针的指向的内容是未定义的,所以对void指针进行sizeof()计算和取值是没有意义的。
函数指针
程序在运行期间,函数是占用内存的,而我们在调用函数时使用的函数名就是这段内存的入口(即起始地址),我们知道指针是可以指向任何内存中的内容的,那么指针也可以指向函数或者说函数的入口地址——函数名。
如果将函数赋给指针使该指针指向函数,这个指针就称为函数指针。
函数指针的定义为:
类型名 (*指针变量名)(参数列表);
即为了让一个指针指向一个函数,我们在声明该指针的时候,需要参考函数原型。
int sum(int x,int y)
{
return x+y;
}
int main()
{
//声明函数指针,为了使指针指向sum函数,所以指针声明需要参考sum原型:
int (*p)(int,int);
p = sum;
cout<<p(4,5)<<endl;
//输出结果9,这里通过指针p实现了对函数sum的调用。
我们一般情况下不会用到函数指针的概念,因为如果仅仅是为了调用一个函数使用函数指针实在是多此一举。而函数指针存在的意义除去C语言的历史原因以外,更多的是应用在回调函数中。
如果一个函数在执行过程中依赖于另外一个函数,而这个被依赖的函数尚未定义。我们举个很简单的例子:
//我们定义一个求和函数,但是这个函数并不是单纯的求和,可能是求平方和、立方和等等。
int xxSum(int a,int b, int (*xx)(int,int)
{
return xx(a,b);
}
int sq(int a)
{
return pow(a, 2);
}
int cube(int a)
{
return pow(a, 3);
}
int main()
{
int x = 3, y = 4;
//这里我们对xxSum函数的调用,依赖于其中xx函数指针。
cout << xxSum(x, y, sq);
//输出25
cout << xxSum(x, y, cube);
//输出91
}
当然,这个例子确实是多此一举,为的是演示指针函数的使用和意义,更具体的应用的是参见qsort函数的调用,这里不再赘述。
指针数组与指向指针的指针
如果有一个数组,数组中的元素都是指针,这个数据就称为指针数组。
T *a[N]; //N是整数类型的常量或常量表达式。
指针也是变量,如果用一个指针指向一个指针,就称该指针为指向指针的指针。
T **p;
这里的指针p指向一个指针,该指针指向一个类型为T的数据。
这里相同类型的指针数据和指向指针的指针是可以相互赋值的,因为我们知道一个数组名即指向这个数组初始地址的指针。