深入理解指针
前言
首先,我们来理解以下指针的概念:
- 指针就是个变量,用来存放地址,而地址标识一块内存空间。
- 指针的大小是固定的4或8个字节(32位或64位)。
- 指针是有类型的,其类型决定了指针运算的步长,以及解引用的操作权限。
一、字符指针
字符指针,顾名思义,就是指向字符类型的指针。
例如:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
字符指针保存的就是一个字符的地址。
还有一种使用方式:
int main()
{
char* pstr = "hello";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}
该如何理解 char* pstr = “hello”;呢,是把一个字符串放到指针变量里去了吗?
其实不是的,只是把字符串首字符的地址放入了pstr中。同时这句代码声明的一个只读字符串,是不能修改的,加上const修饰。
让我们再来看一个例子:
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 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
这段代码结果是什么呢?它们的地址都相同吗?
请看下面的打印结果:
str3和str4的地址是一样的,这是因为它们指向的是同一个常量字符串。C/C++会把常量字符串存储到一个单独的内存区域,当有多个常量指针指向同一个字符串时,它们实际上指向的是同一块内存的地址。
但是用相同的常量字符串去初始化不同的数组就会开辟不同的内存块,所以说str1和str2不同,str3和str4相同。
二、指针数组
指针数组,顾名思义,就是一个存放指针的数组。比如:
- int* arr1[10]; //整型指针数组
- char* arr2[10]; //一级字符指针数组
- char** arr3[10]; //二级字符指针数组
三、数组指针
1.数组指针的定义
首先,数组指针,它是一个指针。所以其所指向的元素类型是一个数组。
int *p1[10];
int (*p2)[10];
那上面两种那种是数组指针呢?
我们来分析一下,首先第一个p1首先和[]结合,因为*的优先级较低,所以它是个数组,元素类型是整型指针。第二个p2首先是一个指针,指向的类型是一个元素数量为十的整型数组。所以第二种才是数组指针。
2.数组名和&数组名
我们知道数组名代表的是首元素地址,&数组名呢?
先来看一段代码:
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
其结果是:
可以发现二者打印的地址都是一样的。但是他们两个真的一样吗?
再来看一段代码:
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr + 1);
printf("&arr+1= %p\n", &arr + 1);
return 0;
}
其结果如下:
虽然他们指向的地址是一样的,但是指针的运算确实不同的,因为我们知道,指针的运算是根据指针所指向的类型决定的,既然运算结果不同,那他们的类型也一定不同。
实际上:&arr表示的是整个数组的地址,虽然地址是从首元素地址开始的,但是其却不是首元素地址。而对数组地址 + 1就会跳过一个数组的大小。
四、函数指针
函数指针就是指向函数的指针:
int f(int);
int (*pf)(int) = &f;`在这里插入代码片`
上面两条语句,第一个声明了一个函数,参数为int类型,返回值为int类型的函数。第二行定义并初始化了一个函数指针,指向的函数类型就是第一行的函数类型。
1.函数指针数组
函数指针数组,首先来说是一个数组,数组元素是函数指针。如下:
int f(int); //函数
int (*pf)(int) = &f; //函数指针
int (*pfarr[5]) (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;
}
void menu()
{
printf("******************************\n");
printf("****** 1.add 2.sub ******\n");
printf("****** 3.mul 4.div ******\n");
printf("********** 0.exit **********\n");
printf("******************************\n");
}
int main()
{
int input;
int (*pfarr[5])(int, int) = { 0, add, sub, mul, div };
do
{
menu();
printf("请选择计算方式:");
scanf("%d", &input);
if (0 == input)
{
printf("退出程序!\n");
}
else if (input > 0 && input < 5)
{
int x;
int y;
printf("请输入操作数:");
scanf("%d %d", &x, &y);
int temp = pfarr[input](x, y);
printf("%d\n", temp);
}
else
{
printf("输入错误,请重新输入!\n");
}
} while (input);
return 0;
}
通过转移表调用函数,实现不同的操作。
2.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
首先我们来看一个qsort函数:
int cmp_int(const void* e1, const void* e2)
{
if (*(int*)e1 < *(int*)e2)
{
return -1;
}
else if (*(int*)e1 > *(int*)e2)
{
return 1;
}
else
{
return 0;
}
}
int main()
{
int arr[10] = { 8,6,2,9,7,3,5,1,4,0 };
int len = sizeof(arr) / sizeof(arr[0]);
qsort(arr, len, sizeof(int), cmp_int);
int i;
for (i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
qsort的第四个参数就是一个函数指针,通过这个函数指针在qsort函数中对这个函数进行调用。
总结
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表示首元素的地址。
- 可以使用函数指针来实现回调函数。
- 转移表也使用函数指针,但应注意,这些函数必须具有相同的函数原型。