C语言基础之指针

                                                指针

用指针可以实现对内存地址直接操作,实现函数参数的引用传递,减少传递函数参数的开销,得到多个返回值,能有效而方便的处理数组好字符串,可以有效地表示复杂的数据结构,能动态分配内存。

地址和指针的概念
内存是以字节为单位的饿一个连续的线性存储地址空间,从0开始编号,每一个字节都有一个编号,这个编号叫内存地址。对于一个整型变量x,C语言的编译系统为它分配了两个字节这两个字节的第一个字节地址即为变量x的地址,比如把2004,2005两个存储单元分配给x,则x的地址即为2001.

1.直接访问
直接访问即通过该变量的名字来访问,例:
int x=2;
printf("%d",x);

2.间接访问
间接访问是通过另一边变量访问。把变量的地址放到另一变量中,使用时先找到后者,再从中取出前者的地址,就可以去相应的地址单元中访问变量了。

指针的概念
在间接访问中,通过地址找到相应的变量,可以说,地址“指向”变量。在C语言中,将地址形象化地称为指针。变量的指针即为变量的地址。如果一个变量专门用来存放另一变量的地址,则它被称为"指针变量"。
例:交换两个整数的值
#include<stdio.h>
void main()
{
int a=3,b=5,t;
int *p,*q;    //定义整型指针变量p,q
p=&a;
q=&b;
t=&p;         //交换*p和*q的值,即交换a和b的值
*p=*q;
*q=t;
}

指针的定义
基本格式:  基本类型名 *指针变量名
指针变量所能移动的变量由定义时的基类型却东,不能指向其他类型的变量。基类型不同,执着呢变量在移动时的步长也不同,如指针变量的基类型为整型,则该指针类型变量每次移动1个位置即2个字节。
char *s;   //s为指向字符型指针变量

初始化
格式: 基类型名*指针变量名=&变量名;
说明:&是取地址运算符,即将变量的地址复制给指针变量名
注意:给指针变量赋值时,只能用同类型变量的地址或者其他已赋值的同类型指针变量,不能用整数或其他飞地址进行赋值。
例:  int *p;p=1000;     //这样是不行的
但一种情况可以,给指针变量赋0值。在stdio.h中有宏定义:  #define NULL 0
即定义了符号常量NULL,其值为0.也将NULL称为空指针。若有:
int *p=NULL;
表示p指向0号单元。系统保证0号单元不用来存放有效数据。

指针变量的引用
&:取地址运算符
*:指向运算符,表示右边指针变量所指的变量。
*p表示p所指的变量a。
&和*都是单目运算符,优先级相同,结合方向为右结合。两者互为反运算,可相互抵消。
注意:此处的*是运算符,表示访问指针变量所指的变量,而定义指针变量的*不是运算符,表示定义的是指针变量。

例:利用函数调用交换两个整数a和b的值
#include<stdio.h>
void swap1(int x,int y)
{
  int t;
t=x;
x=y;
y=t;
}

void swap2(int *x,int *y)
{
 int t;
t=*x;  //*x表示的是x地址指向的那个值
*x=*y;
*y=*t;
}

void main()
{
int a=3,b=5,*p=&a,q=&b;
printf("before swap: a=%d,b=%d\n",a,b);
swap1(a,b);
printf("after swap1: a=%d,b=%d\n",a,b);
swap2(a,b);
printf("after swap2: a=%d,b=%d\n",a,b);
}


运行结果:
before swap: a=3,b=5
after swap1: a=3,   b=5
after swap2: a=5,   b=3

由此可见,函数swap1改变的是形参本身的值,函数调用结束后主函数中实参a,b的值没有交换过来,函数swap2改变的是形参所指变量的值,函数调用后,实参p,q所指变量a,b的值发生了改变(p,q本身没有改变)。函数swap2的参数传递方式就是引用传递。

void swap3(int *x,int *y)
{
 int *t;
t=x;                      //*x表示的是x地址指向的那个值
x=y;
y=*t;
}
运行结果:
before swap: a=3,b=5
after swap3: a=3,    b=5
由以上结果可见,函数swap3虽然也是指针变量作为形参,调用时也是指针变量作实参,但是a,b的值没有交换过来。原因在于该函数改变的是形参本身的值,也就是说,不管形参,实参是否为指针类型,只要在函数内部改变的是形参本身的值,这些都无法带回到主调函数。

输出数组a[10]的方法:
int i,*p;
for(i=0;i<10;i++)
printf("%7d",a[i]);                  //数组名,下标法输出

for(i=0;i<10;i++)
printf("%7d",*a(i+1));            //数组名,指针法输出

for(p=a;p<a+10;p++)
printf("%7d",*p);             //通过指针变量输出

for(i=0,p=&a[0];i<10;i++)
printf("%7d",p[i]);                   //指针变量,下标法输出    由p=a,所以p[i]=a[i]

