slide1 n.
Download
Skip this Video
Loading SlideShow in 5 Seconds..
第十章 指 针 PowerPoint Presentation
Download Presentation
第十章 指 针

Loading in 2 Seconds...

play fullscreen
1 / 81

第十章 指 针 - PowerPoint PPT Presentation


  • 100 Views
  • Uploaded on

第十章 指 针. 指针的基本概念 指向变量的指针变量 指向数组元素的指针变量 指向字符串的指针变量 指针变量做函数参数 指针数组和指向指针变量的指针. 10.1 地址和指针的概念. 地址和指针的概念. 首先回忆一下:内存单元的组织形式?. “ 位”和“字节”;“内存单元的内容”和“内存单元的地址”. “ 位”是最小的电子线路单元,每一个“位”可以保存一个二进制数,即 0 或 1 。 “字节”是由若干个“位”组成的,是基本的数据存储单元,即编译系统是以“字节”为基本单元为数据分配存储空间的。.

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about '第十章 指 针' - jerry-mays


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
slide1
第十章 指 针
  • 指针的基本概念
  • 指向变量的指针变量
  • 指向数组元素的指针变量
  • 指向字符串的指针变量
  • 指针变量做函数参数
  • 指针数组和指向指针变量的指针
slide3
地址和指针的概念

首先回忆一下:内存单元的组织形式?

“位”和“字节”;“内存单元的内容”和“内存单元的地址”

  • “位”是最小的电子线路单元,每一个“位”可以保存一个二进制数,即0或1。
  • “字节”是由若干个“位”组成的,是基本的数据存储单元,即编译系统是以“字节”为基本单元为数据分配存储空间的。
  • 内存中每一个字节都有一个唯一的“编号”,这就是内存单元的地址。
  • 字节单元中保存的数据,即是内存单元的内容。
slide4
地址和指针的概念

同时,编译系统把每个变量所占字节单元的地址信息和变量名对应起来

程序定义变量int a;

编译系统为每一个定义的变量分配相应数目的字节单元

编译系统首先根据变量名与地址的对应关系,找到变量a的地址

程序中访问该变量a=234;

然后,把常量234放到由该地址所指向的内存单元去

再回忆一下:数据在内存中是如何存储和如何读取的?

1. 定义变量,系统分配内存单元:

2. 访问变量:首先确定变量的地址

slide5
地址和指针的概念

把变量a 的地址保存到指针变量中

p = &a;

程序定义普通变量 int a ;

程序定义指针变量 int *p ;

然后把常量234放到该值(地址)所指向的内存单元去

首先取出变量 p的值(实际上就是a 的地址)

*p=234 ;

这种利用变量名及其对应的地址信息(该信息由系统自动维护,无法操纵)存取变量值的方式称之为“直接访问”方式。

我们也可以采取另一种称之为“间接访问”的方式,即把某个变量的地址保存在另一个变量里。

“直接访问”方式: a = 234;

“间接访问”方式: *p = 234;

slide6
地址和指针的概念

指针就是内存单元的地址。

专门用于保存地址的变量叫做

指针变量。

指针变量的值就是地址。指针

变量指向该地址所指向的单元。

slide7

常量

10

0

23

int b

int c

int* p

int** x

int* q

int** y

指针的指针变量

变量

int a

地址常量 &a &b &c

10

p放的是a单元的地址,所以*p==10

指针变量

指针变量的地址&p &q

&a

p的内容是&a: a的地址/a的指针, 所以p指向a

a单元的内容 a

a单元的地址(&a)所指单元(a)的内容 *(&a)

&p

**x 即*(*x) 即*p 即10

slide9
变量的指针和指向变量的指针变量
  • 变量的指针就是变量的地址(编译系统分配给该变量的内存单元的地址)
  • 用于保存变量的指针的变量就是指向变量的指针变量。
  • 定义指针变量

定义的一般形式: 类型说明符 *指针变量名;

如: int *pa, *pb;

float *pf1, *pf2;

char *ch1, *ch2;

说明:

① “类型说明符”表示该指针变量可以指向的变量的类型,即该指针变量可以而且只能保存这种类型的变量的地址;

② 一个“*”号表示定义的变量是一个能够保存普通变量地址的指针变量,而不是一个保存具体数据的普通变量。

slide10
指针变量的赋值

指针变量只能保存地址。可以用下面的三种方式给它赋值:

1. 使用取地址运算符 & ,把地址值赋给指针变量

比如:

int *pa, *pb, a, b;

pa = &a; pb = &b;

2. 把另一个指针变量的值赋给指针变量

比如:

int *pa, *pb, a, b;

pa = &a; pb = pa;

3. 给指针变量赋NULL值(NULL是在 stdio.h 头文件中定义的符号常量,该常量代表整数0),表示该指针变量本身的值为0,它不指向任何内存单元。

比如:

int *pa, *pb, *pc;

pa = NULL;pb = 0; pc = ‘\0’;

slide11
使一个指针变量指向同类型的变量

pb

b

pa

a

&b

&a

?

定义指针变量后,怎样使它指向一个同类型的普通变量呢

方法:只需把某个同类型变量的地址(指针)赋值给该指针

变量即可。然后,我们就可以说,该指针变量指向了某个普

通变量。

比如:

int *pa, *pb, a, b;

pa = &a; pb = &b;

slide12
使一个指针变量指向同类型的变量

注意:某个类型的指针变量只能保存同类型的变量的指针

(即一个指针变量只能指向同类型的变量)

比如:

int *pa, *pb, a, b;

float *p1, *p2, f1, f2;

pa = &a; pb = &b; /* 正确的赋值 */

p1 = &a; p2 = &b; /* 错误的赋值 */

