指针4
今天继续指针相关的知识。
二级指针
二级指针就是指向指针的指针。指针本质是个地址,存放地址的变量就是指针变量。而指针变量本身也有地址,如果使用一个新的指针指向这个指针变量,那么这个新的指针就是二级指针了。看一下代码:
int a = 0;
int* pa = &a;//一级指针
int** ppa = &pa;//二级指针
上面的ppa就是二级指针。
指针数组
指针数组的本质是数组。只不过数组里存储的元素是指针。一个指针数组的形式如下:
int* arr[5]
数组名是arr,数组里有5个元素,每个元素的类型是int*。
指针数组模拟二维数组
看一个栗子:
int arr1[4] = {0};
int arr2[4] = {0};
int arr3[4] = {0};
int* parr[3] ={arr1,arr2,arr3};
需要访问数组中的某个元素的时候:
parr[i][j];
这里需要注意,二维数组的元素是一维数组。
字符指针变量
看一下下面的代码:
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";//常量字符串,一样的内容只保存一份
if (str1 == str2)
printf("STR1 == STR2");
else
printf("STR1 != STR2");
printf("\n");
if (str3 == str4)
printf("STR3 == STR4");
else
printf("STR3 != STR4");
return 0;
}
这里的结果是STR1!=STR2,而STR3==STR4。有const修饰的字符串是常量,会放在静态区。而一模一样的常量不会存储2份,所以str3==str4。而str1和str2都是数组,他们的存储空间在栈区,因此地址是不一样的。
数组指针和使用
数组指针的本质是指针。这个指针指向的对象是数组。由于数组名是数组的首地址,那么&数组名就是取到整个数组的地址。当需要存放类似的地址时,就需要数组指针了。看一个栗子:
int arr[10] = {0};
int (*p)[10]=&arr;
这里p就是数组指针。注意,这种写法是固定的,不能省略小括号。*p说明这是个指针,int在里面说明p指向的数组元素是int,[10]就是元素个数了,和在一起,int [10]说明指针的对象是一个有10个 int型元素的数组。
二维数组传参的本质
数组传参的时候,本质是传首地址。而这个首地址是个地址,所以是可以用数组指针作为参数传入的。而二维数组的首元素是个一维数组,那么我们就需要传一个一维的数组指针进去。看一下代码:
void test(int (*arr)[5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
printf("%d ", *(*(arr + i) + j));
printf("%d ", (*(arr+i))[j]);
}
printf("\n");
}
}
这里,分析一下int (*arr)[5]。*arr: 指针,int: 元素类型是int ;[5]: 5个元素。所以传进来的是一个有5个int型元素的一维数组的地址。
这个test函数是会打印数组中的每一个元素的。看一下下面的部分,一个一个分析。
arr[i][j]: 最普通的引用方式,和之前的二维数组引用方式没有任何区别。这是因为arr就是一个数组名。
*(*(arr + i) + j):指针和解引用的方式。注意到arr[i] == *(arr+i) 就可以很容易的理解了。
(*(arr+i))[j]:混合方式。
函数指针
函数指针就是指向函数地址的指针。它的使用和数组指针非常类似。先看一下代码:
int Add(int x, int y)
{
return x + y;
}
int main()
{
//对于函数,函数名和&函数名都是函数的地址
printf("%p\n", &Add);
printf("%p\n", Add);
int (*pf)(int,int) = &Add;
printf("%d\n", (*pf)(3, 5));
printf("%d\n", pf(3, 5));
return 0;
}
Add其实就是函数的地址,在使用&Add的时候,类比于数组指针,就可以写出来:
int (*pf)(int,int) = &Add;
这里这样分析:
*pf: 指针
int:指针指向函数的返回值类型
(int ,int ): 函数的参数。
所以Add就是一个返回类型为INT,参数是2个整型的函数。
下面看一个比较绕的:
(*(void(*)())0)();
一层一层分析:
void(*)():函数指针类型,返回类型是void,参数是void
(void(*)())0:0前面加了括号就是强制类型转换,这就是把0强制转换成一个类型是void(*)()的指针;
(*(void(*)())0)(): 调用0地址处的函数,函数参数是void,返回值是void。
再练习一个:
void (*signal(int, void(*)(int)))(int);
先看一下signal(int, void(*)(int)): 这是一个函数声明,参数是int 和 void(*)(int),一个参数是int型,另一个参数是一个函数指针。拿掉这个函数,剩余的是:void(*)(int)。这就是signal函数的返回值类型了。返回值也是一个函数指针,被指向的函数返回类型是void,参数是int。本质上这个语句是个函数声明。实际写成:void(*)(int) signal(int,void(*)(int)) 有助于理解,但是语法不支持。只能写成void (*signal(int, void(*)(int)))(int);
本文详细介绍了指针的二级概念、指针数组、字符指针变量、数组指针与使用、二维数组传参的本质以及函数指针的概念,通过实例帮助读者掌握C语言中这些复杂但关键的概念。
135





