1.指针与数组的关系
1.1 数组名
先看代码:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
return 0;
}
运行结果是这样的:
我们可以看到,数组名和数组首元素地址打印出的结果一模一样。其实,数组名就是数组首元素的地址。但有两个例外:
- sizeof(数组名):
sizeof
中单独放数组名,表示的是整个数组,计算的是整个数组的大小,单位是字节。 - &数组名:取出的是整个数组的地址。(整个数组的地址和数组首元素的地址是有区别的)。
除此之外,任何地方的数组名只表示首元素地址。
我猜,这个时候,有些朋友会运行下面的代码,想找出arr
和&arr
的差异:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
printf("&arr = %p\n", &arr);
return 0;
}
运行出来三个一模一样的地址,这时,屏幕面前的朋友会质疑区分arr
和&arr
的必要性。
其实他们的差异可以体现在指针的运算上,如arr
与arr+1
相差4个字节,而&arr
与&arr+1
相差40个字节。他们本身的大小不同,运算的跨度自然也相异。
有兴趣的朋友可以运行一下下方的代码:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0]+1);
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr+1);
return 0;
}
1.2 使用指针访问数组
代码如下:
#include<stdio.h>
int main(){
int arr[10] = {0};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
int *p = arr;
for(i=0; i<sz; i++){
scanf("%d",p+i);//可以写arr+i
}
for(i=0; i<sz ; i++){
printf("%d",*(p+i));//可以写*(arr+i)
}
return 0;
}
这段代码中说明了指针访问数组的方式。
我们知道数组元素的访问是用arr[i]
的,那arr
已经赋值给p
,用p[i]
可以达到同样效果吗?
答案是可以,有以下代码:
#include <stdio.h>
int main()
{
int arr[10] = {0};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
int* p = arr;
for(i=0; i<sz; i++)
{
scanf("%d", p+i);
}
for(i=0; i<sz; i++)
{
printf("%d ", p[i]);
}
return 0;
}
运行结果与第一段代码别无二致,所以我们可以知道本质上p[i]
等价于*(p+i)
,同理arr[i]
等价于*(arr+i)
。
数组元素的访问在编译器处理时,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。
1.3 一维数组传参的本质
先看代码:
#include<stdio.h>
void test(int arr[]){
int sz2 = sizeof(arr)/sizeof(arr[0]);
printf("sz2 = %d\n", sz2);
}
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz1 = sizeof(arr)/sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
test(arr);
return 0;
}
运行结果如下:
我们可以看到,在函数内部是没有正确获得数组的元素个数的。
我们知道,数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。
所以函数形参理论上应该用指针变量来接收首元素的地址。正是因为函数的参数部分的本质是指针变量,所以当首元素地址传入函数时,数组名变成了一个彻彻底底的地址,sizeof(arr)返回的是一个地址的大小。
注:
一维数组传参,函数形参的部分既可以写成数组的形式,也可以写成指针的形式。如下函数代码段:
void test(int* arr){//指针形式的形参
printf("%d",sizeof(arr));//计算的是一个指针变量的大小
}
1.4 指针数组
指针数组,听到这个名字,有些朋友就会郁闷了,指针数组是指针还是数组?
我们可以类比一下,整形数组,即存放整形的数组,既然这样,那指针数组不就是存放指针的数组咯。
1.4.1 指针数组的定义
我们在定义整形数组和字符数组时,分别用了int
和char
,所以数组的内容是什么类型就用什么类型来定义。
如定义一个字符指针数组和整形指针数组:
char* arr[5] = {NULL};
int* arr1[6] = {NULL};
1.4.2 指针数组模拟二维数组
代码实现如下:
#include<stdio.h>
int main(){
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};
int arr3[] = {3,4,5,6,7};
int* p[3] = {arr1,arr2,arr3};
int i = 0;
int j = 0;
for(i=0;i<3;i++){
for(j=0;j<5;j++){
printf("%d\t",p[i][j]);//等同于 *(*(p+1)+j)
}
}
return 0;
}
上方代码模拟出了二维数组的效果,但与二维数组有所差异,它的行与行之间不是连续的。
1.5 数组指针变量
1.5.1 对数组指针变量形式的理解
数组指针变量,顾名思义,是用来存放数组地址的。形式如下:
int (*p)[10]
解释:p
先与*
结合,说明p
是一个指针变量,然后指针指向的是一个大小为10个整形的数组。所以p
是一个指针,指向一个数组,叫数组指针。
数组指针的初始化方式如下:
int arr[10] = {0};
int(*p) = &arr;
注:
[ ]
的优先级高于 *
号,所以必须加上()
保证p
先和 *
结合。
1.5.2 二维数组传参的本质
二维数组传参代码如下:
#include<stdio.h>
void test(int a[3][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",a[i][j]);
}
printf("\n");
}
}
int main(){
int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
test(arr, 3, 5);
return 0;
}
二维数组可以看做每个元素为一维数组的数组。那么二维数组的首元素就是第一行,是一个一维数组。如下图所示:
二维数组传参传的也是首元素的地址,也就是第一行这个一维数组的地址,拿上方代码举例子,二维数组中元素是int [5]
;所以首元素地址的类型就是数组指针类型 (int(*)[5])
。当然,函数形参也可以写成指针形式的。如下方函数代码段:
#include<stdio.h>
void test(int (*p)[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",*(*(p+1)+j));
}
printf("\n");
}
}
2. 指针与函数的关系
2.1 函数指针变量
函数指针变量是用来存放函数地址的,函数地址可以通过函数名和&函数名的方式获得。
函数指针类型解析如下:
2.1.1 函数指针变量的创建
函数指针变量的创建有两种情况,代码如下:
无参数时:
void test(){
printf("hello world!!");
}
void (*pf1)() = test;
void (*pf2)() = &test;
有参数时:
int Add(int x, int y){
return x+y;
}
void (*pf3)(int, int) = Add;//x和y写上或省略都可以
void (*pf4)(int x,int y) = &Add;
2.1.2 函数指针变量的使用
先看代码:
#include<stdio.h>
int Add(int x, int y){
return x+y;
}
int main(){
int (*pf3)(int, int ) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));
return 0;
}
输出结果:
可以通过函数指针调用指针指向的函数。
分享俩个有趣的代码:
代码一:
(*(void (*)())0)();
代码二:
void (*signal(int , void(*)(int)))(int);
这两段代码都出自《C陷阱与缺陷》。
2.2 函数指针数组
函数指针数组的定义形式如下:
int (*parr[2])();
parr
先和 [ ]
结合,说明 parr
是数组,数组的内容是 int (*)( )
类型的函数指针。
函数指针数组的用途:转移表
例如,计算器的一般实现:
#include<stdio.h>
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;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
}while(input);
return 0;
使用函数指针数组的实现:
#include<stdio.h>
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;
}
int main()
{
int x, y;
int input = 1 ;
int ret = 0;
int (*p[5])(int x.int y) = { 0, add, sub, mul, div };//转移表
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if(input<=4 && input >= 1)
{
printf( "输⼊操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
printf( "ret = %d\n", ret);
}
else if(imput = 0){
printf("退出计算器\n");
}
else{
printf("输入有误\n");
}
}while(input);
return 0;
}
3. 字符指针变量
字符指针char*
有两种使用方式。
一般使用:
int main(){
char ch = 'a';
char* pc = &ch;
*pc = 'a';
return 0;
}
还有一种使用方式如下:
int main(){
const char* pstr = "hello";
return 0;
}
代码 const char* pstr = “hello”;的本质是把字符串hello首字符的地址放到了pstr中。
《剑指offer》中有一到和字符串相关的题,我们来一起看一下:
#include <stdio.h>
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
不同。