指针常量和常量指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const int i = 20; 
int const i = 20;
//它们是完全相同的。这一点我们是要清楚。总之,你务必要记住 const 与int 哪个写前都不影响语义。


const int *pi;
int const *pi;
//上面两种写法是一样的,所指向的值不可变,指向可变
/*
1)const 修饰的是整个*pi(注意是*pi 而不是 pi)。所以*pi 是常量,是不能被重新赋值的。
2)pi 前并没有用 const 修饰,所以 pi 是指针变量,能被赋值重新指向另一内存地址的。
*/

int *const pi;
//指向不可变,所指向的值可变
/*
1)pi 因为有了 const 的修饰,所以只是一个指针常量:也就是说 pi 值是不可修改的(即 pi 不可以重新指向其他变量了)
2)整个*pi 的前面没有 const 的修饰。也就是说,*pi 是变量而不是常量,所以我们可以通过*pi 来修改它所指向的变量的值
*/

const int * const i;
//指向、所指向的值均不可变

C 语言中函数参数的传递

值传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "stdio.h"
void Exchg1(int x, int y)
{
int tmp;
tmp = x;
x = y;
y = tmp;
printf("x = %d, y = %d\n", x, y);
}
int main()
{
int a = 4,b = 6;
Exchg1(a, b);
printf("a = %d, b = %d\n", a, b);
return 0;
}

程序输出的结果是:

x = 6, y = 4.

a = 4, b = 6.

一个预备的常识

1
2
3
4
int a = 4; 
int x;
x = a;
x = x + 3;

a = 4、x = 7

Exchg1(a, b)时所完成的操作代码如下所示。

1
2
3
4
5
6
int x = a; /* ← */ 
int y = b; /* ← 注意这里,头两行是调用函数时的隐含操作 */
int tmp;
tmp = x;
x = y;
y = tmp;

请注意在调用执行 Exchg1 函数的操作中我人为地加上了头两句:

1
2
int x = a; 
int y = b;

这是调用函数时的两个隐含动作。它确实存在,现在我只不过把它显式地写了出来而已。问题一下就清晰起来啦。(看到这里,现在你认为函数里面交换操作的是 a、b 变量或者只是 x、y 变量呢?)

原来 ,其实函数在调用时是隐含地把实参 a、b 的值分别赋值给了 x、y,之后在你写的Exchg1函数体内再也没有对 a、b 进行任何的操作了。交换的只是 x、y变量。并不是a、b。当然a、b的值没有改变啦!函数只是把a、b的值通过赋值传递给了** x**y,函数里头操作的只是x、y的值并不是a、b的值。这就是所谓的参数的值传递了。

正是因为它隐含了那两个的赋值操作,才让我们产生前述的迷惑(以为 a、b 已经代替了 x、y,对 x、y 的操作就是对 a、b 的操作了,这是一个错误的观点啊!)。

