再谈指针

通过指针引用多维数组

多维数组元素的地址

我们先来对多维数组的存储性质进行介绍。int a[3][4]={{1,3,5,7},{1,2,3,4},{0,0,0,0}};,a是数组名,a数组为3行4列。3个行元素a[0],a[1],a[2]。而每一个行元素又是一个一维数组,包含4个列元素,从行到列的理解方式也是符合C编译原理的。那么我们可以认为a是由3个一维数组组成的。现在开始引入地址与指针,我们来想一想这时a代表着什么?(类比指针),没错是二维数组首元素的地址,但并不是一个简单的整型元素,而是由4个整型变量组成的一维数组,这就是行指针(类似)为什么说类似呢因为这还不是指针,只是个数组名。

那么顺理成章,a+1就是代表序号为1的行的起始地址,a+2,a+3也同理,即a+1指向a[1] (a+1的值是a[1]的起始地址,&a[1][0]),我们继续考虑一下a[0][1]的地址该怎么表示呢?是a+1,还是a+0+1呢?当然都不是,这样的表示就是指向的序号为1的行起始地址,因为我们操作的是行指针,这是问题就来了如何操作列指针呢?a[0]为一维数组名,那么我们对a[0]进行操作不就好了嘛,a[0]+1即是答案,a[i]就是列指针(类似)

由上文可知a[1]等价于(a+1),那么a[0]+1就可以表示为(a+0)+1,切忌写成(a+0+1),请读者自行思考运算符的优先级与行列指针的转换与操作。我们现在知道了a+1是指向行的行指针,a[1]是指向列的列指针,a[1]等价于(a+1),那么就是在行指针之前加*就成为列指针,反之,在列指针之前加&就成为行指针,即&a[0]&*(a+0)a+0~a

指向多维数组的指针变量

真正的行指针和列指针要来喽! 指向数组元素的指针变量简而言之,将二维数组一维化,考察以下程序

#include<stdio.h>
int main()
{
	int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	int *p;
	for(p=a[0];p<a[0]+12;p++)
	{
		if((p-a[0])%4==0) printf("\n");//相对位置的应用大家还记得吗? 
		printf("%d ",*p);
	}
	return 0;
 } 
运行结果
1 3 5 7
9 11 13 15
17 19 21 23

p为一个int* 的指针变量,指向数组元素,再通过相对位置的运算不断指向下一个元素。基于数组按行排列编译的原理可知二维数组a[n][m]中a[i][j]的相对位置计算方法为 a[i][j]=a[0][0]+(i*m+j)

指向由m个元素组成的一维数组的指针变量 存在行列指针的操作,考察以下程序

#include<stdio.h>
int main()
{
	int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	int (*p)[4];
	p=a;
	int x,y;
	scanf("%d %d",&x,&y);//输出指定位置的数
	printf("%d",*(*(p+x)+y)); //这个表示不陌生了吧,相对第一种表示更简洁明了
	return 0;
 } 
运行结果
1 2
13

这是一个新的指针类型int (p)[4] 我们来好好研究一下,这表示一个指向包含4个整型元素(列数一定要确定,大家知道这是为什么吗,在后面传参也有这种要求)的一维数组的指针变量,注意 int (p)[4]与int p[4]截然不同,后者为指针数组下文将会提到,因为方括号运算级高那么p先和[4]结合成为数组再与前面的 * 结合为指针数组。为了更好理解,我们来对比 int a[4]与int (p)[4],p就是指向一维数组的指针,p本身就是行指针,*p就是列指针。(tips:p的值是该一维数组的起始地址,但是他们的基类型不同,p指向的是行,一整行)

用指向数组的指针作函数参数 重点在于一一对应,考察以下程序

`#include<stdio.h>
int main()
{
	void average(float *p,int n);
	void search(float (*p)[4],int n);
	float socre[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
	average(*score,12);
	search(score,2);
	return 0;
} `

average函数传递的是 指向数组元素的指针变量,search函数传递的是指向由m个元素组成的一维数组的指针变量,所以对应的实参average函数的是*score即score[0],search函数的是score,原理上文已阐述,不再赘述。

通过指针引用字符串

写在前面:与整型引用情况类似,掌握整型后学习字符串不成问题,这里主要讲述字符串的几处特殊点

字符串的引用方式

`char *string="I love China";`   等价于

`char *string;//定义一个char*变量 
string="I love China!"//把字符串第一个元素的地址赋给字符指针变量string`

定义string,char*型的指针变量称为字符指针变量,C语言中没有字符串变量。再对string初始化实际上是把字符串第1个元素的地址赋给指针变量string。再通过printf("%s",string) %s这个格式符进行输出,自动输出至末尾的‘\0’结束

字符指针作函数参数 类比整型指针

字符指针变量和字符数组的比较

  1. 字符数组由若干个元素组成,每个元素放一个字符,而字符指针变量中存放的是地址(字符串第一个字符的地址)

  2. 赋值与初始化可以对指针变量赋值但不能对数组名赋值 我们回忆一下数组的初始化就明白 char str[14]="I love China" 不能写成char str[14]; str[]="I love China",指针变量初始化的含义为把字符串第一个元素的地址赋给string,而字符数组的初始化是把字符串赋给数组中的每个元素。

  3. 存储单元 编译时为字符数组分配若干存储单元以存放各元素的值,而对字符指针变量只分配一个存储单元。所以如果我们定义了字符指针变量,就要及时把一个字符(数组)变量的地址赋给它,给它确定的存储单元,因为未赋值的指针变量值是不可预料的,不知道指向内存中的哪一段存储单元,有可能是空白的用户存储区(好的情况),也可能是已存放数据的有用内存段,这就会破坏了程序或有用数据,甚至破坏了系统!例如这样的写法是严禁的char *a;scanf("%s",a);//a的指向不可预料!

  4. ** 指针变量的值可以改变而数组名不可改变** srting=string+7可以改变指向,但str=str+7是错误的

  5. 字符数组中各元素的值可以被改变(可以再赋值)而字符指针变量指向的字符串常量中的内容是不可以被取代的

