本文由瓦斯202贡献
ppt文档可能在WAP端浏览体验不佳。建议您优先选择TXT,或下载源文件到本机查看。
第 6章 指 针
●指针基础 ●指针与数组 ●指针与函数
6.1 指针基础
6.1.1 指针就是地址 1. 程序实体的内存地址 一个程序一经编译,在其执行过程中, 一个程序一经编译,在其执行过程中, 就会为变量、数组以及函数分配存储空间。 就会为变量、数组以及函数分配存储空间。 这些变量、数组、函数都称为程序实体, 这些变量、数组、函数都称为程序实体, 也具有某一种数据类型。 也具有某一种数据类型。这些被分配了内 存空间的程序实体,都具有自己的内存地 存空间的程序实体, 址。
#include  int main(void) { int i1,i2; float f1,f2; double d1,d2; printf("数据大小:int,%d;float,%d;double,%d\n", sizeof(i1),sizeof(f1),sizeof(d1)); /* 输出类型宽度 */ printf("%ld,%ld\n",&i1,&i2);/* 输出变量地址 */ printf("%ld,%ld\n",&f1,&f2); printf("%ld,%ld\n",&d1,&d2); return 0; }
解释: (1)变量存储空间的分类顺序:先声明的后分 配;撤销的顺序与之相反:先建立的后撤销。这种 机制就称为栈机制,好像往一个只能允许进出一个 盘子的桶里放盘子,先放进的后拿出。在C语言程序 中,局部变量就是被分配在栈区的,并且是以高端 为栈底建立的。所以后建的变量的地址小。 (2)每个变量只有一个地址,但占用的空间不 同。空间的大小因类型而异。同时,数据的存储方 式也不同,如实型数据采用浮点存储,而整型数采 用定点存储。
2. 指针的概念 从根本上说,目标程序是按照地址访 问这些程序实体的。C语言不仅提供了用变 量名访问内存数据的能力,还提供了直接 使用内存地址访问内存数据的能力。这个 内存地址就称为指针。它好像一个指路标 指向要访问的内存数据。
6.1.2 指针变量及其定义
1. 指针变量的概念 变量可以用来存放数值(如整数、实数等),也 可以用来存放地址(另一个变量的地址),这种专 门用于存储指针(地址)的变量就称为指针变量。 2. 指针变量的定义 在定义指针变量时,需要用指针声明符*表示 此变量不是一般的变量,而是用来存放其他变量 地址的指针变量。由于每一个变量都是属于一个 特定类型的,因此在定义指针变量时,需要声明 该变量的类型,以便能通过指针能正确访问特定 类型的数据。 定义一个指针的语法格式为: 基类型标识符 * 指针变量名;
6.1.2 指针变量及其定义
int * pi1,*pi2; double *pd1,*pd2; 说明: (1)“基类型”,就是指针要指向的数据的类型。 (2)定义指针变量时,在指针变量名前加符号 “*”。“*”称为
指针声明符,用于说明它后面 的名字是一个指针变量名。例如语句: int i1,i2,*pi1,*pi2; 定义了两个数据变量i1和i2,还定义了两个指 向int类型的指针pi1和pi2。
3. 指针变量的初始化和赋值
和其他变量一样,指针变量也可以初始化。可以用变 量的地址对指针变量进行初始化,但必须注意:该变量的 类型必须和指针变量的基类型相同。也可以用一个指针变 量的值给另一个指针变量赋值,但它们应该具有相同的基 类型。例如: int i1,i2,i3; int *pi1=&i1,*pi2=&i2; pi1=&i3; pi2=pi1; 注意,不要将一个变量的值赋给指向它的指针变量。 例如 pi1=i1;或pi2=i1; 都是错误的。应该是将变量的地址赋给指向它的指针 变量。如: pi1=&i1;或pi2=&i1;
6.1.3 指针变量的引用
1. 引用指针变量 当一个指针变量被初始化或被赋值后,它就 指向一个特定的变量。这时,就可以使用指针访 问它所指向的内存空间。在C语言中使用指针访 问它所指向的内存空间的方法是在指针变量名前 加一个“*”号。例如 int i,*pi; pi = &i; *pi=5; 此处的“*”是“指针运算符”。又称为“间 接访问运算符”,它作用于指针变量。*pi表示指 针变量pi所指向的存储空间,即变量i。*pi相当于 变量i。
2. 使用指针变量需要注意的问题 (1)使用指针,首先应当区分指针变量与它所指向 的存储单元之间的不同。 #include  int main(void) { int i1=10,i2=20,*p,*p1,*p2; p1=&i1;p2=&i2; printf (“i1=%d,*p1=%d;i2=%d,*p2=%d\n”,i1,*p1,i2,*p2); p=p1;p1=p2;p2=p; printf (“i1=%d,*p1=%d;i2=%d,*p2=%d\n”,i1,*p1,i2,*p2); }
p1
&i2 i2
交换前指 针指向 交换后指 针指向 i1
p2
&i1
#include  int main(void) { int *p1,*p2,i1=10,i2=20,i; p1=&i1;p2=&i2; printf (“i1=%d,*p1=%d;i2=%d,*p2=%d\n”,i1,*p1,i2,*p2); i=*p1;*p1=*p2;*p2=i; printf (“i1=%d,*p1=%d;i2=%d,*p2=%d\n”,i1,*p1,i2,*p2); }
i=*p1 p1 &i2 i2 *p1=*p2 ② i1 ③*p2=i i ①
p2
&i1
(2)可以引用指针所指向的单元的值,但应 注意,指针必须经过初始化或赋值,使它有确定 的值,指向有效的程序实体,才能正确地引用其 指向的单元的内容。如果指针变量未经赋值,它 并不是没有值,而是一个未知或不确定的值,它 所指向的存储单元也是未知的或不确定的。在未 知和不确定的存储单元中存储的可能是无用数据, 也可能是系统的重要数据。读取无用数据的操作 是无意义的;有些非法入侵者也可以利用这种方 法获取系统的重要数据;而向存储有重要数据的 位置写入新的数据可能会造成系统的潜在危险, 甚至可能造成系统瘫痪。所以,通常把没有指向 有效程序实体的指针称为无效指针。
3. 关于运算符“*”和“&”的讨论 指针和
所指向的存储单元(变量)之间可以进行两种 运算:“*”和“&”。假定有定义: int x=10; int *px=&x; 则通过“*”和“&”运算,可以建立如下相当关系: px ~ &x: 指针px的值就是变量x的地址。 px ~ x: *px就是px所指向的变量,即x。 : *&x ~ px ~ x: 先对x取地址,就是x的指针&x,再对 指针进行间接访问,就是变量x。 &*px ~ &x ~ px:先对指针进行间接访问运算就得到x, 再对x取地址,也就是x的指针。 它们都是一元运算符,具有自右向左的结合性,并且 优先级高于算术运算而低于自增/自减运算
符。例如, *px++,相当于*(px++),即取与px所指单元相邻的 前一个单元中的内容。 (*px)++ ,为先取px所指单元的值,然后将其加1。
6.1.4 指针的移动和比较
指针不能进行下列运算: 两个指针相加、相乘、相除、移位;、 不同指针之间的相减、赋值。 指针可以进行的运算: 移动; 同类型指针的比较和相减运算;
? ? ?
1. 移动 通过移动指针可以改变指针指向的内存 位置。移动指针的方法有两种: ? 同类型指针间赋值; ? 与小整数相加、减. (1)同类型指针间赋值 同类型指针间赋值,就是使一个指针指 向另外一个指针所指向的位置。
#include  int main(void) { int i1,i2; int *pi1=&i1,*pi2=&i2; printf(“指针间赋值前: pi1=%ld,pi2=%ld\n",pi1,pi2); pi1=pi2; /* 指针间赋值 */ printf(“指针间赋值后: pi1=%ld,pi2=%ld\n",pi1,pi2); return 0; } 运行结果: 指针间赋值前:pi1=215052,pi2=215048 指针间赋值后:pi1=215048,pi2=215048
(2)指针与小整数相加、减 #include  int main(void) { int i,*pi; double d,*pd; pi=&i;pd=&d; printf("pi=
%ld,pi+1=%ld\n",pi,pi+1); printf("pd=%ld,pd+1=%ld\n",pd,pd+1); return 0; } 运行结果: pi=215052,pi+1=215056 pd=215040,pd+1=215048 通过指针与小整数的相加减去引用它所指向的数据单 元是很危险的。
指针变 量
地址
地址 指针变 量 1245040 pd
赋值前
赋值后
pi1 pi2
1245048 1245052 1245056
pd+1 pi pi+1
1245048 1245052 1245056
2. 同类型指针间的比较和相减运算 指针间的的比较和相减运算,主要用于指向 同一数组的两个元素的两个指针之间,例如有一 整型数组a,如已定义: int a[10]; int*p1=&a[1],*p2=&a[5]; 如p1的值为2000,p2的值为2016,则p2-p1的 值不等于16,而等于两个数组元素下标之差,即 两个元素之间的元素个数。 指向同一数组不同元素的两个指针之间的关 系运算,是比较它们之间的地址大小。如果p1>p2, 表示p1指向下标值小的元素,p2. 指向下标值大的 元素。如果两个指针相等,表明它们指向同一数 组元素。
6.1.5 指向指针变量的指针与多级指针
一个指针变量可以指向一个整型
数据, 或一个实型数据,或一个字符型数据,也 可以指向一个指针型数据。这就是指向指 针的指针。指向指针的指针形成二级指针。
二级指针 一级指针 ppd pd doublel类型
ppi
pi
int类型
#include int main(void) { int i=8,*pi,**ppi; pi=&i;ppi=π printf("&i=%ld,pi=%ld\n”,&i,pi); printf("&pi=%ld,ppi=%ld\n”,&pi,ppi); printf("i=%d,*pi=%ld,**ppi=%ld\n”,i,*pi,**ppi); return 0; } 运行结果:
6.1.6 指向void类型的指针 指向void类型的指针
ANSI C标准允许使用空基类型(void)指针, 即不指定指针指向一个固定的类型,定义形式为: void *p; 这表示指针变量p不指向一个确定的类型数据。 它的作用仅仅是用来存放一个地址,而不能指向 非void类型的变量。例如下面的写法是不对的: int *p1; void *p2; int i; p2=&i; printf (″%d″,*p2);
如果确实需要将&i的值放在p2中,应先进行强制类型 转换,使之成为(void *)类型,在将p2赋值给p1时,同样应 进行类型转换。例如: p2=(void *)&i; p1=(int *)p2; printf (″%d″,*p1); 注意:执行p1=(int *)p2后,p2本身的类型并没有改变。 只是在向p1赋值前先进行转换,生成一个(int *)型的临时数 据赋给p1。 p1 也可以定义一个返回基类型为void的指针的函数: void *f (int x,int y) 函数f带回的是一个基类型为“空”的地址。如果想在主调 函数中引用此地址,也需根据需要进行强制类型转换。例 如: int a,b; char *p; void *f(int,int); p=(char*)f(a,b);
6.2 指针与数组
6.2.1 数组元素的指针引用 1. 一维数组元素的指针引用 通过对数组的讨论,已经知道数组元素在内 存是连续存储的,并通过下标引用数元素。下标 增1,数据改变一个数据单元位置。在学习了指针 之后,已经看到定义了一个指针后,可以通过指 针的移动来引用连续的存储单元。前已说明,数 组名代表数组首元素的地址,即是一个指针。那 么,数组元素和指针之间有什么关系呢? C语言的数组元素有两种引用方式:下标引 用方式和指针引用方式。请看下面的例子。
sizeof 指针#include  int main(void) { int a[5]={1,3,5,7,9},i,*p; printf("下标法:"); for(i=0;i<5;i++) printf ("%d,",a[i]); printf ("\n数组名法:"); for(i=0;i<5;i++) printf ("%d,",*(a+i)); printf ("\n指针变量法:"); for(p=a;p<a+5;p++) printf ("%d,",*p); printf ("\n"); return 0; }
? ? ?
运行结果如下: 下标法:1,3,5,7,9, 数组名法:1,3,5,7,9, 指针变量法:1,3,5,7,9,
指针变量法 *(p=a) *p++ *p++ *p++ *p++ 数组名法 *a *(a+1) *(a+2) *(a+3) *(a+4) 下标法 a[0] a[1] a[2] a[3] a[4] 1 3 5 7 9
2. 多维数组元素的多级指针引用 C语言用一维数组来解释多维数组:例 如把二维数组解释为以一维数组为元素的 一维数组;把三维数组解释为以
二维数组 为元素的一维数组;……。对一个二维数 组a来说,可以把它看成是由下列元素组成 的一维数组: a[0],a[1],a[2],…,a[I],… 这里,a[i]既是是广义一维数组a的一个 元素,又是一个一维数组a[i]的名字,是指 向a[i]的起始元素的指针常量。
二级(行) 一级(列) 指 针 指 针 a a[0] a[0]+1 a[0]+2 (a+1) a[1] a[1]+1 a[1]+2
元 素 地 址 (&a[0][0]) (&a[0][1]) (&a[0][2])
数组元素 一级(列) 二级(行) 指针引用 指针引用 a[0][0] a[0][1] a[0][2]
*a[0] *(a[0]+1) *(a[0]+2) *a[1] *(a[1]+1) *(a[1]+2)
**a *(*a+1) *(*a+2) **(a+1) *(*(a+1)+1) *(*(a+1)+2)
(&a[1][0]) (&a[1][1]) (&a[1][2])
a[1][0] a[1][1] a[1][2]
由图中可以得到如下结论: (1)二维数组a,可以看成由两个元素组成的向量, 这两个元素一个称为a[0],一个称为a[1]。按照上节的讨论, 数组名a指向a[0],a+1指向a[1]。 而a[0]和a[1]本身又都是一
维数组,它们分别由 {a[0][0],a[0][1],a[0][2]}和{a[1][0],a[1][1],a[1][2]}组成。 由于数组名是指针,所以a[0]和a[1]都是一级指针,它 们的基类型(所向的存储单元中的数据的类型)是int类型。而 ( ) int 数组a是由两个一级指针组成的数组,这两个指针具有相同 a 的类型(指向基类型为int的指针变量),并且a指向a数组首元 素(即a[0]的首地址&a[0])。所以,a是一个指向指针的指 针,即二级指针。也就是说,一个二维数组名是一个二级 指针。 (2)从图中可以看出,a[0]的值是&a[0][0](例如 1245032),a的值是a[0]地址,实际上a[0]地址也与&a[0][0] (即1245032)相同。
(3)从图中可以看出,一个二维数组的元素可以用下 标法引用,也可以用一级指针引用,还可以用二级指针引 用。有下面的引用关系: a[i][j] ~ *(a[i]+j) ~ *(*(a+i)+j) #include  #define N 2 #define M 3 int main(void) { int i,j,a[N][M]={{10,11,12},{20,21,22}}; for(i=0;i<N;i++) for(j=0;j<M;j++) printf("a[%d][%d]=%d,*(a[%d]+%d)=%d,*(*(a+%d)+%d )=%d\n", i,j,a[i][j],i,j,*(a[i]+j),i,j,*(*(a+i)+j)); }
#include  #define N 2 #define M 3 int main (void ) { static int a[N][M]={1,2,3,4,5,6}; int *arr[N]={a[0],a[1]}; int i,j,**p=arr; for (i=0;i<M;i++) printf ("%d ",*(*p+i)); printf("\n");
for(j=0;j<N;j++) for (i=0;i<M;i++) printf ("%d ",*(*(p+j)+i)); printf("\n"); return 0; } 运行结果如下: 123 123456
二级指针变量p p p+1 arr[0] arr[1]
指针数组arr a[0] a[1] *p
二维数组a 1 2 3 *(p+1) 4 5 6 a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2]
*p+1
*(p+1)+1 *(p+1)+2
6.2.2 多字符串的存储与处理
前面介绍了多级指针与多维数组。现在 讨论用什么样的存储方式处理多个字符串 比较合适。 假定有多个字符串,按照数组与指针的 关系组合,可以有如下几种定义方式: char str[M
][N]; char *str[N]; char **str;
在这3种声明中,每一种声明都使用了两个类 型声明符(char、[ ]和*)。在这种情况下,如何确定 所声明的变量的含义呢?一般说来,这些类型声 明符实际上是运算符在声明中的应用。它们虽然 在这里不是作为运算符使用,但在优先级和结合 性上还是要按照运算符的规则与名字相结合。因 此可以采用下面的方法来理解: 首先按照优先级看哪个声明符应当与名字相结 合。在优先级相同的情况下按结合性看哪个声明 符与名字结合。 剩下的声明符是补充说明。 下面结合图来对上述3种表示方式的意义进行 说明。假定图中需要存储的字符串有下列5个: “C”
“C++” “Visual BASIC” “Java” “Ada” (1)char str[M][N]; ① 在这个声明中,有两个相同的数组类型说明符[], 按照由左向右的结合性,可以首先确定str是一个大小为M 的向量(一维数组)。 ② 对于数组自然要说明类型。由剩下的char和[N]补充 说明,这个数组是长度为N的字符数组类型,即它的每个 元素都是长度为N的字符数组。 所以,这个语句定义的str是字符数组类型的数组,或 者说str是二维字符数组。如果在定义数组时进行以下的初 始化: char str[5][13]={ “C”,“C++”,“Visual BASIC”,“Java”,“Ada”}; 则数组中的存储情况如图所示。用这种方式存储的几 个字符串占有连续的存储空间。
(2)char *str[N]; ① 在这个声明中,有两个不相同的数组类型声明符[] 和*。其中数组类型声明符的优先级别高,可首先确定str是 一个大小为N的一维数组。 ② 剩下的char和*补充声明:这个数组的每个元素都是 字符类型指针。 所以,这个语句定义的str是字符指针数组。其存储方 式如图所示。用这种方式存储的几个字符串长度可以不同, 不一定占有连续的存储空间。 (3)char **str; ① 在这个声明中,有两个相同的类型声明符*。按照 由右向左的结合性,可以首先将后面的一个*与名字结合, 得出结论:str是一个指针。 ② 对于指针就要声明指向什么。由剩下的char和*补充 声明:这个指针是指向字符指针的。 所以,这个语句定义的str是指向字符指针的指针,即 指向字符的二级指针。
二维字符 ① []的结合为“左向右” 数组 故str是一个大小为M的 str 数组 str[0] char str[M] [N] str[1] str[2] str[3] ②str的元素是字符数 组 str[4]
字符 ① 故 是 一个大小为 的数组 数
C \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 有 连 续 存 储 空 间 C + + \0 \0 \0 \0 \0 \0 \0 \0 \0 V i s u l B A S I C \0 J a v a \0 \0 \0 \0 \0 \0 \0 \0 A d a \0 \0 \0 \0 \0 \0 \0 \0 \0
不 一 定 占 有 连 续 存 储 空
的元素是字符

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。