指针的进阶

本章重点:

  1. 字符指针
  2. 数组指针
  3. 指针数组
  4. 数组传参和指针传参
  5. 函数指针
  6. 函数指针数组
  7. 指向函数指针数组的指针
  8. 回调函数
  9. 指针和数组面试题的解析

复习:

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
  4. 指针的运算。

1、字符指针(char*)

一般情况:

int main()

{

char ch=‘w’;字符变量ch

char *pc=&ch;将字符变量赋值给指针pc,pc的类型是char *

return 0;

}

还有一种情况是:

int main()

{

const char* pstr="hello world";//这里是把字符串“hello world”的首字符的地址放到指针pstr里

printf("%s\n",pstr);

return 0;

}

意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中

下面通过代码加深理解:

指针的进阶使用_函数指针

指针的进阶使用_数组指针_02

​编辑

结果是

指针的进阶使用_函数指针_03

指针的进阶使用_函数指针_04

​编辑

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当

几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化

不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

2.指针数组(是一个数组,用来存放指针的)

指针数组的复习

int*  arr 1[10]; //整形指针的数组

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

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

对于指针数组的理解:

指针的进阶使用_数组_05

指针的进阶使用_数组_06

​编辑

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

指针的进阶使用_数组_07

指针的进阶使用_数组_08

​编辑

3.数组指针(是指向数组的指针,存放数组的地址)

3.1 数组指针的定义

arr(数组名)是首元素的地址

&arr[0] 是首元素的地址

&arr 是数组的地址

int *p1[10]; 因为[ ]的优先级高于“ * ”,所以p1先于[ ]结合成为数组然后再和“ * ”结合成为指针

int (*p2)[10]; 用了( )强制将p2先于“ * ”结合成为指针再和[ ]结合成为(指针)数组

3.2 &数组名VS数组名

&arr 表示的是整个数组的地址,而不是数组首元素的地址。

感受一下

指针的进阶使用_数组指针_09

指针的进阶使用_数组_10

​编辑

很明显arr+1(步长为4个字节)和&arr+1(步长为40个字节)跳过的指针步长不一样,数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40

3.3 数组指针的使用

指针的进阶使用_数组指针_11

指针的进阶使用_数组_12

​编辑

数组名arr,表示首元素的地址, 但是二维数组的首元素是二维数组的第一行所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址可以数组指针来接收

对于上述函数print2中打印的对象,可能还有些模糊,那接下来就再次对其进行解读:

p指的是首元素的地址(也就是arr当中的第一行)加一个" * "(*p)就是arr数组(此时的arr是一个一维数组,因为我们传过来的参数就是首元素的地址,也就是把arr[3][4]看成一个一维数组,即忽略了列相当于arr[3]后面的那个[4]就相当于固定了的一个常量,所以每次跳过一个步长,就会是一个行的所有元素)的首元素,相当于数组里面的arr[i]((*p)+ i )中的i就是指每行,此时这个((*p)+ i )再加上j就是每行里面的每个元素了,也就是二维数组里的列(此时指的是地址)对其整体解引用就是元素本身了,相当于数组里面的arr[i][j]。

让我们再来对数组加深下理解:

int arr[5]; arr是一个数组,数组里面有5个元素,每个元素的类型是int

int *parr1[10]; parr1先于[ ]结合成为数组,然后数组parr1[10]与“ * ”结合成为指针,这是一个指针数组,数组里面有10个元素, 指针的类型是int *

int (*parr2)[10]; parr2首先和“ * ”结合称为指针,然后(*parr2)和[10]结合成为数组,这是一个数组指针,指针指向数组,数组里有10个元素,每个元素的类型是int

int (*parr3[10])[5]; parr3[10]是一个整体说明数组parr3有10个元素,然后parr3[10]和“ * ”结合成为指针,(*parr3[10])再和[5]结合成为数组,这是一个数组指针,也是一个二维数组,parr3有10个元素,相当于10行,每行里面有5个元素,也就是5列,parr3这个有10个元素的数组指针,指向一个数组,这个被指向的数组里面有5个元素,每个元素的类型是int

4.数组参数、指针参数

4.1一维数组传参

指针的进阶使用_数组指针_13

指针的进阶使用_数组指针_14

​编辑

4.2 二维数组的传参

指针的进阶使用_数组指针_15

指针的进阶使用_数组_16

​编辑

4.3 一级指针传参

指针的进阶使用_函数指针_17

指针的进阶使用_数组_18

​编辑

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

可以传一个(1)变量的地址(2)存放变量的一级指针的变量

指针的进阶使用_函数指针_19

指针的进阶使用_数组_20

​编辑

4.4 二级指针的传参

指针的进阶使用_数组_21

指针的进阶使用_数组指针_22

​编辑

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

