一、指针变量
<一> 指针简介
指针是C语言中非常重要的数据类型,可以说不掌握指针就没有掌握C的精华。
我们可以通过地址找到所需的变量单元,地址指向该变量单元,因此地址形象化地称为“指针”。一个变量的地址称为该变量的指针,指针变量的值是地址,而且指针变量只能存放地址。指针变量就是用来存放地址的变量。
<二> 指针变量的定义
1、定义的格式
变量类型 *指针变量名;
int *p;// 定义了指针变量p
-
指针变量 p 只能指向 int 类型的数据
-
-
指针变量只能存储地址
-
-
指针作用:能够根据一个地址访问对应的存储空间
2、先定义后赋值
-
简单取值
int *p;// 定义了指针变量p
int a = 10;
p = &a; // 指针变量p指向了变量a
printf(“%d”, *p);
-
简单改值
*p = 9; // 访问指针变量p指向的存储空间,对其进行赋值
3、定义的同时赋值
int a= 10;
int *p= &a;
4、实现修改实参
void change(int *n)
{*n=10;}
函数调用完毕后,改变实参的值
<三> 指针实例
1、交换两个数的值
void swap(char *a, char *b)
{
int temp;
temp=*a;
*a = *b;
*b = temp;
}
2、利用指针间接返回多个值
int sumAndMinus(int a, int b, int *minus)
{
*minus=a-b;
reurn a+b;
}
<四> 指针探究
1、指针变量所占用的存储空间
任何指针都占用8个字节的存储
int *i;
char *c;
printf(“i=%zd,c=%zd\n”,sizeof(i),sizeof(c));
注:sizeof的返回值类型为unsigned long,%zd格式输出
2、为何指针变量要分类型?
示例:
int i = 2;
char c = 1;
char *p = &c;
printf("c的值为%d", *p);
结果为:c的值为1
int i = 2;
char c = 1;
int *p = &c;//注意区别,编译有警告
printf("c的值为%d", *p);
结果为:
c
的值为
513
分析:结果不同是因为指针变量类型不同
printf(“c的值为%d”, *p); *p表示从p所指向的存储空间取值,p为int类型,占4个字节,也就是从地址ffc8处开始取4个字节数据,取值为00000000 0000 0000 0000 0010 0000 0001
即十进制513。对于赋值*p=10;则会从起始地址开始把10存储在4个字节中。
所以指针分类型是为了赋值时要把数据存放在多少个字节内,取值时取出多少个字节内的数据。
<五>注意点
-
指针变量只能存储地址
-
-
指针变量未经初始化,不要拿来访问其存储空间
-
-
int *p; 指针变量 p 只能指向 int 类型的数据 , 不要指向其他类型数据
-
-
%p 输出指针里面存储的地址值
-
-
清空指针: p = 0; 或 p = NULL;
int *p; // 定义变量时*仅仅象征p为指针变量,没有其他含义
*p = 9; // 此处的*作用为访问指针变量p指向的存储空间
注意
int *p = &a;//把a的地址给了指针变量p
*p = &a;//把a的地址赋给指针变量p所指向存储空间,即a=&a,不可这样写
二、指针与数组
1、指向一维数组元素的指针
int ages[5]={5,6,7,8,9};
int *p;
p=&ages[0]; // 指针变量p指向了ages[0],即数组首元素
利用一个指针来接受一个数组,指针变量指向了数组首元素,数组名代表数组中首元素(即序号为0的元素)的地址,因此下面两句等价
p=&ages[0];
p=ages;
注:数组名不代表整个数组,只代表数组首元素的地址。p=ages的作用是把ages数组的首元素的地址赋给指针变量p,并不是把数组ages各元素的值赋给p。
定义指针变量时对其初始化
int *p=&ages[0];
int *p=&ages;
2、数组元素的访问方式
数组名[下标] ages[i]
指针变量名[下标] p[i]
*(p+i)
3、指针变量的+1究竟加多少,取决于指针的类型
char * 1
int * 4
double* 8
(1)如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,p-1
指向同一数组中的上一个元素。执行p+1时并不是将p的值(地址)简单地加1,而是加上一个数组元素所占的字节数。
(2)如果p的初值为&ages[0],则p+i和ages+i就是数组元素ages[i]的地址,或者说他们指向了ages数组序号为i的元素。
(3)*(p+i)或*(ages+i)是p+i或ages+i所指向的数组元素,即ages[i]。
4、用指针遍历一维数组元素
for(int i=0;i<5;i++)
{
printf("ages[%d]=%d\n",i,*(p+i));
}
每一个循环开始前,一定要让指针指向数组的首元素
5、分析下面两种情况(其中p=ages)
(1)*(p++)和*(++p)
*(p++)等价于*p++,先引用p的值,实现*p运算,然后再是p自增1
前者先取*p值,然后使p加1。输出*(p++)得到ages[0]的值
后者先使p加1,然后取*p值。输出*(++p)得到ages[1]的值
(2)(*p)++和++(*p)
都可说是先取*p值,再使该值加1,但输出结果不同。
输出(*p)++得到的值为ages[0],输出++(*p)得到的值为ages[0]+1。
6、数组名做函数参数
形参和实参的对应关系有一下4种情况:
(1)形参和实参都有数组名
形参数组名x接受了实参数组首元素a[0]地址
int main()
{
int a[10];
f(a,10);
……
}
int f(int x[],int n)
{
……
}
调用函数时,形参x指向a[0],即x=&a[0]
int main()
{
int a[10];
f(a,10);
……
}
void f(int *x,int n)
{
……
}
先使实参指针变量p指向数组a[0],p的值为&a[0],然后将p的值传给形参指针变量x,x的初始值也为&a[0]
int main()
{
int a[10],*p=a;
f(p,10);
……
}
void f(int *x,int n)
{
……
}
(
4
)实参为指针变量,实参为数组名
实参p为指针变量,指向a[0],形参为数组名x,编译系统把x作为指针变量处理,将a[0]的地址传给形参x,是x也指向a[0]。
int main()
{
int a[10],*p=a;
f(p,10);
……
}
void f(int x[],int n)
{
……
}
实参数组名代表一个固定的地址,或者说是指针常量,但形参数组名并不是一个固定的地址,而是按指针变量处理
7、指向二维数组元素的指针
int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
a是二维数组名,代表二维数组首元素的地址,现在的首元素不是一个简单的整形元素,而是由4个整形元素所组成的一维数组,因此a代表的是首行(即序号为0的行)的首地址。a+1代表序号为1的行的首地址。
a[i]从形式上看是a数组中序号为i的元素。如果a是一维数组名,则a[i]代表a数组序号为i的元素的存储单元。如果a是二维数组,则a[i]是一维数组名,它只是一个地址,并不代表某一个元素的值。见下表。
表示形式 | 含义 | 地址 |
a | 二维数组名,指向一维数组a[0],即0行首地址 | 2000 |
a[0],*(a+0),*a | 0行0列元素地址 | 2000 |
a+1,&a[1] | 1行首地址 | 2016 |
a[1],*(a+1) | 1行0列元素a[1][0]的地址 | 2016 |
a[1]+2,*(a+1)+2,&a[1][2] | 1行2列元素a[1][2]的地址 | 2024 |
*(a[1]+2),*(*(a+1)+2),a[1][2] | 1行2列元素a[1][2]的值 | 元素值为13 |
三、指针与字符串
1、定义字符串的方式
(1)利用数组
char s[] = “abcd”;
字符串里面的字符可以修改
场合:字符串内容经常修改
(2)利用指针
char *s = “abcd”; //指针变量s指向了字符串的首字符
或者
char *s;
s = “abcd”;
字符串里面的字符不能修改
场合:字符串内容不需要修改,且经常使用
printf(“%s\n”,s);//从地址s开始输出字符,直至遇到‘\0’
通过字符数组名或字符指针变量可以输出一个字符串,而对一个数值型数组是不能企图用数组名输出它的全部元素
2、示例
字符串的复制
//形参为字符数组
void copy(char from[],char to[])
{
int i=0;
while(from[i]!='\0')
{
to[i]=from[i];
i++;
}
to[i]='\0';
}
//形参为字符指针变量
void copy(char *from,char *to)
{
for(;*from!='\0';from++,to++)
{
*to=*from;
}
*to='\0';
}
3
、字符指针变量和字符数组的比较
(1)字符数组有若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址,字符串第一个字符的地址,绝不是将字符串存放到字符指针变量中。
(2)可以对字符指针变量赋值,但不能对数组名赋值。
char *a;
a=”123”;//正确,将字符串首元素地址赋给指针变量
char str[5];
str=”123”;//错误,数组名是地址,是常量,不能被赋值
(3)编译时为字符数组分配若干存储单元,以存放各元素的值,而对字符指针变量,只分配一个存储单元
四、返回指针的函数
-
指针也是 C 语言中的一种数据类型,因此一个函数的返回值肯定可以是指针类型的
-
返回指针的函数的一般形式为:类型名 * 函数名 ( 参数列表 )
类型名表示返回的指针指向变量的类型,*表示此函数是指针型函数(函数值是指针)
例如:
int *a(int x,int y);
a是函数名,调用它以后得到一个int*型(指向整型数据)的指针,即整型数据的地址,x和y是函数a的形参,为整型
char *test()
{
return "rose";
}
int main()
{
char *name = test();
printf("name=%s\n",name);
return 0;
}
输出结果为:
name=rose
五、指向函数的指针
1、为什么指针可以指向一个函数?
函数作为一段程序,在内存中也要占据部分存储空间,它也有一个起始地址,即函数的入口地址(称为这个函数的指针)。因此,可以利用一个指针指向一个函数。其中,函数名就代表着函数的地址。
指向函数的指针变量主要有两个用途:
a)调用函数
b)将函数作为参数在函数间传递
2、指向函数的指针的定义
定义的一般形式:函数的返回值类型 (*指针变量名)(形参1, 形参2, ...);
int (*p)(int,int);
void test()
{printf(“123\n”);}
void(*p)();//(*p)固定写法,代表指针变量p将来肯定是指向函数;void指针变量p指向的函数没有返回值;()指针变量p指向的函数没有形参
p=test;指针变量p指向了test函数,函数名代表函数地址
(*p)();(*p)取函数 利用指针变量间接调用
test();//直接调用
3、示例
//选择1,调用max函数求大值,选择2,调用min函数求小值
#include<stdio.h>
int max(int x,int y)
{
return x>y?x:y;
}
int min(int x,int y)
{
return x>y?y:x;
}
int main()
{
int (*p)(int,int); //定义指向函数的指针变量
int a=4,b=6,c,n;
printf("选择1还是2?\n");
scanf("%d",&n);
if(n==1) p=max;//输入1,p指向max函数
else
if(n==2) p=min;// 输入2,p指向min函数
c=(*p)(a,b);// 调用p指向的函数
if(n==1)
printf("max=%d\n",c);
else
if(n==2)
printf("min=%d\n",c);
return 0;
}
4
、使用注意
-
定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义四指定类型的函数。
-
如果要用指针调用函数,必须先使指针变量指向该函数,如 p=max; p=min;
-
在给函数指针变量赋值时,只需给出函数名而不必给出参数,如 p=max; p=min;
-
用函数指针变量调用函数时,只需将 (*p) 代替函数名即可,后面括号需要写上实参,如 c=(*p)(a,b);
-
用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同的情况调用不同的函数
-
对指向函数的指针变量不能进行算术运算,所以 p++ , p+1 是无意义的
5、用指向函数的指针作函数参数
指向函数的指针变量的一个重要用途是把函数的地址作为参数传递到其他函数。
上述示例可改为:
#include<stdio.h>
int max(int x,int y)
{
printf("max=");
return x>y?x:y;
}
int min(int x,int y)
{
printf("min=");
return x>y?y:x;
}
int fun(int x,int y,int(*p)(int,int))
{
int c;
c=(*p)(x,y);
printf("%d\n",c);
}
int main()
{
int a=4,b=6,n;
printf("选择1还是2?\n");
scanf("%d",&n);
if(n==1) fun(a,b,max);
else
if(n==2) fun(a,b,min);
return 0;
}
六、指针数组
1、什么是指针数组?
一个数组,其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。
2、定义形式
类型名 *数组名[数组长度];
int *p[4];
p先于[4]结合,形成p[4]形式,这是数组形式,再与*结合,表示此数组是指针类型的
3、
char *name2[3]={“jack”,”rose”,”jake”};
数组存放3个指针,每个指针都指向字符串
也可用二维数组存放
char name[3][10] ={“jack”,”rose”,”jake”};
4、
char *name3[3];
scanf(“%s”,name3);
数组名就是地址,不要&;从地址name3开始,逐个存放在name[]中,且加上’\0’
七、多重指针
指向指针的指针
int a=10;
int *p=&a;
int **pp=&p;
将a的值改为20
a=20;
*p=20;
*(*pp)=20;//等价于**pp=20;