我是靠谱客的博主 多情铃铛,最近开发中收集的这篇文章主要介绍【C语言进阶】指针进阶指针进阶(更深层次的理解):一.字符指针二.指针数组三.数组指针四.数组参数,指针参数七.指向函数指针数组的指针:八.回调函数 ,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

今日所做之事勿候明天,自我所做之事勿候他人。             --歌德

 

目录

指针进阶(更深层次的理解):

一.字符指针

二.指针数组

​三.数组指针

1.数组指针的定义:

 2.&数组名和数组名:

3.数组指针的使用:

四.数组参数,指针参数

 1.一维数组传参:

2.二维数组传参:

3.一级指针传参:

4.二级指针传参:

五.函数指针:

六.函数指针数组:

七.指向函数指针数组的指针:

八.回调函数 


指针进阶(更深层次的理解):

前言:

之前我们已经学习了指针的初阶,接下来我们将进入指针的进阶了,更加深刻的理解指针。

先回忆一下指针的简单知识:

1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

2.指针的大小是固定的4/8个字节(32位平台/64位平台)。

3.指针是有类型的,指针的类型决定了指针的±整数的步长,指针的解引用操作的时候的权限。

4.指针的运算。

一.字符指针

字符指针就是类型为char*。

int main()
{
	char ch = 'a';
	char* p = &ch;//p就是一个字符指针
	return 0;
}
int main()
{
	char* p1 = "abcdef";
	//这里字符串表达式的值就是首字符的地址
	//把字符串首元素的地址存放在p中
	//可以把它理解为字符串数组
	char arr[] = "abcedf";
	char* p2 = arr;
	//唯一的区别就是p2指向的是数组的首元素,而数组的可以修改的。
	//但是p1是不能修改的,因为p1对应的是常量字符串
	*p2 = 'h';
	printf("%s", arr);
    return 0;
}

当我们修改了常量字符串会发现什么呢?

int main()
{
	char* p1 = "abcdef";
	*p1 = 'h';
	return 0;
}

所以常量字符串是不能修改的。 

面试题:

int main()
{
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char *str3= "hello world";
	const char* str4 = "hello world";
	if (str1 == str2)//两个是数组字符串,两个指针也就是地址肯定是不相同的
		printf("str1 and str2 are samen");
	else
		printf("str1 and str2 are not samen");

	if (str3 == str4)//两个是相同的常量字符串,所以指针也就是地址是相同的
		printf("str3 and str4 are samen");
	else
		printf("str3 and str4 are not samen");
	return 0;
}

总结:常量的字符串是不能修改的。而数组里面的字符串是可以修改的。 

二.指针数组

指针数组本质上是数组,数组里的内容是指针。

字符指针数组:

int main()
{
    char* arr[] = { "abcdef","hello","nihao" };//这就是一个字符类型的指针数组
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    for (i = 0; i < sz; i++)
    {
        printf("%sn", arr[i]);
        //arr[i]就是字符串首元素的地址
        //通过首元素的地址就可以打印整个字符串
    }
    return 0;
}

整型指针数组:

int main()
{
    int arr1[] = { 1,2,3,4,5 };
    int arr2[] = { 6,7,8,9,10 };
    int arr3[] = { 11,12,13,14,15 };
    int* arr[] = { arr1,arr2,arr3 };//整型的指针数组
    int i = 0;
    int j = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    for (i = 0; i < sz; i++)
    {
        for (j = 0; j < 5; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("n");
    }
    return 0;
}

因为arr[i]=*(arr+i),所以这里打印我们还可以写成*(*(arr+i)+j)*(arr[i]+j)

三.数组指针

1.数组指针的定义:

数组指针不是数组,而是指针,是指向数组的指针,指针存放的是数组的地址。

int main()
{
    int arr[5] = { 1,2,3,4,5 };
    int(*pa)[5] = &arr;
    //这里的p就是数组指针,记得*和pa一定要括起来,不然就是指针数组了
    return 0;
}

 2.&数组名和数组名:

数组名其实是数组首元素的地址,这个是毋庸置疑的。

但是有两个例外:

1.sizeof(数组名),数组名如果单独放在sizeof内部,这里的数组名表示整个数组,计算的是整个数组的大小。

2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    printf("%pn", arr);
    printf("%pn", arr+1);
 
    printf("%pn", &arr[0]);
    printf("%pn", &arr[0] + 1);
 
    printf("%pn", &arr);
    printf("%pn", &arr + 1);
    return 0;
}

这里可以看出arr和&arr[0]和&arr的地址是一样的,分别加1的地址也是一样的,这就可以说明数组名是首元素的地址是完全没问题的。

