前体知识:格式控制符%p可以输出十六进制格式的地址;%lu可以输出无符号十进制整数地址。
9.1.1 取地址运算 运算符&我们在第一周就见过这个符号“&”了,当时我们规定scanf("%d",&x);中变量之前一定要有这个符号
今天我们要开始学习关于它的知识,&,与加减乘除同类的运算符。
&:获得变量的地址,它的 *** 作对象必须是变量。
那地址是个什么值呢?我们用代码试一下
#include
int main()
{
int i;
printf("0x%x\n",&i);//0x开头,输出16进制数,因为c不会自动补上0x
return 0;
}
程序先给了一个warning,然后输出了0xbff12d70。
warning的提示是,如果想输出地址,不应该用%x而是%p,%p会自带0x开头输出十六进制的变量地址。
因此我们将上面打印一句改为
printf("%p",&i);
此时两种方式输出的值完全相同。然而这个地址很像一个整数,十六进制下的整数。那么试想一下我们先将地址的整数赋值给变量,再输出这个变量的值,得到的结果还一样吗?
#include
int main()
{
int i=0;
int p;
p=(int)&i;//注意这里必须要强制转换为int类型,因为取地址实际上是指针类型
printf("0x%x\n",p);//输出整数p
printf("%p\n",&i);//输出地址
return 0;
}
结果如下:可以看到结果是一样的 (32位,即x86下编译运行时相同,64位下正常的地址是八字节,int是四字节)
因此地址的大小是否与int相同取决于编译器、架构。
&不能取的地址&不能对没有地址的东西取地址
p=(int)&(p+i);
p=(int)&(++i);
p=(int)&(i++);
这三种都不能编译,必须是一个明确的变量
试试&能取的地址- 变量地址(前面已试过,可行)
- 相邻变量(连续定义)的地址(地址也相邻)
- sizeof(&变量)(前面已试过,可行)
- 数组的地址
- 数组单元的地址
- 相邻数组单元的地址
int i=0;//最好初始化这样能确定其地址
int p;
//相邻变量
printf("%p\n",&i);
printf("%p\n",&p);
结果如图:
十六进制的c=12,因此两变量地址相差4,即一个int的字节大小。我们此时是在32位架构下编译,因此说明了,相邻变量在内存中的地址也是相邻的。
接下来试试取数组的地址:
#include
int main()
{
int a[10];
printf("%p\n",&a);
printf("%p\n",a);
printf("%p\n",&a[0]);
printf("%p\n",a[0]);
printf("%p\n",&a[1]);
return 0;
}
对于第二行的输出值,我们直接使用了a,上次我们直接使用数组名还是对于数组的赋值
int a[10]=0,b[10];
b=a;
这样的错误代码进行说明,然而在上方输出框中可以看到,直接拿a输出地址同样成功,通过了编译并且和取地址之后再输出的值相同。
a[0]和a[1]的地址相差4,为一个int的值。
9.1.2 指针看了上面的取地址符的各种应用,你可能会想:我学这个有什么用?
如果能将变量地址取出,然后传递给一个函数,是否能通过这个变量的地址实现在函数中访问变量呢?
有没有感觉似曾相识:scanf("%d",&i);
我们将一个地址传给scanf,它可以从其中取得我们输入的整数并放到变量i中。那么它是如何做到的呢?它是如何保存变量的地址的呢?什么样的变量可以接收取地址符得到的地址呢?
指针一个指针类型的变量就是保存地址的变量。
int *p=&i; p是一个指针,指向一个int类型,并把i的地址交给p(也就是p的值=i的地址)
int i;
int* p=&i;//将i的地址交给p,或者说 p指向i,此时p的值=i的地址
int* p,q;//星号*靠近int
int *p,q;//星号*远离int
//二者的意思都一样,都表示p是一个指针指向int,*p是int类型,q也是int类型
通常我们用p来表示指针,p是英文point的缩写。
指针变量指针变量的值是内存的地址,与之相对的,普通变量的值就是实际的值
作为参数的指针(函数中使用指针)
void f(int *p);
//函数在被调用时可以得到某个变量的地址
int i=0;f(&i);
//在函数里可以通过这个指针访问外面的局部变量i
void f(int *p);
int main()
{
int i=6;
printf("&i=%p\n";&i);
f(&i);
return 0;
}
void f(int *p)
{
printf(" p=%p\n",p);
printf("*p=%d\n",*p);
}
此时我们就可以在函数中访问变量i,意味着我们可以读写i,读是完成了,那写呢?
引入一个新运算符:*。*是一个单目运算符,用来访问指针的值所表示的地址上的变量。
*p:访问对应地址上的变量
- *p可以做左值也可以做右值。
- 左值:放在复制号左边,实际上是表达式计算的结果,是一个特殊的值。
- 因此如果*p为左值时,代表了将右边的计算结果赋值给对应地址上的变量。如原变量为i=6,*p=&a;*p=5,则此时i的值更改为5.
printf中的*p作为一个整体可以看作一个整数
输出结果为:
之前我们说,函数的调用时发生的参数的转移,是一种值的传递,传值进函数后就与原来变量没关系了。然而当传递进去的是地址时,就可以在函数内部访问外部变量,得以修改其值。
指针运算符——& 与*前文提到:&是取得变量的地址,*是取得对应地址的变量。从字面上来看,二者互为对方的逆过程。事实上二者确实是互为反作用。
- *&y:对一个地址取对应的变量==y
- &*y:对一个指针取对应的地址==指针(因为存放变量的地址),也就是对指针取地址还是指针
为什么
int x;
scanf("%d",x);
没写&,但是编译没报错。
如果正好是32位架构,整数和地址所占字节一样大,编译器会认为这就是你传入的地址。因此scanf函数不能将你输入的值正确传入给变量,而是传到了别的地方。因此运行一定是会报错的。
9.1.3 指针的作用 指针应用场景1 :void swap(int *pa,int *pb)
{
int t=*pa;
*pa=*pb;
*pb=t;
}
此时只需要传入两个地址即可完成交换,原理是相同的,但传入指针可以改变原始值。而且正常函数只能返回一个值
也就是说,其作用是返回多个值,且某些值只能通过指针带回。传入的参数是需要进行编辑的变量。
指针应用场景2:在函数中与return分工返回值和状态
- 函数通过return返回运算状态,指针返回结果。
- 一般用return0或-1等不属于有效范围内的值表示出错(多在文件 *** 作中出现)
#include
int divine(int a,int b,int *result);
int main(void)
{
int a=5;
int b=2;
int c;
if(divine(a,b,&c))
{
printf("%d/%d=%d\n",a,b,c);
}
return 0;
}
int divine(int a,int b,int *result)
{
int ret=1;
if(b==0) ret=0;
else
{
*result=a/b;
}
return ret;
}
当被除数=0,即b=0时,函数会直接返回状态值ret为0,而函数又位于if条件处,此时不会输出任何值。当正常时就会将a/b=c输出。实现了分开返回值与状态。
这种方法的适用条件是运算中可能会出错,在C++/JAVA等语言中可以采用异常机制来解决这个问题。
指针最常见的错误- 定义指针变量,但没有指向任何变量就使用了指针
int i=1;
int *p=0;//给的不是地址
*p=5;
printf("%d",i);
这几个语句的意思是,在一个地址为0的内存中写入5这个值,而0这个地址不一定支持写入,因此程序大概率会出错,且具有不确定性。
9.1.4 指针与数组将变量传递到函数中,函数得到的是变量的值。将指针传递到函数中,得到的是地址。那么把数组传递进去呢?我们用sizeof测试一下
可以看到,在主函数中数组的大小就是每个数组元素占据的空间之和。而在函数中数组大小正好为一个地址/指针的大小。
warning信息提示说:函数里的数组sizeof返回的是int*的大小而不是int[]的大小
我们再查看下数组在主函数和函数的地址:
可以看到无论在主函数还是函数中,数组a的地址相同。也就是说传到函数里的a就是外面的数组。
我们再试试,如果在函数里直接更改数组的值,到主函数里会不会发生改变?
于是综合一下前面的测试,我们得出结论:传递到函数的数组实际上是个指针!这也就解释了,为什么在函数的参数表中数组要留个括号,为什么在方括号里写上数组大小也没用。
那它既然是个指针,直接在函数头写指针如何?
没问题!编译运行都ok!warning都没有
数组作为函数参数以下四种写法等价:
有没有一种可能,数组和指针之间有一种联系?
确实有:
数组变量是特殊的指针- 数组变量本身其实是一种地址
- 在取数组地址时不需要用到&,直接写数组名就可以。
- 但是在取数组单元的地址时需要&,因为数组单元表示变量
因此我们在第一节取地址符中a==&a[0]
- []运算符可以对数组做,也可以对指针做(1)
- * 运算符可以对指针做,也可以对数组做(2)
(1)
int min=1;
int *p=&min;
printf("*p=%d",*p);
printf("p[0]=%d",p[0]);
结果为:
其含义是:p的值为min的地址,此时p指向min所在的空间,p[0]意思是假设指向的是min[1],一个只有一项的数组。输出的就是所指的地址上面的第一个整数。
(2)
#include
int main()
{
int a[]={1,2};
printf("*a=%d\n",*a);
return 0;
}
虽然a是数组,但想输出*a依然可以得出其值。直接当指针用就好了。
- 数组变量是const的指针,所以不能被赋值
- 这也就解释了为什么不能将一个数组直接赋值给另一个数组
int a[]={1,2,3};
int b[]; == int *const b;
const加在这里意味着b是一个常量,而常量的定义是:一旦被创建后,其值就不能再改变。不能被改变,创建并初始化这个数组之后就不能再去代表别的数组了。
所以,数组是一个常量指针。
9.1.5 指针与constC语言const的用法详解,C语言常量定义详解 (biancheng.net)http://c.biancheng.net/view/2041.html可以作为参考资料
常量的定义是:一旦被创建后,其值就不能再改变。
const是一个修饰符,加在变量前面,这个变量就不能被修改。指针也是一个变量,然而指针指向的也是变量。这两者都可以加上const限制。
指针是const指针不能修改,也就是不能再得到地址
指针指向的变量是const不能通过指针修改
p指向别人,ok
i自增,赋值,ok
通过p更改i的值,不行
变量可以直接 *** 作,但不能通过指针间接 *** 作
这些变化的区别- int i;
- const int *p1 = &i;
- int const *p2 = &i;
- int *const p3 = &i;
const在*号前,表示指针指向的变量不能修改。即第一种和第二种含义一致。
const在*号后,表示指针不能被修改。
转换const与非const当要传递的参数的类型比地址还大的时候,这是常用的手段:既能用较少的字节数传递值给参数,又能避免函数修改外面的变量
const数组const int a[]={1,2,3,4,5,6};
数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
所以必须通过初始化进行赋值
保护数组值当数组传递进指针时,传递的是数组地址,因此函数中可以修改数组的值
为保护数组不受破坏,可以设置参数为const
int sum(const int a[],int length);
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)