【指针(4)-- 深入理解指针】
作者主页:lightqjx
内容专栏:C语言
目录
一、字符指针
(1)介绍
(2)与数组的比较
二、数组指针
三、函数指针
四、函数指针数组
一、字符指针
(1)介绍
字符指针 是 存放字符地址的指针,是指向字符的指针,它的类型为 char* 。
对于单个字符来说,可以这样使用:
#include<stdio.h>
int main()
{char ch = 'a';char* pc = &ch;*pc = 'w';printf("%c\n", *pc);return 0;
}
其中pc就是一个字符指针。
对于常量字符串,一般是这样使用的:
#include<stdio.h>
int main()
{char* p = "abcdef";printf("%s\n", p);return 0;
}
其中,p就是一个字符指针。对于常量字符串,虽然通过p可以打印出整个字符串,
但是,本质上,是把字符串首字符的地址存到了p中。
但是,在一些比较严谨的编译器中,这样写也会出现一些问题。比如这样写:
#include<stdio.h>
int main()
{char* p = "abcdef";*p = 'w';printf("%s\n", p);return 0;
}
就会出现:
所以,就不能这样写代码。这是因为这是字符串常量,不能修改,当对它进行修改时会导致程序崩溃。因此应该要加上const后,这才是标准写法。即:
const char* p = "abcdef";
若此时在对它进行修改,则会出现:
这时若对它进行修改程序就会报错,更加方便识别。
(2)与数组的比较
当然,在理解了字符指针后,就应该也要知道它的在内存中的开辟方式。来看一下代码:
#include <stdio.h>
int main()
{char str1[] = "hehe";char str2[] = "hehe";const char* str3 = "hehe";const char* str4 = "hehe";if (str1 == str2)printf("same\n");elseprintf("not same\n");if (str3 == str4)printf("are same\n");elseprintf("not same\n");return 0;
}
它的运行结果:
----为什么呢?
这时因为数组和字符指针对于相同字符串的开辟空间的方式有差异。
对于数组来说,每创建一个数组,都会开辟一个内存块,即str1和str2中的"hehe"是在不同的空间中的,所以数组名str1和str2的指向的地址就不同。当几个指针指向同一个字符串的时候,他们实际会指向的是同一块内存。所以str1和str2不同,str3和str4相同。
二、数组指针
数组指针其实是数组指针变量,它是一个指针变量,下面使用类比的方法可以更好的理解它:
我们知道:
- 字符指针变量,存放是字符变量的地址,指向的是字符变量
- 整型指针变量,存放的是整型变量的地址,指向的是整型变量
- 所以,数组指针变量 ---> 存放的就是数组的地址,指向的是数组。
这就是数组指针变量的基本意义。
而数组指针变量的声明格式则是:
数据类型 (*变量名) [一维数组长度];
因为对于一个数组来说,也是有地址的,数组的地址就是通过“ &+数组名 ”来得到的,而数组指针变量就可以存放这个地址,所以数组指针变量的使用就可以向下面这样:
int arr[5] = { 1,2,3,4,5 };
int (*p)[5] = &arr; //&arr是指整个数组的地址
这里的p就是数组指针变量。
这时使用编译器来观察就可以发现,&arr和p的类型和值是完全一样的:
对于数组指针变量类型的解析:
在了解了数组指针变量之后,那么什么时候会使用到它呢?
下面就用一个示例来加深理解:模拟二维数组进行打印
一般我们传递二维数组参数时,形参就可以直接用二维数组来接收,即:
void Print(int p[3][4], int row, int col);
void Print(int p[][4], int row, int col);//行可以省略,但列不可以省略
而现在我们不采用这种写法,来模拟二维数组,那么就需要先理解二维数组传参的本质。
我们知道二维数组,可以看作一维数组的数组,即二维数组的每个元素都是一个一维数组,所以二维数组的首元素就是第一个一维数组,即第一行。
根据数组名是数组首元素的地址的规则,二维数组的数组名表示的就是第一行的地址,即一维数组的地址。所以二维数组在传参时传递的就是这个二维数组中一维数组(即第一行)的地址。在上述整型二维数组中,数组首元素的带有4个整型的一维数组,所以该一维数组的类型就是:int [4] , 所以第一行的地址类型就是: int (*)[4] ,因此,在函数传递二维数组地址时的参数类型就应该就可以写成这种数组指针变量类型的形式。
#include <stdio.h>
void Print(int(*p)[4], int row, int col)
{int i, j;for (i = 0; i < row; i++){for (j = 0; j < col; j++){printf("%d ", p[i][j]); //写法不唯一}printf("\n");}
}
int main()
{int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };Print(arr, 3, 4);//打印二维数组return 0;
}
上述代码的输出部分写法是不唯一的。
因为p是数组指针变量,存放的是二维数组第一行的地址,也就是说 p==&(第一行的一维数组的数组名) ,对它解引用也可以得到第一行的一维数组的数组名,然后就可以对其中的元素进行访问了,简而言之,如果p只想第一行一维数组的地址,那么p+1指的就是第二行一维数组的地址。
printf("%d ", (*(p + i))[j]);
printf("%d ", *(*(p + i) + j));
这样也是可以的。
三、函数指针
函数指针即函数指针变量。即指向函数的指针,存储的是一个函数的地址。
函数指针的使用:
int add(int a,int b);//已知一个函数//函数指针的声明
int (*pf)(int a, int b) = &add;
int (*pf)(int, int) = &add;//参数类型只写类型也可以
第二种写法,在这个声明中,参数名称a和b被省略了。这是因为在函数指针的声明中,参数名称是可选的。编译器只需要知道参数的类型,而不需要知道它们的名称。
当我们实际使用到它时,我们就会发现,即:
int (*pf) (int a, int b) = add;
int (*pf) (int, int) = add;
也是正确的。因为在C和C++中,函数名本身就代表了函数的地址,在这里,add既是函数名,也代表了该函数的地址。
这些就是将函数的地址存储在一个地址中的函数指针的方法。
对函数指针变量进行解析:
而这个函数指针的类型则是:
int (*) (int a,int y)
函数指针的使用:
#include <stdio.h>
int Add(int x, int y)
{return x+y;
}
int main()
{int(*pf)(int, int) = &Add;//&符号可以省略printf("%d\n", (*pf)(2, 3));//输出5printf("%d\n", pf(3, 5));//输出8return 0;
}
(*pf)(2, 3):这是通过函数指针调用函数的传统方式。*pf3 解引用函数指针,然后调用它。
pf(3, 5):在C语言中,函数指针可以直接像函数名一样使用,不需要解引用。因此,pf3(3, 5) 和 (*pf3)(3, 5) 是等价的。
四、函数指针数组
函数指针数组是存放函数指针的数组,即存储函数地址的数组。
函数指针的定义:
int (*parr[3])();
parr 先和 [ ] 结合,说明 parr是数组,数组的大小是 3。,数组的每个元素是 int (*)( ) 类型的函数指针。每个函数指针指向的函数:返回类型是 int。() 表示无参数。
所以,int (*parr[3])(); 可以理解为:
parr 是一个包含 3 个函数指针的数组,每个指针指向一个无参数且返回 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 a, b;int input = 0;int ret = 0;do{printf("************************\n");printf("** 1.add 2.sub **\n");printf("** 3.mul 4.div **\n");printf("************************\n");printf("请选择:");scanf("%d", &input);switch (input){case 1:printf("请输入两个操作数:");scanf("%d%d", &a, &b);ret = add(a, b);printf("%d\n", ret);break;case 2:ret = sub(a, b);printf("请输入两个操作数:");scanf("%d%d", &a, &b);printf("%d\n", ret);break;case 3:printf("请输入两个操作数:");scanf("%d%d", &a, &b);ret = mul(a, b);printf("%d\n", ret);break;case 4:printf("请输入两个操作数:");scanf("%d%d", &a, &b);ret = div(a, b);printf("%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 a, b;int input = 0;int ret = 0;do{printf("************************\n");printf("** 1.add 2.sub **\n");printf("** 3.mul 4.div **\n");printf("************************\n");printf("请选择:");scanf("%d", &input);int (*pf[5])(int, int) = { 0,add,sub,mul,div };//函数指针数组的运用if (1 <= input && input <= 4){printf("请输入两个操作数:");scanf("%d%d", &a, &b);ret = (*pf[input])(a, b);}else{printf("输入错误\n");}} while (input);return 0;
}
当然,关于指针的使用还有许多,比如:指向函数指针数组的指针等,它们可以按照类比,得出它们各自的使用方法。
- 在此感谢各位的阅读!