第九章-指针

目录

 

在C 语言中指针的重要性主要体现在哪几个方面?

指针到底有什么好处?

读程序写出运行结果

是否存在显示指针的值的方法?

 指针的算术运算

如果指针是地址,那么这是否意味着诸如p+j这样的表达式是把加上j 后的地址存储在p中呢?

空指针NULL与零地址

既然0 用来表示空指针,那么我猜想空指针就是字节中各位都为零的地址,对吗?

空指针 NULL

宏NULL表示什么?

指针变量和变量的指针有何不同?

使用指针的基本原则是什么?

使用未初始化的指针(Uninitialized Pointer)的后果是什么?

指针变量相比于其他类型的变量有什么特殊性?

什么情况下考虑使用函数指针?

如何为函数指针进行初始化?

本章常见错误

为什么要使用指针?直接用变量就行了呗

/*【例 9.1】使用取地址运算符&取出变量的地址,然后将其显示在屏幕上。*/ 

/*【例 9.2】使用指针变量在屏幕上显示变量的地址值。*/

/*【例 9.3】使用指针变量,通过间接寻址输出变量的值。*/

/*【例 9.3】使用指针变量,通过间接寻址输出变量的值。*/

 /*【例 9.5】演示程序按地址调用的例子。*/ 

/*【例9.6】从键盘任意输入两个整数,编程实现将其交换后再重新输出。*/ 

/*指针法:从键盘输入某班学生某门课的成绩(每班人数最多不超过40人),当输入为负值时,表示输入结束,试编程计算并打印最高分*/ 

/*【例9.8】修改例8.8 中的排序函数,使其既能实现对学生成绩的升序排序,又能实现对学生成绩的降序排序。*/ 

习题9.6 函数指针,计算定积分

我是最棒的!我做对了 !是因为输出的格式没有相应改动成%f所以才得不到正确答案!

        常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错

传入函数的 数组,成了什么?

 int a[] = {5, 15, 34, 54, 14, 2, 52, 72};

 int *p = &a[5]; p[-2]的值为54(a[5]是2,p[-2]是54)

   int a[] = {5, 15, 34, 54, 14, 2, 52, 72}; 

   int *p = &a[1];(a[1]=15,p[2]=54)

   p[2]的值为54

malloc 所需的头文件

动态分配空间,空间是以字节为单位的,所以需要malloc(number*sizeof(int))数量乘以类型的大小

返回的结果是void*,需要强制类型转换为自己需要的类型(int*)malloc(n*sizeof(int))

指针做加减:实际上是地址之差除以指针类型的大小,表示其中可以放几个这个类型的值

用-1作为循环结束条件,指针指向数组第一个值,不断的取出p并且把指针后移

p得到了数组的首地址0,q得到数组地址5,所以q-p=5

指针比较


在C 语言中指针的重要性主要体现在哪几个方面?

(1)指针为函数提供修改变量值的手段。
(2)指针为C 的动态内存分配系统提供支持。
(3)指针为动态数据结构(如链表、队列、二叉树等)提供支持。
(4)指针可以改善某些子程序的效率。

指针到底有什么好处?

  1. 动态分配的数组
  2. 对多个相似变量的一般访问
  3. (模拟)按引用传递函数参数
  4. 各种动态分配的数据结构,尤其是树和链表
  5. 遍历数组
  6. 高效地按医用“复制”数组和结构,特别是作为函数参数的时候。

读程序写出运行结果