p1 = a; p2 = 1234; /* 错误的赋值 */

Error: Cannot convert 'int *' to 'float *'

Error: Cannot convert 'int' to 'float *'

slide13
两个有关指针的运算符

&a

pa

a

*pa

① 取地址运算符:& ( 只能取变量或数组名,数组元素的地址)

② 指针运算符(间接访问运算符):* (单目)

优先级:与取地址运算符&同级

结合性:右结合性

用法: *指针变量名 (访问地址所指单元(的内容))

比如:int a, *pa; pa = &a;

则: &a 为变量a的地址;等价于 pa

*pa 代表指针变量pa所指向的存储单元,等价于 a

slide14
通过指针变量访问变量:例10-1

#include “stdio.h”

void main( )

{ int a, b;

int *pa, *pb;

a = 100; b = 200;

pa = &a; pb = &b;

printf(“a=%d, b=%d\n”, a, b);

printf(“a=%d, b=%d\n”, *pa, *pb);

}

运行结果: a=100, b=200 a=100, b=200

让pa和pb 分别指向变量a和b

使用变量名访问变量:输出变量a和b的值

使用变量的地址访问变量:输出pa和pb指向的内存单元的值。实际上就是变量a和b的值

slide15
有关指针变量的说明

① 一个指针变量可以指向任何一个同类型的普通变量;但是,

在某一个时刻,它只能指向某一个同类型的变量

int *pa, *pb, a, b;

pa = &a; pb = &b; pa = &b; pb = &a;

② 让指针变量指向某个同类型普通变量的方法是:把该普通

变量的地址保存到指针变量中。

③ 必须对指针变量进行了正确合法的初始化后,才能使用该指针

变量访问它所指向的内存单元。未指向内存单元的指针变量,不能用。

int *pa, *pb, a, b;

pa=0x1000;*pa = 100;

*pb=200;

或 printf(“*pa=%d\n”, *pa);

slide16
有关指针变量的说明

④ 即使对指针变量进行了正确合法的初始化后,也只能用该

指针变量访问合法的允许访问的内存单元。不能使用该指针

变量去随意访问其它不确定的内存单元,否则,结果是不可

预料的。

int *pa, *pb, a, b;

pa = &a; *pa = 100;

pb = &b; *pb = 200;

printf(“value after a = %d\n”, *(pa + 1));

printf(“value after b = %d\n”, *(pb + 1));

*(pa + 1) = 1000;

*(pb + 1) = 2000;

正确安全使用指针变量

错误使用指针变量

错误使用指针变量而且可能很危险

slide17
有关运算符*和&的说明

pa

100

200

  • ① 假设有 int a, *pa; pa = &a; 则:
  • &*pa 相当于 &a,因为 *pa 就相当于 a
  • *&a 相当于 a ,因为 &a 就相当于 pa,*pa 相当于 a
  • (*pa) ++ 相当于 (a ++)

② 注意 (*pa) ++ 和 *pa ++ 的区别

由于 ++ 和 * 为同一运算级别,则根据结合性, *pa ++ 相当于*(pa ++) ,与(*pa) ++是完全不同的。

*(pa ++):先得到pa当前指向的单元的值(未用),再使pa自增

(*pa) ++:是pa所指向的单元的值自增

*(pa ++) :得到100(未用),pa自增

(*pa) ++ :pa所指向单元的值100加1,

得到101,而pa不变,

slide18
指向变量的指针变量做函数参数

?

为什么要引入指针变量?指针变量的用途到底是什么?

指针变量的用途存储地址值,可以表示动态分配的构造型数据,处理字符串,等等,其中一个用途是做函数的参数。

回忆一下:

以前的函数,都是用普通变量做形参和实参。由于函数调用时参数的传递过程是由“实参→形参”的“单向值传递”,函数接收到的只是实参的值(准确的说,是实参的拷贝),所以在函数中我们无法改变实参的值。

但是:

如果我们能把要修改的变量的地址传给函数,而函数又能接收到这个地址,那么在函数中,我们就可以改变这个地址所指向的单元,而这个单元实际上就是要修改的变量的单元,那么就达到了修改该变量的目的。

slide19
指针变量做函数参数:例10-2

#include “stdio.h”

void Swap(int *p1, int *p2)

{

int temp;

temp = *p1; *p1 = *p2; *p2 = temp;

}

void main( )

{ int a, b; int *pa, *pb;

a = 100; b = 200;

pa = &a; pb = &b;

printf(“a=%d, b=%d\n”, *pa, *pb);

Swap(pa, pb);

printf(“a=%d, b=%d\n”, a, b);

}

用指针变量作形参

交换两个形参指针变量所指向的内存单元的值

也可以直接用变量的地址作实参,即:

Swap(&a, &b);

用指针变量作实参

slide20
指针变量做函数参数的执行过程

pa

a

pb

b

&a

100

&b

200

p1

p2

&a

&b

pa

a

pb

b

&a

200

&b

100

  • 调用函数之前:

100

200

  • 调用函数开始:
  • pa和pb分别单向值传递给p1和p2

然后交换p1和p2所指向的内存单元(实际上就是a和b)的值

  • 调用函数返回:
  • p1和p2释放单元,但a和b的值已经交换
slide21
错误的Swap函数

考虑一下:如果不用指针变量做参数,仍然用普通变量做参数实现Swap函数,能否达到交换两个变量值的目的?

#include “stdio.h”

void Swap(int, int);

void main( )

{ int a =100, b = 200

printf(“a=%d, b=%d\n”, a, b);

Swap(a, b);

printf(“a=%d, b=%d\n”, a, b);

}

void Swap(int p1, int p2)

