109
程程程程 cs.sjtu 2011.9 程程程程 - 1 7 7 第第第第 第第第第 第第 第第 第第第第第 第第第第第第第 第第第第第第 第第第第第第 第第第第第第第第第第第第 第第第第第第第第第 第第第第第第第第第 第第第第第第第

第 7 章 间接访问 — 指针

  • Upload
    todd

  • View
    145

  • Download
    4

Embed Size (px)

DESCRIPTION

第 7 章 间接访问 — 指针. 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针 指向多维数组的指针 指向函数的指针. 本章将介绍 C++ 语言的一个重要的特性:指针,为了成为一个优秀的 C++ 语言程序员,你必须掌握指针并熟练地使用它们。. 指针介绍. 指针 是内存的 地址 并可作为数据 是一个灵活和危险的机制 允许 共享 处理数据 允许内存 动态 分配(只要需要,而非预先定义). 指针的概念. 指针就是把地址作为数据处理 指针变量:存储地址的变量 - PowerPoint PPT Presentation

Citation preview

Page 1: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 1

第第 77 章 间接访问章 间接访问——指针指针 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针 指向多维数组的指针 指向函数的指针

Page 2: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 2

指针介绍指针介绍 本章将介绍 C++语言的一个重要的特性:指针,

为了成为一个优秀的 C++语言程序员,你必须掌握指针并熟练地使用它们。

指针•是内存的地址并可作为数据•是一个灵活和危险的机制•允许共享处理数据•允许内存动态分配(只要需要,而非预先定义)

Page 3: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 3

指针的概念指针的概念 指针就是把地址作为数据处理 指针变量:存储地址的变量 变量的指针:当一个变量存储另一个变量的

地址时,那我们说它就是那个变量的指针 使用指针的目的:提供间接访问

Page 4: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 4

指针的概念 指针的概念 续续 如在某一程序中定义了 int x = 2; 如系统给 x 分配的空间是 1000 号

单元,则指向 x 的指针是另一个变量 p , p 中存放的数据为 1000

1000 号单元的内容有两种访问方式: 访问变量 x (直接访问) 访问变量 p 指向的单元的内容(间

接访问)

1000

2 1000x

p

Page 5: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 5

定义指针变量定义指针变量 定义指针变量要告诉编译器该变量中存放的是一个地址。 指针变量的主要用途是提供间接访问,因此也需要知道指针指向的单元的数据类型 指针变量的定义 类型标识符 * 指针变量; 如: int *intp; double *doublep; int *p, x, *q;

Page 6: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 6

指针变量的操作指针变量的操作 如何让指针指向某一变量?因为我们不知道系统分配给变量的真正地址是什么。

用地址运算符 “ &” 解决。如表达式 “ &x” 返回的是变量 x 的地址。如: intp = &x; & 运算符后面不能跟常量或表达式。如 &2 是没有意义的, &(m * n + p ) 。也是没有意义的

如何通过指针变量处理和改变它所指向的单元的值? 用引用运算符 “ *” 解决。如 *intp 表示的是 intp 指向的这个单元的内容。如: *intp = 5 等价于 x = 5

在对 intp 使用引用运算之前,必须先对 intp 赋值

Page 7: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 7

指针实例指针实例如有:int X, *intp, Y;X=3;Y=4;intp=&X;

1000intp

10044Y

10003X

如执行:*intp=Y+4;

1000intp

10044Y

10008X

注意:不能用 intp=100;因为我们永远不知道变量存储的 真实地址,而且程序每次运行变量地址可能都不同。

Page 8: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 8

指针使用指针使用 指针变量可以指向不同的变量。如上例中 intp 指向 x ,我们可以通过对 intp 的重新赋值改变指针的指向。如果想让 intp指向 y ,只要执行 intp=&y 就可以了。这时, intp 与 x 无任何关系。 同类的指针变量之间可相互赋值,表示二个指针指向同一内存空间。 空指针

指针没有指向任何空间 空指针用常量 NULL表示, NULL的值一般赋为 0 不能引用空指针指向的值

Page 9: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 9

指针变量的使用指针变量的使用设有定义 int x, y; int *p1,*p2;

1000 x

1004 y

1008 p1

1012 p2

执行语句:x=23;y=234;

1000 23 x

1004 234 y

1008 p1

1012 p2

执行语句:p1=&x;p2=&y;

1000 23 x

1004 234 y

1008 1000 p1

1012 1004 p2

执行语句:*p1=34;p2=p1;

1000 34 x

1004 234 y

1008 1000 p1

1012 1000 p2

Page 10: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 10

指针实例指针实例有以下结构

Ap1 a

Bp2 b比较执行 p1=p2 和 *p1= *p2 后的不同结果。

解: A

p1 a

B

p2 b

B

p1 a

