取地址运算
sizeof是一个运算符可以给出某个字节或某个变量在内存中所占的字节数,写法是:sizeof(i)。
#include <stdio.h>
int main()
{
double a;
int b = 6;
printf("sizeof(double)=%ld\n",sizeof(double));
printf("sizeof(b)=%ld\n",sizeof(b));
return 0;
}
sizeof(double)=8
sizeof(b)=4
--------------------------------
Process exited after 0.01784 seconds with return value 0
上述代码可知double占用8字节内存,b占用4个字节的内存,1个字节是8个比特。
运算符&的作用是取得变量的地址,所以&的操作数必须是变量。 ::: tip C语言的变量是放在内存中的,每一个变量都有相应的占有一定的内存空间(可以用sizeof获得),占有内存空间就会有相应的地址。 :::
#include <stdio.h>
int main(void)
{
int i = 0;
printf("0x%x\n",&i);
printf("%p\n",&i);
printf("%lu\n",sizeof(int));
printf("%lu\n",sizeof(i));
printf("\n");
int p = &i;
printf("0x%x\n",p);
printf("%p\n",p);
return 0;
}
0x62fe18
000000000062FE18
4
4
0x62fe18
000000000062FE18
--------------------------------
Process exited after 0.01819 seconds with return value 0
::: tip %p 是以16进制的形式输出内存地址。不过%p的输出字符为8个前2个为00。 %x 也是以16进制的形式输出内存地址。%x只输出6个字符,前面加0x表示是16进制数。 ::: ::: warning 通过sizeof计算出的变量的地址大小和数据类型,和int是否完全相同,和编译器的架构有关。 :::
#include <stdio.h>
int main(void)
{
int i = 0;
int p;
printf("%p\n",&i);
printf("%p\n",&p);
printf("%d",sizeof(p));
return 0;
}
000000000062FE1C
000000000062FE18
4
--------------------------------
Process exited after 0.02182 seconds with return value 0
如上,16进制中的C表示12,和8相比相差4,即p的大小。 通过观察上述代码和结果,可以发现1C的位置是变量i,18的地址是变量p。先定义的变量i在比后定义的变量p更高的地方。原因是变量i和变量p都是本地变量,分配在内存堆栈中,在堆栈中分配变量是自顶向下分配的,即先定义的变量地址更高,后定义的变量地址更低。
有关数组的地址
#include <stdio.h>
int main(void)
{
int a[10]; //定义一个10个int组成的数组a
printf("%p\n",&a);
printf("%p\n",a); //把数组的名字试图交给printf取地址
printf("%p\n",&a[0]); //取出数组a中第一个元素(a[0])的地址
printf("%p\n",&a[1]);
return 0;
}
000000000062FDF0
000000000062FDF0
000000000062FDF0
000000000062FDF4
--------------------------------
Process exited after 0.01508 seconds with return value 0
上述代码结果表明,&a == a == &a[0] < a[1],继续计算下去可得数组a中的元素位置相隔都是4。
指针
scanf
如果能够取得变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量? 指针类型的变量是保存地址的变量。通常使用p来表示指针。 如果有一个变量int i,就可以有一个int p = &i;。星号*表示p是一个指针指向int,变量i的地址传给p。 ::: tip 当p中的值为i的地址时,可以认为p指向i。 ::: ::: warning int p,q;和int p,q都表示p是一个指针指向int,q是一个普通int类型变量。 *p表示p是指针,不能认为是int。如果认为p和q都是指针,需要写成int p,q; ::: 变量的值是内存的地址。 普通变量的值是实际值,指针变量的值是具有实际值的变量的地址。
作为参数的指针
当把一个指针作为参数的时候可以用以下写法
#include <stdio.h>
void f(int *p); //指针p作为f函数的参数
int main(void)
{
int i = 0;
printf("&i=%p\n",&i);
f(&i); //调用f函数时要把i的地址交给函数f,不能把变量i本身或i的值交给f
g(i); //把i的值传递给函数g
return 0;
}
void f(int *p)
{
printf("p=%p\n",p);
}
void g(int k)
{
printf("k=%d\n",k);
}
&i=000000000062FE1C
p=000000000062FE1C
k=0
--------------------------------
Process exited after 0.01575 seconds with return value 0
上述程序中,函数f取得的是变量i的地址,通过f函数中的指针p,可以得到main函数中的变量i的地址,即可以访问到变量i,而函数g中的变量k的值和main中的变量i相同,不能说明k和i有关系。
访问地址变量
访问意味着可读可写,如果有了变量地址,想要访问地址上的变量,需要用到运算符。 这里的*星号是一个单目运算符*(只有一个算子),用来访问指针的值所表示的地址上的变量。 星号加变量得到的指针可以做左值也可以做右值,即可以放在赋值号的右边读取,也可以放在赋值号的左边写入,如int k = p和p = k+1。
通过指针读写变量:
#include <stdio.h>
void f(int *p); //指针p作为f函数的参数
int main(void)
{
int i = 6;
printf("&i=%p\n",&i);
f(&i); //调用f函数时要把i的地址交给函数f,不能把变量i本身或i的值交给f
g(i); //把i的值传递给函数g
return 0;
}
void f(int *p)
{
printf("p=%p\n",p);
printf("*p=%d\n",*p); //通过指针访问到了p所指的int i的值
*p = 10; //修改指针*p的值
}
void g(int k)
{
printf("k=%d\n",k); //调用函数g查看变量i的值
}
&i=000000000062FE1C
p=000000000062FE1C
*p=6
k=10
--------------------------------
Process exited after 0.01728 seconds with return value 0
上述代码中第16行表示可以通过指针访问到变量,经过第17行修改指针值,第9行调用函数g时,访问main中变量i的值,看到变量i的值和指针的值一起发生变化,即完成了对变量值的修改。 ::: tip 上述程序中,变量i的地址通过&i传递给p,p的值是i的地址,p就代表了i,就可以通过修改p的指针来修改i的值,即针对*p的改动,实际是对变量i的值的改动。 :::
指针与数组
函数参数表中的数组
把数组作为值传递给一个函数,在函数的参数表中有一个变量接收这个数组,函数会接收到什么?实验程序如下:
#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,12,13,14,16,17,21,23,55,};
int min,max;
printf("main sizeof(a)=%lu\n",sizeof(a)); //main中数组a的大小
printf("main a=%p\n",a); //main中查看数组a的地址
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max); //函数调用
printf("a[0]=%d\n",a[0]); //查看minmax函数中修改过的a[0]的值
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)); //minmax中数组a的大小
printf("minmax a=%p\n",a); //minmax中查看数组a的地址
a[0]=1000; //minmax中修改a[0]的值
*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)=68
main a=000000000062FDD0
minmax sizeof(a)=8
minmax a=000000000062FDD0
a[0]=1000
min=2,max=1000
--------------------------------
Process exited after 0.01662 seconds with return value 0
上述程序中的第9行和第21行分别查看在函数调用前后数组a的大小,在输出结果中的第1行和第3行,函数调用前后的大小不同,函数调用后不能使用sizeof得到正确的元素个数。 第10行和第22行代码查看数组a的地址,输出结果完全一致,说明在minmax函数中的数组a就是main中的数组a。 第23行代码修改数组a中的某一个值,通过第12行代码,查看函数调用后数组a中值,输出结果第5行a[0]的值被修改。 由上述可以判定,int a[]就是指针,可以用*a代替a[],指针可以有第23行a[0]、第26行a[i]等数组的用法。 ::: tip 函数参数表中的数组实际上是指针,sizeof(a) == sizeof(int )。 对于这样的指针可以使用数组运算符[]进行运算。 ::: *上图并不是指类型等价,只是在出现在参数表中作为参数原型是等价的。**
数组变量是特殊的指针
::: warning 数组变量本身表达地址,取数组地址时可以不加&符号直接用数组变量的名字就可以得到数组的地址:int a[10];int p=a;无需使用&取地址。但是数组的每一个单元表达的是单个变量,需要用&取地址。a == &a[0]; ::: []运算符可以对数组做,也可以对指针做。如果有一个数组p[0],就相当于p。p[0] <==>a[0],如下:
#include <stdio.h>
int main(void)
{
int min;
int *p = &min;
printf("*p=%d\n",*p);
printf("p[0]=%d\n",p[0]);
return 0;
}
*p=32765
p[0]=32765
第8行代码中的p[0]是指如果p所指的位置是一个数组,p[0]是数组的第一个单元。
*运算符可以对指针做,也可以对数组做,如下:
#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,12,13,14,16,17,21,23,55,};
int min,max;
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("*a=%d\n",*a);
return 0;
}
void minmax(int a[],int len,int *min,int *max)
{
int i;
a[0]=1000;
*min = *max = a[0];
for( i=1 ; i<len ; i++ ){
if( a[i] < *min ){
*min = a[i];
}
if( a[i] > *max){
*max = a[i];
}
}
}
*a=1000
--------------------------------
Process exited after 0.01637 seconds with return value 0
上述代码第10行对数组a输出*a,可以得到a[0]的值。
数组变量是const的指针,数组变量不能做互相赋值,即int b[] = a是错误的写法,int b可以被看做是int * const b,const的含义是指b是常数,不能改变,即数组是一个常量指针。