{ int temp;

temp = p1; p1 = p2; p2 = temp;

}

用普通变量作实参

用普通变量作形参

交换两个形参变量的值

slide22
错误的Swap函数的执行过程

a

b

100

200

p1

p2

100

200

a

b

100

200

  • 调用函数之前:
  • 调用函数开始:
  • a和b分别单向值传递给p1和p2

200

100

然后交换两个形参变量p1和p2的值

  • 调用函数返回:
  • p1和p2释放单元,但a和b的值却没有变化,因为形参不会把值传回给实参。
slide23
课堂练习

◆ 1. 下若有语句 char *p1, *p2, *p3, *p4, ch;则不能正确赋值的语句是( ) 。

(A) p1 = &ch; scanf(“%c”, p1);

(B) *p2 = getchar( );

(C) p3 = &ch; *p3 = getchar( );

(D) scanf(“%c”, p4);

答案: (B) (D)

◆ 2. 对于两个同类型的指针变量,它们之间不能进行的运算是( ) 。

(A) + (B) - (C) = (D) ==

答案: (A)

slide24
课堂练习

◆ 3. 下面的Swap函数,能否达到交换两个变量值的目的?

#include “stdio.h”

void Swap(int*, int*);

void main( )

{ int a =100, b = 200, *pa, *pb;

pa = &a; pb = &b;

printf(“a=%d, b=%d\n”, a, b);

Swap(pa, pb);

printf(“a=%d, b=%d\n”, *pa, *pb);

}

void Swap(int *p1, int *p2)

{ int *temp;

temp = p1; p1 = p2; p2 = temp;

}

答案:不能。

因为该函数交

换的是两个形

参指针变量本

身的值,而并

没有交换它们

所指向的变量

的值。

变量的值,通过指针变量形参函数调用,有可能改变

指针变量的值,通过指针变量形参函数调用,不可能改变

slide26
数组和指向数组元素的指针变量

◆ 数组的每一个元素都相当于一个同类型的变量,也都在内存中占

据存储单元,也都有相应的地址。

◆ 既然可以定义指向变量的指针变量,当然也可以定义指向数组元

素的指针变量,用它保存的是数组中某一个元素的地址。

◆ 数组的名字就是数组的首地址(即数组第一个元素的地址)

◆ 用于保存数组名字的变量就是指向数组元素的指针变量。

指向数组元素的指针变量和指向变量的指针变量是同一种类型的

指针变量,指向数组元素的指针变量加,减数在一定范围内是安全的

◆ 引用数组元素既可以用数组名加下标法,也可以用指针法。

其本质都是使用指向数组元素的指针。

slide27
指向数组元素的指针变量的定义
  • 指向数组元素的指针变量的定义
  • 定义方法: 类型说明符 *指针变量名;

int *pa, *pb, a[10], b[10];

float *pf1, *pf2;

char *str1, *str2;

可见,定义指向变量的指针变量和定义指向数组元素的指针

变量的方法是一样的。

slide28
使一个指针变量指向同类型的数组元素

?

定义指针变量后,怎样使它指向一个同类型的数组元素呢

方法:只需把某个同类型的数组的首地址(指针)赋值给该

指针变量即可。然后,我们就可以说,这个指针变量指向了

该数组元素。注意:有的地方把它称为指向数组的指针变量

其实,是数组元素的指针变量

比如:

int *pa, *pb, a[10], b[10];

pa = &a[0]; pb = &b[0];

/*数组首地址就是数组第一个元素的地址*/

或者:

int *pa, *pb, a[10], b[10];

pa = a; pb = b; /*数组名代表首地址*/

pa=&a

slide29
通过指针变量引用数组元素
  • 能够通过指针变量引用数组元素的前提:已经定义了一个指针变量,并且已经给它赋值使它指向某个同类型的数组元素。在有效的范围内对它加,减整数, 运算后它指向仍然是数组元素
  • 访问方法: 指针法或下标法

如果指针变量p的初值为&a[0],则:

① p+i 或 a+i就是a[i]的地址,即它们指向a数组中下标为 i 的元素

p + i ==&a[i] &a[i] == a + i

② *(p+i) 或 *(a+i)就是它们所指向的数组元素,即 a[i]

*(p + i) ==a[i] *(a + i) == a[i]

③ 指针变量也可以使用下标法,如 p[i] 和 *(p + i) 等价

slide30
通过指针变量引用数组元素

&a[0]

=a+0

=p+0

*(a+0)=

*(p+0)=

a[0]

&a[1]

=a+1

=p+1

*(a+1)=

*(p+1)=

a[1]

&a[2]

=a+2

=p+2

*(a+2)=

*(p+2)=

a[2]

&a[9]

=a+9

=p+9

*(a+9)=

*(p+9)=

a[9]

如有: int a[10], *p; p = a; 或 p = &a[0];

  • 注意: 在对指向连续存储单元的指针变量进行加减运算时,数字1并不代表加减一个字节,而是代表该指针变量所属数据类型的数据所占的字节单元的长度。如int型指针变量加减1时,指针实际移动4个字节;float 形指针变量加减1时,指针实际移动8个字节;依次类推。
10 4 1
通过指针变量引用数组元素:例10-4-1

◆ 编程从键盘接收数组中的全部元素并输出。

/* 方法1:用数组名和下标 */

#include “stdio.h”

void main( )

{ int a[10], n;

for(n = 0; n < 10; n ++)

scanf(“%d”, &a[n]);

printf(“\n”);

for(n = 0; n < 10; n ++)

printf(“%d ”, a[n]);

}

特点:

直观易懂。

系统内部计算元素地址。每访问一个新的元素就重新计算一次地址。