B

p2 b

Page 11: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 11

指针的初始化指针的初始化 指针在使用前必须初始化。 和别的变量一样,定义指针不初始化是一个比

较普通的错误。 没有初始化的指针可能指向任意地址,对这些

指针作操作可能会导致程序错误。 NULL是一个特殊指针值,称为空指针。它的

值为 0 。它可被用来初始化一个指针,表示不指向任何地址。

思考: int *p; *p = 5; 有什么问题?

Page 12: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 12

第第 77 章 间接访问章 间接访问——指针指针 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针 指向多维数组的指针 指向函数的指针

Page 13: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 13

指针运算和数组指针运算和数组 指向数组元素的指针

数组元素是一个独立的变量,因此可以有指针指向它。如: p = &a[1], p = &a[i]

数组元素的地址是通过数组首地址计算的。如数组的首地址是 1000 ,则第 i 个元素的地址是 1000 + i * 每个数组元素所占的空间长度

Page 14: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 14

指针与数组指针与数组 在 C++中,指针和数组关系密切,几乎可以互

换使用 数组名可以看成是常量指针,对一维数组来说,

数组名是数组的起始地址,也就是第 0 个元素的地址

如执行了 p=array,则 p 与 array是等价的,对该指针可以进行任何有关数组下标的操作

Page 15: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 15

例如:有定义 int a[10], *p 并且执行了 p=a, 那么可用下列语句访问数组 a 的元素for ( i=0; i<10; ++i ) cout << p[i];

Page 16: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 16

指针运算指针运算

指针 +1 表示数组中指针指向元素的下一元素地址;

指针 -1 表示数组中指针指向元素的上一元素地址;

合法的指针操作: p + k, p - k, p1 - p2

指针保存的是一个地址,地址是一个整型数,因此可以进行各种算术运算,但仅有加减运算是有意义的。指针运算与数组有密切的关系

Page 17: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 17

数组元素的指针表示数组元素的指针表示 当把数组名,如 intarray ,赋给了一个同类指

针 intp 后, intarray 的元素可以通过 intp 访问。第 i 个元素的地址可表示为 intp + i ,第 i 个元素的值可表示为 *(intp + i) 。

通过指针访问数组时,下标有效范围由程序员自己检查。

如输出数组 a 的十个元素

Page 18: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 18

方法 3 : for ( p=a; p<a+10; ++p ) cout << *p ;

方法 2 : for ( i=0; i<10; ++i ) cout << *(a+i);

方法 1: for ( i=0; i<10; ++i ) cout << a[i];

方法 4 : for ( p=a, i=0; i<10; ++i ) cout << *(p+i);

方法 5: for ( p=a, i=0; i<10; ++i ) cout << p[i] ;

下列程序段 有无问题? for ( i=0; i<10; ++i ) { cout << *a ; ++a; }

Page 19: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 19

指针和数组的区别指针和数组的区别 虽然通过指针可以访问数组,但两者本质是不同的。 在定义数组时为数组的各个元素分配了全部的存储区,

而在定义指针时,仅仅分配四个字节的存储区存放指针地址。只有把一个数组名付给了对应的指针后,指针才能当作数组使用

如有: int array[5], *intp;

array

intp

当执行了 intp = array后

Page 20: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 20

第第 77 章 间接访问章 间接访问——指针指针 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针 指向多维数组的指针 指向函数的指针

Page 21: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 21

动态分配动态分配 在 C++语言中,每个程序需要用到几个变量,

在写程序前就应该知道。每个数组有几个元素也必须在写程序时就决定。

有时我们并不知道我们需要多大的数组元素直到程序开始运行。因此希望能在程序中根据某一个当前运行值来决定数组的大小。如设计一个打印魔阵的程序,我们希望先输入魔阵的阶数,然后根据阶数定义一个矩阵

Page 22: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 22

动态分配方法动态分配方法 这些问题的解决方案就是内存的动态分

配。我们定义一个指针,并让它指向一个合适的内存。如:

int *scores; scores = 内存的起始地址;

Page 23: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 23

动态内存分配与回收动态内存分配与回收 C++中由 new 和 delete两个运算符替代

- 运算符 new用于进行内存分配: 申请动态变量: p = new type; 申请动态数组: p = new type[size];

申请动态变量并初始化: p = new type(初值 ) ; - 运算符 delete 释放 new分配的内存:

释放动态变量: delete p;

释放动态数组: delete [] p;

Page 24: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 24

动态内存分配与回收动态内存分配与回收// 为简单变量动态分配内存,并作初始化int main(){ int *p; p = new int(99); // 动态分配内存,并将 99 作为初始化值赋给它 cout<< *p; delete p; return 0;}

Page 25: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 25

动态内存分配与回收动态内存分配与回收// 动态字符串的使用int main(){int *p; char *q;

