C语言第八章——指针
取地址运算——&
printf("%p",&i);//输出i的地址
变量取地址
&后面必须有明确的变量,&(i+k)
和&(i++)
都是不行的
相邻的变量取地址
int i;
int p;
printf("%p",&i);
printf("%p",&p);
就可以看到i的地址是6c,p的地址是68,中间差了4,也就是说他们是挨着存放的
进一步的,其实本地变量在内存中的存储是一个堆栈
数组取地址
这里会发现,输出a的地址和以地址格式输出a 和a[0]的地址其实是一回事,而且数组的每个元素也是挨着排列的
数组单元的地址
相邻数组的地址
指针——*
指针变量就是保存地址的变量
指针 *是一个单目运算符,用来访问指针的值所表示的那个地址的变量
int i=6;
int *p=&i;
看如上代码,表示
- 有一个变量,变量名叫 i,这个变量的值为6,这个变量的地址是0x20
- 另外有一个变量,变量名叫p,这个变量的值为0x20,这个变量的地址是0x36(假设)
是非常好理解的,如下图
int *p;
int* p;
看如上代码,两行表达的意思是一样的,都表示p是一个指针变量,指向的是一个int变量
- *p是一个int
- 而非p是int *类型的变量,不存在int *类型
注意:这里可能会混淆的地方是指针到底是个变量还是个运算,其实这只是看待同一个问题的两个角度,下面分别来阐述
- 指针是一个变量:即指针也是一个普通的变量,只不过放的不是我们平常的值,而是放的地址,即指针变量就是放着地址的变量,侧重的是p
- 指针是一种运算:是p是一个变量,里面放的是一个地址,对这个变量进行指针运算,即执行操作——找到这个变量处放的值,侧重的是*
注:作为参数的指针
- 在函数里面可以通过指针访问外面的变量,拥有了能够访问外面变量的能力
- 仍以上面的例子来说明:此时p=0x20,而*p=6
之前问题的解释——scanf("%d",&i);
重新来审视这样的一句
scanf("%d",&i);
scanf("%d",i);
同样输入6,但是第二句是错的
解释:
- 第一句是告诉scanf,把6给i,即把6放在0x20的位置上,所以6把原先i 中的0x55给覆盖掉了
- 第二句出现了错误是因为,变成了告诉scanf,把6放在0x55的地方上,而如果0x55那个地方恰好不能写,那就崩溃了,如果巧了恰好能写,运行的结果也肯定不对,因为此时把6放在了0x55上,而0x20的位置还是0x55
指针的使用场合
- 函数要返回多个值,某些值就只能通过指针返回
- 传入的参数实际上是需要保存带回的结果的变量
毕竟函数的返回值只能返回一个值,但是python可以返回多个值,所以才没有指针的吗?
例子:交换两个变量的值
以前不用指针的话,是无法用函数来实现交换变量的
void swap(int *a,int*b)
{
int t=*a;
*a=*b;
*b=t;
}
例子:得到最小值最大值
这个例子比较好的体现了参数的作用是为了带出来结果
int minmax(int *x,int len,int *a,int *b)
{
printf("x=%d",*x);
*a=*x;
*b=*x;
int i;
for(i=0;i<10;i++)
{
if(*a>*(x+i))
{
*a=*(x+i);
}
if(*b<*(x+i))
{
*b=*(x+i);
}
}
}
int main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
int min,max;
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
return 0;
}
例子:两个数做除法的函数
函数返回运算的状态,而结果通过指针返回
(其实还是需要返回多个结果)
在这里插入代码片
因此这种指针的应用场景是 程序可能会出错,在C只能这样做,但在后续的语言中采取了异常机制来解决这个问题
指针和数组
函数、指针、数组
如果函数的参数是普通变量,传入的是值
如果函数的参数是指针变量,传入的是也是值,只是这个值是所指向变量的地址
如果函数的参数是数组呢?传入的是什么?
函数参数表里的数组,实际上就是指针,传入的就是数组的首地址
void minmax(int a[],int len,int *min,int *max)
所以就解释的通了
- 这个参数表里为什么只需要写个
a[]
- 为什么方括号里写数字没用
- 在函数中用sizeof也不能得到元素个数结果
一切的原因都是因为,传入的是数组的首地址,他是个指针
那你要这么说的话,直接写成指针可以吗?
void minmax(int *a,int len,int *min,int *max)
这样子写当然可以,函数中下面运算a[1]
这种东西也是没问题的
- 是不是下面运算
a[1]
这种东西,其实就是这个地址往后的那个地址,而只是恰好数组也是这样排列的
四种函数原型 是等价的
则下面 四种函数原型 是等价的便很好理解的
int sum(int *ar,int n)
int sum(int *,int )
int sum(int ar[],int n)
int sum(int [],int )
数组是特殊的指针
注意1
- 数据变量本身表达地址
- 但是数组的单元表达的是变量,需要用&取地址
int a[10];
int *p=a//不需要用&取地址
即写一个数组表达的就是这个地址
且a==a[0]
注意2
[]
可以对数组做,但其实也可以对指针做
int *p=a;
比如p[0]
其实也是对的,毕竟p也是指向的那个地址,数组也是指向了一个地址,那p[0]
其实就是a[0]
,本质上来讲还是一回事
- 如果p指向是是个普通的变量,也是可以用
[]
的,要充分的理解
int *p=&i;
这里也是可以用p[0]
的,并且表示的就是i,但是p[1]
就不行了,毕竟你一个变量,从地址的角度来看,也能看成只有一个元素的数组,即只有p[0]
注意3
- *运算符可以对指针做,也可以对数组做
*a=1000;//其实就是a[0]=1000
毕竟表示的都是那个地址,所以也非常好理解
注意4
- 实际上,数组是const的指针
所以之前才说不可以做如下的代码来进行数组的赋值,而是应该利用for循环来对每个单元进行遍历
int b[];
b=a;//这样子不行
int *q=a;//这样子却可以
为什么第三句可以,第二句不行呢?
因为数组是const的指针(再次强调)
int b[]
的意思是int * const b
,const在这里来说,这个数组被创建出来了,是这个数组,就不能被修改成别的数组了,const的特性 即b[]是一个常量指针
指针和const
指针是const
和上面的数组是const的指针联系起来了
表示一旦得到了某个变量的地址,不能再指向其他变量
int *const q=&i;//q是const
*q=26;//这个可以
q++;//这个是错的
所指是const
表示不能通过这个指针去修改那个变量(并不能使那个变量变成const)
只是不能通过这个指针来修改那个变量了
const int *p=&i;//q是const
*p=26;//这个不行,因为*p是const,不能通过这个指针来修改那个变量
i=26;//这个肯定是没问题的
p=&j;//这个也是可以的,相当于让这个指针换了一个指向
这些是啥意思??
const int *p1=&i;
int const *p2=&i;
int * const p1=&i;
判断的标准是const在*的前面还是后面
- 如果const在*的前面,则是所指不能被修改
- 如果const在*的后面,则是指针不能被修改
因此前两句是一个意思,是所指不能被修改
第三句是指针不能被修改
const数组来保护数组值
const数组
刚刚已经介绍过,数组变量已经是const的指针了,这里的const表明数组的每个单元都是const,所以必须通过初始化进行赋值
保护数组值
前面的介绍已经发现,由于数组传入函数传的是地址,因此在函数内部可以修改这个数组中的值,但如果我们不能修改呢
可以设置参数为const
即
int sum (const int a[],int length)
const的总结
注意,这一章一共介绍了四种const,分别是
- 指针是const
- 所指是const
- 数组是const的指针
- const数组
其中3是1的其中一种
这一章的本质是在函数 数组 指针的综合
一些反复要琢磨透的东西
scanf
输入时前面要加&,这是为什么?如果不加的话会有什么后果?这个要反复理解透- 只能对变量取地址,因此肯定不能
&(i++)
- 变量的存储方式是堆栈,即先定义的变量存储在更深的地方
- 指针变量就是保存地址的变量,
int *p;
p是个指针变量,指向了个int
型的变量,即*p
是个int
,而非int*
的变量(解释:因为p存放的是个地址,只是个地址,那肯定不存在他是个占4个字节的int还是2个字节的short啊,所以只代表它指向的是个int
类型的变量) - *是个运算,*p是个表达式的值,这个值等于这个地址指向的那个值的值
- ** 数组和指针可以混着用**???不要混乱
其实这样的情况只存在一种情况,即是把数组的首地址赋给了指针,或者说指针指向了数组的首地址这里要深入理解
具体分为两种情况来讲,要理解,以后不一定只有这两种情况。
第一种,如下面程序中数组a相关的程序,展示的是数组a传入了minmax
函数,这里非常好理解是把数组的首地址传给了x,因此,如果用指针表示这个数组的第i个单元,其实是*(x+i)
,即a[0]
可以表示为*x
,a[1]
表示为*(x+1)
,a[2]
表示为*(x+2)
,但是如果你敢写出*x *(x+1) *(x+2)
的形式,其实不就潜意识里默认成指针加一下是将指针移动到后面的一个单元上,那其实就是以每个单元为单位来做操作了,所以出于简单,可以将*x *(x+1) *(x+2)
分别写成x[0] x[1] x[2]
,要充分理解,数组a的表示永远都是a[0] a[1] a[2]
,指针的表示永远都是*x *(x+1) *(x+2)
,只是出于简单,写成了x[0] x[1] x[2]
的形式,但*x *(x+1) *(x+2)
才是标准的用指针表示每个单元的格式
第二种,如下面程序中数组b相关的程序,展示的是int *q=b;仍然是指针q指向数组b,一定要注意这里当然指向的当然还是数组b的首地址
但是会发现在printf("%d",*(b+3));
这一行的时候,其实我把数组作为指针用了,而且使用后得出的结果也是正确的,这里需要进行解释:因为b代表的是这个数组的首地址,这个一定要牢记,因此,其实对于外部来讲,都是这个地址往后的第四个单元,b是指针还是数组已经不重要了,它只是地址
在printf("q[5] =%d",q[5]);
这一行的时候,我又把指针当成数组用了,这个就和第一种情况是一样的了,其实本来应该是*(q+5)
才对,要充分理解
#include<stdio.h>
int minmax(int *x,int len,int *a,int *b)
{
printf("x=%d",*x);
*a=*x;
*b=*x;
int i;
for(i=0;i<10;i++)
{
if(*a>*(x+i))
{
*a=*(x+i);
}
if(*b<*(x+i))
{
*b=*(x+i);
}
}
}
int main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
int min,max;
printf("min=%d,max=%d\n",min,max);
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("min=%d,max=%d\n",min,max);
//上面是数组抽象成地址的第一种情况(同时在函数中演示了数组当成指针用,其实是因为在minmax函数中这本来就是个指针x)
//下面是数组抽象成地址的第二种情况(同时在函数中演示了指针当成数组用)
int b[10]={1,2,3,4,5,6,7,8,9,10};
int *q=b;
printf("%d",*(b+3));//这当然输出的是数组b的第四个单元,即b[3]
printf("%d",q);//q的值用%d表示是6487488,即数组b首地址是 6487488
//当我加上 中括号下标以后,其实就代表了数组每个单元的值,其实数组b也是
//b代表数组的地址,b[n]代表第n个单元 ,这样其实还是统一了
//就是说当 *q=b这种样子时,因为 q[5]其实也相当于 *(q+5)
// 前面是 x[5]其实也相当于 *(x+5)
// 其实数组和指针完全是两个并行的轨道,只是当把数组的地址赋值给指针时,表示的含义重叠了 ,看似是可以混着用了
//或者说一旦遇到*(q+5)这种说法,肯定是讲的数组的这种东西了,或者再严谨一点讲,肯定是线性表的概念了
printf("q[5] =%d",q[5]); //这里当然不是*q[5]
return 0;
}
总结:其实写成数组还是指针没有本质的区别,只是一个问题的两种角度,如果你是从地址的角度去看,那其实就是应该写成*a,*(a+1),*(a+2)
的格式,即指针的格式,如果是从单元的值的角度去看的话,那自然应该写成a[0],a[1],a[2]
,即数组的格式
要透过现状看本质,即
数组能写成指针的格式,完全是因为数组本身表示的就是个首地址,那肯定以这个地址就可以用指针的格式啊
指针能写成数组的格式,完全是因为指针写成*(q+i)的模式其实已经默认变成一个个单元为单位了,那当然可以用数组的格式了啊
有下面的程序就更好理解了,我这个程序根本都没有提到说要定义一个指针,但是直接就能用,因为本质上是数组a代表的是一个首地址,这里就充分理解了
int main()
{
int a[]={4,5,6};
printf("a=%d\n",a);
printf("a[0]=%d\n",a[0]);
printf("*a=%d\n",*a);
printf("a[2]=%d\n",a[2]);
printf("*(a+2)=%d\n",*(a+2));
return 0;
}
运行结果
//同样是这个数组
a=6487568 //输出数组的首地址
a[0]=4 //数组第一个单元
*a=4 //数组第一个单元
a[2]=6 //数组第三个单元
*(a+2)=6 //数组第三个单元
int main()
{
// int number;
// scanf("%d",&number);
int a[]={4,5,6};
int *q=a;
printf("q=%d\n",q);
printf("q[0]=%d\n",q[0]);
printf("*q=%d\n",*q);
printf("q[2]=%d\n",q[2]);
printf("*(q+2)=%d\n",*(q+2));
return 0;
}
运行结果
q=6487552
q[0]=4
*q=4
q[2]=6
*(q+2)=6
- 数组是const的指针为了更好理解,写一个说明:
其实还是因为数组只代表首地址,数组创造出来了,这个地址就不能动了,不像普通的指针,可以进行修改指向的地址,理解6以后理解这一条应该很容易 *p++
相关的
需要注意运算优先级,和*(p+1)
不同,*p++
是先做*p
,然后再++
,往后挪一格,*(p+1)
中因为加号的优先级低于*
,因此得加括号,即*
的优先级比++
和+
都高- 0地址与
NULL
多进程的运行中,所有的应用运行起来都会分配一片虚拟的从0开始的一篇内存地址空间,0地址是不能碰的,但是可以拿0地址做特殊的事情
NULL
是预先定义的符号,表示0地址(想用0地址的时候就用NULL
表示就可以)
10.void *
相关的
这条放在动态内存分配中更好理解了,其实就是本来无论int * short *
,指针所指向的那一片地址单元都是有类型的,比如int *
我们会四个四个单元作为一个整体来看,short*
会两个两个单元作为整体来看,但是void*
想说的是,我指向了一片单元,但是我现在不说我以几个单元作为整体来看了,它没类型,就是一篇地址单元,然后你进行操作吧