10 4 2
通过指针变量引用数组元素:例10-4-2

◆ 编程从键盘接收数组中的全部元素并输出。

/* 方法2:用数组名+偏移量得到元素地址*/

#include “stdio.h”

void main( )

{ int a[10], n;

for(n = 0; n < 10; n ++)

scanf(“%d”, a + n);

printf(“\n”);

for(n = 0; n < 10; n ++)

printf(“%d ”, *(a + n));

}

特点:

利用数组名这个指针常量加上一个变化的地址偏移量来得到元素地址。

每访问一个新的元素就重新计算一次地址。

10 4 3
通过指针变量引用数组元素:例10-4-3

◆ 编程从键盘接收数组中的全部元素并输出。

/* 方法3:用指针变量得到元素地址*/

#include “stdio.h”

void main( )

{ int a[10], n; int *pa = a;

for(n = 0; n < 10; n ++)

scanf(“%d”, pa + n);

printf(“\n”);

for(n = 0; n < 10; n ++)

printf(“%d ”, *(pa + n));

}

特点:

利用一个指向数组的指针变量加上一个变化的地址偏移量来得到元素地址。

指针变量本身的值没有变化。

10 4 4
通过指针变量引用数组元素:例10-4-4

◆ 编程从键盘接收数组中的全部元素并输出。

/* 方法4:用指针变量得到元素地址*/

#include “stdio.h”

void main( )

{ int a[10], n; int *pa = a;

for(n = 0; n < 10; n ++)

scanf(“%d”, pa ++);

printf(“\n”); pa = a;

for(n = 0; n < 10; n ++)

printf(“%d ”, *(pa ++));

}

特点:

利用一个指向数组的指针变量来得到元素地址。

指针变量本身的值在变化。

使用普通变量作循环控制变量。

要时刻注意指针变量的当前值。存在安全隐患

10 4 5
通过指针变量引用数组元素:例10-4-5

◆ 编程从键盘接收数组中的全部元素并输出。

/* 方法5:用指针变量得到元素地址*/

#include “stdio.h”

void main( )

{ int a[10], n; int *pa = a;

for(n = 0; n < 10; n ++)

scanf(“%d”, pa ++);

printf(“\n”);

for(pa = a; pa < a+10; )

printf(“%d ”, *(pa ++));

}

特点:

指针变量本身的值在变化。

使用指针变量作循环控制变量。

要时刻注意指针变量的当前值。

要正确确定循环控制变量初值和终值。

slide36
通过指针变量访问数组元素时的注意事项

注意:在利用指针变量本身的值的改变来访问数组元素时,

要时刻注意指针变量的当前值。

#include “stdio.h”

void main( )

{ int a[10], n; int *pa = a;

for(n = 0; n < 10; n ++)

scanf(“%d”, pa ++);

printf(“\n”); pa = a;

for(n = 0; n < 10; n ++)

printf(“%d ”, *(pa ++));

}

这一句不能少,否则后面输出的结果就不对了。因为此时指针已经指向数组的有效范围之外去了。

slide37
指向数组元素的指针变量的有关运算

如有: int a[10], *pa; pa = a; 或 pa = &a[0];

① pa ++ 或 pa += 1,使 pa 指向下一个元素(即得到

下个元素的地址)

pa -- 或 pa -= 1,使 pa 指向上一个元素(即得到上

个元素的地址)

② * pa ++ 等价于 * (pa ++) ,即先得到 pa 当前指

向的元素的值,然后再使 pa 自增,从而指向下一个元素

③ * ++ pa 等价于 * (++ pa) ,即先使 pa 自增,然

后得到 pa 当前指向的元素的值

④ (* pa) ++ ,则是表示 pa 当前指向的元素的值加1

slide38
指向数组元素的指针变量做函数参数

同指向变量的指针变量一样,指向数组元素的指针变量除了用于访问数组元素之外,主要的用途仍然是用作函数的参数

这时:主调函数向被调函数传递的值是指针(即地址),

因此可以在被调函数内访问(输出或改变)这些地址所指向

的单元的内容。

所谓用指针变量做函数参数,是指用可以保存和接收地址

信息的数据来做函数参数,比如指针变量或数组的名字。因

此有以下几种组合:

1. 实参和形参都是数组名

2. 实参和形参都是指针变量

3. 实参是数组名,形参是指针变量

4. 实参是指针变量,形参是数组名

slide39
指向数组元素的指针变量做函数参数:例

◆编程从键盘接收数组中的全部元素并输出。

现在用函数实现:

把数组元素的输入和输出分别用两个函数来完成。这时就需

要在主函数和用户自定义函数之间传递数组的首地址信息。

而转递地址信息必须使用指针变量或数组名。

slide40
指向数组元素的指针变量做函数参数:例

#include “stdio.h”

void InputArray(int arr[ ], int n)

{ for(int i = 0; i < n; i ++)

scanf(“%d”, &arr[i]);

}

void OutputArray(int arr[ ], int n)

{ for(int i = 0; i < n; i ++)

printf(“%d ”, arr[i]);

}

void main( )

{

int a[10], n = 10; int *pa = a;

InputArray(a, n);

OutputArray(a, n);

}

数组名作形参

数组名作实参

slide41
指向数组元素的指针变量做函数参数:例

数组名作形参

#include “stdio.h”

void InputArray(int arr[ ], int n)

{ for(int i = 0; i < n; i ++)

scanf(“%d”, &arr[i]);

}

void OutputArray(int arr[ ], int n)

{ for(int i = 0; i < n; i ++)

printf(“%d ”, arr[i]);

}

void main( )

{ int a[10], n = 10; int *pa = a;

InputArray(pa, n);

OutputArray(pa, n);

}

