在指针的类型中,有一种指针类型为字符指针 char*
一般使用方法是:
int main()
{
char ch='g';
char* p=&ch;
printf("%c",*p);
return 0;
}
还有另一种使用方法:
#include<stdio.h>
int manin()
{
const char* p="abcdef";//常量字符串,不能被修改。这里的赋值是把字符串中首字符的地址赋值给p
printf("%s\n",p); //使用%s打印字符串的时候,只需要知道字符串首字符的地址就行
//*p='w'; // 如果在给字符指针变量p赋值时没有加const修饰时,写该语句语法上没问题不会显示错误,但在运行时会出错,所以要用const修饰
return 0;
}
例题:
#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不同,str3和str4相同。
上一章我们讲解了指针数组,它是存放指针(地址)的数组,那么指针数组是指针还是数组呢?
数组指针是指针变量
整型指针变量:int * p; 存放的是整形变量的地址,能够指向整形数据的指针。
浮点数指针变量:float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
int (*p)[10];
上面是一个指针变量的声明,p先与*结合,说明p是一个指针变量,然后指针指向的是⼀个大小为10个整型的数组。所以p是 ⼀个指针,指向⼀个数组,叫数组指针。
注:[ ] 的优先级要高于 * 号的,所以必须加上()来保证p先和*结合。
int arr[10]={0};
int(*p)[10]=&arr;
数组指针类型解析:
有了对数组指针的理解我们就能够讲解二维数组的本质了。
过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:
#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;
}
这里的实参和形参都写成了二维数组的形式,其实还有另一种写法
#include <stdio.h>
void test(int (*arr)[5], int r, int c)
//void test(int arr[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 ", *((arr + i) + j));
//printf("%d ", arr[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;
}
arr[i] == *(arr+i) arr[i][j] == *(*(arr+i)+j)
首先我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀行,是个⼀维数组。
因为数组名是数组首元素的地址,所以第一行就是二维数组首元素的地址,第一行的⼀维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5] 。所以⼆维数组传参本质上也是传递了地址,传递的是第一行这个⼀维数组的地址,
总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。
通过前面我们学习的字符指针,数组指针类比,函数指针应变量应是用来存放函数地址的,能通过地址调用函数的。
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
通过上面的代码可知,函数是有地址的,并且在通过指针调用函数时,函数名就是函数的地址,也可以通过 &函数名 的方式获得函数的地址。
如果我们想要存放函数的地址,就需要创建函数指针
void test()
{
printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)()= test;
int Add(int x, int y)
{
return x+y;
}
int(*pf3)(int, int) = Add;
//int(*pf3)(int x, int y) = &Add;
//x和y写上或者省略都是可以的
函数指针解析:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 0;
int* p = &a; //整型指针变量
int arr[5] = { 0 };
int(*pa)[5] = &arr; //数组指针变量
int (*pi)(int,int)=&Add; //函数指针变量
//int (*pi)(int x, int y) = Add;
//int (*) (int,int)函数指针类型
int ret = (*pi)(3, 6);
//int ret = pi(3, 6);
//pi前的*可写可不写,也可以写多个
//函数名和&函数名都是函数的地址,没有区别
printf("%p", &Add);
printf("%p", Add);
return 0;
}
//代码1
(*(void (*)())0)();
代码讲解:
数字 0 本身的类型是整型int,void(*)()是函数指针类型,在它的外面加了一个括号是强制类型转换,将 0 有整型转换成了函数指针类型,所以该代码是一次函数调用,调用0地址处放的那个函数,0地址处放的这个函数是没有参数的,返回类型是void。
//代码2
void (*signal(int , void(*)(int)))(int);
代码讲解:
上面的代码是一次函数声明,函数名字叫signal,signal函数参数有两个,第一个参数类型是int,第二个参数类型是函数指针void(*)(int) 该函数指针指向的函数参数是int,返回值是void signal函数的返回类型也是一种函数指针,类型是void(*)(int)
typedef是⽤来类型重命名的,可以将复杂的类型,简单化。
比如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就方便多了,那么我们可以使用:
typedef unsigned int uint;
//将unsigned int 重命名为uint
指针类型也可以重命名
typedef int* ptr_t;
但是数组指针和函数指针有些不同之处
数组指针类型:
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
函数指针类型:
typedef void(*pfun_t)(int);
我们可以利用这个来简化代码2:
typedef void(*pfun_t)(int);
pfun_t signal(int pfun_t);
数组是⼀个存放相同类型数据的存储空间,之前我们已经学习了指针数组
int * arr[10];
//数组的每个元素是int*
那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组
int (*p[3]) ()
p 先和 [ ] 结合,说明 p 是数组,是int (*)() 类型的函数指针。
函数指针数组的用途:转移表
例:计算器的一般实现:
法一(switch语句):
#include<stdio.h>
menu()
{
printf("*****1.Add 2.Sub******\n");
printf("*****3.Mul 4.Div******\n");
printf("0.exit\n");
printf("请选择:");
}
Add(int x, int y)
{
return x + y;
}
Sub(int x, int y)
{
return x - y;
}
Mul(int x, int y)
{
return x * y;
}
Div(int x, int y)
{
return x / y;
}
int main()
{
int x = 0;
int y = 0;
int ret = 0;
menu();
int input = 0;
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入:");
scanf("%d %d", & x, &y);
ret=Add(x, y);
printf("结果为:%d", ret);
break;
case 2:
printf("请输入:");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("结果为:%d", ret);
break;
case 3:
printf("请输入:");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("结果为:%d", ret);
break;
case 4:
printf("请输入:");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("结果为:%d", ret);
break;
case 0:
printf("退出程序");
break;
default:
printf("输入错误");
break;
}
return 0;
}
法二(使用函数指针数组实现):
#include<stdio.h>
menu()
{
printf("*****1.Add 2.Sub******\n");
printf("*****3.Mul 4.Div******\n");
printf("0.exit\n");
printf("请选择:");
}
Add(int x, int y)
{
return x + y;
}
Sub(int x, int y)
{
return x - y;
}
Mul(int x, int y)
{
return x * y;
}
Div(int x, int y)
{
return x / y;
}
int main()
{
int x = 0;
int y = 0;
int ret = 0;
int(*parr[5])(int,int) = {0,Add,Sub,Mul,Div};
menu();
int input = 0;
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入操作数");
scanf("%d %d", &x, &y);
ret = (*parr[input])(x, y);
printf("%d", ret);
}
else if (input == 0)
{
printf("退出程序");
}
else
printf("输入错误");
return 0;
}
法三(回调函数):
#include<stdio.h>
menu()
{
printf("*****1.Add 2.Sub******\n");
printf("*****3.Mul 4.Div******\n");
printf("0.exit\n");
printf("请选择:");
}
Add(int x, int y)
{
return x + y;
}
Sub(int x, int y)
{
return x - y;
}
Mul(int x, int y)
{
return x * y;
}
Div(int x, int y)
{
return x / y;
}
calc(int(*p)(int, int))
{
int x, y;
int ret = 0;
printf("请输入操作数:");
scanf("%d %d", &x, &y);
ret = p(x, y);
printf("%d", ret);
}
int main()
{
int x = 0;
int y = 0;
int ret = 0;
menu();
int input = 0;
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出程序");
break;
default:
printf("输入错误");
break;
}
return 0;
}