地址传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Exchg2(int *px, int *py) 
{
int tmp = *px;
*px = *py;
*py = tmp;
printf("*px = %d, *py = %d.\n", *px, *py);
}
main()
{
int a = 4;
int b = 6;
Exchg2(&a, &b);
printf("a = %d, b = %d.\n”, a, b);
return(0);
}

它的输出结果是:

*px = 6, *py = 4.
a = 6, b = 4.

看函数的接口部分:Exchg2(int *px, int *py),请注意:参数 px、py 都是指针。

再看调用处:Exchg2(&a, &b);

它将 a 的地址(&a)代入到 px,b 的地址(&b)代入到 py。同上面的值传递一样,函数调用时作了两个隐含的操作:将&a,&b 的值赋值给了 px、py。

1
2
px = &a; 
py = &b;

呵呵!我们发现,其实它与值传递并没有什么不同,只不过这里是将 a、b的地址值传递给了 px、py,而不是传递的 a、b 的内容,

整个 Exchg2 函数调用是如下执行的:

1
2
3
4
5
6
px = &a; /* ← */ 
py = &b; /* ← 请注意这两行,它是调用 Exchg2 的隐含动作。*/
int tmp = *px;
*px = *py;
*py = tmp;
printf("*px =%d, *py = %d.\n", *px, *py);

这样,有了头两行的隐含赋值操作。我们现在已经可以看出,指针px、py的值已经分别是a、b变量的地址值了。接下来,对px、py的操作当然也就是对a、b变量本身的操作了。所以函数里头的交换就是对 a、b 值的交换了,

这就是所谓的地址传递(传递 a、b 的地址给了 px、py),你现在明白了吗?

c++引用传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Exchg3(int &x, int &y) /* 注意定义处的形式参数的格式与值传递不同 */ 
{
int tmp = x; 27x = y;
y = tmp;
printf("x = %d, y = %d.\n", x, y);
}
main()
{
int a = 4;
int b = 6;
Exchg3(a, b); /*注意:这里调用方式与值传递一样*/
printf("a = %d, b = %d.\n”, a, b);
}
输出结果:
x = 6, y = 4.
a = 6, b = 4.
/*

这个输出结果与值传递不同。与值传递相比,代码格式上只有一处是不同的,即在定义处:
Exchg3(int &x, int &y)
但是我们发现 a 与 b 的值发生了对调。这说明了 Exchg3(a, b)里头修改的是 a、b 变量,而不只是修改 x、y 了。
我们先看 Exchg3 函数的定义处Exchg3(int &x, int &y)。参数 x、y 是 int 的变量,调用时我们可以像值传递(如: Exchg1(a, b); )一样调
用函数(如: Exchg3(a, b);)。但是 x、y 前都有一个符号“&”(不是取地址的作用)。有了这个,调用 Exchg3 时函数会将 a、b 分别代替了 x、y 了,我们称:x、y分别引用了 a、b 变量。这样函数里头操作的其实就是实参 a、b 本身了,也就是说函数里是可以直接修改到 a、b 的值了。

最后对值传递与引用传递作一个比较:
1)在函数定义格式上有不同: 值传递在定义处是:Exchg1(int x, int y); 引用传递在这义处是:Exchg3(int &x, int &y);
2)调用时有相同的格式: 值传递:Exchg1(a, b); 引用传递:Exchg3(a, b);
3)功能上是不同的:
值传递的函数里操作的不是 a、b 变量本身,只是将 a、b 值赋给了 x、y。 函数里操作的只是 x、y 变量而不是 a、b,显示 a、b 的值不会被 Exchg1 函数 所修改。
引用传递 Exchg3(a, b)函数里是用 a、b 分别代替了 x、y。函数里操作 的就是 a、b 变量的本身,因此 a、b 的值可在函数里被修改的。

指向另一指针地址的指针

看下面代码:

1
2
3
short int i = 50;
short int *pi = &i;
short int **ppi = π /* 这是一个指向指针的指针,注意有两个“*”号 */

第一句:short int **ppi; —— 声明了一个指针变量 ppi,这个 ppi 是用来存储(或称指向)一个short int * 类型指针变量的地址。

第二句:&pi 那就是取 pi 的地址,**ppi = &pi 就是把 pi 的地址赋给 了 ppi。即将地址值 9 赋值给 ppi。如下图:

内存地址→ 5 6 7 8 9 10 11 12

——–+——————————————————————————————————————–

| 50 | | | 5 | 9 |

| short int i | | | short int pi |short int *ppi|

从图中看出,指针变量 ppi 的内容就是指针变量 pi 的起始地址。于是…… ppi 的值是多少呢?—— 9。 ppi 的值是多少呢?—— 5,即 pi 的值。 *ppi 的值是多少呢?——50,即 i 的值,也是*pi 的值。 呵呵!不用我说太多了,我相信你应明白这种指针了吧!

一个应用实例

一个应用实例
(1)设计一个函数:

1
void find1(char array[], char search, char *pa)

要求:这个函数参数中的数组 array 是以 0 值为结束的字符串,要求在字符串 array 中查找字符是参数 search 里的字符。如果找到,函数通过第三个参数 (pa)返回值为 array 字符串中第一个找到的字符的地址。如果没找到,则为 pa 为 0。
设计:依题意,实现代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
void find1(char array[], char search, char *pa) {
int i;
for (i = 0; *(array + i) != '\0'; i++) {
if (*(array + i) == search) {
pa = array + i;
break;
} else if (*(array + i) == 0) {
pa = 0;
break;
}
}
}

你觉得这个函数能实现所要求的功能吗?
调试:
我下面调用这个函数试试。

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
char str[] = {"afsdfsdfdf\0"};/* 待查找的字符串 */
char a = 'd';/* 设置要查找的字符 */
char *p = 0; /* 如果查找到后指针 p 将指向字符串中查找到的第 1 个字符的地址。 */
find1(str, a, p); /* 调用函数以实现所要操作。 */
if (0 == p) {
printf("Not Find! \n"); /* 如果没找到则输出此句 */
} else {
printf("Find! p = %d", p); /* 如果找到则输出此句*/
}
return (0);
}

分析:
上面代码,你认为会是输出什么呢?
运行试试。
唉!怎么输出的是:Not Find!
而不是“Find!,……”。
明明 a 值为’d’,而 str 字符串的第四个字符是’d’,应该找得到呀!
再看函数定义处:

1
void find1(char array[], char search, char *pa)

看调用处:

1
find1(str, a, p);

依我在第伍篇的分析方法,函数调用时会对每一个参数进行一个隐含的赋值操作。
整个调用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
array = str;
search = a;
pa = p; /* 请注意:以上三句是调用时隐含的动作。*/
int i;
for(i =0; *(array+i) != '\0'; i++)
{
if (*(array+i) == search)
{
pa = array + i;
break;
}
else if (*(array+i)==0)
{
pa=0;
break;
}
}

哦!参数 pa 与参数 search 的传递并没有什么不同,都是值传递嘛(小语:地址传递其实就是地址值传递嘛)!所以对形参变量 pa 值(当然值是一个地址值)的修改并不会改变实参变量 p 值,因此 p 的值并没有改变(即 p 的指向并没有被改变)。
修正:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void find2(char array[], char search, char **ppa)
{
int i;
for (i=0; *(array + i) != 0; i++)
{
if(*(array + i) == search)
{
*ppa = array + i;
break;
}
else if(*(array + i) == 0)
{
*ppa = 0;
break;
}
}
}

主函数的调用处改如下:

1
find2(str, a, &p);

调用函数以实现所要操作。
再分析:
这样调用函数时的整个操作变成如下:

1
2
3
4
5
6
7
8
9
array = str; search = a; ppa = &p; /* 请注意:以上三句是调用时隐含的动作。 */ int i;
for (i = 0; *(array + i) != 0; i++) {
if (*(array + i) == search) {
*ppa = array + i break;
}
else if (*(array+i)==0) {
*ppa=0; break;
}
}

看明白了吗? ppa 指向指针 p 的地址。 对*ppa 的修改就是对 p 值的修改。 你自行去调试。
经过修改后的程序就可以完成所要的功能了。 看懂了这个例子,也就达到了本篇所要求的目的。

函数指针

什么是函数指针

1
2
3
4
5
6
7
8
9
10
11
#include "stdio.h"

void MyFun(int x) {
printf("%d\n", x);
}

int main(int argc, char *argv[]) {
void (*FunP)(int) = MyFun;
MyFun(10);
(*FunP)(20);
}

有了 FunP 指针变量后,我们就可以对它赋值指向 MyFun,然后通过 FunP来调用 MyFun 函数了。看我如何通过 FunP 指针变量来调用 MyFun 函数的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "stdio.h"
void MyFun(int x);
void (*FunP)(int);

int main(int argc, char *argv[]) {
MyFun(10); /* 这是直接调用 MyFun 函数 */

FunP = &MyFun; /* 将 MyFun 函数的地址赋给 FunP 变量 */
FunP = MyFun; /* 将 MyFun 函数的地址赋给 FunP 变量 */


(*FunP)(20);/* (★)这是通过函数指针变量 FunP 来调用MyFun 函数的。 */
FunP(20); /* (★)这是通过函数指针变量来调用 MyFun 函数的。*/
return 0;
}

void MyFun(int x) {
printf("%d\n", x);
}

1)其实,MyFun 的函数名与 FunP 函数指针都是一样的,即都是函数指针。
MyFun 函数名是一个函数指针常量,而 FunP 是一个函数数指针变量,这是它 们的关系。

