内存编址+C语言指针+指针作为函数参数+指向一维数组的指针

上一篇文章:C语言函数(函数嵌套、递归调用)+局部变量和全局变量+extern关键字的使用+Visual Studio简单的使用教程+数据存储类别+内部函数外部函数

内存编址

计算机中的内存是由一个个的存储单元构成的,为了管理这些存储单元,对每个存储单元进行编号,这就是内存编址。普通的变量是有内存地址的,数组也是有内存地址的。我们要访问一些数据时,都是通过地址进行访问的。内存编址是按照字节进行的,每个字节对应一个地址编号。当程序运行时,系统中会有一个内存分配表,每遇到一次变量声明语句或函数调用语句(函数的形参),系统会根据变量的大小在内存中寻找合适的空间分配,并在内存分配表中增加一行记录,记载变量与内存地址的对应关系。
在这里插入图片描述
定义了一个int型的数组a,数组的长度为10。int型的数据占4个字节。数组名a就是这个数组的首地址,即数组第一个元素a[0]的地址。用%d以十进制的形式输出这个数组的首地址。a+1就是a[1]的地址,a+2就是a[2]的地址。6487536就是地址编号,由于int型占4个字节,所以下一个元素的地址为6487540,加了4,后面依次往后类推。
在这里插入图片描述
如果把数组的类型改为double型的,那么每个地址依次加8,因为double型的数据占8个字节。

指针引例及其要点

先看这个引例:

#include <stdio.h>
int main()
{
	int a=1024;//定义一个整型数据,并给它赋初值为1024
	int *p=NULL;//定义一个指针变量,并给它赋初值0//NULL代表的数值是0
	p=&a;//&是取地址符,单目运算符,将变量a的地址取出来赋给指针变量p
	printf("%x,%d",p,*p);//%x前面的博客中我已经说过,是以十六进制数的形式输出
	//直接输出p,则是输出变量a的内存地址,星号是取内容符,是访问地址中的内容
	return 0;
 } 

这张图是运行结果,通过运行结果我们要进行思考:
在这里插入图片描述
输出的第一个数是“64fe14”,这是一个十六进制数,关于十六进制数请参见我之前的一篇博客计算机要点概述一文中的“进制与转换”,指针变量 里面存的是它所指向的变量的内存地址
比如这里我定义的指针变量的变量名为“p”,一开始我给它赋的初值为NULL即0,然后我用取地址符“&”对我之前定义的变量a进行取地址,然后把变量a的内存地址赋给指针变量“p”,然后用十六进制的格式“%x”输出这个十六进制数就是“64fe14”;如果我用格式输出“%d”,对应的输出内容为*p,意思就是将指针变量p所指向的内存地址里面的值输出去,这里指针变量p所指向的是变量a内存地址,所以就把变量a里面的值1024输出去。
要点一

int a=1024;
int *p=NULL;

变量a是int型的,所以定义指针变量p的时候,前面是个int
如果a是浮点型的,那么定义指针变量p的时候,前面也要与之对应,例如:

float a = 1.024;
float *p = NULL;

double a = 1.024;
double *p = NULL;

所以我们可以得出:指针的定义格式为

指针变量所要指向的变量的数据类型 *指针变量的变量名;

要点二
如果不给指针变量赋初值会出现什么情况:
在这里插入图片描述
运行后黑屏,光标一直闪,一段时间后直接运行结束:
在这里插入图片描述
所以一开始定义指针时一定要赋初值0,即空值NULL,否则可能会出现一些bug,没有赋初值的指针叫做悬空指针。
NULL是系统提前已经定义好的常量0,使用时我们不需要再定义#define NULL 0,直接拿来用就行了


要点三:注意区分指针指针变量

  • 指针是一个概念,是计算机内存地址的代名词之一
  • 指针变量本身就是一个变量,只不过存的是其他变量的内存地址

要点四
一个类型名后面跟多个指针变量时,每个指针变量前面的星号不能少,如:

char *p_1, *p_2, *p_3, *p_4;

指针知识点拓充

代码案例

我个人觉得学习一门编程语言,一个比较快捷的方法就是学习有注释的代码,先学习代码,再举一反三。请看代码注释。