#include <stdio.h>
main()
{    
    static int a[]={1,3,5,7};//a数组
    int *p[3]={a+2,a+1,a};//注意这里的a是数组的首地址
    int **q=p;
    printf("%d",*(p[0]+1)+**(q+2));
}

  1. 首先 int *p[3]={a+2,a+1,a}; p 是一个指针数组,它有三个数组元素,每个元素都是一个指针指向整型变量 a[2], a[1],a[0]。
  2. 这使得 a 中的第三个元素 a[2]的地址赋给 p[0]。a 中的第 2 个元素 a[1]的地址赋给 p[1], a 首地址赋给 p[2]。即 p[0] 指向 a[2],p[1]指向a[1],p[2] 指向 a[0]。
  3. 那么p[0]=&a[2], p[0]+1 即为&a[3] , 则有 *(p[0]+1)=*(&a[3]) 即为 a[3]的值 7.
  4. int **q=p; 经过初始化, q 为指针数组 p 的指针变量,它所指向的位置为 p 数组所在的位置。**q=p  --> *q=&p  -->表示q保存的是p 的地址。如果是用*q指向一个变量不需要两个*号,现在是让q指向一个指针数组,对q的操作指向不同的p
  5. 即 q 指针是针对 p 数组来指向的。*q 为指向 p 数组第一个元素,*q=p[0] ,*(q+2)指向 p
  6. 数组第三个元素*(q+2)=p[2], 所以 **(q+2)=*p[2], 即 a[0] 的值 1
  7. 所以, *(p[0]+1)+**(q+2)=7+1=8

是否存在显示指针的值的方法?

调用printf 函数,在格式串中采用转换符%p。(%p是输出地址,而指针就是保存的地址值,所以指针的值的显示用%p)

 指针的算术运算

如果指针是地址,那么这是否意味着诸如p+j这样的表达式是把加上j 后的地址存储在p中呢?

  1. 不是的。变量用于指针算术运算的整数要依赖于指针的类型
  2. 例如,如果p 的类型是 int *,那么p+j通常既可以用 2 ×j 加上 p,也可以用 4 ×j 加上 p,依据就是int型的值要求的是 2 个字节还是 4 个字节。
  3. 如果 p 的类型为 double *,那么 p+j 可能是 8 ×j 加上 p,因为double 型的值通常都是8 个字节长。 

空指针NULL与零地址

既然0 用来表示空指针,那么我猜想空指针就是字节中各位都为零的地址,对吗?

 

  1. 不一定。每个 C 语言编译器都被允许用不同的方式来表示空指针,而且不是所有编译器都使用零地址的。
  2. 例如,一些编译器为空指针使用不存在的内存地址。硬件会检查出这种试图通过空指针访问内存的方式。
  3. 我们不关心如何在计算机内存储空指针。这是编译器专家关注的细节。重要的是,当在指针环境中使用 0 时,编译器会把它转换为适当的内部形式。
  4. 定义指针时如果不知道让指针指向哪里,那么可以用 NULL 对其进行初始化,以避免对未赋值指针的解引用。

  5. 除了对指针进行初始化以外,空指针还常在程序中用为状态比较,指针不能与非指针类型变量进行比较,但可与 NULL进行相等或不等的关系运算。

空指针 NULL

宏NULL表示什么?

  1. NULL实际是表示 0。当在要求指针的地方使用 0 时,会要求 C语言编译器把它看成是空指针而不是整数 0.
  2. 提供宏 NULL 只是为了避免混乱。
  3. 赋值表达式 p = 0;既可以是给数值型变量赋值为0,也可以是给指针变量赋值为空指针。
  4. 而我们无法简单地说明到底是哪一种。相反,赋值表达式p = NULL;却明确地说明p是指针。

指针变量和变量的指针有何不同?

指针变量是指声明为指针类型的变量,而变量的指针通常是指变量的地址。

使用指针的基本原则是什么?

(1)永远清楚每个指针指向了哪里,指针必须指向一块有意义的内存。
(2)永远清楚每个指针指向的对象的内容是什么。
(3)永远不要使用未初始化的指针变量。

使用未初始化的指针(Uninitialized Pointer)的后果是什么?

需要修改的变量未被修改,而不需要修改的内存单元中的内容却可能被意外地修改了,从而导致非法内存访问。

