指针的概念:
1.
指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2.
指针的大小是固定的
4/8
个字节(
32
位平台
/64
位平台)。
3.
指针是有类型,指针的类型决定了指针的
+-
整数的步长,指针解引用操作的时候的权限。
4.
指针的运算。
字符指针
在指针的类型中我们知道有一种指针类型为字符指针
char*
;
一般使用方法:
![](https://img-blog.csdnimg.cn/direct/61c242cc14ab496fa9d86ed7de23d3d2.png)
还有一种使用方法:
![](https://img-blog.csdnimg.cn/direct/8a25fd781f2f41ef8b469377c6ca4f38.png)
这段代码的意思是:定义一个指针pstr,然后将字符串的首元素h的放进去。同理我们可以联想到,当我们定义一个数组的时候,数组名则是数组首元素的地址。
接下来我们来思考下面的题目:
![](https://img-blog.csdnimg.cn/direct/4908cd97bbe24f428509428b56161d6a.png)
该代码的输出如下:
![](https://img-blog.csdnimg.cn/direct/ab0310883f6c4d209cc5655244b0f85d.png)
这里
str3
和
str4
指向的是一个同一个常量字符串。
C/C++
会把常量字符串存储到单独的一个内存区域,
当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始
化不同的数组的时候就会开辟出不同的内存块。所以
str1
和
str2
不同,
str3
和
str4
不同。
指针数组
int*
arr1
[
10
];
//
整形指针的数组
char *
arr2
[
4
];
//
一级字符指针的数组
char **
arr3
[
5
];
//
二级字符指针的数组
指针数组的意思就是存放指针的数组,该数组中的每个元素都是指针
我们来实现一个案例,使用指针数组来模拟实现二维数组
![](https://img-blog.csdnimg.cn/direct/72bf6e741710447abac959104d65e46a.png)
数组指针
数组指针顾名思义就是指向数组的指针
int
(
*
p
)[
10
];
//
解释:
p
先和
*
结合,说明
p
是一个指针变量,然后指着指向的是一个大小为
10
个整型的数组。所以
p
是一个 指针,指向一个数组,叫数组指针。
//
这里要注意:
[]
的优先级要高于
*
号的,所以必须加上()来保证
p
先和
*
结合
当我们定义一个数组的时候是这样的:int arr[10]={0};
我们都知道arr是指向数组首元素的地址,那么&arr表示的就是整个数组的地址,而不是首元素的地址,数组的地址+1跳过的就是整个数组的大小。
![](https://img-blog.csdnimg.cn/direct/ef227b0c7fbb4314a99503dd19ee5f18.png)
我们一般这样来定义数组指针。
我们接下来用一个案例来使用数组指针。
![](https://img-blog.csdnimg.cn/direct/57753b0d5fc2480ba4df26efdc42ff67.png)
我们可以把该二维数组来看作是有着三个一维数组元素的数组,我们都知道数组名是指向数组首元素的地址,所以二维数组的数组名即是指向二维数组中的第一个一维数组的指针,因此我们在传参的时候定义一个数组指针 int (* arr) [5] 来接收这个指针。
在函数中 arr[i][j] 的意思是 *(*(arr+i) + j) 。 arr+i 就是将指针指向该数组的第 i 行,*(arr + i) 就是将指针指向第 i 行的第一个元素,*(arr + i)+ j 就是将指针指向第 i 行的第 j 个元素, *(*(arr+i) + j) 就是第 i 行的第 j 个元素的内容。
int
arr
[
5
]; // int 类型的数组
int *
parr1
[
10
]; // int * 类型的数组
int
(
*
parr2
)[
10
]; // 指向数组的指针
int
(
*
parr3
[
10
])[
5
]; // 存放数组指针的数组
一维数组的传参方式
![](https://img-blog.csdnimg.cn/direct/c6a50fab2c2b4b73a74da4374bbcc085.png)
二维数组的传参方式
![](https://img-blog.csdnimg.cn/direct/5b65a19c105445368eba78793c0f482b.png)
函数指针
我们来看一段代码
输出为:
因此我们可以得知函数的名字就是指向该函数的指针,这两个地址是 test 函数的地址。 那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:
![](https://img-blog.csdnimg.cn/direct/a0a26382fc16477a991936598b370e48.png)
我们可以用void (* p) (int ,int) 这个指针来接收函数的地址 ,因此void (* p) (int ,int) 就是函数指针
接下来我们来介绍两个有趣的代码
//
代码
1
(
*
(
void
(
*
)())
0
)();
//
代码
2
void
(
*
signal
(
int
,
void
(
*
)(
int
)))(
int
);
代码 1 :我们先将0强制转换成void (*)()函数指针类型 ,然后再使用(* 0)()这个函数指针
代码 2:这个代码有些麻烦,我们将其简化,把void(*)(int)这个函数指针类型换个名字
typedef
void
(
*
pfun_t
)(
int
); 这时 我们就把void(*)(int )该类型的名字换成了
pfun_t
最后将代码 2 简化成 pfun_t
signal
(
int
,
pfun_t
);
函数指针数组
int
(
*
parr1
[
10
]) () 函数指针数组顾名思义就是存放 int (*)()函数指针的数组
函数指针数组的用途:
转移表
例子(计算器)
#include <stdio.h>
int
add
(
int
a
,
int
b
)
{
return
a
+
b
;
}
int
sub
(
int
a
,
int
b
)
{
return
a
-
b
;
}
int
mul
(
int
a
,
int
b
)
{
return
a
*
b
;
}
int
div
(
int
a
,
int
b
)
{
return
a
/
b
;
}
int
main
()
{
int
x
,
y
;
int
input
=
1
;
int
ret
=
0
;
do
{
printf
(
"*************************\n"
);
printf
(
" 1:add 2:sub \n"
);
printf
(
" 3:mul 4:div \n"
);
printf
(
"*************************\n"
);
printf
(
"
请选择:
"
);
scanf
(
"%d"
,
&
input
);
switch
(
input
)
{
case
1
:
printf
(
"
输入操作数:
"
);
scanf
(
"%d %d"
,
&
x
,
&
y
);
ret
=
add
(
x
,
y
);
printf
(
"ret = %d\n"
,
ret
);
break
;
case
2
:
printf
(
"
输入操作数:
"
);
scanf
(
"%d %d"
,
&
x
,
&
y
);
ret
=
sub
(
x
,
y
);
printf
(
"ret = %d\n"
,
ret
);
break
;
case
3
:
printf
(
"
输入操作数:
"
);
scanf
(
"%d %d"
,
&
x
,
&
y
);
ret
=
mul
(
x
,
y
);
printf
(
"ret = %d\n"
,
ret
);
break
;
case
4
:
printf
(
"
输入操作数:
"
);
scanf
(
"%d %d"
,
&
x
,
&
y
);
ret
=
div
(
x
,
y
);
printf
(
"ret = %d\n"
,
ret
);
break
;
case
0
:
printf
(
"
退出程序
\n"
);
breark
;
default
:
printf
(
"
选择错误
\n"
);
break
;
}
}
while
(
input
);
return
0
;
}
这是我们用常见的方法来实现计算器,当我们看到这个代码的时候,是不是有一种感觉,就是显得特别冗余,这时我们就引进函数指针数组来解决
#include <stdio.h>
int
add
(
int
a
,
int
b
)
{
return
a
+
b
;
}
int
sub
(
int
a
,
int
b
)
{
return
a
-
b
;
}
int
mul
(
int
a
,
int
b
)
{
return
a
*
b
;
}
int
div
(
int
a
,
int
b
)
{
return
a
/
b
;
}
int
main
()
{
int
x
,
y
;
int
input
=
1
;
int
ret
=
0
;
int
(
*
p
[
5
])(
int
x
,
int
y
)
=
{
0
,
add
,
sub
,
mul
,
div
};
//
转移表
while
(
input
)
{
printf
(
"*************************\n"
);
printf
(
" 1:add 2:sub \n"
);
printf
(
" 3:mul 4:div \n"
);
printf
(
"*************************\n"
);
printf
(
"
请选择:
"
);
scanf
(
"%d"
,
&
input
);
if
((
input
<=
4
&&
input
>=
1
))
{
printf
(
"
输入操作数:
"
);
scanf
(
"%d %d"
,
&
x
,
&
y
);
ret
=
(
*
p
[
input
])(
x
,
y
);
}
else
printf
(
"
输入有误
\n"
);
printf
(
"ret = %d\n"
,
ret
);
}
return
0
;
}
这时我们就解决了一个switch 语句中很多case语句,让代码变得简洁些了
指向函数指针数组的指针
指向函数指针数组的指针是一个
指针
指针指向一个
数组
,数组的元素都是
函数指针
;
void
test
(
const
char*
str
)
{
printf
(
"%s\n"
,
str
);
}
int
main
()
{
//
函数指针
pfun
void
(
*
pfun
)(
const
char*
)
=
test
;
//
函数指针的数组pfunArr
void
(
*
pfunArr
[
5
])(
const
char*
str
);
pfunArr
[
0
]
=
test
;
//
指向函数指针数组
pfunArr
的指针
ppfunArr
void
(
*
(
*
ppfunArr
)[
10
])(
const
char*
)
= &
pfunArr
;
return
0
;
}
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一
个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该
函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或
条件进行响应。
首先演示一下
qsort
函数的使用:
int
int_cmp
(
const
void *
p1
,
const
void *
p2
)
{
return
(
*
(
int *
)
p1
- *
(
int *
)
p2
);
}
int
main
()
{
int
arr
[]
=
{
1
,
3
,
5
,
7
,
9
,
2
,
4
,
6
,
8
,
0
};
int
i
=
0
;
qsort
(
arr
,
sizeof
(
arr
)
/
sizeof
(
arr
[
0
]),
sizeof
(
int
),
int_cmp
);
for
(
i
=
0
;
i
<
sizeof
(
arr
)
/
sizeof
(
arr
[
0
]);
i
++
)
{
printf
(
"%d "
,
arr
[
i
]);
}
printf
(
"\n"
);
return
0
;
}
![](https://img-blog.csdnimg.cn/direct/065c74ebd82f4fc8949f785295dc0e4c.png)
该函数能够将任意类型的数组进行排序。该函数我们需要引进四个参数 (数组名,数组元素的个数,数组每个元素所占字节大小,判断前后两个元素大小的函数)。在这个函数中我们要引进一个判断是否交换顺序的函数,如果这个函数的返回值大于0则交换,小于或等于0则不交换。
我们使用回调函数的方法模拟实现qsort 函数(使用冒泡排序)
#include <stdio.h>
int
int_cmp
(
const
void *
p1
,
const
void *
p2
)
{
return
(
*
(
int *
)
p1
- *
(
int *
)
p2
);
}
void
_swap
(
void *
p1
,
void *
p2
,
int
size
)
{
int
i
=
0
;
for
(
i
=
0
;
i
<
size
;
i
++
)
{
char
tmp
= *
((
char *
)
p1
+
i
);
*
((
char *
)
p1
+
i
)
= *
((
char *
)
p2
+
i
);
*
((
char *
)
p2
+
i
)
=
tmp
;
}
}
void
bubble
(
void *
base
,
int
count
,
int
size
,
int
(
*
cmp
)(
void *
,
void *
))
{
int
i
=
0
;
int
j
=
0
;
for
(
i
=
0
;
i
<
count
-
1
;
i
++
)
{
for
(
j
=
0
;
j
<
count
-
i
-
1
;
j
++
)
{
if
(
cmp
((
char *
)
base
+
j
*
size
, (
char *
)
base
+
(
j
+
1
)
*
size
)
>
0
)
{
_swap
((
char *
)
base
+
j
*
size
, (
char *
)
base
+
(
j
+
1
)
*
size
,
size
);
}
}
}
}
int
main
()
{
int
arr
[]
=
{
1
,
3
,
5
,
7
,
9
,
2
,
4
,
6
,
8
,
0
};
//char *arr[] = {"aaaa","dddd","cccc","bbbb"};
int
i
=
0
;
bubble
(
arr
,
sizeof
(
arr
)
/
sizeof
(
arr
[
0
]),
sizeof
(
int
),
int_cmp
);
for
(
i
=
0
;
i
<
sizeof
(
arr
)
/
sizeof
(
arr
[
0
]);
i
++
)
{
printf
(
"%d "
,
arr
[
i
]);
}
printf
(
"\n"
);
return
0
;
}
这里面我们引进了void * 指针,该指针可以接收任意类型的指针,我们在使用的时候再将他强制转换成我们想用的类型
![](https://img-blog.csdnimg.cn/direct/0ec769fdbb0144189c4ef24dfb9198cf.png)