目录
十、指针
10.1地址和指针的概念
10.2变量的指针和指向变量的指针变量
定义一个指针变量
指针变量的引用
指针变量作为函数参数
十、指针
10.1地址和指针的概念
我们要想了解什么是指针,就必须弄清楚数据在内存中是如何存储的,又是如何读取的。
如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。编译系统根据程序中定义的变量类型,分配一定长度的空间。例如,一般为整型变量分配2个字节,对单精度浮点型变量分配4个字节,对字符型变量分配1个字节。内存区的每一个字节都有一个编号,这就是“地址”,它相当于旅馆中的房间号。在地址所标志的内存单元中存放数据,这相当于旅馆房间中居住的旅客一样。
区别:内存单元的地址和内存单元的内容。
内存单元的地址是用来定位内存中的特定位置的,而内存单元的内容则是存储在那个位置上的实际数据。通过地址可以访问和修改内容,而内容则是程序和操作系统实际处理的数据。
在程序中,一般是通过变量名来对内存单元进行存取操作的。但其实程序经过编译后已经将变量名转换为变量的地址,对变量值的存区都是通过地址进行的。
直接访问和间接访问的区别:
直接访问:指程序或硬件通过内存地址直接访问内存中的数据。访问过程简单快捷,因为地址直接指向数据所在的位置。例如:
int arr[10]; // 数组声明
arr[2] = 5; // 直接访问数组第3个元素
间接访问:通过一个中间的指针或索引来访问内存中的数据。实际的数据位置由指针或索引间接确定。例如:
int arr[10];
int *ptr = arr; // 指针指向数组
*(ptr + 2) = 5; // 通过指针间接访问数组第3个元素
指针:一个变量的地址称为该变量的“指针”。
指针变量:用一个变量专门存放另一个变量的地址(即指针)。
指针变量的值(即指针变量中存放的值)是地址(即指针)。
指针是一个地址,而指针变量是存放地址的变量。
10.2变量的指针和指向变量的指针变量
变量的指针就是变量的地址。
存放变量地址的变量就是指针变量,它用来指向另一个变量。
为了表示指针变量和它所指向的变量之间的关系,在程序中用“ * ”符号表示“指向”的对象,如果已定义i_pointer为指针变量,则(*i_pointer)是i_pointer所指向的变量。如图:
可以看到,*i_pointer也代表一个变量,它和变量i是同一回事。
下面两个语句作用相同:
//1、
i = 3;
//2、
*i_pointer = 3; //将3赋给指针变量i_pointer所指向的变量
定义一个指针变量
C语言规定所有变量在使用前必须定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和其他类型的变量,它是用来专门存放地址的,必须将它定义为“指针类型”。我们看看下面的这个例子:
int i, j;
int *pointer_1, *pointer_2; //定义两个指针变量,指向整型变量的指针变量。
定义指针变量的一般形式为:
基本类型 *指针变量名;
下面都是合法的定义:
float *pointer_3;
char *pointer_4;
那么,怎样使一个指针变量指向另一个变量呢?可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向该变量。例如:
pointer_1 = &i; //将变量i的地址存放到该指针中,因此pointer_1就“指向”了变量i
pointer_2 = &j;
在定义指针变量时要注意的两点:
-
指针变量前面的“ * ”表示该变量的类型为指针型变量。指针变量名是pointer_1,pointer_2,而不是 *pointer_1, *pointer_2。这与定义整型或浮点型变量的形式不同的。
-
在定义指针变量时必须指定其基本类型。因为不同类型的数据在内存中所占的字节数是不相同的,因此必须指定指针变量所指向的变量的类型,即基类型。一个指针变量只能指向同一个类型的变量,不能忽而指向一个整型变量,忽而指向一个实型变量。
需要特别注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中。下面的赋值是错误的:
float a; //定义a为float类型的变量
int *pointer_1; //定义pointer_1为基类型为int的指针变量
pointer_1 = &a; //将float型变量的地址放到指向整型变量的指针变量中,是错误的
指针变量的引用
牢记:指针变量中只能存放地址(指针),不要将一个整数(或任何其他非地址类型的数据)赋给一个指针变量。下面的赋值是不合法的:
*pointer_1 = 100; //pointer为指针变量 100为整数
两个相关的运算符:
-
&:取地址运算符。
-
*: 指针运算符。(或称“间接访问”运算符),取指针所指向的对象的内容。
例如,&a为变量a的地址,*p为指针变量p所指向的存储单元的内容(即p所指向的变量的值)。
通过指针变量访问整数变量。
#include<stdio.h>
int main()
{
int a,b;
int *pointer_1, *pointer_2;
a = 100, b = 10;
pointer_1 = &a; //把变量a的地址赋给pointer_1
pointer_2 = &b; // 把变量b的地址赋给pointer_2
printf("%d,%d\n",a,b);
printf("%d,%d\n",*pointer_1,*pointer_2);
return 0;
}
//输出:
100,10
100,10
对“&”和“*”运算符,再作说明:
如果已经执行了语句
pointer_1 = &a;
(1)&*pointer_1的含义是什么?
“&”和“ * ”两个运算符的优先级相同,但按自右而左方向结合,因此先进行pointer_1运算,它的变量就是a,再执行&运算。因此,&pointer_1与&a相同,即变量a的地址。
(2)*&a的含义是什么?
先进行&a运算,得到a的地址,再进行*运算,即&a所指向的变量,也就是变量a。 *&a和 *pointer_1的作用是一样的,它们都等价于变量a,即 *&a 与 a等价。
(3)(*pointer_1)++相当于a++。
注意括号是必要的,如果没有括号,就成为了*pointer_1++,我们知道,++和 * 为同一优先级别,而结合方向为自右而左,因此它相当于 *(pointer_1)++。由于++在pointer_1的右侧,是“后加”,因此先对pointer_1的原值进行 * 运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。
输入a和b两个整数,按先大后小的顺序输出a和b。
#include<stdio.h>
int main()
{
int *p1, *p2, *p, a, b;
scanf("%d%d",&a,&b); //输入 5 9
p1 = &a;
p2 = &b;
if(a<b)
{
p1 = &b; //p1指向值大的b的地址
p2 = &a;
}
printf("a=%d,b=%d\n",a,b); //输出:a=5,b=9
printf("max=%d,min=%d\n",*p1,*p2); //输出:max=9,min=5
return 0;
}
指针变量作为函数参数
函数的参数不仅可以是整型、浮点型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。
下面通过一个例子来说明:
题目要求同上一题,即对输入的两个整数按大小顺序输出。
现用函数处理,而且用指针类型的数据作函数的参数。
#include<stdio.h>
int main()
{
void swap(int *p1, int *p2);
int a, b;
int *pointer_1, *pointer_2;
scanf("%d%d",&a,&b); //输入 5 9
pointer_1 = &a; //将a的地址赋给指针变量pointer_1 ,使pointer_1指向a
pointer_2 = &b; //将b的地址赋给指针变量pointer_2 ,使pointer_2指向b
if(a<b) swap(pointer_1,pointer_2); //注意传递是指针变量,在函数调用时,将实参变量的值传送给形参变量 “值传递”
printf("max=%d,min=%d\n",a,b); //输出:max=9,min=5
return 0;
}
// 到这里 有
//p1 和 pointer_1都指向变量a
//p2 和 pointer_2都指向变量b
//接着执行swap函数的函数体 ,使 *p1 和 *p2 的值交换
void swap(int *p1, int *p2) //用户自定义函数 作用:交换两个变量a和b的值
{
int temp;
temp = *p1; // *p1就是a, 是整型变量 所以赋给的temp必须是整型变量
*p1 = *p2;
*p2 = temp;
}
//函数调用后,形参p1和p2不复存在(已释放)
注意,本例题采取的方法是交换a和b的值,而p1和p2的值不变。这与前一个例子刚好相反。
我们通过上面这个例子,可以知道在执行swap函数后,变量a和b的值都改变了。请仔细分析一下,这个改变是怎么实现的。这个改变不是通过将形参值传回实参来实现的。我们可以思考一下能否通过下面的函数来实现a和b的互换。
void swap(int x,int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
如果在main函数中调用swap函数:
swap(a,b);
会有什么结果呢?
在函数调用时,a的值传给x,b的值传给y。执行完swap函数后,x和y的值是互换了,但未影响到a和b的值。
在函数结束时,变量x和y释放了,main函数中的a和b并未互换。
也就是说,由于“单向传递”的“值传递”方式,形参值的改变不能使实参值随之改变。
为了使在函数中改变了的变量值能被main函数所用,不能采取上述把要改变值的变量作为参数的方法,而应该用指针变量作为函数的参数,在函数执行过程中使指针变量所指向的变量值发生变化,函数调用结束后,这些变量值的变化依然保留下来,这样就实现了“通过调用函数使变量值发生变化,在主调函数(如main函数)中可以使用这些改变了的值”的目的。
如果想通过函数调用得到n个要改变的值,可以:
(1)在主调函数中设n个变量,用n个指针变量指向它们;
(2)然后将指针变量作实参,将这n个变量的地址传给所调用的函数的形参;
(3)通过形参指针变量,改变该n个变量的值;
(4)主调函数中就可以使用这些改变了值的变量。
可以按此思路,再看一遍上述的代码。
请注意,不能企图改变指针形参的值而使指针实参的值改变。可以看一下下面的程序:
#include<stdio.h>
int main()
{
void swap(int *p1, int *p2);
int a, b;
int *pointer_1, *pointer_2;
scanf("%d%d",&a,&b); //输入 5 9
pointer_1 = &a;
pointer_2 = &b;
if(a<b) swap(pointer_1,pointer_2);
printf("max=%d,min=%d\n",*pointer_1,*pointer_2); //输出:max=5,min=9
return 0;
}
void swap(int *p1, int *p2)
{
int *p;
p = p1;
p1 = p2;
p2 = p;
}
编写此程序的意图是:交换pointer_1和pointer_2的值,使pointer_1指向值大的变量,其设想是:
(1)先使用pointer_1指向a,pointer_2指向b;
(2)调用swap函数,将pointer_1的值传给p1,pointer_2的值传给p2;
(3)在swap函数中使p1与p2的值交换
(4)形参p1、p2将地址传回实参pointer_1和pointer_2,使pointer_1指向b,pointer_2指向a。然后输出 *pointer_1 和 *pointer_2,想实现两个值的交换。
但是,这是办不到的,假设我们输入“5 9”后,程序实际输出为“max=5,min=9”。
问题出现在第(4)步。
C语言中实参变量和形参变量之间的数据传递是单向的“值传递”的方式。指针变量作函数参数也要遵循这一规则。不可能通过调用函数来改变实参指针变量的值,但可以改变实参指针变量所指变量的值。
我们知道,函数的调用可以(而且只可以)得到一个返回值(即函数值),而运用指针作为参数,可以得到多个变化了的值。如果不用指针是难以做到这一点的。
最后,我们再通过这一道题的代码来检验一下我们对这一节知识点的掌握程度吧!
输入a、b、c这3个整数,按大小顺序输出。
#include<stdio.h>
int main()
{
void exchange(int *q1,int *q2,int *q3);
int a,b,c,*p1,*p2,*p3;
scanf("%d%d%d",&a,&b,&c); //5 2 9
p1=&a;
p2=&b;
p3=&c;
exchange(p1,p2,p3);
printf("%d %d %d\n",a,b,c); //9 5 2
return 0;
}
void exchange(int *q1,int *q2,int *q3)
{
void swap(int *pt1,int *pt2);
if(*q1<*q2) swap(q1,q2);
if(*q1<*q3) swap(q1,q3);
if(*q2<*q3) swap(q2,q3);
}
void swap(int *pt1,int *pt2)
{
int temp;
temp=*pt1;
*pt1=*pt2;
*pt2=temp;
}
声明:本文章为个人学习笔记,资料整理参考谭浩强《C程序设计(第三版)》如有错误,欢迎大家指正!