(1)一级指针的地址(2)二级指针变量本身(3)存放一级指针数组的数组名

5. 函数指针

函数也是有地址的,接下来让我们看一下函数的地址

指针的进阶使用_函数指针_23

指针的进阶使用_数组_24

​编辑

这两个地址是函数test的地址

对于函数地址的保存:

指针的进阶使用_数组_25

指针的进阶使用_数组_26

​编辑

接下来带大家看两个有趣的代码:

//代码1

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

//代码2

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

指针的进阶使用_函数指针_27

指针的进阶使用_函数指针_28

​编辑

在此给大家再普及一点知识对于平时我们使用指针如int* p其中int *是指针类型p是指针变量的名称,所以当指针变量的类型去掉剩下的就是指针类型,然后再来看这两个代码。

指针的进阶使用_函数指针_29

指针的进阶使用_数组指针_30

​编辑

signal是一个函数声明,signal的参数有两个,一个是int另一个是函数指针,该函数指针指向的函数参数是int返回类型是void

signal函数的返回类型也是一个函数指针,该函数的参数是int,返回类型是void

而对于以上的表达式可读性较差,我们对其进行简化处理

指针的进阶使用_数组_31

指针的进阶使用_数组指针_32

​编辑

对于函数指针

指针的进阶使用_数组_33

指针的进阶使用_数组指针_34

​编辑

6. 函数指针数组

指针的进阶使用_数组指针_35

指针的进阶使用_数组_36

​编辑

函数指针数组的用途:转移表

指针的进阶使用_数组指针_37

指针的进阶使用_数组指针_38

​编辑

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

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

如何定义呢?

指针的进阶使用_数组指针_39

指针的进阶使用_数组指针_40

​编辑

8. 回调函数

回调函数就是一个通过函数指针调用的函数:如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用它所指向的函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进

行响应。

qsort(quick sort) - 库函数 - 排序

MSDN上面对qsort函数的描述是这样的

指针的进阶使用_数组_41

指针的进阶使用_数组指针_42

​编辑

头文件的引用是:include

指针的进阶使用_数组_43

指针的进阶使用_函数指针_44

​编辑

演示一下sqort函数的实现:

指针的进阶使用_数组指针_45

指针的进阶使用_数组_46

​编辑

对于void* 类型指针的介绍

指针的进阶使用_数组指针_47

指针的进阶使用_函数指针_48

​编辑

使用回调函数,模拟实现qsort(采用冒泡函数的方式)

//void qsort(void* base
// //第一个参数:待排序数组的首元素地址
// size_t num
// //第二个参数:待排序数组的元素个数
// size_t width
// //待排序数组的每个元素的大小 - 单位是字节
// int(__cdecl* compare)(const void* elem1, const void* elem2));
// //第四个参数:是函数指针,比较两个元素的所用函数的地址
// //这个函数使用者自己实现
// //函数指针的两个参数是:带比较的两个元素的地址
//struct Stu
//{
// char name[20];
// int age[5];
//};
//cmp_stu_by_age(const void* e1, const void* e2)
//{
// return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
//}
//void test2()
//{
// struct Stu s[3] = { {"xiaoming",18},{"laowang",30},{"gebi",25} };
// int sz = sizeof(s) / sizeof(s[0]);
// bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
//}
Swap(char* buf1, char* buf2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
//第四个参数中的const最好还是加上,不然有些编译器会报错
//因为不知道具体传过来的参数是什么类型的第一个参数就用void*无具体类型的指针
//好处是可以接收任何类型的指针或者地址
//坏处就是不可以进行解引用以及对于指针的加减的操作
//那么第三个参数就没有固定的指针步长,所以就用一个宽度width来确定每个元素大概是多少的字节
//对于数据存储的情况就有了一个大致的规格标准
{
int i = 0;
//趟数
for (i = 0; i < sz - 1; i++)
{
//每一趟比较的对数
int j = 0;
for (j = 0; j = sz - i - 1; j++)
{
//两个元素的比较
if (cmp((char*)base + j * width, (char*)base + (j + 1) + width) > 0)
{
//交换
//因为不同类型数据俩个元素的比较方法也不同
//所以把这个比较的方法抽离出来变成一个函数来实现
//通过函数指针来调用所指向元素
Swap((char*)base + j * width, (char*)base + (j + 1) + width, width);
//因为bubble_sort函数接收的元素类型是void*是不能够进行减价运算的
//所以我们将元素强制转换成为char*的元素进行抽象的(通过j以及width的配个)加减的运算
//因为char*是一个字节,再加上j与width的配合可以对于元素的宽度进行控制
}
}

}
}
int cmp_int(const void* e1,const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void test1()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
//用bubble_sort(冒泡函数)来实现各种各样类型的数据
}
int main()
{

test1();
//test2();
return 0;
}