指针变量相比于其他类型的变量有什么特殊性?

  1. 指针变量是指针类型的变量。指针变量与其他类型变量的共性是都在内存中占据一定大小的存储单元,而且必须“先定义,后使用”。
  2. 指针变量的特殊性是,指针变量中保存的内容只能是地址(例如,变量的地址,函数的地址等),指针变量必须必须初始化后才能使用,否则将指向不确定的存储单元。
  3. 指针变量只能指向同一基类型的变量,此外,指针变量可参与的运算只能是:加、减整数,自增、自减、关系、赋值。

什么情况下考虑使用函数指针?

函数指针主要应用于编写通用性更强的函数。两个典型的实例是:
1)计算函数的定积分
2)编写通用的排序函数,既能按照升序排序,又能按照降序排序。

如何为函数指针进行初始化?

  1. 函数指针(Function Pointer)就是指向函数的指针变量。
  2. 函数指针变量用函数在内存中的入口地址(函数名)进行初始化,编译器将不带()的函数名解释为该函数的入口地址。

本章常见错误

正确:int a;p=&a; 

  1. 指针变量的类型决定了指向内容要读几位,要和指针所指变量的类型一致,而是个变量它就得有类型。 
  2. 类型关键字代表指针变量要指向的变量的数据类型,即 指针变量的基类型 (Base Type )

