概念理解
int a=1;
int *pointer_a=&a; //pointer_a=&a;
(1)地址和指针的定义
内存区中每一个字节都有一个“编号”。
地址包括一个编号信息和一个数据类型信息。
C语言中地址=位置信息(纯地址)+所指向数据的数据类型=指针=&a
指针就是一个变量的地址,只能用&获取。
(2)指针变量定义
指针是一个地址,指向存储变量的内存空间所在,带数据类型。
指针变量是存放该指针(地址)的变量。
(3)指针变量定义
a、一个完整的地址&a,包括一个纯地址信息和一个数据类型。因此在定义指针变量时,必须指明基类型。
b、*符号表示这是一个指针变量。*后面的pointer_a是指针变量名。
c、pointer_a存放了整型变量a的地址&a(数据类型+位置信息)。
(4)运算符
&:取地址运算符,&a是变量a的地址;
pointer_a:是指针变量,里面存储了一个带数据类型的地址,可以说pointer_a指向一个地址。
*:指针运算符(间接访问符号),*pointer_a代表指针变量pointer_a指向地址存储的数据。
(5)变量理解
a、编译时,为整型变量a分配一个内存空间,这个空间有一个地址。
b、编译后,c语言变为机器语言,此时变量名a已经没有了,变成了一个带数据类型的地址 信息。变量名a只是在编写代码时给我们看的。
c、在运行程序时,我们对变量名a的任何操作(包括赋值、读数据),都会被自动转化为对这个地址中存储数据的操作。
d、&a取到的是这个地址,而不是地址中存放的数据。此时就可以理解为什么scanf()的输入是&a(或者指针变量)而不是a。对于scanf()来说,我们希望从键盘读取到的数据,被存放到某个地址中去,因此函数输入参数是一个地址变量,如果直接写a,我们输入的不是地址,而是这个地址中存储的数据。函数中处理的是一个单向值传递的局部变量。如果直接输入值,不会对实参发生影响。因此要将地址作为值传递进函数中,才能对实参进行实际修改。&a等价与pointer_a,因此也可以:
scanf(“%d”, pointer_a)==scanf(“%d”,&a)
(6)图解
编译后变量名a已经不存在了,他变成了一个地址信息(整型变量类型+地址信息2000)。此时对a的全部操作,变成了对这个地址中存储数据1的操作。
编译后指针变量pointer_a变成了地址信息(指针变量类型+地址信息2012),其中存放了整型变量a的地址&a。我们可以说指针变量pointer_a指向一个整型。此时可以用指针运算符*来间接访问地址&a存储的数据。
引用指针变量
(1)直接访问和间接访问
对变量值的访问都是通过地址进行的。
a、直接访问:
直接使用整型变量a的变量名来引用。系统直接根据地址找到值。
a=1;
printf(“%d”,a);
b、间接访问:
通过指针变量存放的地址信息,来访问这个地址中的值。
a的地址&a赋值给了指针变量pointer_a,即pointer_a指向的地址中存放了&a。
此时可以使用*pointer_a来使用&a存储的数据。这是间接访问。
(2)引用指针变量
a、赋值:
*pointer_a=1; //将整数1赋值给pointer_a当前指向的地址,即a。
b、引用指针变量的值
printf(“%o”,pointer_a);//以八进制形式输出一个地址&a。
(3)指针变量作为函数的值
用函数对变量的值进行写操作(包括赋值、交换、输入等)都需要通过指针来实现。这是因为函数实参到形参的传递是一个单向的值传递。如果直接对值进行处理,函数结束后函数中的局部变量就释放了,不会实际影响到主调函数中的值。因此需要将指针即地址传入函数中,才能实际对这个地址中存储的值进行写操作。
需要注意的是,指针变量也是一个变量。如果在被调函数中改变指针变量的值,而不是指针变量所指向数据的值,也是不可以实现的。因此改变指针变量的值,在函数调用完成后,不会对主调函数中的指针变量产生影响。
程序举例:
#include<stdio.h>
int main(){
int a=1;
int b=2;
int *pointer_a=&a;
int *pointer_b=&b;
printf("交换前:a=%d,b=%d\n",a,b);
void swap(int *,int *);
swap(pointer_a,pointer_b);
printf("使用swap()交换后:a=%d,b=%d\n",a,b);
void swap1(int *,int *);
//初始化
a=1,b=2;
pointer_a=&a;
pointer_b=&b;
swap1(pointer_a,pointer_b);
printf("使用swap1()交换后:a=%d,b=%d\n",a,b);
void swap2(int *,int *);
//初始化
a=1,b=2;
pointer_a=&a;
pointer_b=&b;
swap2(pointer_a,pointer_b);
printf("使用swap2()交换后:a=%d,b=%d",a,b);
return 0;
}
/*
这种方式是对的。交换的是指针变量所指向的地址中值。函数中的变量是指针变量,但是我们操作的是这个指针变量所指向的值,而不是这个指针变量。
*/
void swap(int *pointer_a,int *pointer_b){
int temp;
temp=*pointer_a;
*pointer_a=*pointer_b;
*pointer_b=temp;
}
/*
这个函数的问题在于,将指针变量pointer_a赋值给了指针变量pointer_temp。之后指针pointer_temp指向的地址也变成了a。*pointer_a=*pointer_b改变了a的值,所以*pointer_temp的值也变了,起不到temp的作用。
*/
void swap1(int *pointer_a,int *pointer_b){
int *pointer_temp;
pointer_temp=pointer_a;
*pointer_a=*pointer_b;
*pointer_b=*pointer_temp;
}
/*
改变指针变量的值,无法影响到主调函数中指针变量的值。因此没有效果。
*/
void swap2(int *pointer_a,int *pointer_b){
int *temp;
temp=pointer_a;
pointer_a=pointer_b;
pointer_b=temp;
}
输出结果:
交换前:a=1,b=2;
使用swap()交换后:a=2,b=1
使用swap()1交换后:a=2,b=2
使用swap()2交换后:a=1,b=2
指针与数组
(1)概念
int a[10]={0,1,2,3,4,5,6,7,8,9};
int *pointer_a;
pointer_a=a; == pointer_a=&a[0]; //将数组首元素的地址赋值给指针变量
数组元素的指针就是数组元素的地址。
使用指针法比使用下标法占内存少,运行速度快。
数组名不代表整个数组,而是指向数组首元素的地址,&a[0]。实际上并不存在这么一个指针变量的实际存储单元,他只是一种地址的计算方式,因此这个指针变量是不能被重新赋值的。
&a和a单纯比较数值是一样的。但是他们的含义不同。a是这个一维数组第一个元素a[0]的地址,而&a则代表这整个数组a[10]的地址。a+1跳过个一个基类型的字节数,而a+1跳过整个数组元素个数的字节数。
(2)引用数组元素
a、下标法
最慢的方法,每次都需要计算地址*(a+i)。
a[0],a[1]……
b、地址法
数组名a实际上就是一个指针型常量。
int a[10];
*(a+1); == a[1];
c、指针法
int *pointer_a=a;
*pointer == a[0]
*(pointer_a++) == a[1]
a++是不行的,因为数组名是一个指针型常量,但是pointer_a++是可以的。
(3)地址+i的理解
a、因为地址元素是包含了数据类型的,数组也包含了数据类型,pointer+1的含义是加上一个数组元素类型所占用的字节数,这个字节数由指针变量的基类型决定。
pointer+i == pointer+i*d d是基类型的字节数
b、a[0]中的[]实际上是变址符,a[i]其实就是按照a+i计算地址。
c、如果int *p1=&a[0],int *p2=&a[5],那么p2-p1=5。
地址相减有意义,但是相加没有意义。
(4)运算符
*和++的优先级相同,因此自右往左结合
*p++ == *(p++) //先*p,再p++
*(++p) //先p++,再*p
(5)比较大小
指针是可以相互比较大小的。
int a[10];
int *pointer_a=a;
for(;pointer_a<a+10;pointer_a++)
printf(“%d”,*pointer_a);
此时比较的是一个地址。
数组作为函数参数
(1)函数改变原数组值的原理
void swap(int a[])
int a[2];
swap(a); //数组名作为参数,函数调用完后,主调函数中数组也发生变化。
因为数组名是一个指针。
两者等价:
void function(int arr[]) == void function(int *a)
在该函数被调用时,处理的是指针变量所指向的值。因此可以改变原数组值
void function(int a[]){
a[0]++; ==(*a)++
}
(2)函数中数组可以重新赋值的原因
数组名是一个常量指针,是不能改变和赋值的。
形参为数组时,传入实参数组时,传入的实际上是一个指针。被调函数处理的是一个指针变量,而不是一个数组。
int a[3]={0,1,2};
//a++; //这是错误的~~
void swap(int a[]){
a++ //这是可以的
//……
}
(3)归纳总结:
如果希望通过调用函数改变一个数组的值:
a、函数形参为数组,实参为数组
b、函数形参为数组,实参为指针
c、函数形参为指针,实参为数组
d、函数形参为指针,实参为指针
指针与二维数组
int a[3][3]={0,1,2,3,4,5,6,7,8};
(1)图解
(2)概念理解:
二维数组名a指向行的,a+i实际跳过整行的全部字节数。
一维数组名a[i]指向列的,a[i]+j是跳过了一个基本类型的字节数。
a和a[i]都是常数型指针变量,都是存储的地址信息,但是实际上并不存在a[i]这样一个实际的数据存储单元,它们只是一种地址的计算方式,单纯的比较它们的值,都是没有意义的,它们指向的值才是实际存储的数据。
在指向行的指针a前面加上一个*,就变成了指向列的指针。即:*(a+0)等价于a[0],仍然指向第一行第一列的元素;
在指向列的指针a[0]前面加上一个&,就变成了指向行的指针。即&a[0]等价于&*a等价于a,仍然指向第一行第一列的元素。
在指向行的指针a前面加上一个&,它的数值不便。但是他的含义已经变了。a代表这个二维数组首行的地址,&a[0];而&a则代表整个二维数组int a[i][j]的首地址。&a+1跳过一整个数组的字节数。不存在&(a+i).
在指向列的指针a[0]前面加一个*,则变成了实际存储的元素。*a[0]=*(*a)=0。
虽然它们的值没有变,但是它们的含义已经变了。因为它们加1的值变化了。
综上所述,不必去纠结于a,&a,*a等等的值(它们的纯数值都是相等的),只要明白它们指向的元素即可,理解他们之间的等价性。对于a和a[i]来说,重要的是理解它们+1之后的变化。
(3)单纯比较值
行 列 列 行
a+i = *(a+i) = a[i] = &a[i]
列加j,跳过j个基类型:
*(a+i)+j=a[i]+j=&a[i][j]
行加j,跳过j行元素:
&a[i]+j=a+i+j
(4)指向二维数组的指针变量(基类型是一位数组)
a、用指针指向列
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int *p=a; //int *p=a[0];int *p=&a[0][0];都是一样的效果。
for(;p<a[0]+12;p++){ //逐个元素指过去
printf(“%d”,*p);
}
p是一个指向列的指针,因此直接p就等与a[0].
b、用指针指向行
基类型 (*指针名)[m];
int (*p)[m];
这样定义的意义是:定义了一个指向一维整型数组的指针变量。p的类型是int()[4]型,p的基类型是一维数组。
此时p指向这个指针数组的首元素;p+i跳过i个数组,即im*4个字节。
用行来理解,即p+i跳过了i行(每行m列)。
如果令p=a,则p变成了二维数组的第一行,是一个指向行的指针。
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int (*p)[4],i,j;
p=a; //此时p指向这个二维数组的第0行
for(;p<a[0]+12;p++){ //输出为每行首个元素
printf(“%d”,**p);
}
printf(“第2行第3列元素为%d”,*(*(p+i)+j));
需要注意的是,p变成了一个指向行的指针。
*p!=*a[0],而是将p转变为列指针,即*p=&a[0]=*a
**p=*&a[0]=**a=a[0][0]
(p+i)=a[i];
*(p+i)+j=a[i]+j
(5)总结
对于二维数组
int stu[m][n];
stu是行指针;
stu+i,stu[i]是第i行;
*(stu+i)+j,stu[i]+j是第i行第j列;
&stu[0]是行指针;
int (*p)[n]是行指针,和stu效果一样;*p列指针,*(*(p+i)+j)=stu[i][j]
int *p=*stu是列指针,和stu[0]效果一样。
指针与字符数组
(1)定义指针指向一个字符数组
char string[]=”I love u”;
char *pointer_string=string;
等价于
char *string=”I love u”;
系统会将字符串按字符数组处理,可以不需要数组名,直接用指针来访问。这个指针指向这个字符串的第一个字符。
(2)输入字符串
char *string,str[10];
string=str;
scanf(“%s”,string);
不能直接对string使用scanf,这是因为编译系统虽然已经给指针变量string分配了内存,但是分配内存的多少是不可预知的。因此需要string=str.
(3)输出字符串
char string1[]=”I love u”;
char *pointer_string1=string;
char *string2=”I love u”;
printf(“%s”,string1);
printf(“%s”,pointer_string1);
printf(“%s”,string2);
这三种方法都可以输出字符串,如果加了*,则输出的是一个字符的值。因为无论是数组名还是指针,都指向第一个字符变量。
(4)两者区别
a、赋值差别,数组不能重新赋值,指针可以重新赋值。
b、存储区别,编译时为字符串数组分配若干个存储单元;但只为数组分配一个存储单元(一般指针变量是4个字节)
c、变值区别,字符串数组中的任意一个字符变量可变;而指针指向的是一个字符串常量的首个字符,这个字符串常量中的字符不能变化。
指向函数的指针
返回类型 (*指针变量名)(形参1,形参2……);
编译系统会将函数的源代码转换为可执行代码,并分配一段存储空间,这个存储空间的首地址就是这个函数的指针。函数名就是函数的指针。每次调用函数都会以该地址作为入口执行代码。
(1)用指向函数的指针调用函数
int main(){
int max(int,int);
int (*pointer_max)(int,int);
pointer_max=max;
int a=10;
int b=6;
int max;
max=(*pointer_max)(a,b); //等价于直接调用函数
//……
}
int max(int a,int b){
//……
}
函数声明:返回类型 函数名(形参1,形参2……);
指针定义:返回类型 (*指针变量名)(形参1,形参2……);
指针变量名=函数名;
使用指针调用函数,即以这个地址作为入口调用函数。这个指针只能指向规定返回值,规定形参类型的函数。
在给这个指针变量赋值时,只需要给出函数名,不需要给出参数。因为函数名就是这个函数的起始地址,类似于数组名。
(2)用指向函数的指针作为函数参数
指向函数的指针的用途,就是用作一个函数的实参。
在某个函数中,调用另一个函数,但是调用的函数在不同情况下不同,此时就可以用指针实现。
这样就可以编写一个通用接口,来执行不同的专用功能。
int main(){
int fun(int,int,int(*p)(int,int));
int max(int,int);
int min(int,int);
int add(int,int);
int x=34,int y=-21,n;
scanf(“%d”,n);
if(n==1) fun(x,y,max); //将函数名作为参数输入,也就是函数的执行入口
else if(n==2) fun(x,y,min);
else fun(x,y,add);
return 0;
}
/*fun函数是一个统一处理函数,根据不同的情况,输入不同的函数来对值进行处理。*/
int fun(int a,int b,int(*p)(int,int)){
//……
}
int max(int a,int b){}
int min(int a,int b){}
int add(int a,int b){}
函数返回指针值
类型名 *函数名(参数列表)
首先这是一个函数,其次它的返回值是一个指向类型名的指针。
注意和指向函数的指针的区别:
返回类型 (*指针名)(参数列表);
有了括号,使他首先是一个指针,其次才是指向一个函数。
指针数组
类型名 *数组名[数组长度] //实质是一个数组
首先这是一个数组,其中存放的都是指针变量。
类型名 (*指针变量名)[长度] //实质是一个指针变量
返回类型 *函数名(参数列表) //实质是一个函数
注意三者的区别。
(1)指针数组用来指向字符串数组
可以用来保存多个字符串。指针数组中的元素指向这些字符串。
char *name[]={“aaa”,”bbb”,”ccc”,”ddd”};
name是一个指向这个数组第一字符串的指针。因此*name=”aaa”。
等价于
char *name1=”aaa”;
char *name2=”bbb”;
char *name3=”ccc”;
char *name4=”ddd”;
char *name[4]={name1,name2,name3,name4};
name[1]=name1=&”aaa”=*(name+0);
**(name+0)='a';
(2)指向指针的指针
char *name[]={“aaa”,”bbb”,”ccc”,”ddd”};
这里的name是一个数组名,实际上是一个指针。这个指针指向一个数组的首位元素的地址。这个数组的首位元素是一个字符串,实际上也是一个指针变量。因此name就是一个指向指针的指针。
char **p=name; //说明p是一个指向指针的指向
等价于
char **p;
p=name;
*(p+i)=*(name+i)=name[i]=第i个指针=第i个字符串;
(3)指针数组作为main函数的形参
int main(int argc,char *argv[]){}
argc是参数个数,argv则是一个指针数组,指向一系列参数字符号。
注意,这里的argc包括文件名。
./a.out aaa bbb ccc //argc=4