指针变量作实参

slide42
指向数组元素的指针变量做函数参数:例

#include “stdio.h”

void InputArray(int *arr, int n)

{ for(int i = 0; i < n; i ++)

scanf(“%d”, arr + i);

}

void OutputArray(int *arr, int n)

{ for(int i = 0; i < n; i ++)

printf(“%d ”, *(arr + i));

}

void main( )

{ int a[10], n = 10; int *pa = a;

InputArray(a, n);

OutputArray(a, n);

}

指针变量作形参

数组名作实参

slide43
指向数组元素的指针变量做函数参数:例

指针变量作形参

#include “stdio.h”

void InputArray(int *arr, int n)

{ for(int i = 0; i < n; i ++)

scanf(“%d”, arr + i);

}

void OutputArray(int *arr, int n)

{ for(int i = 0; i < n; i ++)

printf(“%d ”, *(arr + i));

}

void main( )

{ int a[10], n = 10; int *pa = a;

InputArray(pa, n);

OutputArray(pa, n);

}

指针变量作实参

slide44
课堂练习

◆ 1. 若有语句 int a[ ]={1, 2, 3, 4, 5}, *pa, n;

pa = a; 0≤n≤4,则( )是对a数组元素地址的正确

引用。

(A) a + n (B) a ++

(C) & pa (D) &*pa

答案: (A) (D)

◆ 2. 若有语句 int a[ ]={1, 2, 3, 4, 5}, *pa, n;

pa = a; 0≤n≤4,则( )是对a数组元素的错误引用。

(A) *(a + n) (B) *(&a[n])

(C) pa + n (D) pa[n]

答案: (C)

slide45
课堂练习◆3. 写出下面程序执行的结果。

#include “stdio.h”

void main( )

{ int a[5] ={1, 2, 3, 4, 5}, n, *pa; for(pa = a; pa < a + 5; pa ++) printf(“%d ”, *pa); *pa = a[0]; printf(“\n”); for(n = 0; n < 5; n ++) *(a + n) = *(a + n + 1); for(n = 0; n < 5; n ++) printf(“%d ”, a[n]);

}

  • 运行结果:
  • 1 2 3 4 5
  • 3 4 5 1
  • 报应用程序出错

第一个打印循环结束时pa指向a+5,系统并没有分配该地址给a数组

将a[0] 赋给该地址上的单元,冲掉了其他内存程序内容。这种破坏

的影响,有后发性,要到运行那个程序时才看得出来

slide47
字符串的指针和指向字符串的指针变量

◆ C语言是用连续的字节单元来保存字符串的,字符串以指针表示。

如果字符数组的初始化值是字符串,字符串就保存在字符数组中

字符串指针和字符数组名是同一个东西。

◆ 字符串的指针就是字符串中第一个字符所在内存单元的地址

◆ 可以定义指向字符串的指针变量,如果给它赋值字符串,它保存

的是字符串中第一个字符所在内存单元的地址。与字符数组名

类似,也是指向连续的字节单元的首地址

◆ 指向字符串的指针变量实际就是一个指向字符型变量的字符指针

变量,它用来存放字符串的指针常量

slide48
指向字符串的字符指针变量的定义
  • 指向字符串的指针变量的定义 char *指针变量名;

char string1[80], string2[80];

char *str1, *str2;

其中,string1和string2、str1和str2都是字符指针类型的数据,都可以保存字符的地址信息,所不同的是:

string1和string2是字符指针常量,它们表示的值无法改变,指向固定的内存单元,可以通过改变数组元素值,在不同时间放不同的字符串到这个固定的内存单元,这些不同的字符串有相同的地址指针常量值

而str1和str2是字符指针变量,它们存储的值可以改变,可以指向任何字符数组指针常量,字符串指针常量,任何字符变量的地址.如果不给它赋值,它不指向任何地址,不指向任何内存单元。

slide49
使一个字符指针变量指向字符串

?

定义字符指针变量后,怎样使它指向一个字符串呢

方法:只需把某个字符串的指针赋值给该字符指针变量即可。

然后,我们就可以说,这个字符指针变量指向了该字符串。

上述操作有两种情况:

1.先定义字符数组保存字符串,然后再定义字符型指针变

量指向该数组首元素;这时指针变量指向的内容可以改变

2.不定义字符数组,直接定义字符型指针变量,然后把字

符串赋值给该指针变量。这时指针变量指向的内容不能改变

注意区分 :指针变量的内容,指针变量指向的内容

slide50
使一个指针变量指向字符串:第一种情况

1.先定义字符数组保存字符串,然后再定义字符型指针变量指向该数组首元素,也就是指向该字符串;

#include “stdio.h”

void main( )

{ char str[80] = “Hello, World”;

char *pstr = str; /* 定义字符型指针变量 */

printf(“%s\n”, str); /* 用数组名访问*/

printf(“%s\n”, pstr); /* 用指针变量访问*/

str[0]=‘A’; *pstr=‘a’;}

str是字符数组名,包含了字符数组的首元素地址信息,相当于一个字符型数据的指针,所以可以把它赋值给一个字符型的指针变量。然后就可以用该指针变量来访问字符串。

slide51
使一个指针变量指向字符串:第二种情况

2.不定义字符数组,直接定义字符型指针变量,然后把字符串赋值给该指针变量。

#include “stdio.h”

void main( )

{ /* 定义字符型指针变量,同时用字符串常量初始化 */

/* 表示把字符串的指针保存在指针变量里 */

char *pstr = “Hello, World”;

printf(“%s\n”, pstr);/* 用字符指针变量访问*/

// *pstr=‘A’;

}

