上一篇文章:C语言函数(函数嵌套、递归调用)+局部变量和全局变量+extern关键字的使用+Visual Studio简单的使用教程+数据存储类别+内部函数外部函数
C语言指针+指针作为函数参数+指向一维数组的指针
内存编址
计算机中的内存是由一个个的存储单元构成的,为了管理这些存储单元,对每个存储单元进行编号,这就是内存编址。普通的变量是有内存地址的,数组也是有内存地址的。我们要访问一些数据时,都是通过地址进行访问的。内存编址是按照字节进行的,每个字节对应一个地址编号。当程序运行时,系统中会有一个内存分配表,每遇到一次变量声明语句或函数调用语句(函数的形参),系统会根据变量的大小在内存中寻找合适的空间分配,并在内存分配表中增加一行记录,记载变量与内存地址的对应关系。
定义了一个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;
}