for(i=0,p=a;i<10;i++)
printf("%7d",*p(i+1));             //指针变量,指针法输出

注:p=a等价于p=&a[0],因为数组名a表示的就是数组第一个元素的位置


指针运算
1.指针变量可以进行指向运算和赋值运算;
2.指针变量可以进行加减运算;指针变量没加减1,等于向前或后移动了sizeof(基类型)的字节数
3.指针变量可以进行关系运算。>、<,!=等运算,表示位置关系。

注:++*p相当于++(*p)相当于使*p的值加1,再使用*p的值。
       (*p)++:先使用*p的值,然后使*p的值加1
       *p++相当于*(p++),先使用*p的值,再使p的值加1
*++p相当于*(++p),先使p得值加1,再使用*p的值

通过指针访问数组元素

例:
for(p=a;p<a+10;p++)
printf("%7d",*p);             //通过指针变量输出

数组元素a[i]的地址为: &a[i],a+i,p+i
                              则: a[i]=*(a+i)=*(p+i)
由于p=a,则p[i]=a[i]

字符串指针
定义字符串的指针的同时可以用字符串常量对其初始化,例:
char *s1="C programing",*s2;
也可以定义后再赋值: s2="Computer"
C语言对字符串常量仍然按字符数组的方式处理,根据字符串常量的实际长度加1来分配一段连续的内存空间。在用字符串常量给字符指针赋值时,是将该字符串
常量在内存的起始地址赋给字符指针,使得字符指针指向该字符串常量。s2不是指向整个字符串,而是指向第一个字符,因为s2是字符型指针变量,只能指向一个字符,因为*s2="Computer"是错误的。
用字符数组和字符型指针变量都是可以实现字符串的存储和运算,但是两者是有区别的: 字符串指针变量本身是一个变量,用来次南方字符串的首地址,字符数组是由若干个数组元素组成的,它可以用来存放整个字符。所以用指针变量表示字符时可以不在乎它的长度。

指针数组
数组中的每个元素都是指针变量
定义:基类型*数组名[数组长度];

说明:
1.[]优先级高于*
2.可以在定义时对指针数组初始化

定义一个字符指针数组,大小等于字符串的个数,每个元素可以指向一个字符串。

例:
#include<stdio.h>
void main()
{
char*string[3]={"Monday","Tuesday","Friday"}:
int i;
for(i=0;i<7;i++)
printf("%s\n",string[i]) ;
}

指针数组作main函数的形参
main(intargc,char*argv[])
main函数对应的实参由程序运行时的命令行参数给定。参数argc为整型,对应命令行中的参数个数,包括执行文件名;参数argc为字符型的指针数组,每个数组元素是一指向字符串的指针,对用命令行中各参数的名字。 
例:
#include<stdio.h>
main(intargc,char*argv[])
{
int i;
for(i=1;i<argc;i++)
printf("%s\n",argv[i]);
}

在dos下运行,输入
C:\test.exe ChangshaBeijing                //这里argc=3,表示有三个命令行参数(文件名也算一个),而argc[0]指向“file”

运行结果:
Changsha
Beijing

