目录
一、字符指针
储存字符串的指针
一般来讲,我们可以创建一个字符变量,并在其中,放一个字符,再用一个指针指向这个变量,这就是一个指向字符的指针。但是,指针里面还饿可以放字符串,如下:
当p里面放的是字符串时:此处赋给p的是首字符的地址(即a的地址)
此时里面放的字符串为常量字符串,相当于p的常量变大了(用 *p 修改字符串首位时会报错)(有的编译器会报警告)
因此可以用const修饰,更严谨(这样写的话,如果后面出现了改变*p的操作,编译器会直接提示错误)
在打印字符串时,提供首地址,程序就会开始打印,在遇到 \0 时停止打印
const修饰的字符指针在内存的储存
注意:这里比较的是地址。,而不是内容
p1==p2 说明p1和p2指向同意字符串(常量字符串放在内存的只读区内,不能被修改,为了节约资源,只会在内存里存一份,因此p1 p2 两个指针指向的是同一个位置)
而arr1 和arr2 是两个独立数组,需要各自独立的空间,即首元素地址不同
二、指针数组
指针数组的定义
整形数组:存放整形的数组;字符数组:存放字符的数组;以此类推,指针数组就是存放指针的数组
指针数组的简单运用
由指针数组的定义可知,数组中储存的数据类型为指针,而c语言中,许多数据都可以是指针,比如数组名等,那么,通过对指针数组的运用,我们可以实现一些如类似二维数组的功能
int main()
{
int arr1[] = { 1,2,3,4 };
int arr2[] = { 3,4,5,6 };
int arr3[] = { 6,7,8,9 };
int* parr[] = { arr1,arr2,arr3 };
//这里的数组名前面如果加上&会报警告
int i = 0;
for (; i < 3; i++)
{
int j = 0;
for (; j < 4; j++)
{
printf("%d ", parr[i][j]);
printf("%d ", *(parr[i] + j));
//两种方法都可以成功输出
}
}
return 0;
}
注意:通过指针数组实现二维数组的功能,本质不是二维数组(二维数组中的数据是在内存中连续存放的,这里的三个数组不一定)
三、数组指针
数组指针的定义
整形指针:指向整形的指针;字符指针:指向字符的指针;再一次以此类推,数组指针就是指向数组的指针。
int *p1[10];
//p1是指针数组,意为大小为10的数组内存的类型是int*
int (*p2)[10];
//p2是数组指针,即指向数组的指针,每个元素为int类型,大小为10
//注意:[] 的优先级高于*,因此要用()来保证p和*先结合
&数组名,得到数组地址,数组的地址需要一个变量来存,即数组指针就是用来存放数组地址的
即:arr 为 int ,&arr 为 类型* 地址名 [ ]
数组指针前面的int是数组类型,[] 中为数组大小(大小不能漏,否则会有警告)
(注意:数组指针不是二级指针)
数组指针的使用
当数组的大小未定义,而通过初始化自动分配时,数组指针也必须写清楚数组的大小
因为p是指向数组的,所以*p相当于数组名,即数组首元素地址
指针数组在一维数组的使用:
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int(*parr)[10] = &arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (; i < sz; i++)
{
printf("%d ", *(*parr + i));
}
return 0;
}
可以用,但是及其别扭(合法,但有病)
数组指针一般不用在一维数组,而是二维甚至三维数组
void print1(int arr[3][5], int x, int y)
//对二维数组的每一行进行打印
{
int i = 0;
for (; i < x; i++)
{
int j = 0;
for (; j < y; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print2(int(*p)[5], int x, int y)
//此处p指向第一行,p+1指向第二行,即*p相当于拿到一整行
{
int i = 0;
for (; i < x; i++)
{
int j = 0;
for (; j < y; j++)
{
printf("%d ", *(*(p + i) + j));
//此处*(p + i)拿到第i + 1行的首元素地址,*(*(p + i) + j)拿到第i + 1行第j + 1个元素的地址
printf("%d ", p[i][j]);
//效果同上
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
print1(arr, 3, 5);//直接传递数组
print2(arr, 3, 5);//传递数组名,而二位数组的首元素是它的第一行
//即arr表示第一行的地址
return 0;
}
这种用法只能说是能用,但还是麻烦,因为直接传递数组时,传递的也是地址,此处并未体现使用指针的好处。
关于数组的几个区别
int arr1[10];//数组
int *arr2[10];//整形指针数组
int (*arr3)[10];//数组指针
int (*arr4[5])[10];//存放数组指针的数组
//*arr4[10]即名为arr4指针数组,[10]代表指针指向的数组里有5个元素
四、数组参数、指针参数的传递
一维数组传参
//1、数组传参
//假设调用test函数,参数为数组arr,大小为10
//接收格式:
void test(int arr[]);
void test(int arr[10]);
void test(int *arr);
//2、指针数组传参
//假设调用test函数,参数为数组arr,数组内容为int*,大小为10
//即
int *arr[10] = {0}
//接收格式:
void test(int*arr[10]);
//以数组形式接收
void test(int **arr)
//arr2是指针数组,以二级指针的形式接收
二维数组传参
//假设调用test函数,传递啊一个arr[3][5]的数组
//接收格式:
void test(arr[3][5]);
void test(arr[][5]);
//对于二维数组传参,只能省略第一个[],第二个不能省
//或者用指针数组的方式接收
//注意:二维数组的地址为第一行的地址,即一个一维数组整个数组的地址,
//不能放在一个一级指针或者二级指针里面,即不能用一级/二级指针接收
void test(int (*arr)[5]);
//此处int (*arr)[5]的arr为数组指针,对应一个每行5个元素的数组
指针传参
指针传参时,是什么指针,形参一般就用什么接收
当函数接收的参数为指针时,只要传过去的参数类型能匹配,就可以传递
形参为二级指针时,可以传递的参数:
**p、&p、指针数组、三级指针解引用
五、函数指针
函数指针的定义
函数指针:指向函数的指针
每个函数都有自己的地址
函数指针的储存格式:返回类型 (*指针名)(参数) = &函数名
以test为例: int (*pf)(int,int) = &test(&可以不写)
函数指针的用法
定义函数指针的时候的星号不能去掉, 否则就不是指针了
直接用指针调用函数(调用时,pf前面的星号可以不写,或者随便写几个(写星号一定要带括号),写一个为标准写法)
通过该方法,可以把一个函数传到另一个函数里进行调用(直接调用也可以,这种情况下调用属于多此一举)
以下是一个利用函数指针实现的简单计算器
//用函数指针解决代码冗余(回调函数)
void menu()
{
printf("1.add\n");
printf("2.sub\n");
printf("3.mul\n");
printf("4.div\n");
printf("0.exit\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
//重新封装一个函数
void calc(int(*pf)(int, int))
//通过函数指针调用函数,能够去掉每个case中的冗余代码
//否则需要在每个case中添加用于输入数字和调用对应函数的代码
{
int x = 0;
int y = 0;
int ret = 0;
printf("enter two numbers:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
do
{
menu();
scanf("%d", &input);
int ret = 0;
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("exit");
break;
}
default:
printf("error");
break;
}
} while (input);
return 0;
}
函数指针数组
既然可以用一个指针指向函数,那么把函数指针放在指针数组中,就是函数指针数组、
一个合理的函数指针数组,可以简化代码,使代码的可读性和美观性更好
int(*arr[4])(int, int) = { Add,Sub,Mul,Div };
//arr就是函数指针数组
//只有参数相同、返回类型相同的函数才能放进同一数组
比如,我们可以用函数指针数组来改进上面这段计算器的代码
void menu()
{
printf("1.add\n");
printf("2.sub\n");
printf("3.mul\n");
printf("4.div\n");
printf("0.exit\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int (*parr[5])(int, int) = { 0,Add,Sub,Mul,Div };//前面加个0,使内容对应选项
//又称转移表
do
{
menu();
scanf("%d", &input);
int ret = 0;
if (input == 0)
{
printf("exit");
}
else if (input >= 1 && input <= 4)
{
printf("enter two numbers:");//把输入两个数的选项放进每个case里面,会有很多冗余代码
scanf("%d %d", &x, &y);
ret = parr[input](x, y);//同input的值直接调用函数
printf("%d\n", ret);
}
else
{
printf("error\n");
}
} while (input);
return 0;
}//利用函数指针数组简化代码
指向函数指针数组的指针
六、回调函数
回调函数的定义
回调函数就是一个通过函数指针调用的函数。如果把一个函数的指针作为参数传递给另一个函数,当这个指针被调用时,其指向的函数就是回调函数。
示例代码见:函数指针的用法
一点鄙见
有一说一,作为一个小白,刚开始学指针的时候确实非常懵,。就是不太理解为什么要有这个东西,很多操作,比如调用函数、传递数组等,都是一行代码可以搞定的事情,为什么要多此一举去用指针,而且有时候很难理解代码写的是什么。后来学的东西多了,慢慢发现,好像指针还不错,很多地方可以用指针来简化代码,而且有些东西不用指针是没办法实现的,比如最基础的,在函数里面修改实参,很多人说指针是双刃剑,确实,指针用好了,写出来的代码可以非常优雅,指针用不好写出来的可能就是一个大bug,短短几行代码可以有一万个报错。怎么说呢,一个好的Coder可以被叫做代码诗人,写出来的代码可以像诗一样优美,那我觉得,用好指针应该是成为一名诗人的基础之一,希望我能早日掌握吧。