2)但函数名调用如果都得如(*MyFun)(10)这样,那书写与读起来都是不 方便和不习惯的。所以 C 语言的设计者们才会设计成又可允许 MyFun(10)这种 形式地调用(这样方便多了并与数学中的函数形式一样,不是吗?)。

3)为统一起见,FunP 函数指针变量也可以 FunP(10)的形式来调用。

4)赋值时,即可FunP = &MyFun形式,也可FunP = MyFun。

上述代码的写法,随便你爱怎么着!
请这样理解吧!这可是有助于你对函数指针的应用喽! 最后 ——
补充说明一点,在函数的声明处:

1
2
void MyFun(int); /*不能写成 void (*MyFun)(int)。*/ 
void (*FunP)(int); /*不能写成 void FunP(int)。*/

定义某一函数的指针类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "stdio.h"

void MyFun(int x);

typedef void (*FunType)(int);

FunType FunP; /*然后用 FunType 类型来声明全局 FunP 变量*/
int main(int argc, char *argv[]) {
FunType FunP;
MyFun(10);
FunP = &MyFun;
return 0;
}

void MyFun(int x) {
printf("%d\n", x);
}

定义一个名为 FunType 函数指针类型,而不是一个 FunType 变量。 然后,FunType FunP这句就如PINT px;一样地声明一个 FunP
变量。

