运算符&
- scanf("%d", &i);里的&
- 获得变量的地址,它的操作数必须是变量
- 地址的大小是否与int相同取决于编译器
&不能取的地址
- &(a+b)?
- &(a++)?
- &(++a)?
scanf
- 如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量?
- scanf()的原型应该是怎样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?
指针
- 就是保存地址的变量
int i;
int* p = &i; (point)
int* p,q;
int *p,q;
表明 *p是一个int类型,并不包括q
作为参数的指针
- void f(int *p);
- 在被调用的时候得到了某个变量的地址:
- int i=0; f(&i);
- 在函数里面可以通过这个指针访问外面的这个i
#include <stdio.h>
void f(int *p);
int main(void)
{
int i = 6;
printf("&i=%p\n", &i);
f(&i);
return 0;
}
void f(int *p)
{
printf("p=%p\n", p);
}
我们可以这样来理解,根据运行结果来说,我们看到在main里面 i 的地址是62,我们把这个地址取出来传给f函数,到了f函数里呢,这个地址也不会变,仍然是62。所以就可以说现在的p是一个指针,它指向了i这个变量。
#include <stdio.h>
void f(int *p);
void g(int k);
int main(void)
{
int i = 6;
printf("&i=%p\n", &i);
f(&i);
g(i);
return 0;
}
void f(int *p)
{
printf(" p=%p\n", p);
}
void g(int k)
{
printf("k=%d\n", k);
}
现在我们在这个程序里加入一个g函数,这个函数以整数作为一个输入,我们把 i 传给这个函数,在这个过程当中,我们在g函数里面得到的只是 i 的值6,与外面的 i 之间没有任何的关系。
而现在我们通过指针变量p得到了 i 的地址,这使得f函数变得拥有能够访问外面的 i 的能力了。
那么新一轮的问题又来了,怎么访问它的呢?
访问,意味着读或者写,读是得到了那个变量的值,可如果我们想改那个值呢?请继续往下学习:
访问那个地址上的变量
- *是一个单目运算符(只有一个算子),用来访问指针的值所表示的地址上的变量
- 可以做右值也可以做左值
- int k = *p; 读值
- *p = k+1; 写值
#include <stdio.h>
void f(int *p);
void g(int k);
int main(void)
{
int i = 6;
printf("&i=%p\n", &i);
f(&i);
g(i);
return 0;
}
void f(int *p)
{
printf(" p=%p\n", p);
printf("*p=%d\n", *p);
}
void g(int k)
{
printf("k=%d\n", k);
}
我们试着输出了p,发现p=6,意味着通过
p这个指针,我们访问到了int所指的那个 i 里面的值。
下面我们来试着让*p=26
void f(int *p)
{
printf(" p=%p\n", p);
printf("*p=%d\n", *p);
*p = 26;
}
输出结果告诉我们k的值变成了26,意味着经历了f函数的调用后,i 的值被改了。
在学函数的时候,老师就强调过,C语言的函数调用的时候发生的参数的转移是一种值的传递,我们把值传进了函数,所以在函数里面,函数的参数和调用它的地方没有任何的联系。
而现在,情况有点不一样了,我们仍然坚持说在这个时候发生的,是值的传递。我们修改的p其实就代表了外面的那个 i,当我们在做p=26的时候,我们实际做的事情是对 i 去做的。这,就是指针。
左值之所以叫左值
- 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果
- a[0] = 2;
- *p = 3;
- 是特殊的值,所以叫左值
指针的运算符&*
- 互相反作用
- *&yptr -> * (&yptr) -> * (yptr的地址) ->得到那个地址上的变量 -> yptr
- &*yptr -> &(*yptr) -> &(y) -> 得到y的地址,也就是yptr -> yptr
传入地址
- 为什么
- int i; scanf("%d", i);
- 编译没有报错
因为你忘了&符号,然后你正好又是个整数,然后正好你是32位的架构的话,整数和你的地址是一样大的,你把一个整数传进去和你把一个地址传进去,对于scanf来说是看不出区别的,它会以为你传进去的 i 是 i 的地址。所以编译不见得会报错,但运行时一定会出错。
指针应用场景一
- 交换两个变量的值
#include <stdio.h>
void swap(int *pa, int *pb)
{
int t = *pa;
*pa = *pb;
*pb = t;
}
int main(void)
{
int a = 5;
int b = 6;
swap(&a,&b);
printf("a = %d, b = %d\n",a,b);
return 0;
}
运行结果:a = 6, b = 5
指针应用场景二
- 函数返回多个值,某些值就只能通过指针返回
- 传入的参数实际上是需要保存带回的结果的变量
查找当前数组中的最大值与最小值
#include <stdio.h>
void minmax(int a[], int len, int *min, int *max)
{
int i;
*min = *max = a[0];
for (i=1;i<len;i++) {
if (a[i] < *min) {
*min = a[i];
}
if (a[i] > *max) {
*max = a[i];
}
}
}
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55};
int min,max;
minmax(a,sizeof(a)/sizeof(a[0]),&min, &max);
printf("min = %d, max = %d\n", min, max);
return 0;
}
运行结果:min = 1, max = 55
指针运用场景三
- 函数返回运算的状态,结果通过指针返回
- 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错:
- -1或0(在文件操作中会看到大量的例子)
- 但是当任何数值都是有效的可能结果时(比如0和-1都在你的返回有效范围内),就得分开返回了
- 而分开返回我们往往是这样:状态——用函数返回(return);实际的值——通过指针参数返回
- 这样做的好处是:我容易把函数返回的结果放到if语句里
举例:两个整数相除
#include <stdio.h>
/*
@return 如果除法成功,返回1;否则返回0
*/
int divide(int a, int b, int *result);
int main(void) {
int a = 5;
int b = 2;
int c;
if (divide(a,b,&c)) {
printf("%d/%d=%d\n",a, b, c);
}
return 0;
}
int divide(int a, int b, int *result) {
int ret = 1;
if (b == 0) ret = 0;
else {
*result = a/b;
}
return ret;
}
在除数为0的情况下,返回值为0。
- 后续的语言(C++,Java)采用了异常机制来解决这个问题sss
指针最常见的错误
- 定义了指针变量,还没有指向任何变量,就开始使用指针。