不是说&arr是全部的地址吗?那为什么和arr的地址是一样的呢?但是当&arr+1的地址就完全不一样了。 

printf("%pn", &arr);
	         //地址0133FB1C
printf("%pn", &arr + 1);
	         //地址0133FB44

地址:01333FB1C和0133FB044总共差16进制的28,28转换为十进制是40,由此得出&arr+1跳过的是整个数组40个字节。

但&arr和arr的地址是一样的呢?因为它们都是从首地址开始的,虽然它们的值是一样的,但是他们所表达的含义是不同的。

3.数组指针的使用:

常规指针的用法打印数组内容:

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
    }
    return 0;
}

数组指针打印(用的很少,不提倡使用):

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int(*p)[10] = &arr;
    //p--&arr
    //*p--*&arr
    //*p--arr
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", (*p)[i]);
    }
    return 0;
}

使用数组指针打印二维数组:

void print(int(*p)[5], int row, int col)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            //printf("%d ",p[i][j]);
            printf("%d ", *(p[i] + j));
        }
        printf("n");
    }

}
int main()
{
    int arr[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
    print(arr, 2, 5);
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以用数组指针来接收
    return 0;
}

这里有一道题我们就可以来理解一下二维数组的数组名的含义:

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", *(ptr1 - 1), *(ptr2 - 1));
  return 0;
}

 &aa的类型是int (*)[2][5],加一操作会导致跳转一个int [2][5]的长度,直接跑到刚好越界的位置。减一以后回到最后一个位置1处。*(aa + 1)相当于aa[1],也就是第二行的首地址,自然是5的位置。减一以后由于二维数组空间是连续性的,会回到上一行末尾的6处。

int arr[5];
int* arr[5];//指针数组
int(*arr)[5];//数组指针
int(*pa[10])[5];//这个怎么理解呢

pa是和[10]连在一起的,所以pa是一个数组,而数组里面是10个指针,而指针里面又是数组。

相当于数组里面是数组指针。 

理解:

四.数组参数,指针参数

 1.一维数组传参:

           //一维数组传参
void test(int arr[])//数组接收没问题
{}
void test(int arr[10])//数组接收没问题
{}
void test(int *arr)//指针接收没问题
{}
void test2(int *arr[20])//指针数组接收也没问题
{}
void test2(int **arr)//指针接收也没问题,类型和原类型一样,都是int*
{}
int main()
{
    int arr[10] = { 0 };
    int* arr2[20] = { 0 };
    test(arr);
    test2(arr2);
    return 0;
}

2.二维数组传参:

         //二维数组传参
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])//使用数组指针可行
{}
void test(int **arr)//使用二级指针更不可行
{}
int main()
{
    int arr[3][5] = { 0 };
    test(arr);
    //这里的arr代表第一行的地址,只能用数组指针来接收
}

3.一级指针传参:

 //一级指针传参
void print(int* p, int sz)//使用指针接收,也可以使用数组接收
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%dn", *(p + i));
    }
}
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9 };
    int* p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    print(p, sz);
    return 0;
}

4.二级指针传参:

          //二级指针传参
void test(int** ptr)//二级指针接收
{
    printf("num=%dn", **ptr);
    //第一次解引用找到p,第二次解引用找到num=10
}
int main()
{
    int num = 10;
    int* p = &num;
    int** pp = &p;
    //pp就是&p
    test(pp);
    test(&p);
    return 0;
}

五.函数指针:

函数指针就是指向函数的指针。函数有地址吗?

int Add(int x, int y)
{
    return x + y;
}
int main()
{
    printf("%pn", &Add);
    printf("%pn,Add");
    //&函数名就是函数的地址
    return 0;
}

所以&函数名和函数名是一样的,没有区别。 

int Add(int x, int y)
{
    return x + y;
}
int main()
{
    int (*pa)(int, int) = Add;//pa就是一个指向函数的函数指针
    //int(*)(int,int)就是pa的类型
    //参数和返回类型是和函数是一一对应的,这里的参数x,y可以不写
    int ret = (*pa)(3, 5);//这里的(*pa)只是为了方便理解,不写*也是可以的
    //int ret = Add(3, 5);
    //int ret = pa(3, 5);
    printf("%dn", ret);
    return 0;
}

这里我们来理解两个比较复杂的表达式:

第一个:

int main()
{
	(*( (void (*)()) 0 ) )( )
	//我们如何来里理解这个代码呢
	//首先我们看最里面void(*)()这就是一个函数指针类型,无返回类型,无参数
	//然后(void(*)())0就是把0强制转换为void(*)()这个函数指针
	//然后(*( (void (*)()) 0 ) )这个就是函数的解引用操作
	//最后再加个括号,(*((void (*)()) 0))(),整个代码就是一次函数调用
	return 0;
}

