之前简略的学习了指针,总结一下之前学习指针的概念
指针是存放地址的变量,把地址存放到变量里叫指针变量。
指针的大小是固定的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;
}
题解:
指针数组是存放指针的数组,数组中存放的是指针
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* (*pd)[5]
floart* 代表指向的是类型是float*
(*pd)代表指针
[ 5 ] 代表指向的是 5 个元素
int arr[10];
数组的指针是怎么用的呢?
数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
例如:
正常二维数组的使用
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
函数来举例回调函数
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;
}
#include
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
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
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;
}
本文章讲解的是个种指针的使用和分析,详细的概括了指针数组、数组指针、函数指针、函数值指针数组、指向函数指针数组的指针、回调函数
以上文章是个人的见解,可能会写的有些不太全面,如有什么缺漏或错误,可以在评论区留言或私信