第九章 指针
C程序中的变量都是存储在计算机内存特定的存储单元中的,内存中的每个单元都有唯一的地址
通过取地址运算符&可以获得变量的地址
//L9-1
#include <stdio.h>
int main()
{
int a = 0, b = 1;
char c = 'A';
printf("a is %d, &a is %p\n", a, &a);
printf("b is %d, &b is %p\n", b, &b);
printf("c is %c, &c is %p\n", c, &c);
return 0;
}
```c
//运行结果
a is 0, &a is 0060FF0C
b is 1, &b is 0060FF08
c is A, &c is 0060FF07
%p格式符,表示输出变量a、b、c的地址值
地址值是一个十六进制的无符号整数,其字长一般与主机的字长相同
- 变量的地址:变量在内存中所占的存储空间的首地址
- 变量的值:变量在存储空间中存放的数据
- 变量的名字:对程序中数据存储空间的一种抽象
指针:存放变量的地址的特殊类型的变量
类型关键字 *指针变量名;
类型关键字代表指针变量要指向的变量的数据类型,即指针变量的基类型
指针变量的定义只是声明了指针变量的名字及其所能指向的数据类型,并没有说明指针变量究竟指向了哪里
//L9-2
#include <stdio.h>
int main()
{
int a = 0, b = 1;
char c = 'A';
int *pa, *pb; /* 定义指针变量pa和pb */
char *pc; /* 定义指针变量pc */
pa = &a; /* 初始化指针变量pa使其指向a */
pb = &b; /* 初始化指针变量pb使其指向b */
pc = &c; /* 初始化指针变量pc使其指向c */
printf("a is %d, &a is %p, pa is %p, &pa is %p\n", a, &a, pa, &pa);
printf("b is %d, &b is %p, pb is %p, &pb is %p\n", b, &b, pb, &pb);
printf("c is %c, &c is %p, pc is %p, &pc is %p\n", c, &c, pc, &pc);
return 0;
}
```c
//运行结果
a is 0, &a is 0060FF0C, pa is 0060FF0C, &pa is 0060FF00
b is 1, &b is 0060FF08, pb is 0060FF08, &pb is 0060FEFC
c is A, &c is 0060FF07, pc is 0060FF07, &pc is 0060FEF8
指针变量未被初始化意味着指针变量的值是一个随机值,无法预知它会指向哪里
为避免忘记指针初始化给系统带来潜在危险,习惯上在定义指针变量的同时将其初始化为NULL
如int *pa = NULL;
在定义指针变量的同时可以对指针变量进行初始化
如int *pa = &a;
间接寻址运算符:
- 直接寻址:直接按变量名或者变量的地址存取变量的内容的访问方式
- 间接寻址:通过指针变量间接存取它所指向的变量的访问方式
间接寻址运算符*用来访问指针变量指向的变量的值
//L9-3
#include <stdio.h>
int main()
{
int a = 0, b = 1;
char c = 'A';
int *pa = &a, *pb = &b; /* 在定义指针变量pa、pb和pc的同时对其初始化 */
char *pc = &c;//*作为指针类型说明符用于指针变量的定义
printf("a is %d, &a is %p, pa is %p, *pa is %d\n", a, &a, pa, *pa);
//*作为间接引用运算符,用于读取并显示指针变量中存储的内存地址所对应的变量的值
printf("b is %d, &b is %p, pb is %p, *pb is %d\n", b, &b, pb, *pb);
printf("c is %c, &c is %p, pc is %p, *pc is %c\n", c, &c, pc, *pc);
return 0;
}
```c
//运行结果
a is 0, &a is 0060FF00, pa is 0060FF00, *pa is 0
b is 1, &b is 0060FEFC, pb is 0060FEFC, *pb is 1
c is A, &c is 0060FEFB, pc is 0060FEFB, *pc is A
输出*pa的值和输出a的值是等价的,因此修改了*pa的值也就相当于修改了a的值
指针的解引用:引用指针变量所指向的变量的值
指针只有在真正指向了一块有意义的内存后,才能访问它的内容
使用指针需要恪守:
- 永远清楚每个指针指向了哪里
- 永远清楚每个指针指向的对象的内容是什么
- 永远不要使用未初始化的指针变量
按值调用
//L9-4
#include <stdio.h>
void Fun(int par);
int main()
{
int arg = 1 ;
printf("arg = %d\n", arg);
Fun(arg); /* 传递实参值的拷贝给函数 */
printf("arg = %d\n", arg);
return 0;
}
void Fun(int par)
{
printf("par = %d\n", par);
par = 2; /* 改变形参的值 */
}
```c
//运行结果
arg = 1
par = 1
arg = 1
函数形参值的改变并未影响实参值的改变
这是因为传给函数形参的值只是函数调用语句中实参值的副本
因此,按值调用的方法不能在被调函数中改变其调用语句中的实参值
模拟按引用调用
//L9-5
#include <stdio.h>
void Fun(int *par);
int main()
{
int arg = 1 ;
printf("arg = %d\n", arg);
Fun(&arg); /* 传递变量arg的地址值给函数 */
printf("arg = %d\n", arg);
return 0;
}
void Fun(int *par)
{
printf("par = %d\n", *par); /* 输出形参指向的变量的值 */
*par = 2; /* 改变形参指向的变量的值 */
}
```c
//运行结果
arg = 1
par = 1
arg = 2
将函数的形参声明为指针类型,使用指针变量作为函数形参
形参接收到的数据只能是一个地址值
函数Fun()使用间接寻址运算符*改变了形参指向的实参的值
模拟按引用调用是一种常用的从函数中返回修改了的数据值的方法
//L9-6
#include <stdio.h>
void Swap(int *x, int *y);
int main()
{
int a, b;
printf("Please enter a,b:");
scanf("%d,%d", &a, &b);
printf("Before swap: a = %d, b = %d\n", a, b);/* 打印交换前的a,b */
Swap(&a, &b); /* 按地址调用函数Swap() */
printf("After swap: a = %d, b = %d\n", a, b);/* 验证a,b是否互换 */
return 0;
}
/* 函数功能:交换两个整型数的值 */
void Swap(int *x, int *y)
{
int temp;
temp = *x; /* 执行图7-5(b)中的步骤① */
*x = *y; /* 执行图7-5(b)中的步骤② */
*y = temp; /* 执行图7-5(b)中的步骤③ */
}
```c
//运行结果
Please enter a,b:15,8
Before swap: a = 15, b = 8
After swap: a = 8, b = 15
用指针变量作函数参数
//L9-7
#include <stdio.h>
#define N 30
void FindMax(int score[],long num[],int n,int *pMaxScore,long *pMaxNum);
int main()
{
int score[N], maxScore;
int n, i;
long num[N], maxNum;
printf("How many students?");
scanf("%d", &n); /* 从键盘输入学生人数n */
printf("Input student's ID and score:\n");
for (i=0; i<n; i++)
{
scanf("%ld%d", &num[i], &score[i]); /* 字母d前为字母l */
}
FindMax(score, num,n, &maxScore, &maxNum);/* 按地址调用函数 */
printf("maxScore = %d, maxNum = %ld\n", maxScore, maxNum);
return 0;
}
/* 函数功能:计算最高分及其相应学生的学号 */
void FindMax(int score[],long num[],int n,int *pMaxScore,long *pMaxNum)
{
int i;
*pMaxScore = score[0]; /* 假设score[0]为当前最高分 */
*pMaxNum = num[0]; /* 记录score[0]的学号num[0] */
for (i=1; i<n; i++) /* 对所有score[i]进行比较 */
{
if (score[i] > *pMaxScore) /* 如果score[i]高于当前最高分 */
{
*pMaxScore = score[i]; /* 用score[i]修改当前最高分 */
*pMaxNum = num[i]; /* 记录当前最高分学生的学号num[i] */
}
}
}
```c
//运行结果
How many students?5
Input student's ID and score:
120310122 84
120310123 83
120310124 88
120310125 87
120310126 61
maxScore = 88, maxNum = 120310124
函数FindMax()的前3个形参在函数调用前必须确定其值,因此称为函数的入口参数
而指针变量的值在函数调用结束后才能被确定,因此指针形参称为函数的出口参数
函数指针:指向函数的指针
指向函数的指针变量中存储的是一个函数在内存中的入口地址
函数名就是这个函数的源代码在内存中的起始地址
编译器将不带()的函数名解释为函数的入口地址
//L9-8
#include <stdio.h>
#define N 40
int ReadScore(int score[]); /* 成绩输入函数原型 */
void PrintScore(int score[], int n); /* 成绩输出函数原型 */
void AscendingSort(int a[], int n); /* 升序排序函数原型 */
void DescendingSort(int a[], int n); /* 降序排序函数原型 */
void Swap(int *x, int *y); /* 两数交换函数原型 */
int main()
{
int score[N], n;
int order; /* 值为1表示升序排序,值为2表示降序排序 */
n = ReadScore(score); /* 输入成绩,返回学生人数 */
printf("Total students are %d\n",n);
printf("Enter 1 to sort in ascending order,\n");
printf("Enter 2 to sort in descending order:");
scanf("%d", &order);
printf("Data items in original order\n");
PrintScore(score, n); /* 输出排序前的成绩 */
if (order == 1)
{
AscendingSort(score, n); /* 按升序排序 */
printf("Data items in ascending order\n");
}
else
{
DescendingSort(score, n); /* 按降序排序 */
printf("Data items in descending order\n");
}
PrintScore(score, n); /* 输出排序后的成绩 */
return 0;
}
/* 函数功能:输入学生某门课的成绩,当输入负值时,结束输入,返回学生人数 */
int ReadScore(int score[])
{
int i = -1;
do{
i++;
printf("Input score:");
scanf("%d", &score[i]);
} while (score[i] >= 0);
return i;
}
/* 函数功能:输出学生成绩 */
void PrintScore(int score[], int n)
{
int i;
for (i=0; i<n; i++)
{
printf("%4d", score[i]);
}
printf("\n");
}
/* 函数功能: 选择法实现数组a的升序排序 */
void AscendingSort(int a[], int n) /* 升序排序函数定义 */
{
int i, j, k;
for (i=0; i<n-1; i++)
{
k = i;
for (j=i+1; j<n; j++)
{
if (a[j] < a[k]) k = j;
}
if (k != i) Swap(&a[k], &a[i]);
}
}
/* 函数功能:选择法实现数组a的降序排序 */
void DescendingSort(int a[], int n) /* 降序排序函数定义 */
{
int i, j, k;
for (i=0; i<n-1; i++)
{
k = i;
for (j=i+1; j<n; j++)
{
if (a[j] > a[k]) k = j;
}
if (k != i) Swap(&a[k], &a[i]);
}
}
/* 函数功能:两整数值互换 */
void Swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
```c
//运行结果
Input score:90
Input score:91
Input score:92
Input score:86
Input score:97
Input score:-1
Total students are 5
Enter 1 to sort in ascending order,
Enter 2 to sort in descending order:2
Data items in original order
90 91 92 86 97
Data items in descending order
97 92 91 90 86
//L9-9
#include <stdio.h>
#define N 40
int ReadScore(int score[]);
void PrintScore(int score[], int n);
void SelectionSort(int a[], int n, int (*compare)(int a, int b));
int Ascending( int a, int b );
int Descending( int a, int b );
void Swap(int *x, int *y);
int main()
{
int score[N], n;
int order; /* 值为1表示升序排序,值为2表示降序排序 */
n = ReadScore(score); /* 输入成绩,返回学生人数 */
printf("Total students are %d\n",n);
printf("Enter 1 to sort in ascending order,\n");
printf("Enter 2 to sort in descending order:");
scanf("%d", &order);
printf("Data items in original order\n");
PrintScore(score, n); /* 输出排序前的成绩 */
if (order == 1)
{
SelectionSort(score, n, Ascending);/*函数指针指向Ascending()*/
printf("Data items in ascending order\n");
}
else
{
SelectionSort(score, n, Descending);/*函数指针指向Descending()*/
printf("Data items in descending order\n");
}
PrintScore(score, n); /* 输出排序后的成绩 */
return 0;
}
/* 函数功能:输入学生某门课的成绩,当输入负值时,结束输入,返回学生人数 */
int ReadScore(int score[])
{
int i = -1;
do{
i++;
printf("Input score:");
scanf("%d", &score[i]);
} while (score[i] >= 0);
return i;
}
/* 函数功能:输出学生成绩 */
void PrintScore(int score[], int n)
{
int i;
for (i=0; i<n; i++)
{
printf("%4d", score[i]);
}
printf("\n");
}
/* 函数功能:调用函数指针compare指向的函数实现对数组a的交换法排序 */
void SelectionSort(int a[], int n, int (*compare)(int a, int b))
{
int i, j, k;
for (i=0; i<n-1; i++)
{
k = i;
for (j=i+1; j<n; j++)
{
if ((*compare)(a[j], a[k])) k = j;
}
if (k != i) Swap(&a[k], &a[i]);
}
}
/* 使数据按升序排序 */
int Ascending( int a, int b )
{
return a < b; /* 这样比较决定了按升序排序,如果a<b,则交换 */
}
/* 使数据按降序排序 */
int Descending( int a, int b )
{
return a > b; /* 这样比较决定了按降序排序,如果a>b,则交换 */
}
/* 函数功能:两整数值互换 */
void Swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
void SelectionSort(int a[], int n, int (*compare)(int a, int b))
告诉编译器函数SelectionSort的形参compare是一个指针变量,该指针变量可以指向一个两个整型形参、返回值为整型的函数
该函数指针既可以指向函数Ascending(),也可以指向函数Descending()
因此可以用这样一个通用的排序函数,实现升序或降序排列
虽然定义函数指针时并未指明该指针指向了哪个函数,但在程序中可以通过赋值操作将函数指针分别指向不同的函数,以实现在一点调用不同的函数
利用函数指针编程有助于提高程序的通用性、简洁性,减少重复的代码