指向函数的指针

  1. 什么是函数的指针 如果在程序中定义了一个函数,在编译时会把函数的源代码转换为可执行代码并分配一段储存空间。这段储存空间有一个起始地址,也称为函数的入口地址。每次调用函数时都从该地址入口开始执行此段函数代码。函数名代表函数的起始地址。调用函数时,从函数名得到函数的起始地址并执行函数代码。函数名就是函数的指针,代表函数的起始地址可以定义一个指向函数的指针变量用来存放某一函数的起始地址,这就意味着该指针变量指向该函数,例如int(*p)(int,int),p指针可以指向函数类型为整数且有两个整型参数的函数,此时指针类型为int(*)(int,int)(上一篇有提到)

  2. 用函数指针来调用函数 使用方法十分简单,考察以下程序

    int main() { int max(int x,int y); int(*p)(int,int); p=max;//使p指向max函数 int a=1; int b=2; int c=(*p)(a,b)//通过指针调用max函数 }
    注意几点 int(*p)(int,int)中(p)的括号不能丢,p先与结合成为指针变量,后面的(int,int)代表指向函数。如果写成int *p(int,int)由于括号优先级高于*,则相当于声明了一个返回整型指针值的函数。p=max相当于将函数max的入口地址赋给指针变量,调用 *p 就是调用max函数,对于该指针无加减运算。

用指向函数的指针作为函数参数 指向函数的指针变量的一个重要用途就是把函数的入口地址作为参数传递到其他函数,考察下列程序即可一目了然

`
#include<stdio.h> 
int main()
{
	int max(int x,int y);
	int min(int x,int y);
	int sum(int x,int y);
	int a=1;
	int b=2;
	int c=fun(a,b,max);//通过指针调用max函数
	int c=fun(a,b,min);//通过指针调用min函数
	int c=fun(a,b,sum);//通过指针调用sum函数
}
int fun(int a,int b,int(*p)(int,int))//指向函数的指针作为参数
{
	return (*p)(a,b);
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int sum(int a,int b){return a+b;}
` 

从此例看到不论调用max还是min,函数fun都没有改变,只是改变实参函数名而已。有利于我们模块化设计程序,也增加函数使用的灵活性。

返回指针值的函数

一个函数可以返回整型、实型等,也可以返回指针型的数据,即地址。定义返回指针值的函数原型一般形式为:类型名 * 函数名(参数列表),具体用法可以参考以下程序(查找二维数据第k行数据)

#include<stdio.h> 
int main()
{
	int *search(int(*pointer)[4],int n);
	int *p;
	int a[2][2]={{1,2},{1,2};
	int k;	
	scanf("%d",&k);
	p=search(a,k);
	for(int i=0;i<2;i++) printf("%d",*(p+i));
	return 0;
}
int *search(int(*pointer)[4],int n);
{
	int *pt;
	pt=*(pointer+n);//行指针的应用
	return pt;
}

如果对参数传递和类型还有不清楚的话,建议回顾一下前文。

指针数组和多重指针

一个数组,若其元素均为指针类型数据,称为指针数组。定义一维指针数组的一般形式为 类型名 *数组名[数组长度],指针数组比较适合用来指向若干个字符串,是字符串的处理更加方便灵活。比如这时有若干个长度不一的字符串需要开一个二维字符数组去存储。但在定义二维数组时要指定列数,若按最长串来开辟空间必然浪费许多存储单元。所以我们可以用指针数组中的元素分别指向每个字符串的首字符的地址。下面考察将字符串按字母顺序(由小到大)输出的程序

`#include<stdio.h>
#include<string.h>
int main()
{
	void sort(char *name[],int n);
	void print(char *name[],int n);
	char *name[]={"Follow","BASIC","Great Wall","China","Computer"};
	int n=5;
	sort(name,n);
	print(name,n);
	return 0;
 } 
 void sort(char *name[],int n)
 {
	char *temp;
	for(int i=0;i<n-1;i++)//冒泡排序 
	{
		for(int j=0;j<n-i-1;j++) if(strcmp(name[j],name[j+1])>0)
		{
			temp=name[j];
			name[j]=name[j+1];
			name[j+1]=temp;
		}
	}
 }
 void print(char *name[],int n)
 {
	for(int i=0;i<n;i++) printf("%s\n",name[i]);
 	return ;
 }
	运行结果
	BASIC
	China
	Computer
	Follow
	Great Wall`

这里便体现出指针数组的优势,直接指向各字符串的首地址。sort函数中的指向互换,实际上改变了指针所指的字符串首地址。

指向指针数据的指针变量 定义:类型名**p 不常用有用到再说QAQ

动态分配内存

动态内存是相对静态内存而言的。所谓动态和静态就是指内存的分配方式。动态内存是指在堆上分配的内存,而静态内存是指在栈上分配的内存。
前面所写的程序大多数都是在栈上分配的,比如局部变量、形参、函数调用等。栈上分配的内存是由系统分配和释放的,空间有限,在复合语句或函数运行结束后就会被系统自动释放。而堆上分配的内存是由程序员通过编程自己手动分配和释放的,空间很大,存储自由。

关于malloc calloc relloc与free的使用、void*指针网上很多文章已经讲的很清楚,这里就不再赘述

关于指针的基本知识就讲到这里,还需要多加上机实践才能熟练运用

posted @ 2022-06-18 10:52  DBDZJM  阅读(48)  评论(0)    收藏  举报