[C语言]指针进阶详解

2024-06-04 9575阅读

[C语言]指针进阶详解 第1张

指针是C语言的精髓所以内容可能会比较多,需要我们认真学习


目录

1、字符指针

2、指针数组

3、数组指针

3.1数组指针的定义

3.2&数组名vs数组名

3.3数组指针的使用 

4、数组传参和指针传参

4.1一维数组传参

4.2二维数组传参

4.3一级指针传参

4.4二级指针传参

5、函数指针

6、函数指针数组

7、指向函数指针数组的指针

8、回调函数

8.1回调函数定义

8.2qsort库函数的用法

1、字符指针

在指针的类型中我们知道一种指针类型为字符指针char*

一般使用:

int main()

{

  char ch='w';

  char* pc=&ch;//*表示pc是指针,char表示pc指向的对象ch他的类型是char

  return 0;

}

#include
int main()
{
  char* pc="abcdef";
  printf("%s\n",pc);
  return 0;
}

看这样一个代码的运行结果:

[C语言]指针进阶详解 第2张 这里需要注意pc里面放的并不是整个字符串,而是a的地址,非要放在pc(4字节)中是放不下的,而打印的是字符串(%s),我们只要知道首字符a的地址就能向后访问得到整个字符串的地址在char* 前面加上const会更加好一些。看这样一道题:

#include
int main()
{
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	if (p1 == p2)
		printf("p1==p2\n");
	else
		printf("p1!=p2");
	if (arr1 == arr2)
		printf("arr1==arr2");
	else
		printf("arr1!=arr2");
	return 0;
}

[C语言]指针进阶详解 第3张 这说明p1和p2指向同一个字符a,字符串abcdef是常量字符串,放在内存中的只读内存区,不能改变它的值,没有必要存在多份,只在内存中存一份,所以p1和p2都指向a的地址,p1就等于p2.而arr1和arr2不相等,是因为arr1[ ]和arr2[ ]是两个独立的数组,,每一个都在内存中开辟了一份独立的空间,所以地址肯定是不相同的。

2、指针数组

顾名思义指针数组就是存放指针的数组(本质是一个数组)

int* arr1[10];//整型指针的数组

char* arr2[10];//一级字符指针的数组

char **arr[5];//二级字符指针的数组 

#include
int main()
{
	int arr1[] = { 1,2,3,4 };
	int arr2[] = { 2,3,4,5 };
	int arr3[] = { 3,4,5,6 };
	int* arr[3] = { arr1,arr2,arr3 };//每个数组的首元素地址,每个元素的类型是int*
	int i = 0;//代表arr的每个元素的下标
	for (i = 0; i  

[C语言]指针进阶详解 第4张

用了一个指针数组把三个一维数组关联起来了,模拟实现二维数组,但本质上并不是二维数组,因为这三个数组在内存中并不是连续存放的但二维数组在内存中是连续存放的。

3、数组指针

3.1数组指针的定义

数组指针的本质是指针。

整型指针:int* p;能够指向整形数据的指针

浮点型指针:float* p;能够指向浮点型数据的指针

所以说数组指针就是能够指向数组的指针。比如:

int (*p)[10];(*p)代表p是指针,指向的是整型数组中的10个元素,每个元素是int类型。

注意:[ ]的优先级要高于*号,因此必须加上()来保证p先和*相结合。

3.2&数组名vs数组名

对于下面的数组:

int arr[10];

arr和&arr的区别:

我们知道arr是数组名,数组名表示数组首元素的地址。那么&arr表示的是什么?

#include
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);
	return 0;
}
//数组名通常表示的是数组首元素的地址,但有两个例外:
//1.sizeof(数组名),这里的数组名表示的是整个数组
//2.&数组名,这里的数组名依然表示的是整个数组,所以&arr取出的是整个数组的地址

这里我们会发现运行结果显示,三个地址是完全相同的,但是&arr的步长和arr的步长是不同的,arr+1(类型是int*)跳过4个字节,但&arr+1跳过28(16进制)字节,即40字节。那么应该怎么存放整个数组的地址呢?

存放数组首元素的地址:int *p=arr;

存放整个数组的地址:int (*p)[10];(数组指针用来存放整个数组的地址)。它的类型为int (*)[10]。

3.3数组指针的使用 

#include
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i  

这样用我们会感到相当别扭,很不舒服,所以很不建议这样用! 建议大家用在二维数组上。