第二个:

int main()
{
	void (*signal(int, void(*)(int)))(int); 
	//signal先和后面的括号结合,第一个参数是int,第二个参数是void(*)(int)类型的函数指针
	//该函数指针指向的函数参数是int,返回类型是void
	//signal(int, void(*)(int))这个的返回类型又是void(*)(int)
	//函数名也有,返回类型也有,参数也有,这个整体的代码就是一个函数的声明
	//就可以理解为 void(*)(int) signal(int, void(*)(int))
	//但是语法是不支持这样写的
	return 0;
}

如果你觉得第二个表达式比较复杂,你还可以用 rypedf (类型重命名)来简化代码:

typedef void(*pf_t)(int);  //就是将void(*)(int)重新起个名字为pf_t
//不能写成typedef void(*)(int) pf_t ,这样语法不支持
int main()
{
	void (*signal(int, void(*)(int)))(int);
	//这个代码就可以简化成下面的代码
	pf_t signal(int, pf_t);
	return 0;
}

六.函数指针数组:

函数指针数组本质是数组,只不过数组的元素是函数指针。

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(*pa)(int, int) = Add;
	int(*pb)(int, int) = Sub;
	//这里pa和pb都是函数指针,并且类型都是一样的,我们就可以把它们放在一个函数指针数组里面
	int(*ptr[4]) = { Add,Sub,Mul,Div };//这就是一个函数指针数组
	return 0;
}

在函数指针数组使用的时候和数组的使用反式大同小异,都是通过下标引用操作符。

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(*ptr[4])(int ,int) = {Add,Sub,Mul,Div};
	printf("请你输入要计算的两个值:>");
	int i = 0;
	int x = 0;
	int y = 0;
	scanf("%d %d", &x, &y);
	for (i = 0; i < 4; i++)
	{
		int ret = ptr[i](x, y);
		printf("%dn",ret);
	}
	return 0;
}

通过上述的例子我们就可以实现一个可以计算加减乘除的计算器

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(*pa)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请你输入要计算的值:");
	scanf("%d %d", &x, &y);
	int ret = pa(x, y);
	printf("计算的值为%dn", ret);

}
int main()
{
	int(*ptr[5])(int,int) = {NULL,Add,Sub,Mul,Div};
	int input = 0;
	do
	{
		printf("请你输入要计算的方法n");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出计算n");
			break;
		case 1:
			Calc(Add);
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		default:
			printf("输入错误,请重新输入n");
			break;
		}
	} while (input);
	return 0;
}

七.指向函数指针数组的指针:

int Add(int x, int y)
{
	return 0;
}
int main()
{
	int(*ptr[5])(int, int) = { Add };//这个是函数指针数组
	int(*(*AAptr)[5])(int, int) = &ptr;
	//因为*和AAptr先结合,说明AAptr就是一个指针
	//然后指针又和数组结合,剩下的int(* )(int ,int)就是这个数组的元素的类型:函数指针
	//所以AAptr就是一个指向函数指针数组的指针变量
	return 0;
}

八.回调函数 

回调函数其实就是一个通过函数指针调用的函数!假如你把A函数的指针当作参数传给B函数,然后在B函数中通过A函数传进来的这个指针调用A函数,那么这就是回调机制。

我们来举个例子来理解一下这句话,就像之前的计算器就是一个回调机制。

int A(int x, int y)
{
	return x + y;
}
void B(int(*ptr)(int, int))
{
	printf("输入你要计算的两个值:");
	int x = 0;
	int y = 0;
	scanf("%d %d", &x, &y);
	int ret = ptr(x, y);//通过传过来的指针来访问A函数,这就是回调函数
	printf("%dn", ret);
}
int main()
{
	int(*pa)(int, int) = A;
	B(A);//把A函数的地址传给B函数
	return 0;
}

感谢大家的支持,接下来的内容将会是qsort快排,敬请期待。 

最后

以上就是多情铃铛为你收集整理的【C语言进阶】指针进阶指针进阶(更深层次的理解):一.字符指针二.指针数组三.数组指针四.数组参数,指针参数七.指向函数指针数组的指针:八.回调函数 的全部内容,希望文章能够帮你解决【C语言进阶】指针进阶指针进阶(更深层次的理解):一.字符指针二.指针数组三.数组指针四.数组参数,指针参数七.指向函数指针数组的指针:八.回调函数 所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部