#include <stdio.h>
int main()
{
	int a = 3, *p;//定义一个常量a和一个指针变量p,指针变量可以和普通的变量混合定义
	p=&a;//对变量a取地址,并把a的地址赋给指针变量p
	printf("a=%d, *p=%d\n",a,*p);
	*p=10;//用*p来间接代表变量a
	printf("a=%d, *p=%d\n",a,*p);
	printf("Enter a:");
	scanf("%d",&a);
	printf("a=%d, *p=%d\n",a,*p);
	(*p)++;
	printf("a=%d, *p=%d\n",a,*p);
	return 0;
}

在这里插入图片描述

  • 利用指针输出不同类型的变量
#include <stdio.h>
main()
{
	int a,*pa;
	float b,*pb;
	char c,*pc;
	double d,*pd;
	pa=&a;
	pb=&b;
	pc=&c;
	pd=&d;
	scanf("%d %f %c %lf",pa,pb,pc,pd);
	printf("%d %x %x %x\n",pa,pb,pc,pd);//输出指针变量中的内存地址
	//指针变量pa中的内存地址我以十进制数的形式输出,其他三个指针变量,我以十六进制数的形式输出
	printf("%d %f %c %f\n",*pa,*pb,*pc,*pd);//输出指针变量所指向的变量中的内容
}

在这里插入图片描述

  • 利用指针实现两个值的交换
#include <stdio.h>
int main()
{
	int a=1,b=2,c;
	int *pa=&a, *pb=&b;
	c=*pa;
	*pa=*pb;
	*pb=c;
	printf("%d %d\n",a,b);
	int *pc=0;
	printf("%x %x %x\n",pa,pb,pc);
	
	pc=pa;
	pa=pb;
	pb=pc;
	printf("%x %x %x\n",pa,pb,pc);
	return 0;
}

在这里插入图片描述

指针变量的算术运算

  • 指针虽然存放的是其他变量的地址,但也可以参与算术运算。例如:指针可以加、减一个整数
  • 一个指针加一个整数n时,将指针从当前位置前移n个数据的单位,而不是n个字节,如下图:
    在这里插入图片描述
  • 两个指针变量也可以相减,得到的结果是这两个指针变量之间相差了多少个数据
  • 两个指针也可以比较大小,通过比较的结果可以看出哪个指针变量在前,哪个指针变量在后,如果相等,两个指针变量所指向的元素是同一个元素。

指针变量作为函数的参数

三个变量从小到大排序:

#include <stdio.h>
void swap(int *p,int *q);
main()
{
	int a,b,c;
	printf("please input a, b & c:\n");
	scanf("%d%d%d",&a,&b,&c);
	if(a>b)
		swap(&a,&b);
	if(a>c)
		swap(&a,&c);
	if(b>c)
		swap(&b,&c);
	printf("%4d%4d%4d\n",a,b,c);
}
void swap(int *p,int *q)
{
	int t;
	t=*p;
	*p=*q;
	*q=t;
}

代码也可以改为:

#include <stdio.h>
void swap(int *p,int *q);
main()
{
	int a,b,c;
	int *p=&a,*q=&b,*t=&c;
	printf("please input a, b & c:\n");
	scanf("%d%d%d",&a,&b,&c);
	if(a>b)
		swap(p,q);
	if(a>c)
		swap(p,t);
	if(b>c)
		swap(q,t);
	printf("%4d%4d%4d\n",a,b,c);
}
void swap(int *p,int *q)
{
	int t;
	t=*p;
	*p=*q;
	*q=t;
}

指针与一维数组

  • 对于一维数组来说,数组名代表了数组的首地址,也就是数组的指针,但数组名是常量指针,也就是说其值是不可改变的

指向一维数组的指针的定义

int a[5]={0};
int *p=a;

也可以写成:

int a[5]={0};
int *p=&a[0];

因为数组名指向数组的首地址,所以数组a[i]的地址也可以表示为(a+i),而引用a[i]元素也可以使用*(a+i)的形式。实际上,在编译时,对数组元素a[i]就是处理成*(a+i)

