概念
指针是一种数据类型,同样支持赋值,+和-操作。指针的值代表了内存中的一个地址,一般可以通过指针间接的操作数据。
创建一个指针
例如,我们要创建一个指向int
类型的指针,我们可以编写如下代码。
int val = 10;
int* ptr = &val;
在这里“ptr”就是指向“int”类型的指针,其值为变量“val”的地址。
使用指针操作数据
继续使用之前的代码,我们可以通过指针变量“ptr”读写变量“val”的值,具体操作方式如下。
// 通过指针读取变量的值
printf("%d\n",*ptr);
// 通过指针修改变量的值
*ptr = 20;
printf("%d\n",*ptr);
通过如上代码,我们就可以通过指针来获取和修改变量的值了。
理解指针
指针在本质上也是一种数据类型,只不过指针的值代表了内存中的一个地址,对于指针的定义,其形式为value_type* ptr
其中value_type
为指针所要指向的类型,ptr
为指针变量。在这里,ptr
的类型为value_type*
。基于此理解,我们可以很容易的理解多级指针的概念。
多级指针
基于以上的理解,我们可以得知指针的定义为value_type* ptr
。那么,当value_type
本身就是指针类型时,使用这种形式定义的指针就是二级指针。例如以下代码。
typedef int* IntPtr;
IntPtr* ptr;
在这里,我们定义了新的数据类型IntPtr
,其本质上是int*
,然后我们定义了一个指针ptr
,从指针的定义方式上可以得出,变量ptr
指向的变量其类型应该是IntPtr
,而IntPtr
又是int*
类型的别名,所以我们定义出了一个指向int*
类型的指针ptr
。该变量ptr
就被称为int
类型的二级指针。
由以上定义的方法我们可以很容易的推出更多级指针的定义方式。
当然,以上代码只是为了方便理解而编写的,在应用中,对于二级指针或多级指针,我们有更简单的定义方式,示例代码如下。
int** ptr;
这里的变量ptr
与之前定义的ptr
是完全等价的。
函数中使用指针
指针的用途是很多的,其为开发工作可以提供许多便利。一下就简单的使用几种常见的用法。
(1) 用于函数出参:众所周知,C语言的函数只能有一个返回值,但是有时候我们传出多个值,除了包装一个结构体返回多个值之外,我们还可以使用指针作为传出参数。示例代码如下。
void addAndSub(int n1, int n2, int* addRes, int* subRes) { *addRes = n1 + n2; *subRes = n1 - n2; }
以上代码将会计算n1与n2的和与差,然后通过addRes和subRes两个指针传出,以达到多返回值的目的。
(2) 用于修改传入参数:C语言里,所有的参数都是以值拷贝的形式传入函数的,在这个时候,不论我们对形参如何修改,都无法影响到函数外部的变量,此时我们就可以使用指针传入参数,从而可以在函数内部修改函数外的变量。示例代码如下。void valMod10(int* val) { *val %= 10; }
如上函数,会将传入的参数
val
指向的整形值进行对10取余运算,并且修改其值。
(3) 传入大数据:在C语言中,参数都是以值拷贝的方式传入函数的(包括指针)。假设如下代码运行在32位平台typedef struct { int nums[256]; }MyStruct; int numSum(MyStruct s) { int sum = 0; for (int i = 0; i < 256; i++) { sum += s.nums[i]; } return sum; }
上述代码在运行时将会拷贝256个
int
(一般是32位)形数据的大小,有较大的系统开销,而如果我们使用指针改写这段代码。typedef struct { int nums[256]; }MyStruct; int numSum(MyStruct* s) { int sum = 0; for (int i = 0; i < 256; i++) { sum += s->nums[i]; } return sum; }
如上代码中,则只会拷贝一次指针,在32位平台下,指针的大小也为32位,如此简单的修改就可以减少256倍的拷贝开销。
(4) 作为函数参数以接收数组:在C语言里,数组是无法作为形参的,即传入的数组参数都会自动转换为指针参数。例如如下代码。int sum(int* nums, int n) { int sum = 0; for (int i = 0; i < n; i++) { sum += nums[i]; } return sum; } int main() { int arr[10] ={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int arrSum = sum(arr, 10); printf("%d", arrSum); }
当然我们可以修改以上代码中的
sum
函数声明为int sum(int nums[], int n)
,两种声明是等价的。
使用指针操作数组
假设我们有一个需求,需要计算一个数组内所有元素的和,那么我们可以使用如下方法计算。
// 假设数组定义为int nums[256] = {1, 2, 4, ...};
// 求和方法一:不使用指针
int sum = 0;
for(int i = 0; i < 256; i++)
{
sum += nums[i];
}
// 求和方法二:使用指针
int* ptr = nums;
int* pEnd = &nums[256];
do{
sum += *ptr;
}while(++ptr < pEnd)
指针与数组
一般情况下,数组可以隐式的转换为指针,例如如下代码。
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* p = arr;
但是需要注意,对于多维数组,我们并不能简单的使用多级指针来指向,例如如下代码。
int main()
{
int arr[2][2] = { 1, 2, 3, 4 };
int** ptr = arr;
printf("%d", ptr[1][1]);
}
这段代码在运行时将会报错。
正确的写法如下所示。
int main()
{
int arr[2][2] = { 1, 2, 3, 4 };
int(*ptr)[2] = arr;
printf("%d", ptr[1][1]);
}
值得注意的,在作为函数参数时,数组总是会被转换为指针,所以我们无法在函数内直接获取数组的大小,所以在编写代码时,如果需要使用数组作为参数,一定要再添加一个参数指明数组的长度。
函数指针
在C语言的库中,经常会遇到注册回调函数的操作,我们注册的回调函数都会被库代码以函数指针的形式保存起来。
函数指针的定义形式如下:
RetType (*functionPtrName)(ParaType1, ParaType2 ...);
其中RetType
为返回值类型,functionPtrName
为函数指针的变量名,ParaType1, ParaType2 ...
为各个参数的类型。
示例代码如下。
int sum(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int main()
{
int(*fun)(int, int) = sum;
printf("%d", fun(1, 2));
fun = ⊂
printf("%d", fun(3, 2));
}
由上述代码可以看出函数指针有以下几个特点。
- 为函数指针赋值时,取地址运算符(&)可写可不写。在C语言里,针对函数指针,我们为其赋值时不论是否使用取地址运算符其都能正确运行。
- 函数指针定义时的返回值类型,形参列表必须与原函数完全一致。
特别的,在C语言中如果我们定义的函数指针未指明参数列表,则代表可以接受任意个参数。示例代码如下。
int sum(int a, int b, int c)
{
return a + b + c;
}
int sub(int a, int b)
{
return a - b;
}
int main()
{
int(*fun)(); // 未指定形参列表的函数指针
fun = sum; // 可以指向三个参数的函数
printf("%d", fun(1, 2, 3));
fun = ⊂ // 也可以指向两个参数的函数
printf("%d", fun(3, 2));
}
如果我们要定义一个不接受参数的函数指针,则应该使用如下所示的定义方式。
RetType(*fun)(void);
形参列表中的void指明了该函数指针不接受任何带有任何参数的函数。
以上代码均未经验证,如有错误请谅解