p = new int(5); q = new char[10]; strcpy(q, "abcde"); cout << *p << endl; cout << q << endl; delete p; delete q; return 0;}

输出结果:5abcde

Page 26: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 26

动态分配的检查动态分配的检查 new 操作的结果是申请到的空间的地址 当系统空间用完时, new 操作可能失败 new 操作失败时,返回空指针

Page 27: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 27

动态内存分配与回收动态内存分配与回收// 动态分配检查int main(){int *p; p = new int; if(!p) { cout << "allocation failure\n"; return 1;} *p = 20; cout << *p; delete p; return 0;}

Page 28: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 28

assertassert 宏 宏 assert ()宏在标准头文件 cassert 中 assert() 有一个参数,表示断言为真的表

达式,预处理器产生测试该断言的代码。如果断言不是真,则在发出一个错误消息后程序会终止。

Page 29: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 29

#include <iostream>#include <cassert> // 包含 assert 宏的头文件using namespace std;

int main(){ int *p;

p = new int; assert (p != 0); //p 等于 0 ,则退出程序 *p=20; cout << *p; delete p;

return 0;}

Page 30: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 30

内存分配的进一步介绍内存分配的进一步介绍 静态分配:对全局变量和静态变量,编译器为它们分配空间,这些空间在整个程序运行期间都存在 自动分配:函数内的局部变量空间是分配在系统的栈工作区。当函数被调用时,空间被分配;当函数执行结束后,空间被释放 动态分配:在程序执行过程中需要新的存储空间时,可用动态分配的方法向系统申请新的空间,当不再使用时用显式的方法还给系统。这部分空间是从被称为堆的内存区域分配。

OS

Program

Heap 动态分配Stack 自动分配Globe variables

静态分配

Page 31: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 31

内存泄漏 动态变量是通过指针间接访问的。如果该指针被修改,这个区域就被丢失了。堆管理器认为你在继续使用它们,但你不知道它们在哪里,这称为内存泄露。 为了避免出现孤立的区域,应该明白地告诉堆管理器这些区域不再使用。可以采用 delete操作,它释放由 new申请的内存。 当释放了内存区域,堆管理器重新收回这些区域,而指针仍然指向堆区域,但不能再使用指针指向的这些区域。 要确保在程序中同一个区域释放一次。 释放内存对一些程序不重要,但对有些程序很重要。如果你的程序要运行很长时间,而且存在内存泄漏,这样程序会耗尽所有内存,直至崩溃。

Page 32: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 32

动态空间分配示例动态空间分配示例 输入一批数据,计算它们的和。数据个数在设计程序时尚无法确定。 存储一批数据应该用数组,但 C++ 语言的数组大小必须是固定的。该问题有两个解决方案:

开设一个足够大的数组,每次运行时只使用一部分。缺点:浪费空间 用动态内存分配根据输入的数据量申请一个动态数组

Page 33: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 33

#include <iostream>using namespace std;

int main(){ int *p, i, n, sum=0;

cout << "An array will be created dynamically.\n\n" ; cout << "Input an array size n followed by n integers:"; cin >> n;

if (!(p = new int [n])) exit(1); for (i=0; i<n; ++i) cin >> p[i]; for(i=0; i<n; ++i) sum += p[i]; delete [] p;

cout << "Number of elements:" << n << endl; cout << "Sum of the elements:" << sum << endl;

return 0;}

可改为:p = new int [n];assert( p != NULL);

Page 34: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 34

第第 77 章 间接访问章 间接访问——指针指针 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针 指向多维数组的指针 指向函数的指针

Page 35: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 35

字符串再讨论字符串再讨论 字符串的另一种表示是定义一个指向字符的

指针。然后直接将一个字符串常量或字符串变量赋给它

如 char *String , ss[ ] =“abcdef”;

String = “abcde”; String = ss;

Page 36: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 36

String

“abcde”Program

OS

数据段或代码区栈

String = String = ““abcdeabcde””;; 的执行结果的执行结果•字符串常量存储在一个称为数据段的内存区域里 •将存储字符串” abcde” 的内存的首地址赋给指针变量String。

Page 37: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 37

String

“abcdef\0”

ProgramOS

数据段栈

String = ssString = ss 的执行过程的执行过程将字符数组 ss 的起始地址存入String

Page 38: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 38

String = new char[5];String = new char[5];strcpy(String, strcpy(String, ““aaaaaa””))

String

ProgramOS

数据段栈

堆“aaa\0”

•动态变量存储在堆工作区 •将存储字符串” aaa” 的内存的首地址赋给指针变量String。

Page 39: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 39