为什么要使用指针?直接用变量就行了呗

  1. 变量的作用域不同,例如其他函数想要对变量读写的话,就需要靠把自己的地址传给函数,在函数中由一个指针类型来保存这个地址,来实现读写。
  2. 间接寻址是指通过存放变量地址的其他变量来访问该变量。也就是说,通过间接寻址找到最终的目标数据,需要两次寻址,第一次寻址得到的是目标数据存放的地址,第二次寻址是利用第一次寻址得到的地址来访问目标数据。
  3. 间接寻址运算符(Indirection Operator),也称指针运算符(Pointer Operator)或解引用运算符(Dereference Operator),即*。间接寻址运算符*用来访问指针变量指向的变量的值。
  4. 引用指针所指向的变量的值,也称为 指针的解引用(Pointer Dereference )。
  5. 直接按变量名或者变量的地址存取变量的内容的访问方式,称为 直接寻址( ( DirectAddressing )

/*【例 9.1】使用取地址运算符&取出变量的地址,然后将其显示在屏幕上。*/ 

/*【例 9.1】使用取地址运算符&取出变量的地址,然后将其显示在屏幕上。*/
#include <stdio.h>

int main(){
	int a=0,b=1;
	char c='A';
	printf("a is %d\n&a is %p\n",a,&a);
	printf("b is %d\n&b is %p\n",b,&b);
	printf("c is %d\n&c is %p\n",c,&c);
	return 0;
} 

/*【例 9.2】使用指针变量在屏幕上显示变量的地址值。*/

/*【例 9.2】使用指针变量在屏幕上显示变量的地址值。*/
#include <stdio.h>

int main(){
	int a=0,b=1;
	char c='A';
	int *pa, *pb;
	char *pc;			/*定义了可以指向字符型和整型数据的指针型变量*/
	printf("a is %d\n&a is %p\npa is %p\n",a,&a,pa);
	printf("b is %d\n&b is %p\npb is %p\n",b,&b,pb);
	printf("c is %d\n&c is %p\npc is %p\n",c,&c,pc);
	return 0;
}

/*【例 9.3】使用指针变量,通过间接寻址输出变量的值。*/

/*【例 9.3】使用指针变量,通过间接寻址输出变量的值。*/
#include <stdio.h>

int main(){
	int a=0,b=1;
	char c='A';
	int *pa=&a, *pb=&b;
	char *pc=&c;			/*把变量的地址值给了指针*/
	printf("a is %d\n&a is %p\npa is %p\n",a,&a,pa);
	printf("b is %d\n&b is %p\npb is %p\n",b,&b,pb);
	printf("c is %d\n&c is %p\npc is %p\n",c,&c,pc);
	return 0;
}

/*【例 9.3】使用指针变量,通过间接寻址输出变量的值。*/

【例题解析】间接引用运算符*用来得到指针变量指向的变量的值。运算时,要求指针已被正确初始化或者已指向内存中某个确定的存储单元。

/*【例 9.3】使用指针变量,通过间接寻址输出变量的值。*/
#include <stdio.h>

int main(){
	int a=0,b=1;
	char c='A';
	int *pa=&a, *pb=&b;
	char *pc=&c;			/*把变量的地址值给了指针*/
	printf("a is %d\n&a is %p\npa is %p\n*pa is %d\n",a,&a,pa,*pa);
	printf("b is %d\n&b is %p\npb is %p\n*pb is %d\n",b,&b,pb,*pb);
	printf("c is %d\n&c is %p\npc is %p\n*pc is %d\n",c,&c,pc,*pc);  
     /* pc是一个指针,间接寻址运算符*用来访问指针变量指向的变量的值。*/
	return 0;
}

 /*【例 9.5】演示程序按地址调用的例子。*/ 

/*【例 9.5】演示程序按地址调用的例子。*/ 
#include <stdio.h>

void Fun(int *par);

int main(){
	int arg=1;
	printf("原始的arg=%d\n\n",arg);
	Fun(&arg);			/*传递实参值,相当于拷贝值给函数*/
	printf("从函数中带回的arg=%d\n\n",arg);
	printf("hh 按地址调用修改参数成功啦!")  ; 
	return 0;
}

void Fun(int *par){
	printf("传入函数的arg=%d\n\n",*par);
	*par=2;     	
	/*不加*号的指针表示的是地址,加上*号才是对变量操作*/
}

/*【例9.6】从键盘任意输入两个整数,编程实现将其交换后再重新输出。*/ 

/*【例9.6】从键盘任意输入两个整数,编程实现将其交换后再重新输出。试分析下面的程
序能实现这一功能吗?如果不能,那么如何修改程序呢?*/ 
#include <stdio.h>

void Swap(int *pa,int *pb);

int main(){
	int a,b;
	scanf("%d %d",&a,&b);
	Swop(&a,&b);			/*传递实参值,相当于拷贝值给函数*/
	printf("从函数中带回的交换后的a=%d,b=%d\n\n",a,b);
	return 0;
}

void Swap(int *pa,int *pb){
	int temp;
	temp=*pa;
	*pa=*pb;
	*pb=temp;     	
}

/*指针法:
从键盘输入某班学生某门课的成绩(每班人数最多不超过40人),
当输入为负值时,表示输入结束,试编程计算并打印最高分*/ 

/*指针法:
从键盘输入某班学生某门课的成绩(每班人数最多不超过40人),
当输入为负值时,表示输入结束,试编程计算并打印最高分*/ 
#include <stdio.h>
#define MAXSIZE 40
 
int Top(int grade[],int count,int *top); 
 
int main() {
	int grade[MAXSIZE];
	int Sgrade;
	int sum=0;                    //对学生成绩求和 
	int count=0;                  //计数总人数,必须初始化 
 
	for(int i=0;i<MAXSIZE-1;i++){
		scanf("%d",&Sgrade);
		if(Sgrade==-1) break;    /*如果输入-1代表输入结束 */
		grade[i]=Sgrade;         /*给数组赋值,这里是把学生的成绩放在数组里*/
		count++;
	}	
	int top;                     /*定义变量用来保存最高分*/
	Top(grade,count,&top);
	printf("count=%d,max=%d\n",count,top);
}

/*函数功能:找最高分*/
int Top(int grade[],int count,int *top){   
	*top=grade[0];
	for(int i=1;i<count;i++){
		if(grade[i]>*top){
			*top=grade[i];
		}
	}
}

/*【例9.8】修改例8.8 中的排序函数,
使其既能实现对学生成绩的升序排序,又能实现对学生成绩的降序排序。*/ 

【例题解析】升序排序函数AscendingSort()和降序排序函数 DescendingSort()仅 if 语句不同而已,其他语句完全一致。

/*【例9.8】修改例8.8 中的排序函数,
使其既能实现对学生成绩的升序排序,又能实现对学生成绩的降序排序。*/ 
#include <stdio.h>
#define MAXSIZE 40
 
void Sort(int grade[],int count,int (*f)(int a,int b));
int Ascending(int a,int b); 
int Descending(int a,int b);
 
int main() {
	int grade[MAXSIZE];
	int Sgrade;
	int count=0;						//记数总人数,必须初始化 
 
	for(int i=0;i<MAXSIZE-1;i++){
		scanf("%d",&Sgrade);
		if(Sgrade==-1) break;			//如果输入-1代表输入结束 
		grade[i]=Sgrade;				//给数组赋值 
		count++;
	}
	printf("降/升序,请输入 1/0:");
	int num;									/*保存用户的升降序请求*/ 
	scanf("%d",&num);
	if(num==1) 	Sort(grade,count,Ascending);  
 /*指针函数的函数名就是地址,所以实参直接写函数名即可*/ 
	else Sort(grade,count,Descending);
}
//交换法排序 ,每轮找一个最大或最小的放好 
void Sort(int grade[],int count,int (*f)(int a,int b)){
 
	int i,j;
	for(i=0;i<count;i++){
		for(j=i+1;j<count;j++){
			if( (*f)(grade[j],grade[i])){   
	 /*小心,自己写的时候就忘记了这句(*f)(grade[j],grade[i]),
        指针函数要传值,传到对应函数中返回的是:真或假
	 这里的函数指针返回的都是真,但是判断为真的条件不同
	 升序的时候判断后面的大于前面的就为真,为真就交换
	 降序的时候判断后小于前,为真就交换,实现了交换法排序的功能*/ 
				int temp;
				temp=grade[i];
				grade[i]=grade[j];
				grade[j]=temp;
			}
		}
	} 
	
	for(int i=0;i<count;i++){				//打印排序后的数组 
		printf("%d ",grade[i]);
	}	
}

int Ascending(int a,int b){
	return a>b;       /*对传入的参数比大小,为真返回1,为假返回0*/
}

int Descending(int a,int b){
	return a<b;
}

习题9.6 函数指针,计算定积分

我是最棒的!我做对了 !是因为输出的格式没有相应改动成%f所以才得不到正确答案!

/*函数指针计算定积分*/ 
#include <stdio.h>

float Fun1(float a);
float Fun2(float a);
void integral(float a,float b,float (*f)(float a),float *result); 

int main(){
	
	float a=0.0,b=1.0;
	float c=0.0,d=3.0;
	float result;/*用result把结果值带回来*/
	integral(a,b,Fun1,&result);
	printf("y1=%f\n",result);
	
	integral(c,d,Fun2,&result);
	printf("y2=%f\n",result);
}

void integral(float a,float b,float (*f)(float a),float *result){
	 *result=0.0;
	 //两次调用这个函数,每次result都会被修改,所以需要重新置为0 
	 float h;
	 int n=100;
	 h=(b-a)/n;
	  /*要保存结果值,因为这里要分部分来求得,所以需要各部分相加*/ 
	 for(int i=1;i<n;i++){   /*计算累加和的部分*/
	 	*result+= (*f)(a+i*h); 
	 }
	 *result+=((*f)(a)+(*f)(b))/2;
	 *result*=h; 
}

float Fun1(float a){
	 return 1+a*a;
}

float Fun2(float a){
	return a/(1+a*a);
}

 

/*函数指针计算定积分(实在不知道哪里错了,屈服的看了源程序的改动后)*/ 
#include <stdio.h>

float Fun1(float a);
float Fun2(float a);
float Integral(float a,float b,float (*f)(float a)); 

int main(){
	
	float a=0.0,b=1.0;
	float c=0.0,d=3.0;

	float y1=Integral(a,b,Fun1);
	float y2=Integral(c,d,Fun2);
	printf("y1=%f\ny2=%f\n",y1,y2);       

	return 0;
}

float Integral(float a,float b,float (*f)(float a)){
	 float s,h;
	 int n=100,i;
	 h=(b-a)/n;
	 s=((*f)(a)+(*f)(b))/2;
	  /*要保存结果值,因为这里要分部分来求得,所以需要各部分相加*/ 
	 for(i=1;i<n;i++){   /*计算累加和的部分*/
	 	s+= (*f)(a+i*h); 
	 } 	 
	 
	 return s*h; 
}

float Fun1(float a){
	 return 1+a*a;
}

float Fun2(float a){
	return a/(1+a*a);
} 

/*指针应用场景3:两个整数做除法:如果除法成功返回1否则返回0

        函数用return返回运算的状态,指针返回结果

        常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错

        -1或0 是常用的

        本题:两个整数做除法:如果除法成功返回1否则返回0*/ 

  1. 本题中出除法成功与否用的是返回值,但是除法的结果用指针带回了,要带回去的值,给它的时候加好&,函数中的时候加好*,就可以带回
  2. 判断除数是否为0,如果为0则不能做除法,如果为1,做除法并带回
  3. 本题中的除法函数的返回值是0或1所以可以直接将函数放在if里面判断,divide函数中返回1代表可以做除法,所以输出商

指针最常见的错误:定义了指针变量,还没有指向任何变量就开始使用指针

如:int *p; *p=12;  是错误的,

传入函数的 数组,成了什么?

  1. 如果传入函数的是变量,接受到的是变量的值
  2. 传入函数的指针,那么接受到的是指针的值,代表了外面的那个变量
  3. 传入函数的数组,其实是一个指针,但能在函数中当做数组来运算(所以在函数中对数组做sizeof会得到8,是因为数组进入函数之后变成了指针指向数组的首地址)
    1. 事实上数组变量是特殊的指针
    2. 数组变量本身表达地址
      1. int a[10];int *p=a; 是不需要&取地址符号的,*p=a就得到了数组的地址
      2. 数组的单个单元表达的是变量,需要用&地址符号
      3. a==&a[0] 数组的地址就是数组中第一个元素的地址
    3. []运算符可以对数组做,也可以对指针做
      1. p[0]<==>a[0]
      2. int min=2; int *p=&min; 此时*p=p[0]=2
    4. *运算符可以对指针做,也可以对数组做
      1. *a=25
    5. 数组变量是const的指针,所以不能被赋值----两个数组之间不能赋值
    6. 因为把数组传入函数时,传递的是地址,所以那个函数内部可以修改数组的值。为了保护数组不被函数破坏,可以设置参数为const    int sum(const int a[],int length)

 int a[] = {5, 15, 34, 54, 14, 2, 52, 72};

 int *p = &a[5]; p[-2]的值为54(a[5]是2,p[-2]是54)

 

   int a[] = {5, 15, 34, 54, 14, 2, 52, 72}; 

   int *p = &a[1];(a[1]=15,p[2]=54)

   p[2]的值为54

 

动态内存分配

 

  1. malloc 所需的头文件<stdlib.h>

  2. 动态分配空间,空间是以字节为单位的,所以需要malloc(number*sizeof(int))数量乘以类型的大小

  3. 完全可以当做数组来用
  4. 最后要释放空间
  5. 返回的结果是void*,需要强制类型转换为自己需要的类型(int*)malloc(n*sizeof(int))

 

指针运算

  1. 指针做加减:实际上是地址之差除以指针类型的大小,表示其中可以放几个这个类型的值

  2. *p++:取出p所指向的数据,然后把p指向下一个位置。常用于数组类的连续空间操作

用-1作为循环结束条件,指针指向数组第一个值,不断的取出p并且把指针后移

p得到了数组的首地址0,q得到数组地址5,所以q-p=5

指针比较

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值