9.1.4 指针与数组
小实验:
将变量传递到函数中,函数得到的是变量的值。将指针传递到函数中,得到的是地址。那么把数组传递进去呢?
结论:
在主函数中数组的sizeof就是每个数组元素占据的空间之和。而在函数中数组大小正好为一个地址/指针的大小。//warning信息提示说:函数里的数组sizeof返回的是int*的大小而不是int[]的大小
无论在主函数还是函数中,数组a的地址相同。也就是说传到函数里的a就是外面的数组。【是同一个东西】
在函数minmax中改变数组a[0]的大小,结果是主函数的a[0]的值也改变了
传递到函数的数组实际上是个指针!这也就解释了,为什么在函数的参数表中数组要留个括号,为什么在方括号里写上数组大小也没用。
那它既然是个指针,能不能在函数头写指针如何?//没问题!编译运行都ok!warning都没有
自定义函数 数组作为参数 怎么写?
以下四种写法等价:
数组变量是特殊的指针
//数组就是一个地址 指针指向数组第一个[0]的地址
数组变量本身其实是一种地址
在取数组地址时不需要用到&,直接写数组名就可以。
int a[10];
int *p=a; //数组就是一个地址 指针指向数组第一个[0]的地址
但是在取数组单元的地址时需要&,因为数组单元表示变量
ps:因此我们在第一节取地址符中 a等同于&a[0]
[ ]运算符可以对数组做,也可以对指针做
int min=1;
int *p=&min;//p的值为min的地址,此时p指向min所在的空间;
printf("*p=%d",*p);//p=1 //*p的意思是min地址上面的值 也就是1
printf("p[0]=%d",p[0]);//p[0]=1 输出的就是所指的地址上面的第一个整数。
//p[0]意思是p指向的min是一个数组min[1],(min[1]有效下标只有min[0])
//假设指向的是min[1],一个只有一项的数组。输出的就是所指的地址上面的第一个整数。
* 运算符可以对指针做,也可以对数组做
#include <stdio.h>
int main()
{
int a[]={1,2};
printf("*a=%d\n",*a);//*a=1
//虽然a是数组,但想输出*a依然可以得出其值。直接当指针用就好了。
return 0;
}
数组变量是const(常量)的指针,所以不能被赋值
这也就解释了为什么不能将一个数组直接赋值给另一个数组
int a[]={1,2,3};
int b[]=a;//这是不行的 int b[]看作int *const b; 常数不能被改变,不能代表别的数组
int *q=a;//这个是可以的 指针q指向a的首地址a[0]
数组是一个常量指针。
9.1.5 指针与const
//本视频只适用于C99
const是一个修饰符,加在变量前面,这个变量就不能被修改。指针也是一个变量,然而指针指向的也是变量。这两者都可以加上const限制。
指针是const
指针不能修改,也就是不能再得到地址,绑定了!!
//*q=26 可以,因为*q指向的i不是const;但是如果q作运算(试图改变地址),是不可以的
指针指向的变量是const
不能通过指针p修改i的值(通过p更改i的值,不行)
p指向别人,ok //p=&j;可以指向别的地址
i自增,赋值,ok//i不是const可以改变
变量可以直接操作,但不能通过指针间接操作//在指针内部不能去动指针所指向的值
这些变化的区别
判断哪个被const了,就看const在*前面还是后面
const在*号前,表示指针指向的变量不能修改。即第一种和第二种含义一致。
const在*号后,表示指针不能被修改。
int i;
const int *p1 = &i;//指针指向的变量不能修改
int const *p2 = &i;//指针指向的变量不能修改
int *const p3 = &i;//指针不能被修改
转换const与非const
什么时候用上呢?
当要传递的参数的类型比地址还大的时候,这是常用的手段:既能用较少的字节数传递值给参数,又能避免函数修改外面的变量
const数组
const int a[]={1,2,3,4,5,6};//数组其实是常量指针,在前面再加const?
数组变量已经是const的指针了(a不能指向别的数组/不能赋值其他)
这里的const表明数组的每个单元都是const int
所以必须通过初始化进行赋值
保护数组值
当数组传递进指针时,传递的是数组地址,因此函数中可以修改数组的值
为保护数组不受破坏,可以设置参数为const
int sum(const int a[],int length);
9.2.1 指针运算
指针加一?(数组)
char ac[]={0,1,2,3,4,5,6,7,8,9,};//之前一直是int char也是整数类型
char *p = ac;
printf("p =%p\n",p);//p是指针 指向地址
printf("p+1=%p\n",p+1);
两个地址相差1
把数组从char换到int类型,两个地址相差4
为何如此?很简单,char变量占据一字节而int类型占据4字节。指针加一,增加的是sizeof(char/int/double...)
给指针加一表示要让指针指向下一个变量。
在讲到数组和指针的关系时,我们提到可以将指针当作数组操作,也可以将数组当作指针操作。
那么我们试试用*操作符。
*p等价于ac[0]
那么*(p+1)等价于ac[1]
因此*(p+n) 等价于 ac[n]
tips:如果指针不是指向一片连续分配的空间,比如数组,那么这种运算就没有意义了
指针运算
这些算术运算可以对指针做
指针加减一个整数:+、-、+=、-= (往前挪和往后挪的区别)
指针递增或递减:++、--
两个指针相减:
此时得到的结果是不是这两个地址的差
而是(地址差除以sizeof())//在这两个位置之间,有几个这样类型的东西在?
注意要以%d输出结果
int ac[]={0,1,2,3,4,5,6,7,8,9,};
int *p = &ac[0];
int *p = ac;
//3句和4句 是一回事!!!!
int ac[]={0,1,2,3,4,5,6,7,8,9,};
int *p = &ac[0];
int *p1= &ac[5];
printf("p=%p\n",p);
printf("p+1=%p\n",p+1);
printf("p1-p=%d\n",p1-p);
printf("p1=%d\n",p1);
printf("*p1=%d\n",*p1);
//注意:对于指针来说,直接打印%p,p,输出的一定是地址;
//%p,p1-p0 输出的是地址差 在这两个位置之间,有几个这样类型的东西在?
//要输出值 %d,*p
*p++
++的优先级要比*高
//因此先执行p++(取值后自增)然后取地址,但是由于++在调用后才执行
所以其作用是,取出p的地址,再将p移动到下一位
适用于数组类的连续空间操作(如遍历)
在一些cpu上可以被直接翻译为一条汇编语言
int ac[]={1,2,3,4,5,6,};
int *p=&ac[0];
for(int i=0;i<sizeof(ac[])/sizeof(ac[0]);i++)
{
printf("%d\n",ac[i]);
}
现在的遍历操作:
int ac[]={1,2,3,4,5,6,-1};
int *p=&ac[0];
//写法1
for(p=ac;*p!=-1;p++)
{
printf("%d\n",*p);
}
//写法2
for(p=ac;*p!=-1;)
{
printf("%d\n",*p++);
}
//写法3
while(*p!=-1)
{
printf("%d\n",*p++);
}
指针比较
<、<= 、==、 >、 >=、 != 都可以对指针做
实际上是比较它们在内存中的地址。(指向a[0]的指针要比指向a[5]的指针小)
数组中单元地址是线性递增的
0地址-null
在计算机运行时会给所有进程分配一块虚拟内存,其特征是地址从0开始,但是0地址是不能随便碰的!0地址不能被写入,有些甚至不能读取。因此你的指针不应该具有0值.
然而0地址可以用来表示一些特殊的东西:
返回0值代表返回的指针是无效的
定义指针变量时先让指针指向0,这意味着指针没有被真正初始化
C语言定义了一个符号NULL来表示0地址(一些编译器不想让你用0来表示0地址)
指针的类型
无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
但指向不同类型的指针不能直接相互赋值,这是为了避免用错指针
指针强制类型转换
void*(是一个指针) 表示指针不确定指向什么东西,但是指向一块内存空间
强制类型转换 //不要轻易尝试
int *p = &i;
void *q = (void*)p;
//此处将p强制转换为void型指针。这并没有改变p指向的变量的类型,而是通过q去看指向的变量时,不再将i看作int类型而是void类型。