指针的进阶
1.指针就是变量,用来存放地址,地址唯一标识一块内存空间。
2.指针的大小是固定4/8个字节(32位平台/64位平台)
3.指针时有类型,指针的类型决定了指针的+-
整数的步长,不同类型的指针,决定了指针解引用后能访问几个字节。
float* 加1,加4个字节
int* 加1,加4个字节
double* 加1,加8个字节
char* 加1,加1个字节
4.指针的运算
对于void *p
类型指针,它是无类型指针,其主要在传参时使用,对于这种类型的指针不能对它进行+-
法的操作,此处p+1
或*p
就属于不合法的。
#include<stdio.h>
int main(){
int a = 10;
void *p = &a;
printf("%d\n", p+1);
printf("%d\n",*p);
return 0;
}
对于void * 类型的指针,加减操作和解引用操作都是不可以的
一、字符指针
字符指针char*
,一般使用:
int main(){
char ch = 'w';
char *pc = &ch; 此时把p就叫做字符指针
return 0;
}
此处对于另一种指针字符串的形式
int main(){
char *ps = "hellp"; 此时ps还是一种字符指针,
指针是用来存放地址的,此处存放的是字符串"hello"在内存当中的地址,存放的是h的地址
而对于这个字符串而言,它是用""双引号来定义的,即它将被存放在常量区(只读区)
}
例1:关于数组存放字符串与指针指向字符串的一道题
int main(){
char str1[] = "hello bit.";//数组中存放的是 h e l l o b i t . \0
char str2[] = "hello bit.";
char *str3 = "hello bit."; //指针存放的是h的地址
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){//双引号引起来的将存放在只读区
//则"hello"在只读区就只有一份
//两个指针都会指向这一个区域
//即->str3和str4都保存的是h的地址
printf("str3 and str4 are same\n");//会输出这一句
}
else{
printf("str3 and str4 are not same\n");
}
}
例2.关于字符指针传参时的一些问题:
//在传参的过程中
void func1(char *p){ //如果这里是char *p
}
int main(){
//那么在调用func1时,有以下几种传法
//第一种
char c = 'a';
func1(&c); //传过去的是一个字符地址
//第二种
char *str3 = "hello bit.";//当我们这样定义了str3
func1(str3); //这里str3存放的就是h的地址
func1(&str3);//是不能这样传的,这里取地址,相当于是取了指针的地址,此时在函数的形参部分就要拿二级指针接受(char **p )
//第三种 传一个数组
char str1[] = "hello bit";
func1(str1); //调用func1时,可用数组名作为实参,因为数组名就代表了数组首元素的地址
}
二、指针数组
指针数组是一个存放指针的数组
int main(){
int* arr[5]; //一个长度为5,里面存放的是int*的数组
}
对于以下三种形式
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5]; //每一个元素是一个二级指针(即一级指针地址)
三、数组指针
数组指针的定义
数组指针是能够指向数组的指针
int main(){
int (*p)[5]; 一个指针,指向一个数组,数组中每个元素为int类型
}
对于下面两种形式分别是什么?
int *p1[10]; ->指针数组
int (*p2)[10]; ->数组指针
[]的优先级要高于*号,所以必须加上()来保证p先和*结合
&数组名 与 数组名
int main(){
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
printf("%d\n", arr); //代表数组首元素的地址
printf("%d\n", &arr[0]); //代表数组首元素的地址
printf("%d\n", &arr); //整个数组的地址
//以上这三个虽然输出一样,但表示的含义并不完全相同
所以此处可以通过指针来接受这个数组地址
int *p = arr; //等号右边是一个数组名,用int *p来保存arr
int *p2 = &arr[0]; //等号右边是对arr第0个元素取地址,也可以用int *p来保存
int (*p3)[10] = &arr;//等号的右边是&arr,表示对整个数组取地址,此时这个指针应该指向这个数组,即指向数组的指针应该是一个数组指针
//这里的p3是一个指针,指向一个长度为10的数组,数组内每一个元素为int 型
}
那么针对上面数组指针,我们提出一个问题,如何通过p3来访问数组当中的元素?
int *p = arr;
对于第1中如何访问,数组中的元素?
这里p代表的数组首元素的地址,则有
for(int i = 0; i<size; i++){
printf("%d\n",*(p+i)); //此处p+i相当于是地址
//对p+i进行解引用即*(p+i)则得到当前地址的内容
//这里 *(p+1) 就相当于 p[1] 的意思
int *p2 = &arr[0]; //与第一种情况类似
int (*p3)[10] = &arr;
//此处若进行 p3+1 ,则他代表加了一整个数组,在此处相当于加了40个字节,相当于&arr+1
那么此处如何只加一个元素呢?
printf("%d",*(*p3+1)); 此处对p3进行解引用 *p3 就拿到了p3的地址,之后再加一 (*p3+1),此时加1就相当于加4个字节,这样
就可以向后走一位的地址,之后在进行解引用*(*p3+1),就拿到新地址中的值
数组指针的使用
对于二维数组,打印出数组中的每一个元素的方法是:
void Show(int arr[][5], int row, int col){//为了打印这个二维数组中的每一个元素
for (int i = 0; i < row; i++){
for (int j = 0; j < col; j++){
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
int main(){
int arr[3][5] = { 0 }; //我们有一个三行五列的二维数组
Show(arr,3,5);
}
二维数组是一个特殊的一维数组,即它的每一个元素就是一个一维数组。以这个三行五列的二维数组为例,此处这个特殊的一维数组第一个元素就是第一行的这个一维数组。
那么二维数组的首元素是一个一维数组,即二维数组的数组名就代表一维数组的地址
voud Show1(int(*p)[5], int row, int col){ //通过一个数组指针来接收
for (int i = 0; i < row; i++){
for (int j = 0; j < col; j++){
//printf("%d ",p[i][j]);
//printf("%d ",*(p+i)[j]);
printf("%d ",*(*(p+i)+j) );
}
printf("\n");
}
}
int main(){
int arr[3][5] = {0};
二维数组的数组名代表一维数组的地址,一维数组的地址就可以使用数组指针来接受
则此处可以使用这种写法
int (*p)[5] = arr; //对于这种写法,我们就可以将打印数组元素的函数修改为Show1()函数
Show(arr,3,5); //这里arr传递的是数组名
}
分析int (*parr[10])[5];
int (*parr[10])[5]; //存放数组指针的数组
对它进行拆开分析
首先分析最内部()优先级最高部分
parr3[10] 是一个长度为10的数组
数组中存放的是 int (*)[5]
意味着,这个数组长度为10,里面的每一个元素指向一个长度为5的数组,数组中存放的是int类型。
它的存放形式可以用以下代码理解
int main(){
int (*p)[5] = {0}; //数组指针
int (*parr[10])[5] = {p};
数组长度为10,里面每一个元素都是一个数组指针
}
数组参数、指针参数
一维数组传参
void test(int arr[]){
}
void test(int arr[10]){
}
void test(int *arr){
}
int main(){
int arr[10] = { 0 }; //整形数组
test(arr); //数组名代表数组首元素的地址
void test2(int *arr[20]){//原生类型
}
void test2(int **arr){ //用二级指针接收指针的地址
}
int main(){
int *arr2[20] = { 20 }; //数组里面放的int*类型
test2(arr2); //数组名代表数组首元素的地址
//此时数组的首元素是 int*p
// int*p的地址相当于是p的地址,即&p。
}
二维数组传参
void test(int arr[3][5]){ √
}
void test(int arr[][]){× 不能够省略列
}
void test(int arr[][5]){ √
}
void test(int *arr){ × 首元素是一个一维数组的地址,不能拿一个简单的指针来接收
此处的一级指针接受的是 一维数组的数组名 或者是 普通的整形变量的地址
}
void test(int* arr[5]){ // × 这是一个指针数组
}
void test(int(*arr)[5]){ √ 这是一个数组指针,指向一个存放5个整形的数组
}
void test(int **arr){ × 二级指针是用来存放一级指针的地址,此时是一个数组,数组的地址是一个数组指针,而并非指针的地址。
}
int main(){
int arr[3][5] = { 0 };
test(arr); //传了一个二维数组的数组名
//此处首元素代表了一维数组的地址
}
一级指针传参
#include<stdio.h>
void print(int *p, int size){ //int *p 这里传过来的一定是一个int类型的地址
int i = 0;
for (i = 0; i < size; i++){
printf("%d\n", *(p + i));
}
}
int main(){
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int *p = arr; //数组名,表示数组首元素的地址,首元素为1,是一个整形,所以这里可以用int*来接收
int size = sizeof(arr) / sizeof(arr[0]);
//一级指针p,传给函数
print(p, size);
return 0;
}
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
比如:
void test1(int *p){ //此处p接收的是int类型的地址
//*p 表示p是一个指针,传过来的地址是一个int类型的地址
}
viod test2(char *p){ //此处p接收的是char类型的地址
}
二级指针传参
二级指针传参,传的一定是一级指针的地址
#include<stdio.h>
void test(int **ptr){ //此处 *ptr代表ptr是指针,int**ptr表示传过来的类型为int*, int*表示的就是一个指针,所以这里传过来的就是指针的地址
printf("num = %d\n", **ptr);
}
int main(){
int n = 10; //普通变量是10
int *p = &n; //p是一个指针,可以接收n的地址
int **pp = &p; //pp是一个二级指针,接收p的地址,p本身是一个int*类型的
test(pp); //这里的实参pp,与函数中的形参ptr都是int**,都是存放p的地址
test(&p);
return 0;
}
当函数的参数为二级指针时,可以接受什么参数?
void test(char **p){ //p接收的一定是char*类型的
}
int main(){
char c = 'b';
char *pc = &c;
char **ppc = &pc;
char *arr[10]; //指针数组,长度为10,每个元素是char*的
test(&pc);
test(ppc);
test(arr);
return 0;
}
函数指针
指向函数中的指针
#include<stdio.h>
void test(int a){
printf("hello\n");
}
int main(){
printf("%d\n", test);
printf("%d\n", &test);
void(*pfunc)(int) = test; //*pfunc是一个指针,指向一个函数(),这个函数的返回值是void类型
//指针要保存地址,函数名就代表函数的地址
//通过函数的指针来调用这个函数
(*pfunc)(); //先解引用,再利用()来调用函数
}
(*(void(*)())0)();
拆分
void(*)() 一个指针,指向一个函数,返回值为void型,是一个函数指针,相当于此处存放的是一个地址
(地址)0, 对0强转成这个地址,相当于,把0变成了一个地址,地址的类型取决于指针的类型
(*(地址)0)(), 然后解引用
对于这串代码的解释为:
这里调用了一个函数(),前面(*(void(*)())0)相当于这个函数的地址
函数指针数组
int main(){
void(*arr[2])(int,int); arr[2]是一个数组,数组有两个元素。数组里面的每一个元素就是void(*)(int,int)
VOID(* P )(int,int) 此处P指向一个函数,这个函数有两个参数,这两个参数分别是int ,int,这个函数的返回值是vooid
}
用函数指针数组实现一个计算器
这是简单的一个计算器
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 dev(int a, int b){
return a / b;
}
void menu(){
printf("********************\n");
printf("*****1.add****2.sub*\n");
printf("*****3.mul****4.dev*\n");
printf("********************\n");
}
int main(){
int input = 1;
int x = 0;
int y = 0;
int ret = 0;
do{
menu();
printf("请输入你的操作:\n");
scanf("%d", &input);
switch (input){
case 1:
printf("请输入两个操作数:\n");
scanf("%d%d", &x, &y);
ret = add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数:\n");
scanf("%d%d", &x, &y);
ret = sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:\n");
scanf("%d%d", &x, &y);
ret = mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:\n");
scanf("%d%d", &x, &y);
ret = dev(x, y);
printf("%d\n", ret);
break;
default:
break;
}
} while (input);
}
我们对它进行优化,明确一个概念:函数名代表函数的地址,那么将函数名放到数组当中,数组应该是一个函数指针数组
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 dev(int a, int b){
return a / b;
}
void menu(){
printf("********************\n");
printf("*****1.add****2.sub*\n");
printf("*****3.mul****4.dev*\n");
printf("********************\n");
}
/*
函数名代表函数的地址,那么将函数名放到数组当中,数组应该是一个函数指针数组
*/
int main(){
//定义一个函数指针
int(*func[5])(int x,int y) = {NULL, add, sub, mul, dev};
int input = 1;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请输入你的操作:\n");
scanf("%d", &input);
printf("请输入两个操作数:\n");
scanf("%d%d",&x,&y);
if (input <= 1 && input <= 4){
ret = (*func[input])(x,y);
printf("%d\n",ret);
}
}while(input);
return 0;
回调函数
回调函数就是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数
练习题:
练习1:对于char *p = "hello bit"
,双引号引起来的这一段是一个常量字符串,本质是一个常量字符串数组类型,赋给一个指针,相当于把这个数组的首地址赋给指针,即第一个元素h的地址。
练习2:对于以下代码的解释:
int** arr[10] 二级指针数组
int (*arr[10]) 指针数组
char *(*arr)[10] 指针数组的指针(char*数组的指针)
char(*)arr[10] char*的数组
练习3:定义一个函数指针,指向的函数与两个int形参并且返回一个函数指针,返回的指针指向一个有一个int形参且返回int的函数。符合这这段描述的是:int (*(*F))(int, int)(int)
练习4:一个参数为int*,返回值为int的函数指针:int (*fun)(int*)
练习5:一个指向含有10个元素的数组的指针,其中每个元素是一个函数指针
练习6:关于回调函数的补充:
①回调函数就是一个通过函数指针调用的函数。
②回调函数一般通过函数指针实现
③回调函数一般不是函数的实现方调用,而是在特定的场景下,由另外一方调用。
练习7:下面test函数设计正确的是?
char* arr[5] = {"hello", "bit"};
test(arr);
A. void test(char* arr);
B. void test(char** arr);
C. void test(char arr[5]);
D. void test(char* arr[5])
思路:指针的数组传递给子函数变为指针的指针,也就二级指针。即可以写成 char **arr ; char *arr[] ; char * arr[5]
练习8:下面程序的结果:
int main(){
int aa[2][5] = {10,9,8,7,6,5,4,3,2,1};
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf("%d,%d", *(ptr -1), *(ptr2 - 1));
return 0;
}
思路:
&aa的类型是int(*)[2][5](数组指针),加 1 操作会导致跳转一个 int[2][5] 的长度,直接跑到刚好越界的位置。减 1 以后回到最后一个位置 1 处。
*(aa + 1) 相当于 a[1],也就是第二行的首地址,则是5位置。减 1 以后由于多维数组空间的连续性,会回到上一行末尾的6处。
练习9:以下四种形式所代表的含义
int a[10] 普通的int型数组
int (*a)[10] int数组指针
int *a[10] int的指针数组
int (*a[10])(int) int(*)(int)函数指针的数组