C语言把字符串常量处理成一个字符型指针常量,因此把一个指针常量赋值给一个字符指针变量当然是正确的。字符串常量不能修改

slide52
使一个字符指针变量指向字符串:第二种情况

◆ 也可以先定义字符型指针变量,然后再把字符串常量赋值给它,同样正确。比如:

……

char *pstr;

pstr = “Hello, World”;

……

但是对字符数组却不能这样操作。比如:

……

char str[80];

str = “Hello, World”;

……

为什么不对?

slide53
通过字符指针变量访问字符串
  • 能够通过字符指针变量访问字符串的前提是:已经定义了一个字符型指针变量,并且已经把某个字符串的指针赋值给它。
  • 访问方法: 指针法或下标法

如果pstr的初值为字符串的第一个字符的地址,比如有

char *pstr = “Hello, World”; 则有以下事实:

① pstr + i 就是第 i 个字符的地址,即它指向字符串中的第 i 个字符

② *(pstr + i) 或者 pstr[i] 就是它所指向的字符

③ 指针变量也可以使用下标法,即 pstr[i] 和*(pstr + i) 是等价的

*(pstr + i) 或者 pstr[i]都只能够读指向位置的字符,不

能够向指向位置送值

slide54
通过字符指针变量访问字符串

#include “stdio.h”

#include “string.h”

void main( )

{ char *s1= “hello, world”;

int n;

printf( “%s\n”, s1);

for(n = 0; s1 [n] != ‘\0’; n ++) printf( “%c”, s1[n]);

scanf(“%s”,s1);

strcat(s1,”1234”);

s1[0]=‘H’;

}

10 6 2
通过字符指针变量引用字符数组元素:例10-6-2

◆ 编程把字符串s2复制到字符串s1中

/* 用数组名+偏移量得到元素地址,访问元素*/

#include “stdio.h”

void main( )

{ char s1[80] = “”, s2[ ] = “hello, world”;

int n;

for(n = 0; s2[n] != ‘\0’; n ++)

*(s1 + n) = *(s2 + n);

*(s1 + n) = ‘\0’;

printf( “s1=%s\ns2=%s”, s1, s2);

}

特点:

数组名本身不变也不可能被改变

10 6 3
通过字符指针变量引用字符数组元素:例10-6-3

◆ 编程把字符串s2复制到字符串s1中

/* 用指针变量+偏移量得到元素地址,访问元素*/

#include “stdio.h”

void main( )

{ char s1[80] = “”, s2[ ] = “hello, world”;

int n; char *p1 = s1, *p2 = s2;

for(n = 0; *(p2 + n) != ‘\0’; n ++)

*(p1 + n) = *(p2 + n);

*(p1 + n) = ‘\0’;

printf( “s1=%s\ns2=%s”, s1, s2);

}

特点:

指针变量本身的值没有变化

slide57
通过字符指针变量访问字符串时的注意事项

注意:在利用指针变量本身的值的改变来访问字符串时,要时刻注意指针变量的当前值。

#include “stdio.h”

void main( )

{ char s1[80] = “”, s2[ ] = “hello, world”;

char *p1 = s1, *p2 = s2;

for( ; *p2 != ‘\0’; p1 ++, p2 ++)

*p1 = *p2;

*p1 = ‘\0’;

printf( “s1=%s\ns2=%s”, p1, p2);

}

此处用p1和p2来输出字符串,是得不到正确结果的。

slide58
指向字符串的字符指针变量做函数参数

同指向数组元素的指针变量一样,指向字符串的指针变量除了用于访问字符串之外,主要的用途仍然是用作函数的参数。

  • 这时:主调函数向被调函数传递的值是字符串的指针(即

地址),因此可以在被调函数内访问(输出或改变)这些地址

所指向的单元的内容(实际上就是这个字符串)。

需要强调的是:不管是用数组名做函数参数,还是用指针变量做函数参数,在进行参数传递时,仍然是由“实参→形参”的“单向值传递”。 只不过与用普通变量做函数参数不同的是,用指针变量做函数参数时,传递的这个“值”不是普通的具体的数据,而是地址。

slide59
指向字符串的指针变量做函数参数:例10-7

字符串连接的实际过程:

str1

0

str2

0

◆ 编写函数:

void StringCat(char *str1, char *str2)

实现与系统库函数 strcat 同样的功能,把字符串str2复制

到字符串str1中

分析:

slide60
指向字符串的指针变量做函数参数:例10-7

实现代码:用指针变量作函数的形参

/* StringCat:字符串连接 */

/* str1:目标字符串; str2:源字符串 */

void StringCat(char *str1, char *str2)

{

while (*str1 != ‘\0’ ) str1 ++;

while (*str2 != ‘\0’ )

{

*str1 = *str2;

str1 ++; str2 ++;

}

*str1 = ‘\0’ ;

}

slide61

字符串,字符数组比较 指 针

字符串 常指针 常值

字符数组 常指针 变化值

字符数组如果初始化值是字符串,二者的地址合而为一,是数组地址值

不能取字符串地址,但可以把字符串赋给字符指针变量看到其地址,可以取字符数组地址(&数组名)。

由于字符串的常值性,它是能看不能改的。

#include"stdio.h"

void main()

{ char *p; char *s="1234";

char a[10]="1234";

scanf("%s",p);

scanf("%s",s);

scanf("%s",a);

*(s+1)='x';

*(a+1)='y';}

slide62

10.5 指向函数的指针变量

函数也有地址,可以定义指针变量存放函数的地址.

当其用函数指针变量作为形参时,在一个函数中可以

调用以函数地址实参传给的若干函数

由于应用不多,简单举例说明如下

slide63

int max(int x,int y){return(x>y?x:y);}

