“教小白精通编程”系列之“C语言教程” (版权所有,不得转载,擅自抄袭转载将承担法律责任)
7. 指针
内存的地址: 内存是内存单元的依次排列,每个内存单元都有唯一的地址。如同宾馆的每个房间都有一个编号。
变量的指针(地址):变量占据内存的起始地址。
指针(地址)实际上可以看成是一个整数量。
#include <stdio.h>
void main(){
int x = 3;
printf("x=%d,\t the address is %d",x &x); /*其中的&是 “取地址运算符”,用于获得一个变量的地址*/
}
指针变量:存储指针(地址)的变量。
定义指针变量的格式:
类型* 变量名;
或者在定义指针变量时就给它一个地址值。 注意:必须是同一指针类型的值。
类型* 变量名 = 指针值;
例如,
#include <stdio.h>
void main(){
int x = 3;
int* px = &x; /*将变量i的地址用于初始化 “整型指针类型”的变量px, px 和 &x的类型都是同一个类型: “整型指针类型” */
printf("x=%d,\t the address is %d \t %d",x &x,px); /*其中的&是 “取地址运算符”,用于获得一个变量的地址*/
}
如图所示:
因为px保存的是x的地址,我们通常称px指向x (也可以说px是x的指针)。
通过所谓的“取内容运算符”或“间接运算符” ’*‘
可以得到指针变量指向的那个变量,比如通过
printf("%d",*px); /* 其中的*px 就是 x */
因此,我们可以通过px来这样修改x的值
*px = 20;
printf("*px = %d\n",*px); /* 20 */
printf("x = %d\n",x); /* 20 */
注意
1) 类型* 和 类型 是两个不同的类型。比如:
int * : 也是 一个数据类型,称为整型指针类型
int : 整型
因此,下面的代码是错误的:
int x = 10;
int* px = 20; /*错:两边的数据类型不一样,一个是"整型指针类型",一个是“整型” */
x = &x; /* 同样的错误*/
指针变量可以初始化为0,表示不指向任何内存,称为空指针。
int x = 10;
int* px = 0; /*也可以用C语言定义好的宏常量NULL来初始化: int* px = NULL; */
/* C语言定义NULL就是0: #define NULL 0 */
px = &x;
指针的指针
指针变量也占据一块内存,它也有地址(指针)。称为“指针的指针”。
#include <stdio.h>
void main(){
int x = 10;
int *p = &x; /* p 保存的是x的地址,类型是“整型指针类型”*/
int ** q = &p; /* q 保存的是p的地址,类型是“整型指针的指针类型”*/
int y = *p;
int z = *(*q);
printf("%d\t%d\t%d\t%d\t%d\n",x,&x,p,&p,q);
printf("%d\t%d\t%d\t%d\t%d\t%d\n",q,*q,**q,*p,y,z);
}
数组就是指针
数组名是整个数组内存块的起始地址(第一个数组元素的地址)或者说指针。
int a[10];
int * pa;
pa = &a[0]; /*也可以写成:pa = a;
因为 a 和 &(a[0])都表示的是数组的起始地址
(数组的第一个元素的地址) */
可以测试一下:
#include <stdio.h>
void main(){
int arr[] = {1,2,3,4,5,6,7};
int *p = arr; /*数组名就是数组的起始地址*/
int *q = &(arr[0]); /*第一个元素的地址*/
printf("%d\t%d\t%d\t%d\n",arr,&(arr[0]),p,q);
printf("%d\t%d\t%d\t%d\n",arr[0],*arr,*p,*q); /* 用下标运算符[]得到一个数据元素
或取内容运算符*得到整型指针指向的那个整形变量
*/
}
下标访问”就是通过“指针偏移”实现的。
/* arr[2]实际就是通过指针arr向后偏移2个整型量,然后通过取内容运算符*
得到偏移后的指针(arr+2)指向的那个整型量。
即: arr[2]等价于 *(arr+2) */
#include <stdio.h>
void main(){
int arr[] = {1,2,3,4,5,6,7};
printf("%d\t%d\n",arr[2],*(arr+2));
*(arr+2) = 30;
printf("%d\t%d\n",arr[2],*(arr+2));
arr[2] = 40;
printf("%d\t%d\n",arr[2],*(arr+2));
int y = arr[7]; /*错,下标越界了。
7个元素的数组下标只能是:
0,1,2,3,4,5,6*/
}
数组作为函数的形参,实际传递的是数组的指针(数组名就是数组的指针(起始地址))。
/*求n个元素的数组arr中的最小值*/
int Min(int arr[],int n){
int m,i;
if(n<1) return 0; /*空的数组,就假装最小值为0吧*/
m = arr[0]; /* 假设最小值开始为第一个元素的值*/
for(i = 1 ; i<n ;i++)
if(arr[i]<m) /*发现更小的*/
m = arr[i];
return m;
}
上述函数的参数arr实际上是一个“整型指针变量”指向某整型数组的起始地址。因此,改参数完全可以定义为“ int * arr ”
/*求n个元素的数组arr中的最小值*/
int Min(int *arr,int n){
int m,i;
if(n<1) return 0; /*空的数组,就假装最小值为0吧*/
m = arr[0]; /* 假设最小值开始为第一个元素的值*/
for(i = 1 ; i<n ;i++)
if(arr[i]<m) /*发现更小的*/
m = arr[i];
return m;
}
然后可以在某个程序中调用这个函数,例如:
#include <stdio.h>
void main(){
int a = {34,1,56,27,8,19};
int arr = {3,19,56,7,48,23,71,2};
printf("the min of a is %d\n",Min(a,6));
printf("the min of arr is %d\n",Min(arr,8));
}
当然,上述的Min函数也可以写成下面的两种之一:
/*求n个元素的数组arr中的最小值*/
int Min(int *arr,int n){
int m,i;
if(n<1) return 0; /*空的数组,就假装最小值为0吧*/
m = arr[0]; /* 假设最小值开始为第一个元素的值*/
for(i = 1 ; i<n ;i++)
if( *(arr+i) < m ) /*发现更小的*/
m = *(arr+i);
return m;
}
或
/*求n个元素的数组arr中的最小值*/
int Min(int *arr,int n){
int *p = arr,*q = arr+n;
if(n<1) return 0; /*空的数组,就假装最小值为0吧*/
m = *p; /* 假设最小值开始为第一个元素的值*/
for(;p<q; p++)
if( *p < m ) /*发现更小的*/
m = *p;
return m;
}
注意:对于一个指针,可以用加减一个整数即运算符+、-或者++、–运算符进行指针(地址)的偏移,以便指向不同的内存位置,存取不同的数据元素。也可以对两个指针变量用比较运算符如<来比较它们的大小。但指针不是整型量,不能进行其他如乘除、取模等运算。
向函数传递数组
可以定义一个能接受数组作为函数参数的函数,然后调用这个函数时,可以将一个实际的数组传递给它。
数组作为函数参数时,其定义方式有下列三种:
/*函数名param后跟一个空的[],说明param可以接受一个数组*/
void myFunction(int param[]) {
/*函数体 代码*/
}
/*函数名param后跟一个非空的[size],说明param可以接受一个大小为size的数组*/
void myFunction(int param[]) {
/*函数体 代码*/
}
/*函数名param前有一个*,说明param可以接受一个数组*/
void myFunction(int *param) {
/*函数体 代码*/
}
上述3个函数中,实际上上面2个函数都会被编译器转化为第3个函数,也就是说“数组作为函数的形参”实际上是一个指针类型的形参,即将来可以接受一个数组名表示的数组的起始地址!而并不是将一个数组的所有元素传给一个函数,仅仅传递的是一个小小的指针变量。
比如,我们可定义下面的函数,第一个参数表示可以接受一个传入的数组
double getAverage(int arr[], int size) {
int i;
double avg;
double sum = 0;
for (i = 0; i < size; ++i) {
sum += arr[i];
}
avg = sum / size;
return avg;
}
然后我们可以调用这个数组:
#include <stdio.h>
/* 函数声明,表示存在这种格式的函数getAverage,
其第一个参数可以是一个int型数组,第二个参数是这个数组的大小 */
double getAverage(int arr[], int size);
int main () {
/* 5个元素的int整型数组 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* 将数组名(实际是数组对应内存块的起始地址)传给getAverage函数 */
avg = getAverage( balance, 5 ) ;
/* 输出getAverage的返回值 */
printf( "Average value is: %f ", avg );
return 0;
}
也可以传递多维数组(如二维数组)给函数,在定义接受这种多维数组的函数参数时,和接受一维数组的函数参数一样,其第一维的大小也可以不指定。同样,如果我们省略第一维对应的方括号[],则要在参数名前加一个‘’。 并用圆括号括起来 “(参数名)”。
/*
* File : main.c
* Author : zentut.com
* Purpose: Demonstrates multidimensional array & function in C
*/
#include <stdio.h>
#include <stdlib.h>
const int ROW = 2;
const int COLUMN = 3;
/*这2个函数的定义在main函数的调用语句后面,所以要先声明(说明)函数的规范*/
void fill_array(int (*pm)[COLUMN],int row);
void display(int m[][COLUMN],int row);
int main()
{
int i,j;
int m[ROW][COLUMN];
/* 修改数组元素的值 */
fill_array(m,ROW);
/* 显示数组的元素 */
display(m,ROW);
return 0;
}
void fill_array(int (*pm)[COLUMN],int row)
{
int i,j;
printf("Please fill the array's content:\n");
/* 修改数组元素的值 */
for(i = 0; i < row; i++)
{
for(j = 0; j < COLUMN; j++)
{
printf("\nm[%d][%d]:",i,j);
scanf("%d",&pm[i][j]);
}
}
}
void display(int m[][COLUMN],int row)
{
int i,j;
/* 显示数组的元素 */
printf("Array's content:\n");
for(i = 0; i < row; i++)
{
for(j = 0; j < COLUMN; j++)
{
printf("%d\t",m[i][j]);
}
printf("\n");
}
}
执行程序后的输出:
Please fill the array's content:
m[0][0]:1
m[0][1]:2
m[0][2]:3
m[1][0]:4
m[1][1]:5
m[1][2]:6
Array's content:
1 2 3
4 5 6
关于指针的更多内容(如函数指针)请参考: C语言快速入门之“数组、指针、内存分配”
练习
您的打赏是对我最大的鼓励!