本章的主要内容包括:
指针
指针与变量
指针与数组
指针与函数第七章 指针回首页内存地址:计算机内存的组织方式是把所有单元顺序排列,每个单元有一个顺序编号,称单元的地址 。 形象化地叫指针地址本身也是用二进制编码的,任何数据对象在它被使用的时候,都必然有一个确定的存储位置,占据着确定数目的存储单元 。
存储在内存的数据,最终都是根据其存储位置,通过存储单元的地址访问的 。
任何变量,在其存在期间,总有一个确定的,固定的存储位置,即固定的地址 。 变量地址可能作为数据来操作 。
指针变量是 C语言提供的一种操作变量地址的机制 。 指针变量中保存的是其它对象的地址 。
通过指针变量,可以进行对有关对象的访问和处理 。
讨论 C语言操作指针变量和普通变量的特点,什么是直接存取? 什么是间接存取?
讨论使用指针进行程序设计的好处
7.1 地址和指针的概念
7.2.1 指针的定义
1,定义指针变量的一般形式如下:
类型名 *指针变量名 1,*指针变量名 2,..,*指针变量名 n ;
2,空指针空指针是一个特殊的指针,它的值是 0,C语言中用符号常量 NULL( 在
stdio.h中定义 ) 表示这个空值,并保证这个值不会是任何变量的地址 。 空指针对任何指针类型赋值都是合法的 。 一个指针变量具有空指针值表示当前它没有指向任何有意义的东西 。
3,viod指针
( void *) 类型的指针叫通用指针,可以指向任何的变量,C语言允许直接把任何变量的地址作为指针赋给通用指针 。
当需要使用通用指针所指的数据参加运算时,需要写出类型强制转换 。
如通用指针 gp所指空间的数据是整型数据,p是整型指针,用下式转换,p=(int *)gp;
7.2 指针的定义、使用和运算
7.2.2 指针的操作
1,指针赋值
(1) 取地址运算 ( 一元运算符 &) 和指针赋值
(2) 指针变量赋值
(3) 通过标准函数获得地址值
2,间接运算 ( 一元运算符 *)
将一元运算符 ‘ *’ 放在指针变量名前,也可以是地址前,效果是由一个指针得到被它指向的变量,可以像使用普通变量一样使用该表达式 。
3,移动指针移动指针就是对指针变量加上或减去一个整数,或通过赋值运算,
使指针变量指向相邻的存储单元 。 因此,只有当指针指向一串连续的存储单元时,指针移动才有意义 。
7.2 指针的定义、使用和运算
7.2.2 指针的操作
3,移动指针对指针进行加,减运算中,数字,1”不再代表十进制数,1”,而是 1个存储单元长度,整型变量存储单元长度是 2个字节,整型指针移动 1个存储单元就是移动 2个字节,双精度变量存储单元长度是 8,双精度型指针移动 1个存储单元就是移动 8个字节,依此类推 。
程序中移动指针时,不论指针的基类型是什么,只需简单地加,减一个数而不必去管它的具体长度,系统将会根据指针的基类型自动确定位移的字节数最常用的移动操作就是加一和减一操作 ++,--。 它们分别代表指针向地址值增大的方向移动一个存储单元和指针向地址值减少的方向移动一个存储单元 。
两个指向同一串连续单元的指针可以进行相减的运算,结果是两个指针之间元素的个数,可以通过赋值使两个指针指向同一个单元 。
7.2 指针的定义、使用和运算
7.2.2 指针的操作
4,指针比较两个指针指向同一串连续的存储单元时,可以在关系表达式中对其进行比较,判断指针的位置关系,两个指针变量的值相等,
表示它们指向同一个存储单元 。 还可进行是否是空指针的判断 。
指针基类型对指针相关操作的约束和限制:
( 1) 基类型使指针只能指向基类型定义的一类变量 。
( 2) 限制引用操作满足基类型的约束 。 ( 范围,运算,内存表示 )
( 3) 限制指针移动操作的跨度 。
7.2 指针的定义、使用和运算
7.2.3 指针变量的初始化指针变量在定义时可以用任何合法的指针 ( 地址 ) 值进行初始化 。
如果在定义指针变量时没有进行初始化,全局变量和局部静态变量将被自动地初始化为空指针 ( 0) 。 局部自动变量,寄存器变量将不自动初始化,这些变量建立后的值不能确定 。 一定要有明确的变量关联后,才能使用这些变量 。
7.3函数与指针形式:
类型名 函数名 (类型名 形参 1,类型名 形参 2… )
/* 头部 */
{ 说明部分 /* 函数体 */
语句部分
}
7.2 指针的定义、使用和运算
7.3.1 指针作函数参数若函数的形参为指针类型,调用该函数时,对应实参必须是基类型相同的地址值或已指向某个存储单元的指针变量 。 虽然实参和形参之间还是值传递方式,但由于传递的是地址值,所以形参和实参指到了同一个存储单元,函数中,通过形参操作的存储单元,与实参所指是同一单元,因此实参的值发生了改变 。 利用此形式,可以把两个或两个以上的数据从被调用函数中返回到调用函数 。
当需要通过函数改变变量值时,使用指针作函数参数 。
7.3.2 返回指针的函数指针是变量,可以由函数返回 。 返回指针的函数定义方法:
类型名 *函数名 (类型名 形参 1,类型名 形参 2… )
{ 说明部分语句部分
}
7.3函数与指针
7.3.2 返回指针的函数函数体内,return语句的表达式的值必须是地址 。
返回值可在任何有意义的引用处使用 。
7.3.3 函数指针函数指针提供了用指针调用函数的机制 ( 间接调用 ) 。
通过函数名得到的是函数的入口地址 。 函数指针变量存储的是函数的入口地址 。
函数指针变量的定义形式为:
类型名 ( *指针变量名 ) ( 参数类型表 ) ;
(*p)()表示 p是一个指向函数入口的指针变量,它不固定指向哪一个函数,只是定义了这样一个类型的变量,专门存放函数的入口地址,程序中可以先后指向不同的函数 。
7.3函数与指针
7.3.3 函数指针使用函数指针的步骤:
(1)定义函数指针变量 。 形如 int*p();
(2) 函数指针变量赋值:如 p = 函数名;只需给出函数名,不必给出参数 。
(3)通过函数指针调用函数:如 c = ( *p) (实参 );
调用由 p指向的函数,返回值赋给 c。
讨论函数指针和返回指针的函数在定义形式上的差别 。
7.3.4 函数体内指针函数体内的指针有可能通过与指针形参的赋值等操作,指向函数体外的存储单元,因此有可能改变调用函数环境中的值
7.3函数与指针
C语言数组和指针的关系极其密切。通过指针访问数组元素的机制是 C语言特有的。
7.4.1一维数组和指针
7.4.1.1数组名和地址关系数组名在 C语言中被处理成一个地址常量,也就是数组所占连续存储单元的起始地址,一旦定义,数组名永远是数组的首地址,
在其生存期不会改变 。
不能给数组名重新赋值 。 但可以用在数组名后加一个整数的办法,
依次表达数组中不同元素的地址 。
如 int a[10];
a与 &a[0]是等价的,a[1]的地址是 a+1,可用 &a[1]表示 。
对数组元素 a[3],可以用 *(a+3)来引用,也可以用 *&a[3]来引用 。
7.4 数组和指针
7.4.1.1数组名和地址关系例 通过数组首地址引用数组元素,输出数组中全部元素 。
#include<stdio.h>
main()
{ int i,a[]={1,2,3,4,5};
for (i=0;i<5;i++)
printf("%d ",*(a+i));
}
通过 a+i,依次指向了 a数组的每一个元素 。
使用 *(a+i)引用每一个元素的值 。
讨论 C编译对数组元素寻址的操作过程 。
7.4 数组和指针
7.4.1.2通过指针引用一维数组元素通过指针引用一维数组元素需要一个指向数组元素的指针变量,
它的基类型与数组元素的类型相同 。
通过指针引用数组元素是 C语言提供的一种高效数组访问机制 。
设 p指向数组 a某元素地址 。 则:
*p = 5; 将 对应数组元素赋值 5。
p+1 或 ( p++) 也是指针,指向数组下一个元素 。
p+5; 指向 p所指元素的后第五个元素 。
p-1 指向 p所指元素的前一元素 。
指针有效范围必须满足数组空间的限制,避免越界访问 。 这个问题与数组下标越界问题的控制同样重要 。
讨论使用指针引用数组元素与下标法引用数组元素的比较
7.4 数组和指针
7.4.1.3 通过带下标的指针变量引用一维数组元素
C语言中,一对方括号不仅用作表示数组元素的记号,
而且是一种运算符,表示要进行变址运算,在一个基地址上加上相对位移形成一个新地址 。
设,p指向 s数组的首地址时,表示数组元素 s[i]的表达式也可以是 p[i]。 实际上,p不一定要指向 s的首地址,如果 p=&s[2]; 即 p指向 s[2],则 p+3指向
s[5],p[3]引用的数组元素是 s[5]。
且有五种表示 s数组元素 s[i]的方法:
(1)s[i] (2) *(s+i) (3) *(p+i) (4) p[i]
(5)p指向 s[i]使用 *p表示 s[i]
7.4 数组和指针
7.4.1.4指针,数组和函数数组名作函数形参,实质上是一个相应类型的指针参数 。
如,int fun( int a[])与 int fun(int *d) 是完全等价的 。
从形参形式上看,传递一个数组名和一个简单变量地址的方式没有任何区别,函数中无法使用 sizeof判定数组实际参数的元素个数 。 要在函数中知道数组元素个数 ( 操作元素个数 ),应在参数中传入显式的整型值 。
7.4.2多维数组和指针
7.4.2.1二维数组和地址用于说明概念的实例定义,int a[3][4],*p;
1,二维数组由若干个一维数组组成 。
二维数组是由一维数组为元素组成的数组 。 实例定义了一个二维数组 a,a由 3个元素组成,分别是 a[0],a[1],a[2],而 a[0]、
a[1],a[2]中的每一个又是一个由 4个元素组成的一维数组 。
a[0]的 4个元素为 a[0][0],a[0][1],a[0][2],a[0][3],其它依此类推 。
7.4 数组和指针
7.4.2.1二维数组和地址用于说明概念的实例定义,int a[3][4],*p;
2,二维数组名也是一个地址常量二维数组名同样也是一个地址常量,其值为二维数组的首元素的地址 。
实例中 a数组是二维数组,a,(a+1),(a+2)分别是三个数组元素
a[0],a[1],a[2]的地址,因此,a[0]与 *(a+0)等价,a[1]
与 *(a+1)等价,a[2]与 *(a+2)等价 。 同时,a[0],a[1],a[2]
也是三个一维数组的名字,数组名 a的值与 a[0]的值相同,只是 a的基类型为一维数组 ( 一个整行 ),即 a+1的值与 a[1] 的值相同,a+2的值与 a[2] 的值相同,分别表示数组中第 0,第
1,第 2行的首地址 。 二维数组名应理解为一个行指针 。 p=a;
是不合法的,因为 p和 a的基类型不同 。 同样对二维数组名 a,
也不能进行赋值的运算 。
7.4 数组和指针
3,二维数组元素的地址二维数组元素的地址也可以通过每行的首地址来表示 。
a[0],a[1],a[2]是三个一维数组的名字,表示各行的首地址,a[0]是第 0行第 0个元素的地址,
a[0]+1第 0行第 1个元素的地址,,.,a[1] 是第 1行第 0个元素的地址,a[1]+1第 1行第 1个元素的地址,,.,a[i]的移动以元素为单位 。
实例中,&a[0][0]可用 a[0]+0表示,&a[0][1]可用
a[0]+1表示二维数组元素 a[i][j]的地址可以用以下五种表达式求得:
&a[i][j] a[i]+j *(a+i)+j
&a[0][0]+4*i+j a[0]+4*i+j
7.4 数组和指针
7.4.2.2二维数组元素引用
1,通过二维数组地址引用二维数组 a[n][m]的元素 a[i][j]的引用也可以用下面五种方法:
a[i][j] *(a[i]+j) *(*(a+i)+j)
(*(a+i))[j] *(&a[0][0]+m*i +j)
2,通过普通指针引用可以使用一个以数组元素类型为基类型的指针,依次引用二维数组的所有元素,因为这些元素,在内存中,
连续顺序存放 。
实例中,可以用一个 int型指针,依次引用所有数组元素 。 移动单位是 int的长度 。
7.4 数组和指针
7.4.2.2二维数组元素引用
3,通过行指针引用设 int a[3][2],(*ptl)[2]; 说明符 (*ptl)[2]中,圆括号优先级最高,*首先与 ptl结合,说明 ptl是一个指针变量,
再与说明符 [2]结合,说明指针变量 ptl的基类型是一个包含两个 int元素的数组,ptl的基类型与 a的相同,
ptl=a;是合法赋值,ptl+1等价于 a+1,等价于 a[1]。
当 ptl指向 a数组开头时,可以通过下面的方法引用
a[i][j]:
*(ptl[i]+j) *(*(ptl+i)+j) (*(ptl+i)[j] ptl[i][j]
ptl是一个变量,值可变,a是一个常量 。
7.4 数组和指针
7.4.2.3 二维数组名作函数实参
3,通过行指针引用实参和形参类型匹配,赋值兼容 ( 值传递 )
当我们实参传递给函数的是二维数组元素的地址,要求形参指针的基类型与数组元素的类型一致 。 同时,
应传入维数信息给函数 。 形如:
int( int *p,int m,int n)
当我们调用函数的实参是二维数组的名字,则要求函数指针为行指针,因为二维数组名的基类型是一个行,
这时,函数首部可以是下面的几种形式之一:
fun( int a[M][N]) fun(int a[][N]) fun(int (*a)[N])
7.4 数组和指针
7.4.3 使用指针处理字符串
7.4.3.1字符指针字符指针的定义形式为:
char *变量名 ;
1,初始化方式使字符指针指向字符串如,char *str=,Programming”; 它的含义有三:
(1) 定义一个字符指针 p;
(2) 建立一个字符串常量;
2,用赋值运算使指针指向一个字符串如,char *str; str=“string one”; 与第一种初始化方法完全等价 。
3,用字符数组作字符串和用字符指针指向的字符串的区别
7.4 数组和指针
7.4.3.1字符指针
3,用字符数组作字符串和用字符指针指向的字符串的区别如,char *str=“Programming”;与 char a[]=“Programming”;
区别如下:
(1) str是指针变量,可多次赋值,a是数组名表示地址常量,不能赋值,且 a的大小固定,预先分配存储单元 。
(2) 类型,大小不同,str是指针,a是数组 。 它们的存储不同 。
(3) a的元素可重新赋值,不能通过 str间接修改字符串常量的值 。
后果无法预料 。
7.4 数组和指针
7.4.3.2使用字符指针处理字符串和字符数组
1.字符指针用于输入输出字符串输出时,输出项可以是字符串或字符数组,也可以是已指向字符串的字符指针 。
字符串输入时,输入项可以是字符数组,也可以是字符指针 。 字符指针必须已经指向确切的,足够大的存储空间,以便输入字符能放在它所指的具体单元中 。
2.常规字符串处理的指针实现实例演示
7.4.4 指针数组一个数组,其元素均为指针类型数据,称为指针数组 。 指针数组中每一个元素都相当于一个指针变量 。
一维指针数组的定义形式为:
类型名 *数组名 [数组长度 ];
7.4 数组和指针
7.4.4 指针数组例,int *pa[5]; 定义一个 5个整型指针为数组元素的一维数组 。
下图中,用它表示另一个整型数组元素大小关系 。
7.4 数组和指针
7.4.4 指针数组
7.4.4.1字符指针数组表示的字符串数组定义字符型指针数组并通过赋初值来构成字符串数组 。
1,字符指针数组的初始化
char*days[]={“Sunday”,“Monday”,“Tuesday”,
“Wednesday”,“Thursday”,“Friday”,“Saturday”};
2,字符指针数组与二维字符数组比较二维字符数组表示的字符串在存储上是一片连续的空间,但中间可能有很多空的存储单元,因为作为数组定义,需要指定列数为最长字符串的长度加 1,而实际上各字符串长度一般并不相等 。 字符指针数组表示的字符串在空间上是分散的 。
7.4 数组和指针
7.4.4.1 字符指针数组表示的字符串数组
2,字符指针数组与二维字符数组比较例,char color1[][6]={“red”,“green”,“blue”};
char *color[]={“red”,“green”,“blue”};
下图是它们的内存表示示意 。
7.4 数组和指针
7.4.4.2指向指针的指针指针变量的内容是数据的地址,如果数据本身一个指针,这个指针变量就是指针的指针 。
定义指向指针数据的指针变量的形式如下:
char **p;
存储形式如下图
7.4 数组和指针
7.4.4.3命令行参数
main函数可以有参数,指针数组的一个重要应用是作为 main函数的形参 。
讨论 main函数的调用方法 。
main函数的参数,跟在调用命令后,由操作系统传递给主函数,这种机制叫命令行参数 。
讨论 trubo C中,调试带有命令行参数的程序时,参数的设置输入方法 。
1.命令行的一般形式命令行的一般形式为:
命令名 参数 1 参数 2,.....参数 n
命令名和各参数间用空格分隔 。
7.4 数组和指针
7.4.4.3命令行参数
2,C语言如何看待命令行
C语言将命令行看作由空格分隔的若干字符串,每个字符串看作是一个命令行参数 。 由第一个字符串 ( 命令本身 ) 开始从 0编号 。
程序执行时,每个参数被处理作字符串,在程序中按照规定方式使用它们 。 通过主函数的参数获取命令行参数 。
3.主函数原型主函数的原型为,main( int argc,char *argv[]);
其中,argc,命令行上字符串总数 ( 包括命令名本身 )
argv[],字符指针数组,存储命令行的每个字符串,argv[0]是命令名,
argv[1]是第一个参数串,argv[2]是第二个参数串,,..。
argc,argv只是形参名称,可以是其它名称,但类型一定要正确 。
7.4 数组和指针在任何一个变量使用前,都必须完成关于存储方面的有关安排:存放位置,占据多少存储单元 。 这个工作叫存储分配 。
讨论存储空间的静态分配方法讨论引入动态存储管理的好处
7.5.1 C语言标准动态存储管理函数标准动态存储管理函数原型在 标准头文件 <stdlib.h>中描述 。
1.存储分配函数 malloc()
void *malloc(size_tn) ;
形参类型 size_t:足够大的整数 。
返回值类型 ( void *),通用指针,需要通过类型强制转化成特定的指针类型 。
功能:分配一块能够放下大小为 n的存储块,返回指向这个块的指针,如果存储申请不能满足,返回空指针 。
7.5 动态存储管理
7.5.1 C语言标准动态存储管理函数使用动态存储分配函数应该注意以下几点:
(1) 空间大小计算要使用 sizeof函数进行计算 ;
(2) 调用 malloc函数后,一定要检查返回值 ;
(3) 结果强制转换后才能赋值使用 ;
(4) 得到的空间使用时不允许越界 ;
2.带初始化的存储分配函数 calloc()
void calloc(size_t n,size_t size);
形参类型 size_t,n元素个数; size_tsize:单个元素空间大小 。
返回值类型为 ( void *),通用指针,需要通过类型强制转化成特定的指针类型 。
功能:分配一块能够放下大小为 n*size的存储块,全部内容清0,
返回指向这个块的指针,如果存储申请不能满足,返回空指针 。
7.5 动态存储管理
7.5.1 C语言标准动态存储管理函数
3,动态存储释放函数 free()
void free(void *p);
功能:释放指针 p所指的存储块 。 如果 p的值为空,什么也不做 。
调用 free(q)后,p变量的指向没有改变 。 但不能再使用它的值 。 除非重新指向新的数据单元 。
程序中,应养成对不再使用的存储块立刻释放的习惯 。 避免造成存储块丢失 。
4,分配调整函数 realloc()
void *realloc(void *p,size_t n);
功能:更改前面做过的存储分配,指针 p指向一个过去分配的存储块,n表示现在希望的存储块大小,如果新的要求不能满足,
返回 NULL,p仍指向原来的位置 。 新的分配要求可以满足,
返回新存储块的指针,内容与原来块中一样 。 其余部分不进行初始化 。 不能再通过 p指针使用原来的存储块 。
7.5 动态存储管理
7.5.2 C语言标准动态存储管理函数的使用
(1)一定要检查分配成功与否,常用下面的结构:
if ((p=(...*)malloc(...))==NULL) {
.../* 对分配不成功的处理 */
}
(2)分配空间的大小一定要用运算符 sizeof来计算 。
(3)分配成功后关于存储块的管理,系统完全不进行检查 。
(4) 动态存储块的存在期,在其分配成功时开始,只有在用 free语句释放才能导致其存储期的结束 。
7.6 指针实例 -- 注意下面问题
(1)间接引用数组元素的指针出界;
(2)没有定义指针变量的初值即使用指针;
(3)错误指针赋值
(4)错误操作
7.5 动态存储管理第七章 指针本章主要知识点:
指针基本概念 。 变量的地址和变量的值,指针变量的说明,指针变量初始化,指针的内容,指针基本运算 ( 取变量地址,取指针内容,指针移动,指针比较 ),变量与指针的关系 。
指针与函数的关系 。 指针作为函数的参数在函数之间传递,通过指针改变调用函数中的变量,函数返回值为指针类型,指向函数的指针 。
指针与数组的关系 。 数组名与地址关系,使用指针操作数组,二维数组下标与指针关系,函数之间传递数组的指针操作,数组指针与指针数组的概念及两者的区别,mian函数参数 。
使用指针处理字符串 。 关于字符串的基本规定,字符串结束标记,
使用指针操作字符串的基本算法,常用字符串库函数 。
回本章首页