关于指针,大家都知道指针就是用来存储对应类型变量的地址的变量,这一篇文章就来记录我最近学习关于指针的一些新的知识
1 字符指针
字符指针有一些特别,比如下面这条语句
char *ch = "abcdef";
可能会有小伙伴认为,此处是将abcdef的地址放到了ch之中,但实际上,ch中只存储了a的地址,具体证明可以用下列的代码进行证明:
#include<stdio.h>
int main()
{
const char* p1 = "abcdef";
const char* p2 = "abcdef";
char p3[] = "abcdef";
char p4[] = "abcdef";
if (p1 == p2)
printf("p1==p2\n");
else
printf("p1!=p2\n");
if (p3 == p4)
printf("p3==p4\n");
else
printf("p3!=p4\n");
return 0;
}
为什么会输出p1和p2相等而p3和p4不相等呢?
是因为编译器中,abcdef是一个常量字符串,是存放在一个单独的区域(堆区),因此char*类型的p1和p2指向的都是同一个地址,这也证明了像
char * p = "abcdef";
这样写的代码实际上是将字符串的首地址给了p指针,而不是将整个字符串放在了p里面。
2 指针数组
指针数组顾名思义,实际上就是指一种数组,其中的元素全部都是指针变量,如
int * a[20];--->包含20个int* 类型变量的数组
char *b[20];--->包含20个char * 类型变量的数组
利用指针数组可以做到模拟二维数组的目的,虽然不常用,不过在这里依旧给出示范
#include<stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int b[5] = { 2,3,4,5,6 };
int c[5] = { 3,4,5,6,7 };
int* p[3] = { a,b,c };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", *(*(p + i)+j));
}
printf("\n");
}
return 0;
}
当然这个和二维数组依旧是有区别的,因为二维数组的内存地址的一片连续的区域,而这种模拟数组实际上内存并不一定连续的。
3 数组指针
数组指针实际上就是指向数组的指针,
格式为 (指向的数组类型) (* 指针名)[指向数组的大小];
讲到数组指针就有一个绕不过去的问题————那就是关于数组名的使用。
我们都知道,数组名实际上就是数组首元素的地址,但是有两个例外
1:sizeof(数组名) 这里实际上计算的是整个数组的大小
2: &数组名 这里实际上也是取的整个地址的大小。
证明如下:
#include<stdio.h>
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = (&a + 1);
printf("%d ", sizeof(a));
printf("%d ", *(p-1));
return 0;
}
我们可以看到,p指向的是(&a+1)的位置,*(p-1)却是10,因此证明&a是取了整个数组的地址,因此它的加减就是加减整个数组大小的地址,而sizeof(a)也是计算了整个数组的字节大小,由此明白这两个例外。
而数组指针使用方式实际上和&(数组名)脱离不开关系
示例如下:
int a[10] = { 1,2,3,4,5,6,7,8,9,10};
int (*pa)[10] = &a;
这里实际上是将整个数组的地址都给了pa,而*pa则是首元素的地址,
要输出应该这样写:
for(int i = 0;i<10;i++)
printf("%d ",*(*p+i));
但是这种写法其实并不常用,一般都是在传参的时候使用数组指针,比如将二维数组传参时可以用数组指针,示例如下:
#include<stdio.h>
void print(int(*pa)[5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
//printf("%d ", *(*(pa + i) + j));
printf("%d ", pa[i][j]);
}
printf("\n");
}
}
int main()
{
int a[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
print(a, 3, 5);
return 0;
}
两种打印方式都能够打印出来,并且比刚才在main函数中的数组指针更好理解。
4 数组传参,指针形参
1 一维数组传参
当我们将一维数组传参传到函数里面的时候,接收的形参应该是如下几种方式
#include<stdio.h>
void test(int a[10]);
void test(int* a);
void tese(int a[]);
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
test(a);
return 0;
}
一维数组传参比较简单,因此就不详细介绍了。
2 二维数组传参
二维数组传参时,应该有如下几种接受方式
#include<stdio.h>
void test(int a[3][5]);
void test(int a[][5]);
void test(int(*a)[5]);
int main()
{
int a[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
test(a);
}
首先是非指针类型的形参,由于a是一个二维数组,编译器将其作为参数传参过去的时候,编译器可以不知道一共有几行,但是一定要知道一共有几列,这样编译器才能成功的将其分成几组,因此
int a[][5]中的5不可省略。
而虽然传过去的是数组名,但是依旧不可使用int * 来接收,因为这里闯过去的实际上是第一行的地址,因此用数组指针接收。
3 形参是一级指针
当形参是一级指针的时候,我们可以传参的有
1:对应变量的地址
2:一维数组的数组名
3:相同类型的一级指针
4 形参是二级指针
当形参是二级指针的时候,我们可以传参的有
1:一级指针的地址
2:相同类型的二级指针
5 函数指针
这里应该就是各位的知识盲区了,函数指针又是个什么东西;
其实如果有研究过函数栈帧的内容的话,可以知道,函数也是有地址的,因此函数也是可以用指针变量指向的。
#include<stdio.h>
int test(int a)
{
}
int main()
{
int(*pf)(int) = &test;
//int(*pf)(int) = test;
return 0;
}
大概示例就是这个,&函数名和函数名实际上都是函数的地址,而函数指针的类型和函数相同,而里面的括号则是与函数的形参类型一致,不用写出形参的名字。
函数指针的用途之一是解决代码冗余的情况,接下来我来介绍一下函数指针的一个操作:回调函数————>利用函数指针在A函数内部调用BCD等函数。
示例如下:
#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(*arr[4])(int, int) = { Add,Sub,Mul,Div };
for (int i = 0; i < 4; i++)
{
printf("%d ", arr[i](2, 2));
}
return 0;
}
大概调用如下,利用了函数指针数组来计算2+2,2-2,2*2,2/2,结果也是4,0,4,1;
以上就是指针进阶的内容了。