指向数组的指针
a[3][4]
a,a[0],*(a+0),*a,&a[0][0]是相等的。
*(a+0),*a与a[0]等效,表示的是一维数组a[0]的地址,a+1是二维数组第一行(从第0行开始计数)的首地址。
可以得出:a+i,a[i],*(a+i),&a[i][0]等同
另a[i]+j表示的事一位数组a[i]的第j号元素的首地址,等同于&a[i][j].由a[i]=*(a+i),得a[i]+j=*(a+i)+j,是二维数组a的i行j列元素的首地址,因此该元素
等于*(*(a+i)+j

二维数组指针标量定义的一般形式:
类型说明符(*指针变量名)[长度];
说明:
1.“类型说明符”为所指数组的数据类型
2.“*”表示其后的变量时指针类型
3.“长度”表示二维数组分解为多个以为数组时以为数组的长度也就是二维数组的列数。
int (*p)[4]表示的p是一个指针变量,指向二维数组a或指向第一个以为数组a[0],其值等于a,a[0],或&a[0][0],而p+i则指向一位数组a[i].
*(p+i)+j,是二维数组a的i行j列元素的首地址,因此该元素 等于*(*(p+i)+j)

返回值为指针类型的函数
类型说明符*函数名(形参表)
{
……
}
说明:函数名之前加*表示这是一个指针型函数,返回值是一个指针,类型说明符表示返回的指针值所指向的数据类型

指向函数的指针和函数参数
在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存的首地址,可以把首地址赋予一个指针变量,使该指针变量指向这个函数,然后通过指针变量调用这个函数。

定义;
类型说明符(*指针变量名)();
说明:
1.“类型说明符”表示被指函数的返回值的类型
2.“(指针变量名)”表示*后面的变量是定义的指针变量
3.最后的空括号表示的是指针变量所指的是一个函数。
例:
int (*pf)();
表示pf是一个指向函数入口的指针变量,该函数的返回值是整型。

函数指针变量的赋值方法:
函数指针变量=函数名;
利用函数指针变量进行函数调用的方法是
(*函数指针变量)(实参表);

例:利用函数指针变量进行函数调用,求10个证书中最大值及其地址。
#include<stdio.h>
int *max(int a[],int n)
{
int i,m,*p=a;
m=a[0];
for(i=1;i<n;i++)
if(m<a[i])
{
m=a[i];
p=&a[i];
}
return p;
}
void main()
{
int a[10]={51,2,34,24,5,66,7,8,39,23},*p;
int (*ptr)();
ptr=max;
p=(*ptr)(a,10);
printf("the max data is %d, its address is %u\n",*p,p);
}


函数指针变量是一个指针变量,它可以像其他指针变量一样作为函数的参数。

数组名作函数参数
数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址,形参得到该地址后也指向同一数组。同样,指针变量的值也就是地址,
因此,指向数组的指针变量也可作为函数的参数使用。
void print(int *p,int n)
{
int i;
for(i=0;i<10;i++)
 printf ("%5d",*(p+i));
}
void main()
{
int a[10]={2,5,8,3,37,,19,56},*p=a;
   printf(p,10);
}

数组名作函数的参数,必须遵守以下原则:
1.形参和实参可以是数组名或指针变量;
2.实参数组和形参数组必须类型相同,形参数组可以不指明长度;

指向结构体的指针变量
定义和初始化:
structst *p=stu;
表示定义了结构体的指针变量p,并将stu数组的起始位置赋给p,那么指针变量p指向数组元素stu[0],元素*p就是stu[0];

结构体变量成员的访问除了已经知道的"结构体变量.成员名"这种方法外,还可通过指向结构体变量的指针变量访问;
格式1:
(*指针变量).成员名
例:(*p).name代表p所指变量的name成员。需要注意的是“.”,运算符优先于"*",因此需要将*p括起来。

格式2:
指针变量->成员名
例:q->ave+=q->score[i];
其中,q->ave,q->score[i]分别代表了q所指变量的name成员和ave成员。"->"含义为“指向结构体的”,成为指向成员运算符,优先级最高,结合方向为左结合。

注意:  p->n++ 相当于(p->n)++; 而 ++p->n 相当于++(p->n);

用指向结构体变量的指针做函数参数
函数的形参应该是结构体类型的指针变量,实参可以是指向结构体变量的指针变量,也可以是结构体变量的地址。
例:
void average (structst *q)
{
……
}
形参为结构体类型的指针变量q.函数调用语句:
for(p=stu;p<stu+5;p++)
          average(p);       //p也可以改成&stu[i]
实参为指向结构体变量的指针变量。


存储空间的分配和释放

动态空间分配函数

(1)malloc()函数

(类型标志符*)malloc(size)

功能:在内存动态分配一个长度为size的一个连续空间,函数返回值是该区域的首地址。

说明:(类型标志符*)是强制类型转换。因为函数返回的指针是void类型的,用户根据存储空间的用途把函数调用返回的指针强制转换为相应的类型。

size是一个无符号数,单位是字节。

例:stu=(struct st*)malloc(n*sizeof(struct st));  //根据学生动态数n动态分配数组空间

分配n*sizeof(struct st)个字节的空间,并将该空间的起始位置强制转换为结构体指针类型,赋给stu指针变量,那么,stu就指向动态分配的内存空间。

(2)calloc()函数

(类型标识符*)calloc(n,size);

功能:在内存动态分配n个长度为size的连续空间,函数返回值是该区域的首地址。

例:stu=(struct st*)calloc(n,sizeof(struct st));

(3)realloc()函数

(类型标识符*)realloc(void*p,size);

功能:将p所指向的内存块的大小改为size个字节;

说明:若分配成功,返回指向新分配空间的指针,并将原内存块中的内容复制到新内存块中。若内存不够,则返回NULL,原p指向的内存不变。realloc并不能保证调整后的内存空间和原来的内存空间保持同一内存地址。相反,realloc返回的指针很可能指向一个新的地址。所以在代码中,必须将realloc返回的值,重新赋值给一个指针变量。例;

stu_1=(structst*)realloc(stu,(n+10)*sizeof(struct st)); stu=stu_1;

将stu所指的元内存空间扩展为(n+10)*sizeof(struct st)个字节的新空间,将新空间的地址赋给结构体类型的指针变量stu_1,若分配成功,则stu_1不为NULL,再将其赋给stu,使得stu指向新空间。若改为

stu=(structst*)realloc(stu,(n+10)*sizeof(struct st));

则如果分配失败,将NULL赋给stu,且无法再通过stu访问原来的内存空间。

(4)free()函数

free(指针变量名);

功能:释放指针变量名所指示的内存空间。

 











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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值