概述
在程序中一般是通过变量名对内存单元进行存取操作的。其实程序经编译后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的 。
指针是变量的地址,指针变量是只能存放地址的变量。
指针变量的定义、引用
1.定义指针变量的一般形式:基类型 *指针变量名
,如:int *point;
point是指向整型变量的指针变量。
2.指针变量名前的*
仅用于表明该变量的类型为指针型变量。指针变量名为point
,而不是*point
3.不同类型的数据在内存中所占字节数不同,因此在定义指针变量时必须指定基类型。
4.&:取地址运算符 。若有int a;则&a为变量a的地址
。*:取地址符,取指针所指向的对象的内容
例子
void swamp(int *p1,int *p2)
{
int *temp;
*temp=*p1; //此句存在隐患,应先初始化int *temp=NULL,或者直接使用整型变量
*p1=*p2;
*p2=*temp;
}
第3、4行代码存在隐患:temp是指针变量所指向的变量,此段代码中,temp未初始化,temp所指向的单元不可预见,对temp赋值可能会修改系统重要数据。
指针变量作函数参数
1.==指针变量作函数参数时,实参变量和形参变量之间的数据传递是单向的值传递方式==不可能通过调用函数来改变实参指针变量的值,但可以改变实参指针变量所指变量的值
例子
以下程序达不到值交换的目的。因为形参无法将函数中改变后的值传入实参
void swap(int *p1,int *p2)
{
int *p;
p=p1;
p1=p2;
p2=p;
}
一维数组与指针
c语言规定,数组名代表数组中首元素的地址,是一个常量指针。指向数组的指针变量也可以带下标。
例子
/*以下程序想通过指针变量输出数组的10个元素,但出错了*/
#include<stdio.h>
void main()
{
int *p,i,a[10];
p=a; //p此时指向数组a[0]
for(i=0;i<10;i++)
{
scanf("%d",p++);
} //p此时指向a[9]
putchar('\n');
for(i=0;i<10;i++,p++) //p此时指向a[9],应在此处重新令p=a;
{
printf("%d ",*p);
}
putchar('\n');
}
一维数组名作函数参数
1.当用数组名作参数时,如果形参数组中各元素的值发生变化,实参数组元素的值也随之变化
2.c编译都是将形参数组名作为指针变量来处理的
3.数组名作参数的函数被调用时,系统会建立一个指针变量用来存放从主调函数传递过来的实参数组首元素的地址。
4.实参数组名代表一个指针常量,而形参数组名不是指针常量,而是指针变量
例子
#include <stdio.h>
void main()
{
void f(int a[]);
int a[10]={0};
f(a); //实参数组名是常量指针,指向数组a首元素
}
void f(int a[]) //形参数组名是指针变量(实际上函数调用时系统又新建立的同名指针变量)
{
int i;
for(i=0;i<10;i++)
{
printf("%d ",*a++);
}
}
多维数组与指针
对于以下语句:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
可以这样理解:
有一个存放指针的数组,它有3个元素,每一个元素都是一个一维数组(每一个一维数组有四个元素)的首地址。a是这个存放指针的数组的首地址。
字符串与指针
1.字符串的两种访问方式:
用字符数组存放字符串,然后输出;用字符指针指向一个字符串,通过指针的移动实现读写字符串。
2.C语言对字符串常量是按照字符数组处理的。
char *string;
string="I love China"; //把I love China的首字符的地址赋给指针变量string
printf("%s",string);
//注意:以下对字符数组的赋值是错误的
char string[];
string="I love China";
//应该为:
char string[]={"I love China"};
3.和普通数组一样,字符数组可以在定义时整体赋值,但不能在赋值语句中整体赋值
4.在内存中,字符串的最后被自动加了一个’\0’,对字符串进行复制操作时,最后应将’\0’复制过去
5.用指针变量指向一个格式字符串,可以用它代替printf函数或者scanf函数中的格式字符串
#include <stdio.h>
void main()
{
char *mod="a=%d,b=%d";
int a,b;
scanf(mod,&a,&b);
printf(mod,a,b);
}
指向函数的指针
1.一个函数在编译时被分配给一个入口地址,这个函数的入口地址就是函数的指针。函数名代表函数的入口地址。
函数返回值类型 (*指针变量名)(函数参数列表)
比如:int (*p)(int,int); //声明指针p,该指针指向一个函数的入口地址
注意:int *p(int,int); //声明函数p,此函数返回值是指向整型变量的指针
2.每一个函数都占用一段内存单元,拥有各自的起始地址,因此可以用一个指针变量指向一个函数,通过指针变量来访问它指向的函数。
#include <stdio.h>
int main()
{
int sum(int,int);
int mul(int,int);
void hello();
int (*p)(int,int);
void (*q)();
q=hello; //将函数hello的入口地址赋给指针q,不能写成q=hello();
q(); //和hello();等价
p=sum; //将函数sum的入口地址赋给指针p
printf("%d",(*p)(1,2));
p=mul;
printf("%d",(*p)(1,2));
return 0;
}
int sum(int i,int j)
{
return i+j;
}
void hello()
{
puts("hello!");
}
int mul(int i,int j)
{
return i*j;
}
3.函数的调用既可以通过函数名,也可以通过函数指针
4.对于指向函数的指针变量p,p+n,p++,p–等运算无意义
结构体与指针
struct a
{
char m;
int s[10];
};
struct a x;
struct a *p;
p=&x;
(*p).m //等价于:p->m
for(int i=0;i<10;i++)
(*p).s[i] //等价于:p->s[i]
注意:
1.结构体变量作函数参数时,使用.
运算符访问结合体成员。
2.指向结构体变量的指针作函数参数时,用(*p).member
或者p->member
访问结构体成员。涉及链表的操作时,->
更为方便。
指针数组、数组指针
1.指针数组:例如:int *p[4]
,由于[]
比*
优先级高,因此p
先与[4]
结合,形成p[4]
形式,即:有4个元素的数组。然后再与p
前面的*
结合,表示此数组是指针类型的,每个数组元素(相当于一个指针变量)都可以指向一个整形变量。
对于指针数组,本身是个数组,数组里存的是指针
2.数组指针:例如:int(*p)[4]
,因为()
优先级和[]
一样高,所以从左向右结合。因此p
先与*
结合,说明p
是一个指针变量,然后与[4]
结合,说明p的基类型是一个包含4个int
型元素的数组。
对于数组指针,本身是个指针,指针基类是数组
指向指针的指针
1.定义一个指向指针数据的指针变量
例如:char**p;
//因*的结合性是右到左,所以相当于
char *(*p );
例子:
#include<stdio.h>
int main()
{
char *week[]={"Mon.","Tues.","Wed.","Thur.","Fri.","Sat.","Sun."};
int n[]={1,2,3,4,5,6,7};
int *num[]={&n[0],&n[1],&n[2],&n[3],&n[4],&n[5],&n[6]};
char **p,**q;
int i;
for(i=0;i<7;i++)
{
p=week+i;
q=num+i;
printf("%s %d\n",*p,**q);//注意此处有几个*
}
return 0;
}
2.利用指针数组作main函数的形参,可以向程序传送命令行参数(这些参数是字符串)
void main(int argc,char *argv[]) //argc:命令行中参数的个数
指针运算
1.指针变量加或减一个整数
2.指针变量赋值(不能把整数赋给指针变量,反之亦不可)
3.指针变量可以不指向任何变量
4.指向同一数组的指针变量可以相减,差值为两指针之间的元素个数
5.指向同一数组的指针,指向前面(下标小)的小于指向后面(下标大)的