int add(int x,int y){return(x+y);}

int ret(intx){return(x);}

float sub(float x,float y){return(x-y);}

int(*f)(int,int);// 定义函数指针变量 f,由于函数有返回类型,形参表, // 所以定义f时要带上

void main()

{ int a=10,b=2,c;

f=max; //f=add ; //f=ret; //f=sub;

c=f(a,b);

}

slide65

函数的返回值是一个指针,是字符串处理,动态使用内存等工作经常遇到的情况。在string.h中声明的许多函数返回字符指针 ,如 char *strcat(char*,const char*);

动态分配的内存,是用地址值表示的,要带到函数外即要返回地址值。

#include<windows.h>

int *dar (int num)

{ int * ar;

ar=(int *)malloc(sizeof(int)*num);

return ar;

}

malloc 向系统请求分配指定数量的内存单元,

若成功,返回分配单元首地址 是单纯地址信息,

没有该单元的结构信息,是void类型

要把单元用于整数存储,要强制成 int *

slide66

函数定义时,将其返回值类型定义为某种数据类型的指针类型,在return 语句中返回该种数据类型变量的地址值

数据类型 * 函数名(形参表) { 。。。return 数据类型变量地址值 ;}能够从函数中返回的地址值,可以是函数中动态申请的地址值,用static定义的局部静态变量的地址,形参中传入地址处理后的地址。不能是自动变量的地址。

如果编函数求A矩阵加B矩阵结果放入C矩阵,函数中C矩阵的存储结构要么同A,B一样,作为形参传入,要么定义为static。如果函数中定义C数组时未加static,返回C,编译连接不会错,可能运行也不会错。但某次内存紧张,回收的空间又用于别处,运行结果会出错。这就是安全隐患

slide68

简单地说指针即地址,任何变量都占有内存单元,都有地址。变量一但产生,占有内存的位置就固定了,所以任何变量的地址都是常量,如果要用内存单元在不同的时刻存放不同的地址常量值,这种内存单元称为指针变量,简单地说指针即地址,任何变量都占有内存单元,都有地址。变量一但产生,占有内存的位置就固定了,所以任何变量的地址都是常量,如果要用内存单元在不同的时刻存放不同的地址常量值,这种内存单元称为指针变量,

指针变量也有常量地址值,存放指针变量常量地址值的变量称为指针的指针变量。根据变量的地址访问变量内容称为间接访问,成功的间接访问必须是指针变量有所指。指针变量是存地址值的,间接访问往往是通过指针变量实现的。如果定义了一个指针变量,它没有赋以某一常量地址,即没有让它指向某一变量,通过指针变量去间接访问某一变量就要失败。如果把某一变量的地址存入一个指针变量,又把指针变量的地址存入一个指针的指针变量,对某一变量的内容的访问,要经过两次间接访问

slide69

通过指针的指针变量存储的指针变量地址间接访问指针变量,通过指针变量存储的变量地址间接访问变量。通过指针的指针变量存储的指针变量地址间接访问指针变量,通过指针变量存储的变量地址间接访问变量。

int a=100,*p,**pp,b;

p=&a; pp=&p;

b=*p;b=**pp;

如果没有给p,pp以地址值就试图作 b=*p;b=**pp是错误的。

没有变量写不出直接访问的表达式,而指针变量没有指向变量,指针的指针变量没有指向指针变量,是可以写出间接访问的表达式的,一执行即访问不存在的内存单元,所以发生错误

slide70

前面已经介绍,希望通过函数调用修改外部变量值时,要用指针变量作为形参,外部变量地址作为实参。如果希望通过函数调用修改外部指针变量的值,则要用前面已经介绍,希望通过函数调用修改外部变量值时,要用指针变量作为形参,外部变量地址作为实参。如果希望通过函数调用修改外部指针变量的值,则要用

指针变量的指针作为形参,指针变量的地址作为实参。

例:有一字符指针变量,希望通过函数调用,使它指向字符串 void f(char **x) { statica char *s1=“12345”; *x=s1;

} void main() { char *s; f(&s); }

slide71

指针数组 :用来存放指针变量值(指针值)的数组。

一维指针数组 作为一维数组,其数组名是首元素的指针

而元素存放的又是指针变量值,所以

指针数组名就是一个指针的指针。

定义方法: int *p[10]

由于[]优先级高于*,所以首先[]集合p, 定义了一维数组,

然后定义其元素类型是整型变量的指针,一个整型变量指

针占4字节,10个元素连续分配了40个字节。

有别于 int(*p)[10]:

它定义的是有10个整型变量的行的指针,只分配了4个字节。

slide72

字符串是指针,可以存放在字符指针变量中,如果有一组字符串,就要字符串是指针,可以存放在字符指针变量中,如果有一组字符串,就要

用字符指针数组存储了.

char *p,*s[3]={“张衫衫”,”李示”,”王陈树声”};

p=“张衫衫”;s[0]=“张衫衫”;s[0]=“asdfghjkl”;

如果用动态分配产生3个有10个元素的行,这3个整型指针用整型指针

数组存放 int *a[3],i;

for(i=0;i<3;i++)a[i]=(int *)malloc(sizeof(int)*10);

*(a[0])=100;a[0][1]=200;

动态分配时,一维整型指针数组名,可以当作二维整型数组名使用,要

注意 a[i] 是静态分配的指针数组中的单元,和a[i][0] 动态分配的一维

数组中的单元,是不同的单元

slide73

简单地说指针即地址,深入地说指针比地址有更多的信息。简单地说指针即地址,深入地说指针比地址有更多的信息。

