第九章 指针
9.1 变量的内存地址
1 **取地址符&**可以取出变量的地址。
例9.1见文末
2 内存的地址是按字节编号,变量的地址 实际指变量在内存中所占存储空间的首地址 ;变量的值 是变量在存储空间中存放的数据;变量的名字可以看成对程序中数据存储空间的一种随机。
3 声明变量时若没有赋初值,则他们的内容就是随机的、不确定的。
9.2 指针变量的定义和初始化
1 指针 是一种特殊类型的变量 ,专门用于存储 变量的地址值的变量。定义形式为
类型关键字 * 指针变量名
2 指针一定要记得初始化,这非常重要。可以将其初始化为NULL(stdio.h中零值的宏)。
例9.2见文末
3 指向某变量的指针常被简称为某变量的指针。 变量的地址是一个常量 ,不能对其赋值;变量的指针是一个变量,可以对其赋值。
4 指针变量只能指向同一基类型的变量,如int *pa,只能指向int型。
9.3 间接寻址运算符
1 直接寻址 是直接按变量名或变量的地址存取变量的内容的访问(如前几章的方法);间接寻址是通过指针变量间接存取它所指向的变量的访问方式。
2 间接寻址是先通过指针变量 去获得变量的地址值 ,如何再到地址值对应的存储单元中访问变量。
3 指针运算符 (也称间接寻址运算符或解应用运算符),也就是***** ,用来访问指针变量指向的变量的值。
例9.3见文末
4 *在不同的位置具有不同的功能。可以是指针类型说明符 ,也可以作为间接引用符,这两者之间并无关系。
5 指针的解引用是指引用指针所指向的变量的值。
6 使用指针一定要注意(1)清楚每个指针指向了哪里 (指针是指向一个地址,通过地址文末可以找到对应地址上的变量),必需指向一块有意义的内存。(2)清楚每个指针指向的对象 的内容是什么(即指向地址上的变量是什么)。(3)永远不要用未初始化的指针变量。
9.4 按值调用与模拟按引用调用
1 用普通变量作函数参数的方法属于一种按值调用的方法,即程序将函数调用语句中的实参的一份副本传给函数的形参。按值调用不会改变实参的值。
例9.4见文末
2 指针作为函数参数传递时,本质上传递的不是变量的值,而是变量的地址 ,可以实现在函数中 改变实参的值。尽管这种方法实际上也是按值调用 (C语言中所有函数调用都是按值调用),但是被称为模拟按引用调用。
例9.5和例9.6见文末
9.5 用指针变量作函数参数的程序实例
例9.7见文末
1 指针形参所指向的变量的值在调用结束后才被确定,则该指针形参被称为函数的出口参数 ;对应的,在函数调用前必需确定值的被称为函数的入口参数。
9.6 函数指针及其应用
1 函数指针 (指向函数的指针)中存储的是一个函数在内存中的入口地址 ,而函数的入口地址也就是指向存储这个函数的第一条指令 的地址。
2 函数名是这个函数的源代码在内存中的起始位置。
例9.8和例9.9见文末
3 注意int *pa 和int (*pa)表示的是两个含义。
代码:
9.1
使用取地址符&取出变量的地址,然后将其显示在屏幕上。
cs
//例9.1 使用取地址符&取出变量的地址,然后将其显示在屏幕上。
#include<stdio.h>
int main(void)
{
int a=0,b=1;
char c='A';
printf("a is %d,&a is %p\n",a,&a);//%p可以用于输出格式是十六进制的无符号整型
printf("b is %d,&b is %p\n",b,&b);
printf("c is %c,&c is %p\n",c,&c);
return 0;
}
9.2(错误示范版一)
使用指针变量在屏幕上显示变量的地址值(错误示范版)
cs
//例9.2 使用指针变量在屏幕上显示变量的地址值(错误示范版)
#include<stdio.h>
int main(void)
{
int a=0,b=1;
char c='A';
int *pa,*pb;//指向int型的指针变量pa和pb
char *pc;//指向char型的指针变量pc
printf("a is %d,&a is %p,pa is %p\n",a,&a,pa);
printf("b is %d,&b is %p,pb is %p\n",b,&b,pb);
printf("c is %c,&c is %p,pc is %p\n",c,&c,pc);
return 0;
}
9.2(错误示范版二)
使用指针变量在屏幕上显示变量的地址值(错误示范版)
cs
//例9.2 使用指针变量在屏幕上显示变量的地址值(错误示范版)
#include<stdio.h>
int main(void)
{
int a=0,b=1;
char c='A';
int *pa=NULL,*pb=NULL;//注意初始化,初始化后指向地址0,所以依旧不能得到我们需要的指向对应变量的地址的效果
char *pc=NULL;
printf("a is %d,&a is %p,pa is %p\n",a,&a,pa);
printf("b is %d,&b is %p,pb is %p\n",b,&b,pb);
printf("c is %c,&c is %p,pc is %p\n",c,&c,pc);
return 0;
}
9.2(正确版本)
使用指针变量在屏幕上显示变量的地址值(正确示范版)
cs
//例9.2 使用指针变量在屏幕上显示变量的地址值(正确示范版)
#include<stdio.h>
int main(void)
{
int a=0,b=1;
char c='A';
int *pa=&a,*pb=&b;//注意这里的变量a,b,c前面都要有&,因为指针变量的本质是指向一个地址
char *pc=&c;
printf("a is %d,&a is %p,pa is %p\n",a,&a,pa);
printf("b is %d,&b is %p,pb is %p\n",b,&b,pb);
printf("c is %c,&c is %p,pc is %p\n",c,&c,pc);
return 0;
}
9.3(版本一)
使用指针变量,通过间接寻址输出变量的值(版本一)
cs
//例9.3 使用指针变量,通过间接寻址输出变量的值(版本一)
#include<stdio.h>
int main(void)
{
int a=0,b=1;
char c='A';
int *pa=&a,*pb=&b;//定义指针时就初始化
char *pc=&c;
printf("a is %d,&a is %p,*pa is %d\n",a,&a,*pa);
printf("b is %d,&b is %p,*pb is %d\n",b,&b,*pb);
printf("c is %c,&c is %p,*pc is %c\n",c,&c,*pc);//这里的输出与例9.2不同,属于解引用,此处的*含义也不同于定义时的*
return 0;
}
9.3(版本二)
使用指针变量,通过间接寻址输出变量的值(版本二,修改指针变量)
cs
//例9.3 使用指针变量,通过间接寻址输出变量的值(版本二,修改指针变量)
#include<stdio.h>
int main(void)
{
int a=0,b=1;
char c='A';
int *pa=&a,*pb=&b;//定义指针时就初始化
char *pc=&c;
*pa=9;//这里属于将指针变量里对应的内容改为9
printf("a is %d,&a is %p,*pa is %d\n",a,&a,*pa);
printf("b is %d,&b is %p,*pb is %d\n",b,&b,*pb);
printf("c is %c,&c is %p,*pc is %c\n",c,&c,*pc);
return 0;
}
9.4
演示程序按值调用
cs
//例9.4 演示程序按值调用
#include<stdio.h>
void Fun(int par);
int main(void)
{
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;
}
9.5
演示程序模拟按引用调用的例子
cs
//例9.5 演示程序模拟按引用调用的例子
#include<stdio.h>
void Fun(int *par);
int main(void)
{
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;//直接改变形参指向的变量的值
}
9.5补充
扩充,按值调用,但是可以正确返回在函数中改变的值
cs
//例9.5扩充,按值调用,但是可以正确返回在函数中改变的值
#include<stdio.h>
void Fun(int par);
int main(void)
{
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;
return par;//注意这是与之前的按值调用的不同之处,你可以理解成返回了这个值后函数对应的内存才释放,但是这种方法只能返回一个值,不在函数中改变多个值
}
9.6(错误版本)
从键盘上任意输入两个整数,编程实现将其交换后再重新输出。试一试下面程序能否实现该功能。(答案是不能)
cs
//例9.6 从键盘上任意输入两个整数,编程实现将其交换后再重新输出。试一试下面程序能否实现该功能。(答案是不能)
#include<stdio.h>
void Swap(int x,int y);
int main(void)
{
int a,b;
printf("Please enter a,b:");
scanf("%d,%d",&a,&b);
printf("Before swap:a=%d,b=%d\n",a,b);//启用调用函数前
Swap(a,b);
printf("After swap:a=%d,b=%d\n",a,b);//验证实参是否交换
return 0;
}
void Swap(int x,int y)
{
int temp;//用temp作为交换辅助变量
temp=x;
x=y;
y=temp;//在这个函数里的确实现了交换,但是只是相当于副本改变,对实参不会产生影响,和例9.4是一样的道理
}
9.6(正确示范)
从键盘上任意输入两个整数,编程实现将其交换后再重新输出。试一试下面程序能否实现该功能。(正确方法)
cs
//例9.6 从键盘上任意输入两个整数,编程实现将其交换后再重新输出。试一试下面程序能否实现该功能。(正确方法)
#include<stdio.h>
void Swap(int *x,int *y);
int main(void)
{
int a,b;
printf("Please enter a,b:");
scanf("%d,%d",&a,&b);
printf("Before swap:a=%d,b=%d\n",a,b);//启用调用函数前
Swap(&a,&b);//注意这里是传递地址给指针变量,因此需要&
printf("After swap:a=%d,b=%d\n",a,b);//验证实参是否交换
return 0;
}
void Swap(int *x,int *y)//形参是两个指向int型的指针变量
{
int temp;//用temp作为交换辅助变量
temp=*x;
*x=*y;
*y=temp;//也不要忘记*哦
}
9.7(错误版本)
从键盘输入某班学生某门课的成绩(不超过40人,具体人数由键盘输入),试分析下面的程序能否实现计算并输出最高分及相应学生的学号。
(答案是不能,因为同样是按值调用)
cs
//例9.7 从键盘输入某班学生某门课的成绩(不超过40人,具体人数由键盘输入),试分析下面的程序能否实现计算并输出最高分及相应学生的学号。
//(答案是不能,因为同样是按值调用)
#include<stdio.h>
#define N 40
void FindMax(int score[],long num[],int n,int pMaxScore,long pMaxNum);
int main(void)
{
int score[N],maxScore;
int n,i;
long num[N],maxNum;
printf("How many students:");
scanf("%d",&n);
printf("Input student's ID and score:\n");
for(i=0;i<n;i++)
{
scanf("%ld %d",&num[i],&score[i]);//输入是字母l,字母d,不是1d
}//完成输入,学号和成绩之间用空格隔开
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];//把第一个成绩先当成最大成绩
pMaxNum=num[0];
for(i=1;i<n;i++)//i,从1开始,其实相当于是和第二个成绩开始比较
{
if(score[i]>pMaxScore){
pMaxScore=score[i];
pMaxNum=num[i];//成绩和学号都要改变
}
}
}
9.7(正确方法)
从键盘输入某班学生某门课的成绩(不超过40人,具体人数由键盘输入)(正确方法)
cs
//例9.7 从键盘输入某班学生某门课的成绩(不超过40人,具体人数由键盘输入)(正确方法)
#include<stdio.h>
#define N 40
void FindMax(int score[],long num[],int n,int *pMaxScore,long *pMaxNum);
int main(void)
{
int score[N],maxScore;
int n,i;
long num[N],maxNum;
printf("How many students:");
scanf("%d",&n);
printf("Input student's ID and score:\n");
for(i=0;i<n;i++)
{
scanf("%ld %d",&num[i],&score[i]);
}//完成输入,学号和成绩之间用空格隔开
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];//把第一个成绩先当成最大成绩
*pMaxNum=num[0];//不要忘记解引用
for(i=1;i<n;i++)
{
if(score[i]>*pMaxScore){
*pMaxScore=score[i];
*pMaxNum=num[i];//不要忘记解引用 用*
}
}
}
9.8
修改例8.7中的排序函数,使其既能实现对学生成绩的升序排序,又能实现对学生成绩的降序排序。
cs
//例9.8 修改例8.7中的排序函数,使其既能实现对学生成绩的升序排序,又能实现对学生成绩的降序排序。
#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(void){
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;//初始为-1,才能在do-while循环体中加一变成0,作为下标
do{
i++;
printf("Input score:");
scanf("%d",&score[i]);
}while(score[i]>=0);
return i;//返回 的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;//在每次的外层循环中,将i值赋给k,再把k值带到内存循环中比较
for(j=i+1;j<n;j++)//相当于是从当前位置数组的后一个开始,数组的最后一个结束
{
if(a[j]<a[k])//如果后面的值小于前面的k值
k=j;//那么就把小的值赋值给k ,相当于找到后面的数中,最小k是多少,由于k代表的是下标,所以现在只是找下标阶段,数还没交换
}
if(k!=i)//k不等于i,就说明内层循环发生过数的改变,那么就交换当前k为下标的数和i为下标的数,实现数的位置改变
Swap(&a[k],&a[i]);
}
}
void DescendingSort(int a[],int n)
{
int i,j,k;
for(i=0;i<n-1;i++)
{
k=i;//在每次的外层循环中,将i值赋给k,再把k值带到内存循环中比较
for(j=i+1;j<n;j++)//相当于是从当前位置数组的后一个开始,数组的最后一个结束
{
if(a[j]>a[k])//与升序同理,只不过现在是找后面数里面最大的值
k=j;
}
if(k!=i)//k不等于i,就说明内层循环发生过数的改变,那么就交换当前k为下标的数和i为下标的数,实现数的位置改变
Swap(&a[k],&a[i]);
}
}
void Swap(int *x,int *y)
{
//这里用模拟按引用调用才能实现两个数在函数内的实参交换
int temp;
temp=*x;
*x=*y;
*y=temp;
}
9.9
修改例9.8中的程序实例,用函数指针编程实现一个通用的排序指针,既能实现对学生成绩的升序排序,又能实现对学生成绩的降序排序
cs
//例9.9 修改例9.8中的程序实例,用函数指针编程实现一个通用的排序指针,既能实现对学生成绩的升序排序,又能实现对学生成绩的降序排序
#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(void){
int score[N],n;
int order;
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);//第三个形参是一个函数,所以相当于定义里一长串的本质是函数
printf("Data items in ascending order\n");
}
else
{
SelectionSort(score,n,Descending);
printf("Data items in descending order\n");
}
PrintScore(score,n);
return 0;
}
int ReadScore(int score[]){
int i=-1;//初始为-1,才能在do-while循环体中加一变成0,作为下标
do{
i++;
printf("Input score:");
scanf("%d",&score[i]);
}while(score[i]>=0);
return i;//返回 的i就是学生人数
}
void PrintScore(int score[],int n){
int i;
for(i=0;i<n;i++){
printf("%4d",score[i]);
}
printf("\n");
}
void SelectionSort(int a[],int n,int (*compare)(int a,int b))
//重点在这个函数的理解,你可以理解成该compare是一个指针变量,这个指针变量指向一个形参为两个int型并且返回值为int型的函数
{
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;
}
int Descending(int a,int b)
{
return a>b;
}
void Swap(int *x,int *y)
{
//这里用模拟按引用调用才能实现两个数在函数内的实参交换
int temp;
temp=*x;
*x=*y;
*y=temp;
}