函数指针作为某个函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "stdio.h"

int MyFun1(int x);

int MyFun2(int x);

int MyFun3(int x);

typedef int (*FunType)(int); /* ②. 定义一个函数指针类型 FunType,只要函数类型(参数和返回值)相同就可以只用之来定义 */
void CallMyFun(FunType fp, int x);

int main(int argc, char *argv[]) {
CallMyFun(MyFun1, 10); /* ⑤. 通过 CallMyFun 函数分别 调用三个不同的函数 */
CallMyFun(MyFun2, 20);
CallMyFun(MyFun3, 30);
return 0;
}

void CallMyFun(FunType fp, int x) /* ③. 参数 fp 的类型是FunType。*/
{
fp(x);/* ④. 通过 fp 的指针执行传递进来的函数,注意 fp 所指 的函数是有一个参数的。 */
}

int MyFun1(int x) /* ①. 这是个有一个参数的函数,以下两个函数也相同。 */
{
printf("函数 MyFun1 中输出:%d\n", x);
return x;
}

int MyFun2(int x) {
printf("函数 MyFun2 中输出:%d\n", x);
return x;
}

int MyFun3(int x) {
printf("函数 MyFun3 中输出:%d\n", x);
return x;
}

输出结果:

1
2
3
函数 MyFun1 中输出:10
函数 MyFun2 中输出:20
函数 MyFun3 中输出:30

函数类型

函数指针只要返回值和参数相同就可以指向

1
2
void (*p1)(int) ; // 可以指向函数的类型是空返回值且一个int类型的参数
double (*p2)(int, int) ; // 可以指向函数的类型是double类型的返回值且两个int类型的参数