C语言中的各种指针和数组的特点 一次学会所有指针
目录
一、字符指针
第一种字符指针的用法是把一个存放了字符的变量的地址放入一个字符指针当中,此时这个字符指针就会指向这个字符变量,如下:
char a = 'w';
char *p=&a;
*p = 'w';
字符指针的第二种用法是指向一个常量字符串,此时*p为一个常量,不可被修改,因此在定义的时候再char *前面加上const更为合理,表示p所指向的内容不能改变,如下:
//此时const在char *前表示p所指向的内容不能改变
const char * p = "hello";
这种用法的实质是把常量字符串首字符的地址"h"存放到了指针变量p中。
const修饰指针时也有两种情况,就拿这里的字符指针来举例子,当const修饰char *时表示指针所指向的内容不能发生改变,但指针的指向可以改变,若要改变指针指向的内容时会报错
char a = 'w';
char b = 'x';
const char *p1=&a;
const char *p2=&a;
//可以改变指针的指向
p1 = &b;
//这里会报错 因为*p2被const修饰 不能改变p2指向的内容
*p2 = b;
当const修饰变量名时,此时指针的指向不能改变,但指针所指的内容可以发生改变
char a = 'w';
char b = 'x';
char * const p1=&a;
char * const p2=&a;
//这里会报错 因为不可以改变指针的指向
p1 = &b;
//可以改变指针所指的内容
*p2 = b;
二、指针数组
指针数组的本质是一个数组,其中存放的数据类型是指针,就拿int* arr[5]来说,我们可以拿int arr[5]来比较,int arr[5]这个是一个我们很熟悉的数组,这个数组的长度为5,其中存放的每个数据的类型都是int;同样int* arr[5]也是一个数组,这个数组的长度为5,其中存放的每个数据的类型为int*,我们把这样的数组叫做指针数组。
三、数组指针
顾名思义,数组指针就是一个指针,他所指向的内容是一个数组,例如有一个数组int arr[5],
我们要定义一个指针来指向这个数组,那就可以这样,int(*p)[5],其中*p表示这是一个指针变量,[5]表示这个指针指向了一个长度为5的数组,int表示p所指向的数组中存放的数据类型为int。
四、数组名表达的含义
那int arr[5]来举例子,只有在sizeof 数组名和&数组名时,数组名表达的是整个数组,其他情况下,数组名表示的都是数组的首元素地址。
int arr[5]={0,1,2,3,4,5};
int *p1=&arr;//此时的arr表示的是整个数组的地址
int *p2=arr;//此时的arr表示的是数组首元素的地址
int *p3=p1+1;//此时+1表示跳过整个数组大小的地址
int *p4=p2+1;//此时+1表示跳过数组首元素大小的地址
五、指针传参
一维数组在传参时候,本质是把数组首元素的地址传递给形参,因此虽然我们在形参接受数组的时候虽然可以写成数组的形式,但是实际上形参的本质还是一个指针,因此形参[]里的数字没有意义可以省略,所以一维数组传参的时候,我们在形参接受的时候可以有三种形式,如下:
//下面三种形式都可以正常完成形参的传递
test(int arr[5]){}
test(int arr[]){}
test(int *p){}
int main()
{
int arr[5]={0,1,2,3,4};
test(arr);
return 0;
}
二维数组在传参的时候和一维数组有一定的区别。首先我们要了解二维数组在内存空间中也是连续存放的并不是真正的二维形式存放的,例如int arr[2][5]这个数组我们可以看成是两个int [5]的数组拼接在一起的,同时这个二维数组的首元素并不是第一个元素,而是第一个数组,因此我们在进行参数的传递时,可以用数组的形式接收,也可以用一个数组指针来接收,如下:
//下面三种形式都可以正常完成形参的传递
test(int arr[2][5]){}
//在用数组形式接收时,可以不用知道几行 但是必须要知道每行有几个元素
test(int arr[][5]){}
test(int (*p)[5]){}
int main()
{
int arr[2][5]={{0,1,2,3,4},{5,6,7,8,9}};
test(arr);
return 0;
}
当用数组指针来接收时,对指针解引用时也有两种方式,用打印这个数组中的所有元素来举例
Print(int (*arr)[5],int r,int c)
{
for(int i=0;i<r,i++)
{
for(int j=0;j<c;j++)
{
printf("%d",*(arr+i)[j]);//*(arr+i)表示第i行
}
}
}
Print(*arr)[5],int r,int c)
{
for(int i=0;i<r,i++)
{
for(int j=0;j<c;j++)
{
printf("%d",*(*(arr+i)+j));//*(*(arr+i)+j)表示第i行中的第j个元素
}
}
}
//由此可以看出arr[i]和*(arr+i)是等价的
int main()
{
int arr[2][5]={{0,1,2,3,4},{,5,6,7,8,9}};
Print(arr,2,5);
return 0;
}
当函数传递的参数为一个指针时,我们的形参应当用一个指针来接收,我们称这个接收指针的指针变量为二级指针,表现形式如下:
//这里的pp就是一个二级指针变量 我们可以把它拆分成两个部分
//第一个部分是*pp 表示pp是一个指针变量
//第二部分是int* 表示pp这个指针变量指向的内容是int*类型的
void test(int **pp){}
int main()
{
int a=0;
int *p=&a;
test(&p);
return 0;
}
六、函数指针
从名字来看,函数指针也是一个指针,只不过这个指针变量指向的内容是一个函数而已。表现形式如下:
void test(){}
//用()将*和pfun结合起来 表示pfun是一个指针变量
//最后的()表示pfun指向了一个
//void 表示pfun指向的函数的返回值是void类型
void (*pfun)();
如果是要用函数指针调用这个函数,只要用*对这个指针变量进行解引用再传入对应的参数即可。
七、函数指针数组
同样的我们从名字上来理解,函数指针数组的本质就是一个数组,只不过这个数组内存放的每个元素变成了函数指针类型的而已,如下:
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()
{
//这里的pf是一个函数指针变量
//*pf表示pf是一个指针 (int ,int)表示pf指向的函数有两个参数 分别是int和int
//最前面的int表示所指的函数的返回值类型为int
int (*pf)(int ,int)=Add;
//只要在函数指针变量的基础上加一个[]就变成了函数指针数组
int (*parr[4])(int ,int)={Add,Sub,Mul,Div};
return 0;
}
利用这个函数指针数组就可以实现转移表。