- 1.取地址运算
scanf("%d",&i);
里的&:
获得变量的地址,它的操作数必须是变量
查看地址:printf("%x",&i);
地址的大小是否取决于编译器(32位与64位不同)
多个变量的地址
#include<stdio.h>
int main(){
int i = 1;
int p;
printf("i: 0x%x\n",&i);//增加0x是表示16进制
printf("p: 0x%x\n",&p);
return 0;
}
输出结果:
i: 0x62fe4c
p: 0x62fe48
注:不同电脑运行后的地址结果是不尽相同的,但是地址分配的位置差都是相同(紧挨着),具体见推论
推论:
4c和48相隔4个字节,每一个int变量在32位编译器中占据4个字节,因此表示在内存里头这两个是紧挨在一起的。
在定义时,我们先定义的i,后定义的p,在地址上4c>48,内存上的位置分配即i在p上方,原因是c语言内存模型均保存在堆栈(stack)中,堆栈实行自顶向下分配,因此如果再在p后面定义一个q,那么q的地址可以推断出是0x62fe44
&结果的sizeof
#include<stdio.h>
int main(){
int i = 1;
int p;
printf("%lu\n",sizeof(int));
printf("%lu\n",sizeof(&i));
return 0;
}
64位下,输出的是4(字节)和8(字节),32位下,输出的均为4(字节)
数组的地址
#include<stdio.h>
int main(){
int a[10];
printf("%x\n",&a);\\输出数组a的地址
printf("%x\n",a);\\直接输出数组a
printf("%x\n",&a[0]);
printf("%x\n",&a[1]);
return 0;
}
输出结果:
62fe20
62fe20
62fe20
62fe24
可见,
1.取数组的地址和直接取数组本身所求的地址的结果是相同的。
2.数组本身的地址和数组初始位a[0]的地址相同。
3.a[0]至a[1]的地址大小开始递增,相邻数组单位间差距均为4,以此类推。原因同“多个变量的地址”。
- 2.指针
背景:
如果能通过将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量?
指针——就是保存地址的变量
int i;
int* p = &i; \\将i的地址交给p
int* p,q;
int *p,q;
“*”表示的是p是一个指针,表示指向的是一个int类型的变量。3、4行均表示p是一个指针,q只是一个普通的int类型变量,如果想要p,q都是指针,那应该定义为
int *p,*q;
指针变量:
变量的值是内存的地址
普通变量的值是实际的值
指针变量的值是具有实际值的变量的地址
作为参数的指针:
void f(int *p);
在被调用的时候得到了某个变量的地址:
int i = 0;
f(&i);
在f函数里面可以通过这个指针p访问外面的i
示例代码:
#include<stdio.h>
void f(int *p);
int main(){
int i = 6;
printf("&i = %x\n",&i);
f(&i);
//printf("i = ",i);
return 0;
}
void f(int *p){
printf(" p = %x\n",p);
printf("*p = %d",*p);
//*p = 7;
}
}
输出结果:
&i = 62fe4c
p = 62fe4c
*p = 6
对于int *p = i;这件事,p代表的是变量i本身的值,p代表的是变量的地址。
若在f中,最后加上一句p = 7;那么变量i的值也会随之改变,代码见上方注释
指针的运算符&,*
互相反作用:
*&yptr -> *(&yptr) -> *(yptr的地址) ->得到这个地址上的变量 -> yptr
&*yptr -> &(*yptr) -> &(y) ->得到y的地址,也就是yptr ->yptr
对指针求地址→原来的指针
对地址求指针→原来的地址
- 3.指针的应用
在函数中可以直接交换两个变量的值,减少重复动作
void swap(int *a,int *b){
int t = *a;
*a = *b;
*b = t;
}
应用场景:
函数返回多个值,某些值就必须用指针返回
传入的参数实质上是需要保存带回的结果的变量
例题:同时求最大值最小值
#include<stdio.h>
void minmax(int a[],int len,int *max,int *min);
int main(void){
int a[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,34,22};
int min,max;
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("min = %d,max = %d\n",min,max);
return 0;
}
void minmax(int a[],int len,int *min,int *max){//这里的min和max作为形参和上面的min,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];
}
}
}
应用场景2:
函数返回运算的状态,结果通过指针返回
常用的套路是让函数返回特殊的不属于有效范围内的值表示出错:-1或0
但是当任何数值都是有效的可能结果时,就得分开返回
例:两个整数做除法
#include<stdio.h>
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)//除数是0,返回0,除数不是0,指针保存值,返回1,进入printf
ret = 0;
else{
*result = a/b;
}
return ret;
}
但如果b=0时,就不会输出任何结果
后续语言(c++,java)采用了异常机制解决这个问题
指针最常见的错误
定义了指针变量,还没有指向任何变量,就开始使用指针
- 4.指针与数组
还是用上面的minmax代码,增加了printf查看main()、minmax()中数组的size以及地址
#include<stdio.h>
void minmax(int a[],int len,int *max,int *min);
int main(void){
int a[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,34,22};
int min,max;
printf("main sizeof(a) = %lu\n",sizeof(a));
printf("main a = %p\n",a);
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("min = %d,max = %d\n",min,max);
return 0;
}
void minmax(int a[],int len,int *min,int *max){
int i;
printf("minmax sizeof(a) = %lu\n",sizeof(a));
printf("minmax a = %p\n",a);
*min = *max = a[0];
for( i = 1 ; i<len ; i++){
if(a[i] < *min){
*min = a[i];
}
if(a[i] > *max){
*max = a[i];
}
}
}
输出结果为:
main sizeof(a) = 80
main a = 000000000062FDF0
minmax sizeof(a) = 8
minmax a = 000000000062FDF0
min = 1,max = 34
可见,main的sizeof和minmax中的sizeof不同,minmax的sizeof是8,不禁令人联想到在64位中(我电脑编译器是64位)的int变量大小就是8
然后main和minmax中的a的地址是完 全 一 致,这就说明minmax()函数中的int a[]其实就是表示是一个指针
如果在minmax()中添加
a[0] = 1000;
main函数中添加
printf("a[0] = %d\n",a[0]);
那么输出的结果会是1000
更能证明这是一个指针
因此,如果把
void minmax(int a[],int len,int *min,int *max)
修改成
void minmax(int *a,int len,int *min,int *max)
编译也是通过的
因此之前在函数中的sizeof=8也就是sizeof(int*)
PS:在函数中的操作可以用数组的运算符,依然保持a[]运算是可行的
总结:以下四种函数原型是等价的:
int sum(int *ar,int n);
int sum(int *,int);
int sum(int ar[],int n);
int sum(int [],int);
因此,在函数中,数组变量是特殊的指针
数组变量本身表达地址,所以
*int a[10]; int *p = a; //无需用&取地址
但是数组的单元表达的是变量,需要用&取地址
调用函数中,a == &a[0]
[]运算符可以对数组做,也可以对指针做:
函数使用中,p[0] <= => a[0](p是指针)
上述代码中,在main函数中添加
int *p = &min;
printf("*p = %d\n",*p);
printf("p[0] = %d\n",p[0]);
输出结果会是
*p = 2
p[0] = 2
此处p[0]代表等于2的含义是:
在内存中有一min,min分配的地址里面存有一个数2
还有一个指针p,p指向的是min的地址
*p就是指针指向的变量的值
p[0]是指将p所指的地方当作一个数组,取其数组第一项,可以将min在内存中理解为
int min[1]
(当然定义是不能这么用)对于一个int min[1]来说,有效的下标就是p[0],也就是上述提到的
[]运算符可以对数组做,也可以对指针做: 函数使用中,p[0] <= => a[0](p是指针)
运算符可以对指针做,也可以对数组做:
同样的,在main函数中写
printf("*a = %d\n",*a);
输出结果会是
*a = 1000;
就是数组中的a[0],就可以解释为什么当初我们求&a和&a[0]的地址是相同的原因了
数组变量是const的指针,所以不能被赋值
int b[] = a;//错误
int *q = a;//正确
实质上,对于int b[],其本身就是—>int * const b;这个b是常数,不能被改变,所以被创建了之后,它不能代表任何其它数组。(const相当于是define)
结论:一个数组是常量指针,所以不能被赋值,两个数组之间不能直接赋值。
- 5.指针与const
1.指针是const
表示一旦得到了某个变量的地址,不能再指向其它变量
int * const q = &i;//q是const
*q = 26;//ojbk
q++;//ERROR
q的值(i的地址)不能被改变
改变*q的值是可以的,因为地址不变
2.所指是const
表示不能通过这个指针取修改那个变量(并不能使得那个变量成为const)
const int *p = &i;
*p = 26;//ERROR(*p)是const
i = 26;//OJBK
p = &j;//OJBK
不能通过p去修改i,此时固定的是p的值;
但是i可以改变,改变后*p也随之改变;
p也可以成为另一个变量的指针,因为p并没有被const所束缚
思考题:这些是什么意思
int i;
const int* p1 = &i;
int const* p2 = &i;
int *const p3 = &i;
普通定义整型变量i
定义常量指针*p1,p1的值不变
定义常量指针p2,*p2的值不变
定义常量指针p3,地址不变
(判断哪个被const了的标志是const在星号的前面还是后面)
转换
总是可以把一个非const的值转换成const的
void f(const int* x);
int a = 15;
f(&a);//ojbk
const int b = a;
f(&b);//ok
b = a + 1;//Error
void f()的意思就是表示:给函数一个指针,保证不会动指针所指的值
☆应用:
当要传递的参数的类型比地址大的时候(当以后用struct(结构体)的时候),这是常用的手段:既能用比较少的字节数传递给参数,又能避免函数对外面的变量的修改。
const数组
const int a[]={1,2,3,4,5,6};
数组变量已经是const的指针了,这里的const标明数组的每个单元都是const int
所以必须通过初始化进行赋值,否则无法赋值
保护数组的值
因为把数组穿入函数时传递的是地址,所以那个函数内部可以修改数组的值
为了保护数组不被函数破坏,可以设置参数为const
int sum(const int a[],int length);
int sum(const int *a,int length);
2018年9月26日22:09:25 明天继续更新 背单词去……