用指针表示的字符串的操作用指针表示的字符串的操作 可以直接作为字符串操作函数的参数。但必须注意,如果该指针指向的是一个字符串常量时,则使用是受限的。如不能作为 strcpy 的第一个参数 由于在 C++ 中,数组名被解释成指向数组首地址的指针。因此,字符串是用一个指针变量表示,我们可以把此指针变量解释成数组的首地址,通过下标访问字符串中的字符。如

string[3] 的值是 d 。

Page 40: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 40

用指针处理串用指针处理串 目的:编写一个记录串中单词的个数的

函数。 关键技术:要传递一个字符串给函数

Page 41: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 41

字符串作为函数的参数字符串作为函数的参数 字符串作为函数的参数和数组名作为参数传递

一样,可以有两种方法 作为字符数组传递 作为指向字符的指针传递

两种传递方式的本质是一样的,都是传递了字符串的首地址

字符串作为字符数组传递时不需要指定长度。因为字符串操作的结束是依据‘ \0’

Page 42: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 42

#include <ctype>Using namespace std;int word_cnt(const char *s){ int cnt = 0; while (*s != '\0') { while (isspace(*s)) ++s; // 跳过空白字符

if (*s != '\0') { ++cnt; // 找到一个单词

while (!isspace(*s) && *s != '\0') ++s; // 跳过单词 } } return cnt;}

统计字符串中单词数的函数

Page 43: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 43

第第 77 章 间接访问章 间接访问——指针指针 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针 指向多维数组的指针 指向函数的指针

Page 44: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 44

指针作为函数参数和返回值指针作为函数参数和返回值 指针作为函数参数 数组名作为函数参数 返回指针的函数 引用和引用传递 返回引用的函数

Page 45: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 45

指针作为函数参数指针作为函数参数例:编一函数,交换二个参数值。

void swap(int a, int b){ int c; c=a; a=b; b=c; }

希望通过调用 swap(x, y)交换变量 x 和 y 的值

新手可能会编出如下的函数:

因为 C++采用的是值传递机制,函数中 a 、 b值的交换不会影响实际参数 x 和 y 的值

Page 46: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 46

void swap(int *a, int *b){ int c; c=*a; *a= *b; *b=c; }

3 4

xy

a

b交换 x 和 y 的值,可以调用swap(&x, &y)

用指针作为参数可以在函数中修改主调程序的变量值,即实现变量传递。必须小心使用!!!

正确的方法

Page 47: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 47

能解一元二次方程的函数能解一元二次方程的函数 目前为止我们了解到的函数只能有一个返

回值,由 return 语句返回。 一个一元二次方程有二个解,如何让此函

数返回二个解。答案是采用指针作为函数的参数。

由调用程序准备好存放两个根的变量,将变量地址传给函数。在函数中将两个根的值分别放入这两个地址

Page 48: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 48

函数原型函数原型 函数原型可设计为: void SolveQuadratic(double a, double b, double c,

double *px1, double *px2)

函数的调用 SolveQuadratic(1.3, 4.5, 2.1, &x1, &x2)

SolveQuadratic(a, b, c, &x1, &x2) 函数的参数有两类:输入参数和输出参数。一般,输入参

数用值传递,输出参数用指针传递。在参数表中,输入参数放在前面,输出参数放在后面。

Page 49: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 49

原型的改进原型的改进 并不是每个一元二次方程都有两个不同根,

有的可能有两个等根,有的可能没有根。函数的调用者如何知道 x1 和 x2 中包含的是否是有效的解?

解决方案:让函数返回一个整型数。该整型数表示解的情况

Page 50: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 50

完整的函数完整的函数int SolveQuadratic(double a,double b,double c, double *px1,double *px2){ double disc, sqrtDisc; if(a == 0) return 3; // 不是一元二次方程 disc = b * b - 4 * a * c; if( disc < 0 ) return 2; // 无根 if ( disc == 0 ) { *px1 = -b /(2 * a); return 1;} // 等根 // 两个不等根 sqrtDisc = sqrt(disc); *px1 = (-b + sqrtDisc) / (2 * a); *px2 = (-b - sqrtDisc) / (2 * a); return 0;}

Page 51: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 51

函数的调用函数的调用int main(){ double a,b,c,x1,x2; int result; cout << "请输入 a,b,c: "; cin >> a >> b >> c; result = SolveQuadratic(a, b, c, &x1, &x2); switch (result) { case 0: cout << " 方程有两个不同的根: x1 = " << x1 << " x2 = " << x2; break; case 1: cout << " 方程有两个等根: " << x1; break; case 2: cout << " 方程无根 "; break; case 3: cout << " 不是一元二次方程 "; } return 0;}

Page 52: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 52

指针作为函数参数和返回值指针作为函数参数和返回值 指针作为函数参数 数组名作为函数参数 返回指针的函数 引用和引用传递 返回引用的函数

Page 53: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 53

数组传递的进一步讨论数组传递的进一步讨论 数组传递的本质是地址传递,因此形参和实参可以使用

数组名,也可以使用指针。 数组传递是函数原型可写为: type fun(type a[], int size);

也可写为 type fun(type *p, int size);

但在函数内部, a 和 p 都能当作数组使用 调用时,对这两种形式都可用数组名或指针作为实参

建议:如果传递的是数组,用第一种形式;如果传递的是普通的指针,用第二种形式

Page 54: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 54

#include <iostream>using namespace std;

void f(int arr[], int k){cout << sizeof(arr) << " " << sizeof(k) << endl;}

void main(){ int a[10]={1,2,3,4,5,6,7,8,9,0}; cout << sizeof(a) << endl; f(a,10); }

输出:404 4

C++ 将数组名作为参数传递处理成指针的传递

即在 main中, a是数组,占用了 40个字节。而在函数f 中, arr是一个指针

Page 55: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 55

数组传递的灵活性数组传递的灵活性void sort(int p[ ] , int n){...}

main(){int a[100]; ... sort(a, 100); //排序整个数组 sort(a, 50); //排序数组的前 50个元素 sort(a+50, 50); //排序数组的后 50个元素 ...}

Page 56: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 56

实例实例 设计一函数用分治法在一个整数数组中找出最大

和最小值 具体方法是:

如果数组只有一个元素,则最大最小都是他。 如果数组中只有两个元素,则大的一个就是最大数,小的就是最小数。这种情况不需要递归。

否则,将数组分成两半,递归找出前一半的最大最小值和后一半的最大最小值。取两个最大值中的较大者作为最大值,两个最小值中的较小值作为最小值。

Page 57: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 57

设计考虑设计考虑 函数的参数是要查找的数组,传递一个数组要两个参

数:数组名和数组规模 函数返回的是数组中的最大值和最小值,将它们作为

指针传递的参数 查找数组的前一半就是递归调用本函数,传给他的参

数是当前的数组名,数组的规模是原来的一半 查找数组的后一半也是递归调用本函数,传给它的参

数是数组后一半的起始地址,规模也是原来的一半

Page 58: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 58

伪代码伪代码void minmax ( int a[ ] , int n , int *min_ptr , int *max_ptr) { switch (n) { case 1: 最大最小都是 a[0]; case 2: 大的得放入 *max_ptr ,小的放入 *min_ptr ; Default: 对数组 a 的前一半和后一般分别调用 minmax ; 取两个最大值中的较大者作为最大值; 取两个最小值中的较小值作为最小值 } }

Page 59: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 59

void minmax ( int a[] , int n , int *min_ptr , int *max_ptr) { int min1 , max1 , min2 , max2; switch(n) { case 1: *min_ptr = *max_ptr = a[0]; return; case 2: if (a[0] < a[1] ) { *min_ptr = a[0]; *max_ptr= a[1]; } else { *min_ptr = a[1]; *max_ptr= a[0];} return; default: minmax( a, n/2, &min1, &max1 ); minmax( a + n/2, n - n / 2, &min2, &max2 ); if (min1 < min2) *min_ptr = min1; else *min_ptr = min2; if (max1 < max2) *max_ptr = max2; else *max_ptr = max1; return;

}}

Page 60: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 60

指针作为函数参数和返回值指针作为函数参数和返回值 指针作为函数参数 数组名作为函数参数 返回指针的函数 引用和引用传递 返回引用的函数

Page 61: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 61

返回指针的函数返回指针的函数 函数的返回值可以是一个指针 返回指针的函数原型: 类型 * 函数名(形式参数表); 当函数的返回值是指针时,返回地址对应的变

量不能是局部变量。

Page 62: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 62

实例实例 设计一个函数从一个字符串中取出一个子串。 原型设计:

从哪一个字符串中取子串、起点和终点 返回值:字符串可以用一个指向字符的指针表示,所

以函数的执行结果是一个字符串,表示一个字符串可以用一个指向字符的指针

返回值指针指向的空间必须在返回后还存在。这可以用动态字符数组

Page 63: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 63

char *subString(char *s, int start, int end){

int len = strlen(s);if (start < 0 || start >= len || end < 0 ||

end >= len || start > end) {cout << "起始或终止位置错 " << endl;return NULL;

}char *sub = new char[end - start + 2]; strncpy(sub, s + start, end - start +1);sub[end - start +1] = '\0';return sub;

}

Page 64: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 64

指针作为函数参数和返回值指针作为函数参数和返回值 指针作为函数参数 数组名作为函数参数 返回指针的函数 引用和引用传递 返回引用的函数

Page 65: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 65

引用传递引用传递 引用传递是地址传递的另一种更简单明

了的实现方法引用的概念函数中的引用

Page 66: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 66

C++C++ 中的引用中的引用 引用的定义:给一个变量取一个别名 , 使一个内存单元可以通过不同的变量名来访问。 例: int i; int &j=i; j 是 i 的别名, i 与 j 是同一个内存单元。 C++引入引用的主要目的是将引用作为函数的参数。

Page 67: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 67

引用传递引用传递 引用传递是地址传递的另一种更简单明

了的实现方法引用的概念函数中的引用

Page 68: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 68

引用参数引用参数 C++ 引入引用的主要目的是将引用作为函数的参数。

指针参数void swap(int *m, int *n){ int temp; temp=*m; *m=*n; *n=temp;}调用: swap(&x, &y)

引用参数void swap(int &m, int &n){int temp; temp=m; m=n; n=temp;}调用: swap( x, y)

注意:实参必须是变量,而不能是一个表达式

Page 69: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 69

调用 swap(x,y) 时,相当于发生了变量定义 int &m = x

int &n = y

即,形式参数 m 和实际参数 x 共享一块空间,形式参数 n 和实际参数 y 共享一块空间。在 swap 函数中交换了 m 和 n 的值,就相当于交换了 x 和 y 的值。

Page 70: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 70

指针作为函数参数和返回值指针作为函数参数和返回值 指针作为函数参数 数组名作为函数参数 返回指针的函数 引用和引用传递 返回引用的函数

Page 71: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 71

返回引用的函数的主要用途返回引用的函数的主要用途 将函数用于赋值运算符的左边,即作为左值。 int a[] = {1, 3, 5, 7, 9}; int &index(int); //声明返回引用的函数 void main() { index(2) = 25; // 将 a[2] 重新赋值为 25 cout << index(2);} int &index(int j) { return a[j]; } // 函数是 a[j] 的一个引用

Page 72: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 72

第第 77 章 间接访问章 间接访问——指针指针 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针 指向多维数组的指针 指向函数的指针

Page 73: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 73

指针数组与多级指针指针数组与多级指针 指针数组 Main 函数的参数 多级指针

Page 74: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 74

指针数组指针数组 地址本身也是数据,他们也可以像其他数据一

样组织成一个数组 一个数组,如果他的元素均为指针,则称为指

针数组 一维指针数组的定义形式: 类型名 * 数组名 [ 数组长度 ] ; 例如, char *String[10]; 定义了一个名为

String 的指针数组,该数组有 10 个元素,数组的每个成员是一个指向字符的指针

Page 75: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 75

指针数组的应用指针数组的应用 字符串可以用一个指向字符的指针表示,一组字符串可以用一个指向字符的指针数组来表示 例:写一个函数用二分法查找某一个城市在城市表中是否出现。用递归实现 关键问题:

城市表的存储:用指向字符的指针数组查找时的比较:用字符串比较函数

Page 76: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 76

// 该函数用二分查找在 cityTable 中查找 cityName 是否出现//lh 和 rh 表示查找范围,返回出现的位置Int binarySearch(char *cityTable[], int lh, int rh, char *cityName){int mid, result;

if (lh <= rh) {mid =(lh+rh)/2;result= strcmp(cityTable[mid], cityName);

if (result == 0) return mid; // 找到 else if (result > 0)

return binarySearch(cityTable, lh, mid-1, cityName); else return binarySearch(cityTable, mid+1,

rh,cityName); }

return -1; // 没有找到}

Page 77: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 77

函数的应用函数的应用#include <iostream>using namespace std;int binarySearch(char *cityTable[], int lh, int rh, char *cityName);

int main(){char *string[10] = {"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii","jjj"}; char tmp[10];;

while (cin >> tmp) cout << binarySearch(string, 0, 9, tmp) << endl;

return 0;}

Page 78: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 78

“aaa”“bbb”“ccc”“ddd”“eee”“fff”“ggg”“hhh”“iii”“jjj”

Page 79: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 79

指针数组与多级指针指针数组与多级指针 指针数组 Main 函数的参数 多级指针

Page 80: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 80

main 函数的形参 如需要实现: copy a b 之类的功能,可以用带有参数的 main 函数来实现 main 函数有二个形式参数: int argc,

char *argv[ ] argc – 参数的数目(包括命令名本身) argv[ ]— 指向每个参数的指针,是一个指向字符串的指针数组

Page 81: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 81

把参数传递给 main()#include <stdio.h>int main(int argc, char *argv[]){

int i;cout << "argc=“ << argc << endl;for(i=0; i<argc; ++i)

cout << "argv[“ << i << “]=“ << argv[i] << endl;return 0;

}

假设生成的执行文件 myprogram.exe

Page 82: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 82

把参数传递给 main() 续 假设生成的执行文件myprogram.exe 在命令行输入: myprogram 输出结果: argc=1 argv[0]=myprogram

在命令行输入: myprogram try this 输出结果: argc=3 argv[0]=myprogram

argv[1]=try argv[2]=this

Page 83: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 83

mainmain 函数参数实例函数参数实例 编写一个求任意 n个正整数的平均数的

程序 如果该程序对应的可执行文件名为 aveg ,

则可以在命令行中输入 aveg 10 30 50 20 40↙

表示求 10 、 30 、 50 、 20 和 40的平均值,对应的输出为 30 。

Page 84: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 84

设计考虑设计考虑 将这些数据作为命令行的参数

从 argc得到数据的个数 从 argv得到每一个数值,但注意数值是以字符串

表示,要进行计算,必须把它转换成真正的数值 由于每个数据都要转换,而且这个工作很独立,

所以将它设计成一个函数

Page 85: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 85

字符串形式的数字转换到真正的字符串形式的数字转换到真正的数值数值int ConvertStringToInt(char *s){ int num = 0; while(*s) { num = num * 10 + *s - '0'; ++s;} return num;}

Page 86: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 86

计算程序计算程序int main(int argc, char *argv[])

{ int sum = 0;

for (int i = 1; i < argc; ++i)

sum += ConvertStringToInt(argv[i]);

cout << sum / (argc - 1) << endl;

return 0;

}

Page 87: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 87

指针数组与多级指针指针数组与多级指针 指针数组 Main 函数的参数 多级指针

Page 88: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 88

多级指针多级指针 指针指向的内容还是一个指针,称为多级指针 如有定义: char *string[10];

string 是一个数组,数组元素可以通过指针来访问。如果 p 是指向数组 string 的某一个元素,那么 p 指向的内容是一个指向字符的指针,因此 p 就是一个多级指针。 string 也是一个多级指针,不过是一个常指针

Page 89: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 89

多级指针的定义多级指针的定义 两级指针:类型名 ** 变量名; 三级指针:类型名 *** 变量名; 如: int **q;

表示 q 指向的内容是一个指向整型的指针。可以这样使用: int x=15, *p=&x; q = &p;

同样: char **s;

表示 s 指向的内容是一个指向字符的指针q p 15

s “abcde”

Page 90: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 90

多级指针的应用多级指针的应用 可以用指向指针的指针访问指针数组的元素。如#include <iostream>using namespace std;

int main(){ char *city[] = {"aaa", "bbb", "ccc", "ddd", "eee"}; char **p;

for (p=city; p<city+5; ++p) cout << *p << endl;

return 0; }

输出结果:aaabbbcccdddeee

Page 91: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 91

第第 77 章 间接访问章 间接访问——指针指针 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针 指向多维数组的指针 指向函数的指针

Page 92: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 92

二维数组与指针二维数组与指针int a[3][4]; 等价于定义了 3 个变量

a

a[1] a[2]

 a[0]

1 2 3 4

5 6 7 8

9 10 11 12

Page 93: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 93

指向一维数组的指针指向一维数组的指针 a[ ]是一个指针数组,它的每个元素是一个整型

指针,指向每一行的第一个元素。 a 是一个指向一维数组的指针,指向 a[ ]的第一个元素。对 a 加1 ,事实上是跳到下一行

指向一维数组的指针可以这样定义: 类型名 ( *指针变量名) [ 一维数组的元素个数 ] ; 注意:圆括号不能省略,如果省略了圆括号就变成

了指针数组

Page 94: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 94

等价于等价于 a[i][j]a[i][j] 的表达式的表达式a[i][j]

*(a[i] + j)(*(a + i))[j]*(*(a + i) + j)*(&a[0][0] + 5 * i + j)

int a[3][5];

Page 95: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 95

用指向数组的指针输出二维数组用指向数组的指针输出二维数组 aa

int (*p)[4], *q;for (p = a; p < a + 3; ++p) { // 每一行 for (q = *p; q < *p+4; ++q) // 每一列 cout << *q << '\t'; cout << endl;

}

注意:如果输出 a 和 a[0] ,这两个值是相同的。但是,这两个值的含义是不同的,前者是第 0 行的首地址,它的类型是指向由四个元素组成的一位数组的首地址,后者是第 0 行第一个元素的地址,它的类型是整型指针

Page 96: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 96

动态的二维数组动态的二维数组 方法一:用一维动态数组

将它按行序转换成一维数组,用动态的一维数组存储。如一个 3 行 4列的矩阵 a 可以存储为 12 个元素的一维数组

访问 i 行 j列的元素转换成访问一维数组的第 4*i+j 个元素

方法二:用指向指针的指针,可以用 a[i][j] 访问 用指向指针的指针指向一个一维的指针数组 指针数组中的每个元素指向矩阵的每一行的第一个元

Page 97: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 97

int main(){ int **a, i, j, k = 0; a = new int *[3]; for (i = 0; i < 3; ++i) a[i] = new int[4]; for (i = 0; i < 3; ++i)

for (j = 0; j < 4; ++j) a[i][j] = k++; for (i = 0; i < 3; ++i) {

cout << endl; for (j = 0; j < 4; ++j) cout << a[i][j] << '\t';

} for (i = 0; i < 3; ++i) delete [] a[i]; delete [] a; return 0;}

Page 98: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 98

第第 77 章 间接访问章 间接访问——指针指针 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针 指向多维数组的指针 指向函数的指针

Page 99: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 99

函数的指针和指向函数的指针变量 定义:返回类型 (* 指针变量名 )( ) ; 使用 :

赋值 eg. int isdigit(int n, int k); {...} int (*p)(int , int ); p=isdigit; 引用: a=isdigit(n,k); a=(*p)(n,k); 或 a=p(n,k)

函数的指针:指向函数代码的起始地址

Page 100: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 100

函数的指针的用途 菜单选择的实现 作为函数的参数

Page 101: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 101

函数指针的应用函数指针的应用 用函数指针的数组实现菜单选择 例如,在一个工资管理系统中有如下功能:

1 。添加员工; 2 。删除员工; 3 。修改员工信息; 4 。打印工资单; 5 。打印汇总表; 6 。退出。

在设计中,一般把每个功能设计成一个函数。如添加员工的函数为 add ,删除员工的函数为 delete ,修改员工信息的函数为modify ,打印工资单的函数为 printSalary ,打印汇总表函数为printReport 。主程序是一个循环,显示所有功能和它的编号,请用户输入编号,根据编号调用相应的函数。

Page 102: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 102

int main(){ int select; while(1) { cout << "1--add \n"; cout << "2--delete\n"; cout << "3--modify\n"; cout << "4--print salary\n"; cout << "5--print report\n"; cout << "0--quit\n"; cin >> select; switch(select) { case 0: return 0; case 1: add(); break; case 2: erase(); break; case 3: modify(); break; case 4: printSalary(); break; case 5: printReport(); break; default: cout << "input error\n"; } }}

Page 103: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 103

利用指向函数的指针利用指向函数的指针int main(){ int select; void (*func[6])() = {NULL, add, erase, modify, printSalary, printReport}; while(1) { cout << "1--add \n"; cout << "2--delete\n"; cout << "3--modify\n"; cout << "4--print salary\n"; cout << "5--print report\n"; cout << "0--quit\n"; cin >> select; if (select == 0) return 0; if (select > 5) cout << "input error\n"; else func[select](); }}

Page 104: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 104

函数指针的应用函数指针的应用 把函数指针作为函数的参数 例:设计一个通用的快速排序函数,可以排序任何类型的数据 关键问题

如何表示要排序的数据:将快速排序设计成一个函数模板,将待排序的数据类型设计成模板参数

不同类型的数据有不同的比较方式:向排序函数传递一个比较函数来解决。

Page 105: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 105

template <class T>void quicksort(T data[], int low, int high, int (*comp)(T, T)){ int mid; if (low >= high) return; mid = divide(data, low, high, comp); quicksort( data, low, mid-1, comp); quicksort( data, mid+1, high, comp);}

Page 106: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 106

template <class T>int divide(T data[], int low, int high, int (*comp)(T, T)){ T k = data[low]; do { while (low < high && comp(data[high], k)>0) --high; if (low < high) { data[low] = data[high]; ++low;} while (low < high && comp(data[low], k) < 0) ++low; if (low < high) { data[high] = data[low]; --high;} } while (low != high); data[low] = k; return low;}

Page 107: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 107

通用快速排序的应用通用快速排序的应用 如果需要排序一组字符串,待排序的一组

字符串保存在数组 a 中。 a 的定义如下:char *a[]={"aaa", "nnn", "rrr", "fff",

"sss", "ggg", "ddd"};

调用 quicksort(a, 0, 6, strcmp);

Page 108: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 108

通用快速排序的应用通用快速排序的应用 如果要排序一组整型数,则需要定义一个比较函数,如下所示:

int intcmp(int a, int b){ if (a == b) return 0; if (a < b) return -1; else return 1;}

如果整型数组 a 定义如下:int a[] = {7,9,4,3,8,1,2,5,6,0};

调用quicksort(b, 0, 9, intcmp);

Page 109: 第 7 章  间接访问 — 指针

《程序设计 》 cs.sjtu 2011.9

程序设计 - 109

总结总结 本章介绍了指针的概念 ,指针变量的

定义、运算 采用指针,可以使数组有多种访问方式 将指针作为形式参数可以使一个函数与其调用函数共享数据

动态分配内存