13、地址和指针
计算机内存是以字节为单位的存储空间。内存的每一个字节都有一个唯一的编号,这个编号就称为地址。凡存放在内存中的程序和数据都有一个地址。
当C程序中定义一个变量时,系统就分配一个带有唯一地址的存储单元来存储这个变量。例如,若有下面的变量定义:
char a='A';
short b=66;
long c=67;
系统为a、b和c分配1个、2个和4个
字节的存储单元,此时变量存储单元
的第一个字节的地址是该变量的地址。
-------------------------------------
地址 存储单元
FFD2 'A'
FFD3 66
FFD4
FFD5 67
FFD6
FFD7
FFD8
--------------------------------------
14、变量的存储
程序对变量的读取操作(即变量的引用),实际上是对变量所在存储空间进行写入或取出数据。
以前我们引用变量时是通过变量名来直接引用变量,例如赋值运算b=66,系统自动将变量名转换成变量的存储位置(即地址),然后再将数据66放入变量b的存储空间中。这种引用变量的方式是通过变量名,由系统自动完成变量名与其存储地址之间的转换,称为变量的“直接引用”方式。
----------------------------------------------------------------------------------------------------
#include <stdio.h>
int main()
{
int a,b;
int *p; /* 定义指针变量p */
p=&b; /* 将变量b的地址放在变量p中 */
a=3; /* 直接引用变量a */
*p=5; /* 间接引用变量b */
printf("a=%d, b=%d\n",a,b);
return 0;
}
-----------------------------------------------------------------------------------------------------
地址 变量名 地址
1000 a p b
<-----5
2000 1002
----------------------------------------------------------------------------------------------------
15、指针变量
(1)指针变量的定义
指针变量必须先定义后使用
指针变量定义的一般形式为:
类型名 * 指针变量名;
例如: int *p; float *q; char *t;
在定义指针变量时需要注意以下几点:
变量名前面的“*”是一个说明符,用来说明该变量是指针变量,这个“*”是不能省略的,但是它不是变量名的一部分。
类型名表示指针变量所指向的变量的类型,而且只能指向这种类型的变量。
(2)指针变量也允许在定义时进行初始化。
例如:
int i, j;
int *p=&i, *q=&j;
表示:定义了两个指向int型变量的指针变量p和q,p的初值为变量i的地址&i,q的初值为变量j的地址&j,而不是表示:*p的初值为&i,*q的初值为&j。
(3)指针变量的引用
若有赋值语句p=&a;,则
&*p=p。&和*运算符的优先级相同,结合方向为从右到左。&*p等价于&(*p),*p就是变量a,再执行&运算,得到&a,也就是p。因此&*p=&(*p)=&a=p。
*&a=a。*&a等价于*(&a),&a即为p,再执行*运算,得到*p,也就是变量a。因此*&a=*(&a)=*p=a。
可见,&和*运算符在一起,其作用最后抵消
(4)指针变量的引用应注意的地方
关于指针的引用有两点要注意:
指针变量是用来存放地址的,不要给指针变量赋常数值。下列写法是不合法的。
int * p;
p=1000;
指针变量没有指向确定地址前,不要对它所指的对象赋值。例如:
int *p,a=5;
*p=a;
应该在p指向一个确定的变量后,再进行赋值。如
int * p,a=5,b;
p=&b;
*p=a;
16、指针变量作为函数参数
(1)c语言中,函数参数不仅可以是字符型、整型和浮点型等基本类型,还可以是指针类型。
当指针变量作函数参数,其作用是将一个变量的地址传送到另一个函数中。此时形参从实参获得了变量的地址,即形参和实参指向同一个变量,当形参指向的变量发生变化时,实参指向的变量也随之变化。
----------------------------------------------------------------------------------------------------
#include <stdio.h>
void swap (int *p1 , int *p2)
{
int p;
p=*p1;
*p1=*p2;
*p2=p;
}
int main()
{
int a=3,b=4;
int *ptr1,*ptr2;
ptr1=&a; ptr2=&b;
if (a<b) swap(ptr1,ptr2);
printf("%d , %d\n",a , b);
return 0;
}
----------------------------------------------------------------------------------------------------
因此通过指针变量作函数参数,无需返回值和全局变量,主调函数就可以使用被调用函数改变的值。
(2)由于实参和形参之间是单向的值传递,所以要通过被调用函数改变主调函数中变量的值,不能通过要改变值的变量作函数参数实现,而应该用指向该变量的指针变量作函数参数
(3)此外需要注意:如果改变的不是指针形参指向的变量的值,而是改变了指针形参的值,效果是不一样的。
例如将以上程序中的swap函数换成下列语句,则输出a、b的值没有改变。
void swap (int *p1 , int *p2)
{
int *p;
p=p1;
p1=p2;
p2=p;
}
17、指针与一维数组
(1)数组及其元素同样占有存储单元,都有相应的地址。因此指针变量可以指向数组,也可以指向数组元素
(2)指向数组元素的指针变量
定义与赋值
C语言规定,数组名代表数组的首地址,也是第一个数组元素的地址。因此p=&a[0]的赋值语句等价于:
p=a;
指向数组元素的指针也可以在定义时赋初值。
int *p=&a[0];
或 int *p=a;
(3)变量的引用
如有以下的定义和赋值:int a[5], *p=&a[1];
则可以通过指针运算符*来对数组元素进行引用。例如: *p=10;
表示对p所指向的数组元素a[1]赋值
上式等价于 a[1]=10;
C语言规定,如果p指向一个数组元素,则p+1表示指向数组该元素的下一个元素。假设p=&a[0],则p+1表示数组元素a[1]的地址。
对于不同类型的数组元素,p值的改变是不同的。如果数组元素为int型,p+1就意味着p的值加上4,这才能指向数组的下一个元素。
如果p的初值是&a[0],那么:
p+i和a+i都可以表示元素a[i]的地址,即它们都指向数组的第i个元素。a代表数组首地址,a+i也是地址,它的计算方法与p+i相同。
*(p+i) 和*(a+i)都表示指针p+i或a+i所指向的数组元素a[i]的值。
由此可见,引用一个数组元素可以有两种方法:
下标法,如a[i]。
指针法,如*(p+i)。
-------------------------------------------------------------
#include <stdio.h>
int main(void)
{
short dates[4];
short *pti;
short index;
double bills[4];
double *ptf;
pti = dates;
ptf = bills;
printf("%23s %10s\n","short","double");
for (index = 0;index < 4;index++){
printf("pointers + %d: %10p %10p\n",index,pti + index,ptf + index);
}
return 0;
}
dates + 2 == &data[2]//相同的地址
*(dates + 2) == dates[2]//相同的值
-------------------------------------------------------------
实例2:
/* day_mon3.c -- uses pointer notation */
#include <stdio.h>
#define MONTHS 12
int main(void)
{
int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
int index;
for (index = 0; index < MONTHS; index++)
printf("Month %2d has %d days.\n", index +1,
*(days + index)); // same as days[index]
return 0;
}
//days是数组首元素的地址:days + index是元素days[index]的地址
-------------------------------------------------------------
(5)当指向数组元素的指针变量p等于数组名时,p+i与a+i都指向数组的第i个元素,
但是二者使用时仍然有区别。因为作为指针变量的p可以实现自身值的改变,例如p++,使p的值自增。而数组名a是一个代表数组首地址的常量,它自身的值是不能改变的,即a++是不合法的。
指向数组元素的指针变量可以自增或自减,大大方便了数组元素的操作。
------------------------------------------------------------------
输出数组所有的元素
#include <stdio.h>
int main()
{
int *p,i;
int a[5]={1,2,3,4,5};
p=a;
printf("\n");
for (;p<a+5;p++){
printf("%d\t",*p);
}
return 0;
}
------------------------------------------------------------------
18、数组名作为函数参数
数组名作为函数参数时,在函数调用时,是把实参数组的首地址传递给形参数组,使得两个数组共同占用同一段内存空间,这样形参数组中的元素值如发生变化就会使实参数组的元素值也同时变化。数组名就是数组首元素的地址,实际参数是一个数组名,那么形式参数也要是必须是与之相匹配的指针
这种地址的传递也可以不用数组名来进行,而使用指针变量。实参和形参都可以分别使用数组名或指针变量。对应情况有以下四种:
实参:
数组名 数组名 指针变量 指针
形参:
数组名 指针变量 数组名 指针
int sum( int *ar,int n);
int sum(int *,int);
int sum(int ar[],int n);
int sum(int [],int);
-------------------------------------------------------------------------------
实例1:已知一个一维数组的所有元素求和。使用数组作为函数参数
// sum_arr1.c -- sums the elements of an array
// use %u or %lu if %zd doesn't work
#include <stdio.h>
#define SIZE 10
int sum(int ar[], int n);
int main(void)
{
//大理石
int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,20};
long answer;
answer = sum(marbles, SIZE);
printf("The total number of marbles is %ld.\n", answer);
printf("The size of marbles is %zd bytes.\n",
sizeof marbles);
return 0;
}
int sum(int ar[], int n) // how big an array?
{
int i;
int total = 0;
for( i = 0; i < n; i++)
total += ar[i];
printf("The size of ar is %zd bytes.\n", sizeof ar);
return total;
}
-------------------------------------------------------------------------------