指针除了可以表示地址外,还包含有地址上的变量的类型信息,构造信息。地址仅仅代表内存的位置,以字节为单位。而指针还表示了这个位置上的内存的划分方法,使用方法。在定义指针变量时这些信息就附带上了

实型指针,整型指针; 变量指针,指针变量指针,行指针,等包含的信息切然不同。

然而,指针的类型也是可以强制转换的,有意义的是通过类型转换,改变所指内存的构造信息。实型,整型的转换意义不大

如有一维数组,希望当成二维数组访问,或者相反,就需要改变指针附带的内存的构造信息

int a[100], b[10][10], *p, (*pt)[10]; pt=(int(*)[10])a; p=(int*)b; pt[9][9]=123;p[99]=345;

printf(“%d %d”,a[99],b[9][9]); 123 345

slide74

数组和指针(10.3.4)

数组(名) 表示连续分配的 N个单元, 用首单元的指针表示,

一维 type 数组, 单元是 type类型变量, 其指针 type *

二维 type 数组, 单元是 行 , 其指针 type(*)[N]

一维 type指针数组,单元是 type类型指针变量,其指针 type **

所以简单地说:

一维数组是用变量指针表示

二维数组是用行指针表示

一维指针数组是用指针的指针表示

slide75

行这种单元,是构造类型单元,是N个type类型变量的连续分配,行这种单元,是构造类型单元,是N个type类型变量的连续分配,

用 type和N才表示得出行这种类型.然而没有独立的表示行类

型( type [N])的方法,只有在定义行类型变量时 type v [N]

才表示得出这种类型.行的指针类型 type (*)[N],也不是独立的

类型,也要在定义行指针变量时 type (*p)[10]才表示得出来.但

是行类型,行指针类型,在有些时候,如形参数定义,强制类型时

是可以独立使用的

int a[10],b[10][10],*c[10]; int *pa, (*pb)[10], **pc;

pa=a, pb=b, pc=c;

pa=(int *)b, pa=(int *)c;

pb=(int(*)[10])a, pb=(int(*)[10])c;

slide76

数组名间接访问(*数组名)通过首单元的指针间接访问首单元数组名间接访问(*数组名)通过首单元的指针间接访问首单元

一维 type 数组,首单元指针指向type类型变量 ,访问到变量值

二维 type 数组,首单元指针指向行,访问到行,

行是用type类型变量指针表示的,所以访问到的是

type类型变量的指针.

一维 type指针数组,首单元指针指向type类型指针变量,访问到

type 类型变量指针

二维 type 数组,一维 type指针数组都要进行两次间接访问才可

以得到type类型变量值

int a[10]={1},b[10][10]={2},*c[10]={a}; int I;

I=*a,I=**b,I=**c; for(I=0;I<10;I++)c[I]=b[I];

c是int指针数组,只能接受 int *类型数据 ,a, b[I]是int * 类型数据

b不是int *类型数据,c[0]=b 会出现类型错误

I=**c *c访问到a, *a访问到 1

slide77

数组名取地址(&名) 数组名表示连续分配的 N个单元,取地址

即对它表示的东西取地址.我们知道对变量取地址得到变量指针.

一维数组,相当于行,取地址得到行指针(一维数组指针)

type v[N] type (*vp)[N]

二维数组,相当于矩阵,取地址得到矩阵指针(二维数组指针)

type v [M][N], type (*vp)[M][N]

一维指针数组,相当于存放指针的行,

取地址得到存放指针的行指针(一维指针数组指针) type* v[N] type *(*vp)[M]

数组指针对本维数组没有意义,对高一维数组有很大的用处.

任何高维数组,都可以看作一维数组,其元素是低一维的数组。

低一维的数组的指针,就是指向高维数组元素的指针

例如,如行指针,就是指向二维数组元素”行”的指针

int a[10]={1},b[10][10]={2},*c[10]={a};

int (*ppa)[10],(*ppb)[10][10],*(*ppc)[10];

ppa=&a,ppb=&b,ppc=&c;

slide78

int b[10][10] 中

b , b[0], &b, &b[0], &b[0][0] 五个量都表示连续分配的内存块的首地址,作地址值比较是等的,但是各自的特征是

不同的,表示的指针是不同的

b 二维int数组名 int(*)[10] 行指针

b[0] 二维int数组行名 int * 变量指针

&b 二维int数组地址 int (*)[10][10] 矩阵指针

&b[0] 二维int数组行地址 int(*)[10] 行指针

&b[0][0] 二维int数组下标变量地址 int * 变量指针

slide80

指针的指针变量与一维指针数组一维指针数组名表示首元素(指针变量)的地址。普通变量的一维数组,二维数组,首元素不管是单个还是一行,它们都是普通变量,而指针数组的首元素是指针变量,表示普通变量地址的指针变量或者行指针变量 ,都不能够表示指针变量的地址。因为它们不含存放的地址是指针变量地址的信息。而指针的指针变量,就有这个附加信息。一维指针数组名与指针的指针变量等效。在函数的定义和调用中可以角色互换,访问时下标法,指针法可以混用

一维指针数组,可以用来存放静态/动态产生二维数组的行地址。

slide81

数组指针和函数参数

各种数组直接对应的指针变量,以及他们作为函数参数的基本情况

变量,数组, 指针 形式参数 实际参数

int i, *pi; // f(int *) ; f(&i)

int a[100], *pa=a; // f(int[]),f(int *) ; f(a)

int b[10][10] , (*pb)[10]=b; // f(int[][10]),f(int(*)[10]) ; f(b),f(&b[0])

int *c[10] , **pc=c; // f(int *[]),f(int **) ; f(c)

这种基本情况也不是绝对的,只要正确进行数组指针的转换,任何

一种数组,可以作为上面任何一种函数的参数