使用不同方法输出数组元素

main()
{
	int a[5]={1,2,3,4,5};
	for(i=0;i<5;i++)
		printf("%d   ",a[i]);
}

#include <stdio.h>
main()
{
	int a[5]={1,2,3,4,5};
	int *p;
	p=a;
	for(i=0;i<5;i++)
		printf("%d   ",*(p+i));//也可以写成*(a+i)
}

:使用指针下标法输出数组元素

main()
{
	int a[5]={1,2,3,4,5};
	int *p;
	p=a;
	for(i=0;i<5;i++)
		printf("%d   ",p[i]);
}

因为指针p指向数组a,所以可以将指针p看作是数组名,因而可以按照指针下标法来使用


:用移动指针指向各个元素

main()
{
	int a[5]={1,2,3,4,5};
	int *p;
	p=a;
	for(i=0;i<5;i++)
	{
		printf("%d  ",*p);
		p++;
	}
}
//这个程序运行结束时,指针会指向数组以外的内容

也可以写成

for(i=0;i<5;i++,p++)
	printf("%d   ",*p);
//注意这个程序运行结束后指针p会指向数组以外的内容

但是不能写成

for(i=0;i<5;i++,a++)
	printf("%d   ",*a);

因为数组名a是指针常量,常量的值固定不变,所以不能使用a++
注意避免指针指向数组以外的内容



因为指针变量是可以比较大小的,表示的是指针所指向元素的前后位置,所以也可以这样写:

void main()
{
	int a[5]={1,2,3,4,5};
	int *p=a;
	for(;p<a+5;p++)
		printf("%d   ",*p);
}

1~100之间能被9或11整除的数

用指针实现:1~100之间能被9或11整除,但不能同时被9和11整除的所有整数存入数组中

#include <stdio.h>
main()
{
	int a[1000],i,n=0;
	int *p=a;
	for(i=1;i<=100;i++)
	{
		if(i%9==0&&i%11!=0||i%11==0&&i%9!=0)
		{
			*p++=i;
			n++;
		}
	}
	p=a;
	for(i=0;i<n;i++,p++)
	{
		if(i%5==0)
			printf("\n");
		printf("%5d",*p);
	}
}

指向一维数组的指针作为函数的参数

函数调用中,指针可以作为形参接收实参传递的一维数组的数组名。指针接收的是实参数组的起始地址。其实数组名就是这个数组的首地址,本质上都是指针。
下面进行举例:

main()
{
	int a[10];
	...
	fun(a);//其实是将数组a的首地址作为实参传入子函数
	...
}
void fun(int x[])
//int x[]来接受主函数数组首地址,相当于将整个数组a传进子函数
//这样在子函数的函数体中就可以直接用数组的形式来对其进行操作,如下图
{
	...
}

在这里插入图片描述
也可以写成:

main()
{
	int a[10],*p=a;
	...
	fun(p);//指针变量p中存放的是数组a的首地址,本质上和第一个是一样的
	...
}
void fun(int *q)//形参定义了一个指针变量q来接收来自主函数传入的数组首地址
{
	...
}

也可以写成:

main()
{
	int a[10];
	...
	fun(a);
	...
}
void fun(int *q)
{
	...
}

也可以写成:

main()
{
	int a[10],*p=a;
	...
	fun(p);
	...
}
void fun(int x[])
{
	...
}

以上四种本质上都是一样的,在编译的时候都转换为用指针做函数的参数

实现数组升序排列

#include <stdio.h>
#define N 15
void fun(int *a, int *b, int *x);
main()
{
	int a[N]={1,1,2,2,2,3,4,4,4,7,7,7,9,11,15};
	int b[15],n,i;
	fun(a,b,&n);
	for(i=0;i<n;i++)
		printf("%4d",b[i]);
}
void fun(int *a, int *b, int *x)
{
	int i,j=0;
	b[j]=a[0];
	for(i=0;i<N;i++)
	{
		if(b[j]!=a[i])
		{
			j++;
			b[j]=a[i];
		}
	}
	*x=j+1;
}

下一篇文章

指向二维数组的指针

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackey_Song_Odd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值