第八章:指针
指针重要性:
- 数据库–>动态分配内存
- 数据结构–>链表、队列、树、图等
- 操作系统–>改善子程序的效率
- 指针为函数提供修改变量值的手段
8.1-地址和指针
变量的地址
-
计算机中,数据存储在内存中
-
内存:是内部存储器,由存储单元组成的。内存可划分为若干存储单元,每个单元存放8位二进制数,即一个字节,其中存放的数据称为内存单元的内容。
-
内存单元采用线性地址编码,每个单元具有唯一一个地址编码。地址编码是无符号整数型,通常用十六进制数表示。地址一旦编号后固定不变,但存储内容是动态的,经常变化。
-
C编译系统对程序中定义的变量,会根据变量的数据类型为其分配一定字节数并且连续的存储空间。分配的存储单元大小以及存储的数据格式由该变量的数据类型决定。
-
C和C++决定:存储某变量内存空间的首地址称为该变量的地址
short a=3;
char b='A';
float c=2.5;
系统为变量a分配了1000和1001这两个内存单元,1000是变量a的地址。
变量b分配1002这个内存单元,1002是变量b的地址。
变量c分配了1003、1004、1005和1006这4个内存单元,1003是变量c的地址。
如果要输出变量a的值,则先要找到a在内存中的地址
访问方式
直接访问方式
- 直接根据变量名存取变量的值
chort a=3;
char b='A';
float c=2.5;
间接访问方式
- 定义指针变量,将变量的地址存放在此变量中,当要对变量进行存取时先读取该变量的值,得到要存取变量的地址,再对该变量进行访问
- 易混淆概念
变量i:变量的值和变量的地址(指针)
指针:地址
指针变量:存放地址的变量
8.2-指针变量
指针变量的概念
- 专门用来存放内存单元地址的特殊变量:指针变量
- 指针变量中存放的是另一个有值变量的地址
- 变量的指针就是变量的地址。一个指针变量一旦存放了某个变量的地址,该指针变量就指向了这个变量。
int *pointer_1,*pointer_2; //定义指向整型数据的指针变量pointer_1, pointer_2
pointer_1=&a; //把变量a的地址赋给指针变量pointer_1
pointer_2=&b; //把变量b的地址赋给指针变量pointer_2
printf("a=%d,b=%d\n",a,b); //输出变量a和b的值
printf("*pointer_1=%d,*pointer_2=%d\n",*pointer_1,*pointer_2);//用指针变量输出变量a和b的值
指针变量的定义
-
一般形式
数据类型 *指针变量名[=初始地址值]; 数据类型是指向的变量的数据类型 *表示其后面的变量是指针变量 eg: int *p1,*p2; char *p3;
-
注意:指针变量只能存放与它数据类型相同的变量的地址
int *pointer_1;
指针变量名是pointer_1,而不是*pointer_1
指针变量的赋值
- 用变量的地址给指针变量赋值(求地址运算符&)
int a,b,*p;
p=&a;
- 用相同类型的指针变量赋值
int a,*p1,*p2;
p1=&a;
p2=p1
- 赋空值NULL(0)
float *p;
p=NULL
p=0;
- 说明
NULL是一个空指针,空值指针NULL是一个不指向任何存储单元的指针,表示该指针变量的值没有意义。作用是为了避免对没有被初始化的指针变量的非法引用。
指针变量的初始化
- 赋空值NULL
- 用已定义的变量的地址
1.int *p1=NULL;
2.float a,*pf=&a;
指针运算与指针变量的应用
- 基本运算
- 算数运算
- 关系运算
- 赋值运算
指针基本运算规则
- &、*优先级别相同(2级),但为右结合性(单目运算符)
- &、*使用说明:
int a=5,*p=&a;
则&*p含义:&*p-->&a,即p
则*&a含义:*&a-->*p,即a
则(*p)++含义:(*p)++ -->a++
*p++相当于*(p++),先得p所指向得变量的值,然后p+1,p不再指向a
指针的算术运算:指针增量的概念
- 指针增量:指针变量是做++、–、+、-等运算,指针增量的运算不是单纯的算术运算,而是地址按数据个数增加所指的目标变量字节数
float a[10]={0,1,2,3,4,5,6,7,8,9};
float *p=a;
此时p指向数组a的首地址,p++或p+1是p原有的值加上4个字节,若p原有地址值为2000,则p++或p+1的地址值是2004
即向后移动指针变量,使其指向后一个同类型变量
-
C语言地址运算规则规定:
- 指针加减一个整数n,计算结果仍然是一个地址量,它是以运算符的地址量为基点,前方或后方第n个数据的地址
Short int a[20],*p; p=&a[0]; /*指针p指向数组a的第一个元素*/ p+=2; /*移动指针p,使它指向数组a的第3个元素*/
-
指针的算术运算只允许加减运算,不允许乘除及移位运算,不允许两个指针之间进行加运算,也不允许指针加减实型数据
-
两个指针允许进行相减运算,结果是一个整数,表示这两个指针所指地址相差的数据个数
short int *p1,*p2,a[10]= {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; p1=&a[8]; p2=&a[2]; p1-p2=6;
指针的关系运算
-
两指针之间的关系运算表示他们所指向的地址(位置)关系。指针的关系运算符包括:==,!=,<,<=,>,>=
-
指向同一数据类型的指针,才有可能进行关系运算
指针变量作为函数的参数
-
基本类型量作为函数参数时,被调函数内的多个结果值不能为主调函数所用
-
指针变量作函数的参数,传递是变量的地址。通过间接访问方式修改函数外变量的值,可实现被调函数与主调函数间的多个数据共享。
8.3-通过指针引用数组
一维数组与指针
-
数组的指针:数组的__首地址__
- 一维数组的指针:一维数组的首地址,一维数组中第一个元素的地址
- C语言规定:数组名代表数组的首地址
int a[10]中,a与&a[0]等价
-
数组元素的指针:数组元素的地址,如&a[i]
-
指向一维数组元素的指针变量:存放一维数组首地址或数组元素地址的变量
int a[10],*p1,*p2; //定义p1和p2为指向整型变量的指针变量
p1=&a[0]; //将数组的首地址赋给指针变量p1
p2=&a[2]; //将a[2]的地址赋给指针变量p2
- 数组在内存中是一片连续的存储空间,通过指向数组的指针变量进行相加减一个整数的算数运算来移动,就可以访问数组中的其他数组元素
通过指针引用数组元素
-
三种引用数组元素的方式
-
指针法:
*(p+i)和p[i]
-
设有定义float a[10], *p=a;
*p=1;表示对当前所指的数组元素赋值为1
p+1、a+1;表示同一数组中的下一个元素的地址;如float数组元素,p+1指p的值加4个字节,p+所代表的实际地址:p+1×d(d=4)
表示元素的地址可用a+i,p+i
a代表数组首地址,a+i也为地址,实际上为a+i×d
表示元素的内容用*(p+i),*(a+i),a[i];在编译时,对数组元素a[i]就是处理成*(a+i),即按数组首地址加上相对位移量得到要找的元素的地址(基地址+位移),然后找出该单元的内容
-
下标法:
a[i]
-
地址法:
*(a+i)
数组作为函数参数
- 用数组元素作实参与用变量一样,值不会改变
void swap(int x, int y)
{
int t;
t=x; x=y; y=t;
}
调用时,swap(a[1],a[2]);属于单向值传递方式
- 数组名或数组指针作函数参数
f(int array[ ], int n)
{…… }
void main( )
{
int arr[10];
f(arr,10);
……
}
__注意:__当数组名作为函数的参数时,传递的是数组首地址,若形参中数组个元素的值发生变化,实参数组元素的值随之变化
8.4-字符串与指针
字符指针
- C程序中,访问字符串有两种方法:字符数组和字符指针
- 方法1:用字符数组存放一个字符串,然后输出该字符串
- 方法2:用字符串指针指向一个字符串
#include <stdio.h>
void main( )
{
char string [ ]="Hello!";
char *s = "hello";
printf("(1):%s\n",string);
printf("(2):%s\n",s);
}
字符指针作函数参数
- 用字符数组名或字符串指针作参数。在被调函数中改变字符串的内容,在主调函数中可得到改变的字符串。
8.5-函数与指针
- 函数名表示该函数目标代码的存储首地址,即函数的入口地址
利用函数求两数之和
int add(int a,int b)
{ return a+b; }
void main( )
{
int x=add(2,4);
printf("x=%d\n",x);
}
函数指针
-
定义一个指针变量,用来存储函数的起始地址,则此指针变量指向该函数,称为“指针函数”
-
定义:
int (*p)(int,int)
-
用函数指针调用函数
int add(int a,int b) { return a+b; } void main() { int x; int (*p)(int,int) //函数指针定义 p=add; //函数指针赋值 x=p(2,4) //用指针变量调用函数 printf("x=%d\n",x); }
-
函数调用可通过三种形式调用
-
函数名(实参表) add(2,4)
-
(*函数指针名)(实参表) (*p)
-
函数指针名(实参表) p(2,4)
-
-
(*p)()
表示定义一个函数指针变量,专门用来存放函数的入口地址,未赋值时,不固定指向哪一个函数 -
在给函数指针变量赋值时,只需给出函数名,不需要参数
p=add;
-
**用函数指针变量调用函数时,用 ( *p )代替函数名,在( *p )后括号内写上实参
x=( *p )(2,4);
-
对函数指针变量,
p+n、p++、P--
无意义 -
函数指针和数据指针不同
- 函数指针指向数据区,“*数据指针名"是访问该指针所指的数据
- 函数指针指向程序代码区,“*函数指针名”使得程序控制转移到函数指针所指的函数目标代码模块首地址,执行该函数的函数体目标代码
用函数指针作函数参数
- 把函数的入口地址作为参数传递到其他函数。指向函数的指针作为函数参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数
原理:
void fun(int (*x1)(int),int (*x2)(int,int))
{ int a, b, i, j;
a=(*x1)(i); /*调用f1函数*/
b=(*x2)(i,j); /*调用f2函数*/
}
调用时,fun(f1,f2)
将f1、f2的入口地址传给x1、x2
返回指针值的函数
-
把返回值为地址值的函数成为指针函数
-
指针函数定义:
int *p(int x,int y);
指针函数的应用(内存动态分配函数)
- 动态内存分配函数:返回值为指针
- C语言内存镜像
malloc()
- 内存的动态存储区中分配一个长度为size的连续空间,形参size类型定为无符号整型(不允许出现符号)
- 如果分配不成功,返回NULL
void *malloc(unsigned size);
eg:malloc(100) //开辟100字节的临时分配域,函数值为其第一个字节的地址
calloc()
- 内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组
- calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size,即动态数组。如果分配不成功,返回NULL
void *calloc(unsigned n,unsigned size);`
eg:
p=calloc(50,4) //开辟50×4个字节的临时分配域,把首地址赋给指针变量p
free()
- 释放动态存储区
void free(void *ptr)
eg:
free(p); //释放指针变量p所指向的已分配的动态空间
8.6-指针数组和多级指针
指针数组
-
定义:如果数组中的每个元素都是指针类型数据,则这种数组称为指针数组。指针数组中的每一个数组元素相当于一个指针变量
-
定义形式:
数据类型 *数组名[常量表达式] int *ptr[5];
-
指针数组初始化:
数据类型 *数组名[常量表达式]={初值表}; int a[3][2], *aptr[3]={a[0],a[1],a[3]}; //aptr是一维指针数组。aptr[0]指向a[0],aptr[1]指向a[1]
-
指针数组的应用
通过建立指针数组可以来引用二维数组元素 int a[3][2], *aptr[3],i,j; for(i=0; i<3; i++) aptr[i]=a[i];
指向指针的指针(二级指针)
-
定义:只想指针数据的指针变量
-
定义形式
数据类型 **指针变量名
-
说明
- 如果指针变量的值是某一变量的地址,则称为为一级指针。如果指针变量的值是某一指针变量的地址,则称为二级指针。
- 用指针访问另一个变量即为间接访问,一级指针中访问变量称为“单级间址”,二级指针中访问变量称为“二级间址”
-
示例
int i, *ptr, **pptr; ptr=&i; pptr=&ptr; **pptr=2;
命令行参数(指针数组作main()函数的形参)
-
作为main函数的形参**。**在程序执行时,通过命令行将参数传递给程序,以控制程序的执行,这就是命令行参数
-
void main()函数的形参格式为: void main(int argc,char *argv[]);
-
两个特殊的内部形参argc、argv是用来接收命令行实参的,是只有main函数才有的参数。形参名习惯用argc、argv。