C语言进阶指针

之前简略的学习了指针,总结一下之前学习指针的概念

指针是存放地址的变量,把地址存放到变量里叫指针变量。
指针的大小是固定的4个或8个字节(32位平台和64位平台)。
指针是有类型的,类型的大小决定了指针的±整数的步长,指针解引用操作的时候的权限。
指针的加减运算

这个章节,我们继续深入的学习指针

字符指针

字符指针是存放字符类型地址的指针,char * p
例如:

int main()
{
	char x = "w";
	char* p = &x;
	*p = 'r';

	return 0;
}

还有一种使用方式:
在这里插入图片描述

根据以上的知识来做一道练习题,加以巩固

int main()
{
    char str1[] = "hello china";
    char str2[] = "hello china";
    const char* str3 = "hello china";
    const char* str4 = "hello china";
    if (str1 == str2)
        printf("str1 和 str2 相同\n");
    else
        printf("str1 和 str2 不同\n");

    if (str3 == str4)
        printf("str3 和 str4 相同\n");
    else
        printf("str3 和 str4 不同\n");
    return 0;
}

题解:

str 1 和 str 2
在这里插入图片描述

str 3 和 str 4
在这里插入图片描述

指针数组

指针数组是存放指针的数组,数组中存放的是指针

int* arr[3]; 整形指针的数组
char *arr2[4]; 一级字符指针的数组
char **arr3[5]; 二级字符指针的数组

例如:
模拟二维数组
二维数组的地址是连续的
下面数组的地址不是连续存放的
在这里插入图片描述

数组指针

数组指针的定义

数组指针是存放数组的指针

如果想把数组存放到数组指针怎么写呢

int arr[10] = {1,2,3,4,5};

int (*p)[10] = &arr;
这里*p 代表 p 是指针变量,[10]是指向的是一个大小10个整形的数组
所以 p 是数组指针,存放的是数组的地址
  • 如果我想把 float*x[ 5 ]的数组存放到数组指针中,改怎么写呢

float * x[5]
数组指针:·float* (*pd)[5]
floart* 代表指向的是类型是float*
(*pd)代表指针
[ 5 ] 代表指向的是 5 个元素

数组名和&数组名

int arr[10];

  • arr 和 &arr 的区别在哪里呢
  • arr是数组首元素的地址
  • 那 &arr呢?
    看下面代码:
    在这里插入图片描述
    从图片可以看出,二种方式的地址都是一样的
    那在看一段代码:
    在这里插入图片描述
    从上面代码的结果可以看出:
  • arr(5E8) 和 arr+1(5EC) 相差4个字节
  • &arr(5E8)和 &arr+1(610)相差40个字节
  • 从结果可以得知道,&arr表示的是数组的地址,加一就意味这加的是整个数组的大小

数组指针的使用

数组的指针是怎么用的呢?
数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
例如:
正常二维数组的使用