#include
void print1(int arr[3][5], int r, int c)
{
	int i = 0;
	for (i = 0; i  

int (*parr[10])[5];parr先和[5]配对说明是个数组数组的类型是int (*)[5]是个数组指针。所以parr就是存放数组指针的数组 。

4、数组传参和指针传参

写代码时难免会把数组或指针传给函数,那么函数的参数该如何设计呢?

4.1一维数组传参

#include

void test1(int arr1[]) {

}

void test1(int arr1[10]) {

}

void test1(int* arr1) {

}

void test2(int *arr2[10]) { 

} //10可以省略

void test2(int** arr2) {

}

int main()

{

    int arr1[10] = { 0 };

    int* arr2[10] = { 0 };

    test1(arr1);

    test2(arr2);

    return 0;

}

4.2二维数组传参

#include

void test(int arr[3][5]) {

}

void test(int arr[][5]) {

}//二维数组传参,函数的形参设计只能省略行,因为对于一个二维数组,可以不知道有多少行,但是必须知道一行有多少个元素

void test(int (*p)[5]) {

}//二维数组传参传的是第一行元素的地址,需要拿一个数组指针才能接收

int main()

{

    int arr[3][5] = {0};

    test(arr);

    return 0;

}

4.3一级指针传参

#include

void print(int* p, int sz)

{

    int i = 0;

    for (i = 0; i

    {

        printf("%d ", *(p + i));

    }

}

int main()

{

    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

    int* p = arr;

    int sz = sizeof(arr) / sizeof(arr[0]);

    print(p, sz);//p是一级指针变量

    return 0;

}

4.4二级指针传参

#include

void print(int** ptr)

{

    printf("%d\n", **ptr);

}

int main()

{

    int n = 10;

    int* p = &n;

    int** pp = &p;

    print(&p);

    return 0;

}

当函数的参数为二级指针的时候,可以接收什么参数?

可以说指向一级指针的变量,也可以是指向一级指针的数组,二级指针变量本身

5、函数指针

指向函数的指针就叫做函数指针,用来存放函数的地址

#include
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", &Add);
	return 0;
}

[C语言]指针进阶详解 第5张 由此可见打印出来的值就是Add函数的地址。对于函数来说,&函数名和函数名都是函数的地址

那怎么用指针把函数地址存起来呢?

拿此例子来说:int (*pf)(int,int)=&Add;

#include
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = &Add;
	int ret = (*pf)(2, 3);//解引用就相当于找到了这个函数,*是可以省略的
	printf("%d\n", ret);
	return 0;
}

 int main()

{

    (*(void(*) () )0)();//把0强制转换成函数指针类型(我们可以认为0就是一个地址),解引用调用这个函数,但什么参数都没有传,所以本质上是一次函数调用,调用的是0作为地址处的函数

    void(* signal(int,void(*)(int) ) )(int);

    //signal与括号先结合是函数名,其中void(*)(int)是函数指针类型,去掉

    signal(int,void(*)(int) )我们会发现剩下部分也是返回也是一个函数返回类型。也就是说这个代码是一次函数声明。声明的signal函数第一个参数的类型是int,第二个参数的类型是函数指针。该函数指针指向的函数参数是int,返回类型是void;而signal函数的返回类型也是一个函数指针,该函数指针指向的参数是int,返回类型也是void。

    return 0;

}

函数指针的用途:(写一个计算器能够实现简单的加法、减法、除法、乘法)

#include
void menu()
{
	printf("*******************\n");
	printf("****1.add 2.sub****\n");
	printf("****3.mul 4.div****\n");
	printf("****   0.exit  ****\n");
	printf("*******************\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 (*p)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:>\n");
	scanf("%d%d", &x, &y);
	ret = (*p)(x, y);
	printf("%d\n", ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		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("退出程序\n");
			break;
		default:
			printf("选择错误请重新选择:>\n");
			break;
		}
	} while (input);
	return 0;
}

6、函数指针数组

把存放函数的地址存到一个数组中,那么这个数组就叫做函数指针数组,如何定义函数指针数组?

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 (*arr[4])(int,int)={add,sub,mul,div};//arr先于[4]结合表明本质是个数组,数组每个元素的类型为int (*)(int,int)即函数指针类型

    int i = 0;

   for (i = 0; i

   {

      int ret = arr[i](8, 4);

      printf("%d ", ret);

    }

    return 0;

}

有什么用途呢?以上面模拟计算机为例,我们还可以对代码进行简化,并且增加计算机功能时也相对比较容易,极大简化了修改功能时的工作量。

#include
void menu()
{
	printf("*******************\n");
	printf("****1.add 2.sub****\n");
	printf("****3.mul 4.div****\n");
	printf("****   0.exit  ****\n");
    printf("*******************\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 x = 0;
	int y = 0;
	int input = 0;
	int ret = 0;
    //转移表
	int (*pfarr[5])(int, int) = { 0,add,sub,mul,div };//之所以第一个元素放0,是为了,与为了调用时和菜单选项对应起来,比如输入下标为1是调用的是add函数
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出程序\n");
		}
		else if (input >= 1 && input name, ((struct Stu*)e2)->name);//e1是指针变量
	//strcmp函数如果第一个字符串比第二个大返回正数,小于返回负数,否则返回0
}
int main()
{
	struct Stu s[] = { {"张三",15},{"李四",16},{"王五",18}};
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	int i = 0;
	for (i = 0; i name,(s+i)->age);//s虽然本身不是指针,是数组名,但会"退化为"指向第一个元素的指针。这是为了与期望接受指针的函数(qsort)兼容
		//也可以用s[i].name,s[i].age的形式来访问结构体成员
	}
	return 0;
}
}

 

 

 

 

 


    免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

    目录[+]