void print(int str[3][5], int x, int y)
{
	int i = 0;
	for (i = 0; i < x; i++)
	{
		int j = 0;
		for (j = 0; j < y; j++)
		{
			printf("%d ", str[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print(arr, 3, 5);
}

数组指针的使用
数组名arr 代表首元素地址,但是二维数组代表的是第一行的地址
所以函数传arr,其实相当于第一行起始的地址
可以用数组指针来接收

void print(int (*str)[5], int x, int y)
{
	int i = 0;
	for (i = 0; i < x; i++)
	{
		int j = 0;
		for (j = 0; j < y; j++)
		{
			printf("%d ", str[i][j]);
		}
		printf("\n");
	}1/*9*lrew
}

数组参数、指针参数

总结一下数组和指针传参时,函数的参数的设计

一维数组传参

数组传参

二种写法都可以
void Add(int arr[])
{}
数组传参,参数用数组接收,数组的大小可以不写也没有问题

---------------------------------

void Add(int* arr)
{}
数组名相当于首元素地址,参数用指针接收
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	Add(arr);
	return 0;
}

指针传参

void test(int* str[])
{}
指针数组传参,用指针数组接收,数组大小可以不写

------------------------------------------------------

void test(int** str)
{}
int* str 每个元素都是int*类型,,数组名是首元素地址
int* 是一级指针,就可以用二级指针接收

int main()
{
	int* str[10] = {1,2,3,4,5};
	test(str);
	return 0;
}

二维数组传参

数组传参

void test(int arr[3][5])
{}
二维数组传参,用二维数组接收

-----------------------------------

void test(int arr[][])
{}
形参用二维数组接收时,行可以省略,但是列不可以
正确写法:
void test(int arr[][5]);
{}

int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

二维数组指针传参

void test(int* arr)
{}
二维数组的数组名,表示的是第一行第一个的地址
上面写的是一维数组的方法,二维数组不能这样写

-----------------------------------------------

void test(int* arr[5])
{}
同上,这是一维数组的数组指针
访问的是第一行的元素,二维数组不能用

-----------------------------------------------

void test(int** arr)
{}
数组名是第一行第一个元素的地址,不能用二级指针接收
二级指针是用来存存放一级指针的
- 

-----------------------------------------------

void test(int(*arr)[5])
{}
二维数组传参用数组指针接收
*arr 是指针,指向的是第一行第一个元素
每行有5个元素,每个元素是int类型


void test(int** arr)
{}

int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

一级指针传参

void test(int* p)
{}
一级指针变量p传参,用一级指针接收
传参是什么类型,就用什么类型接收

int main()
{
	int arr[] = { 1,2,3,4,5 };
	int* p = arr;
	test(p);
}

如果函数的参数部分是指针:

void test(int* p)

如果想使用上面的函数,要传什么参数才能使用
int a = 10;
int* p = &a;
int arr[10] = {0};

test(&a);  整形变量地址
test(p);   一级指针,用一级指针接收
test(arr);   整形数组名

只要是传过去的是整形指针,就可以使用

二级指针传参

void test(int** ptr)
{}
&p 是一级指针,形参用二级指针接收

ps 是二级指针,传的是二级指针本体,用二级指针接收

int main()
{
	int n = 10;
	int* p = &n;
	int** ps = &p;
	
    test(&p);
	test(ps);

	return 0;
}

如果函数的形参是二级指针,调用函数的时候可以传什么参数

void test(int** ps)
{}

int* p;
int **pd;
int* arr[10];

test(&p);  传 p 的地址
test(pd);  传 pd 二级指针
test(arr); 传 arr 数组名
只要实参的类型能配齐的上就可以

函数指针

函数指针是指向函数的指针

&函数名 - 取出的是函数的地址
在这里插入图片描述
和数组名和&数组名都是类似的,取出的都是数组的地址
对于函数来说,&函数和函数名都是函数的地址

函数指针的使用

void test(int x,int y)
{}

int main()
{

   int (*p)(int ,int) = &test;
   想把函数指针存起来 p就得是个指针
   (*p) 说明是指针
   (int ,int) 是指向的函数的参数的类型
   返回类型是 int
-----------------------------------------

   int ret = (*p)(2,3);
或者 int ret = p(2,3);
   
   (*p) 是找到函数
   (2,3) 是传给函数的参数
-------------------------------------

   printf(“”%d\n",ret);
   return 0;
}

阅读两段有趣的代码:

(*(void (*) ( ) )0 )();

以上代码可以一步一步拆分进行解析:
在这里插入图片描述

第二道代码:

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

思路:
在这里插入图片描述
上面的代码阅读起来是不是难以理解
实际上可以进行类型重定义,更好理解

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

typedef 是类型重命名函数
例如: typedef void(*函数指针名)(参数列表)

typedef void(*pd)(int);void(*)() 类型重命名为 pd

pd signal(int,pd);
写以上代码是不是更好理解一点

函数指针数组

函数指针数组是存放函数指针的数组
就是把函数的地址存到一个数组中,那这个数组就叫函数指针数组
函数指针的定义:

void Add(int x,int y)
void Sub(int x,int y)
函数指针的定义:
int (*pa[ 6 ] )(int int) = {0,Add , Sub};

链接: 函数指针数组具体的应用

指向函数指针数组的指针

大概的介绍一下,这个用的不是太多

指向函数指针数组的指针
概括:
函数指针数组的指针是一个指针
指针指向一个 数组
数组的元素都是 函数指针

如何定义?

之前写函数指针数组
int (*pa[ 6 ] )(int int) = {0,Add , Sub};
把 pa 的地址赋值 ppd
ppd 就是指向函数指针数组的指针
int ( * ( *ppd ) [ 6 ] ) ( int , int ) = &pa;

回调函数

回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

这里使用qsort函数来举例回调函数

在这里插入图片描述
qsort 是使用快速排序的思想实现的排序函数
qsort 可以实现任意的类型的数据排序

qosor的参数的解析:
在这里插入图片描述
在补充一点:
在这里插入图片描述
下面就举例几个 qsort 函数不同数据类型进行排序的例子:
1.整形类型排序:

int qsort_int(const void* e1, const void* e2)
{  //比较二个整形的数据
    //强制类型转换成 int* 类型
	return *(int*)e2 - *(int*)e1;
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);  // 计算数组的大小
	qsort(arr, sz, sizeof(arr[0]), qsort_int); // qsort的参数
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

结果:
在这里插入图片描述
2.字符类型排序:

#include<string.h>

int qsort_char(const void* e1, const void* e2)
{  // 强制类型转换成 char* 类型
	return *(char*)e1 - *(char*)e2;
}
int main()
{
	char arr[] = { "fedcba" };
	int sz = strlen(arr);  // 计算字符串的长度
	qsort(arr, sz, sizeof(arr[0]), qsort_char);// qsort的参数
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%c ", arr[i]);
	}
	return 0;
}

结果:
在这里插入图片描述

3.结构体排序

按照年龄排序

struct stu
{
	char name[20];
	int age;
};
int qsort_name(const void* e1, const void* e2)
{
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
int main()
{
	struct stu s[3] = { {"zhangsan",26},{"lisi",32},"wangfeng",18 };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), qsort_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", s[i].age);
	}
	return 0;
}

结果:
在这里插入图片描述
按照首字母排序
字符比较用 strcmp 来比较大小

#include<string.h>
struct stu
{
	char name[20];
	int age;
};
int qsort_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name , ((struct stu*)e2)->name);
}
int main()
{
	struct stu s[3] = { {"zhangsan",26},{"lisi",32},"wangfeng",18 };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), qsort_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s ", s[i].name);
	}
	return 0;
}

通过上面例子,相信大家已经对qsort有了一定的认识,并且会逐渐的运用
为了进一步理解回调函数,我们自己来模拟一个qosrt对整型排序
用冒泡排序的思想

#include <stdio.h>
 
int cmp(const void* p1, const void* p2)
{
	return *(int*)p2 - *(int*)p1;
}
 
 
void Swap(char* p1, char* p2,int width)
{
	//因为传进来的char*类型,对应的元素只有一个字节
	//我们需要知道外部元素的真正大小,所以需要把width也传进来
	//实现元素之间一个字节一个字节的交换
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}
void my_qsort(void* base, int num, int width, int (*cmp)(const void*, const void*))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < num - 1; i++)
	{
		for (j = 0; j < num - 1 - i; j++)
		{
			//先将元素强转char*类型(指向的对象只有1字节)再乘以元素大小
			//当main函数的数组不是int类型的时候,我们不需要进到qsort来进行修改
			//增强了函数的可移植性
			if (  (cmp ( (char*)base + j * width  , (char*)base + (j + 1) * width ) ) >0)
			{
				//定义一个交换函数实现元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}
int main()
{
	//创建需要操作的数组
	int arr[10] = { 0,9,8,6,5,4,2,1,3,7 };
 
	//模拟实现qsort,用冒泡排序的思维
	my_qsort(arr, 10, sizeof(int), cmp);
 
	//打印
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

总结

本文章讲解的是个种指针的使用和分析,详细的概括了指针数组、数组指针、函数指针、函数值指针数组、指向函数指针数组的指针、回调函数
以上文章是个人的见解,可能会写的有些不太全面,如有什么缺漏或错误,可以在评论区留言或私信

  • 28
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 26
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值