第六讲 数组、指针与字符串

Similar documents
Microsoft PowerPoint - 4. 数组和字符串Arrays and Strings.ppt [兼容模式]

Microsoft PowerPoint - 5. 指针Pointers.ppt [兼容模式]

c_cpp

Microsoft PowerPoint - string_kruse [兼容模式]

OOP with Java 通知 Project 2 提交时间 : 3 月 14 日晚 9 点 另一名助教 : 王桢 学习使用文本编辑器 学习使用 cmd: Power shell 阅读参考资料

新版 明解C++入門編

《计算概论》课程 第十九讲 C 程序设计语言应用

OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数

《C语言程序设计》第2版教材习题参考答案

《C语言程序设计》教材习题参考答案

Microsoft PowerPoint - 8. 运算符重载 Operator Overloading.pptx

chap07.key

程序设计语言及基础

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点

C++ 程序设计 OJ9 - 参考答案 MASTER 2019 年 6 月 7 日 1

C/C++语言 - C/C++数据

NOWOER.OM m/n m/=n m/n m%=n m%n m%=n m%n m/=n 4. enum string x1, x2, x3=10, x4, x5, x; 函数外部问 x 等于什么? 随机值 5. unsigned char *p1; unsigned long *p

运算符重载 为什么要 运算符重载 那些运算符可以重载, 哪些不可以 如何实现运算符重载 实现方式 : 成员函数与非成员函数 类型转换 怎样实现对象与基本数据类型数据的运算 2

Microsoft PowerPoint - 10 模板 Template.pptx

Microsoft PowerPoint - 6. 用户定义类型User-defined Datatypes.ppt [兼容模式]

2013 C 1 # include <stdio.h> 2 int main ( void ) 3 { 4 int cases, a, b, i; 5 scanf ("%d", & cases ); 6 for (i = 0;i < cases ;i ++) 7 { 8 scanf ("%d %d

Microsoft PowerPoint - 3. 函数Functionl.ppt [兼容模式]

FY.DOC

第七讲 继承与多态

Microsoft PowerPoint - C语言课件-9-结构体.pptx

第3章.doc

Generated by Unregistered Batch DOC TO PDF Converter , please register! 浙江大学 C 程序设计及实验 试题卷 学年春季学期考试时间 : 2003 年 6 月 20 日上午 8:3

C++ 程序设计 实验 2 - 参考答案 MASTER 2017 年 5 月 21 日 1

C++ 程式設計

幻灯片 1

2015年计算机二级(C语言)模拟试题及答案(三)

OOP with Java 通知 Project 4: 5 月 2 日晚 9 点

Microsoft PowerPoint - 07 派生数据类型

untitled

C++ 程序设计 OJ4 - 参考答案 MASTER 2019 年 5 月 30 日 1

C 1

Guava学习之Resources

一 关于内存 数据存储 变量分类 局部变量 全局变量 静态变量 请看下面代码, 分析变量类型? int pi = 3; int Area(int r, int *sum) { int b; static int c =0; b = pi * r * r; c += b; *sum = c; retu

Microsoft PowerPoint - L17_Inheritance_v4.pptx

幻灯片 1

Microsoft Word - 新1-12.doc

untitled

OOP with Java 通知 Project 2 提交时间 : 3 月 21 日晚 9 点 作业提交格式 学习使用 文本编辑器 cmd, PowerShell (Windows), terminal(linux, Mac)

C 1 # include <stdio.h> 2 int main ( void ) { 4 int cases, i; 5 long long a, b; 6 scanf ("%d", & cases ); 7 for (i = 0;i < cases ;i ++) 8 { 9

幻灯片 1

C/C++ 语言 - 循环

untitled

Microsoft Word - ch04三校.doc

OOP with Java 通知 Project 3: 3 月 29 日晚 9 点 4 月 1 日上课

PowerPoint 演示文稿

PowerPoint 演示文稿

3.1 num = 3 ch = 'C' 2

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基

北京大学

Microsoft Word - 《C语言开发入门》课程教学大纲-2.doc

Microsoft PowerPoint - 第06讲_继承.ppt [兼容模式]

OOP with Java 通知 Project 3 提交时间 3 月 29 日晚 9 点 Piazza Project 2 投票

CC213

第4章 栈和队列

Microsoft Word - CPE考生使用手冊 docx

面向对象进阶 对象的生存期 静态成员 友元关系 常对象与常成员 对象数组与对象指针 向量类 :vector 字符串类 :string 2

内 容 提 要 指 针 持 久 动 态 内 存 分 配 字 符 串 ( 字 符 数 组 ) 2

程序设计与算法语言 拷贝构造 C/C++ Programming and Algorithms Copy Constructor Dongke Sun ( 孙东科 ) 东南大学机械工程学院 School of Mechanical Engineering South

<4D F736F F F696E74202D20B5DA3032BDB25FC0E0BACDB6D4CFF3312E BBCE6C8DDC4A3CABD5D>

Strings

untitled

( CIP) /. :, ( ) ISBN TP CIP ( 2005) : : : : * : : 174 ( A ) : : ( 023) : ( 023)

Microsoft Word - 01.DOC

Chapter12 Derived Classes

提问袁小兵:

无类继承.key

Microsoft Word - 第3章.doc

OOP with Java 通知 Project 4: 推迟至 4 月 25 日晚 9 点

帝国CMS下在PHP文件中调用数据库类执行SQL语句实例

OOP with Java 通知 : Project 2 提交时间 : 3 月 15 日晚 9 点

OOP with Java 通知 : Project 2 提交时间 : 3 月 14 日晚 9 点 另一名助教 : 王桢

JAVA 单元 2.1 四则运算机 ( 一 ) 单元教学进度设计 教学环节 教学内容 教师学生活动活动 反馈 反馈课前作业完成情况 反馈加分 1. 下面哪些是合法的变量名? ( ) A.2variable 答案 :DEG B..variable2 解答 : C.._whatavariable A:/

Fun Time (1) What happens in memory? 1 i n t i ; 2 s h o r t j ; 3 double k ; 4 char c = a ; 5 i = 3; j = 2; 6 k = i j ; H.-T. Lin (NTU CSIE) Referenc

nooog

ebook39-5

幻灯片 1

文件

第五讲 C++程序的结构

C

Microsoft PowerPoint - 04-Inheritance.ppt

概述

全国计算机技术与软件专业技术资格(水平)考试

Microsoft Word cppFinalSolution.doc

OOP with Java 通知 Project 4: 5 月 2 日晚 9 点

Department of Science and Engineering Computing School of Mathematics School Peking University October 9, 2007

C/C++ System Program Documentation

Microsoft PowerPoint - CPP-Ch Print.ppt [兼容模式]

3. 給 定 一 整 數 陣 列 a[0] a[1] a[99] 且 a[k]=3k+1, 以 value=100 呼 叫 以 下 兩 函 式, 假 設 函 式 f1 及 f2 之 while 迴 圈 主 體 分 別 執 行 n1 與 n2 次 (i.e, 計 算 if 敘 述 執 行 次 數, 不

untitled

c语言面试题总结.doc

untitled

网C试题(08上).doc

Strings

Transcription:

第六章数组指针与字符串

本章主要内容 数组 指针 动态存储分配 指针与数组 指针与函数 vector 的基本用法 字符串 深度探索 2

数组的概念 数组是具有一定顺序关系的若干相 同类型变量的集合体, 组成数组的变量 称为该数组的元素 数组属于构造类型 数组3

数组名的构成方法与一般变量名相同 数组一维数组的声明与引用 一维数组的声明 类型说明符数组名 [ 常量表达式 ]; 例如 :int a[10]; 表示 a 为整型数组, 有 10 个元素 :a[0]...a[9] 使用数组名 [ 下标表达式 ] 必须先声明, 后使用 只能逐个使用数组元素, 而不能一次使用整个数组例如 :a[0]=a[5]+a[7]-a[2*3] 4

例 6. 1 一维数组的声明与引用 #include <iostream> using namespace std; int main() { int a[10], b[10]; for(int i = 0; i < 10; i++) { a[i] = i * 2-1; b[10 - i - 1] = a[i]; } for(int i = 0; i < 10; i++) { cout << "a[" << i << "] = " << a[i] << " "; cout << "b[" << I << "] = " << b[i] << endl; } return 0; } 数组5

a 数组一维数组的存储顺序 数组元素在内存中顺次存放, 它们的地址是连续的 例如 : 具有 10 个元素的数组 a, 在内存中的存 放次序如下 : a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] 数组名字是数组首元素的内存地址 数组名是一个常量, 不能被赋值 6

一维数组的初始化 可以在定义数组的同时赋给初值 : 在声明数组时对数组元素赋以初值 例如 :int a[10]={0,1,2,3,4,5,6,7,8,9}; 可以只给一部分元素赋初值 例如 :int a[10]={0,1,2,3,4}; 在对全部数组元素赋初值时, 可以不指定数组长度 例如 :int a[]={1,2,3,4,5} 数组7

例 : 用数组来处理求 Fibonacci 数列问题 #include <iostream> using namespace std; int main() { int f[20] = {1,1};// 初始化第 0 1 个数 for (int i = 2; i < 20; i++) // 求第 2~19 个数 f[i] = f[i - 2] + f[i - 1]; for (i=0;i<20;i++) { // 输出, 每行 5 个数 if (i % 5 == 0) cout << endl; cout.width(12); // 设置输出宽度为 12 cout << f[i]; } return 0; } 8

例 : 用数组来处理求 Fibonacci 数列问题 运行结果 : 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 9

可以是 'a'..'d' 数组一维数组应用举例 循环从键盘读入若干组选择题答案, 计算并输出每组答案的正确率, 直到输入 ctrl+z 为止 每组连续输入 5 个答案, 每个答案 10

#include <iostream> using namespace std; int main() { const char KEY[ ] = {'a','c','b','a','d'}; const int NUM_QUES = 5; char c; int ques = 0, numcorrect = 0; cout << "Enter the " << NUM_QUES << " question tests:" << endl; while(cin.get(c)) { if(c!= '\n') { if(c == key[ques]) { numcorrect++; cout << " "; } else cout<<"*"; ques++; } else { cout << " Score " << static_cast<float>(numcorrect)/num_ques*100<<"%"; ques = 0; numcorrect = 0; cout << endl; } } return 0; 11 }

运行结果 : acbba ** Score 60% acbad Score 100% abbda * ** Score 40% bdcba ***** Score 0% 12

二维数组的声明及使用 数据类型标识符 [ 常量表达式 1][ 常量表达式 2] ; 例 : int a[5][3]; 表示 a 为整型二维数组, 其中第一维有 5 个下标 ( 0~4 ), 第二维有 3 个下标 (0~2), 数组的元素个数为 15, 可以用 于存放 5 行 3 列的整型数据表格 数组13

二维数组的声明及引用数组 二维数组的声明 类型说明符数组名 [ 常量表达式 1][ 常量表达式 2] 例如 :float a[3][4]; 存储顺序 按行存放, 上例中数组 a 的存储顺序为 : a 00 a 01 a 02 a 03 a 10 a 11 a 12 a 13 a 20 a 21 a 22 a 23 使用 可以理解为 : 例如 :b[1][2]=a[2][3]/2 a a[0] a 00 a 01 a 02 a 03 a[1] a 10 a 11 a 12 a 13 a[2] a 20 a 21 a 22 a 23 下标不要越界 14

将所有数据写在一个 {} 内, 按顺序赋值 例如 :int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; 分行给二维数组赋初值 例如 :int a[3][4] ={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; 二维数组的初始化数组 可以对部分元素赋初值 例如 :int a[3][4]={{1},{0,6},{0,0,11}}; 15

数组作为函数参数 数组元素作实参, 与单个变量一样 数组名作参数, 形 实参数都应是数组名, 类型要一样, 传送的是数组首 到实参数组 数组地址 对形参数组的改变会直接影响 16

例 6-2 使用数组名作为函数参数 主函数中初始化一个矩阵并将每个元素都输出, 然后调用子函数, 分别计算每一行的元素之和, 将和直接存放在每行的第一个元素中, 返回主函数之后输出各行元素的和 数组17

#include <iostream> using namespace std; void rowsum(int a[][4], int nrow) { for (int i = 0; i < nrow; i++) { for(int j = 1; j < 4; j++) a[i][0] += a[i][j]; } } int main() { // 主函数 int table[3][4] = {{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}}; // 声明并初始化数组 18

} // 输出数组元素 for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) cout << table[i][j] << " "; cout << endl; } rowsum(table, 3); // 调用子函数, 计算各行和 // 输出计算结果 for (int i = 0; i < 3; i++) cout << "Sum of row " << i << " is " << table[i][0] << endl; return 0; 19

运行结果 : 1 2 3 4 2 3 4 5 3 4 5 6 Sum of row 0 is 10 Sum of row 1 is 14 Sum of row 2 is 18 20

对象数组 声明 : 类名数组名 [ 元素个数 ]; 访问方法 : 数组通过下标访问数组名 [ 下标 ]. 成员名 21

对象数组初始化 数组中每一个元素对象被创建时, 系统都会调用类构造函数初始化该对象 通过初始化列表赋值 例 : Point a[2]={point(1,2),point(3,4)}; 如果没有为数组元素指定显式初始值, 数组元素便使用默认值初始化 ( 调用缺 省构造函数 ) 数组22

数组元素所属类的构造函数 不声明构造函数, 则采用缺省构造函数 各元素对象的初值要求为相同的值时, 可以声明具有默认形参值的构造函数 各元素对象的初值要求为不同的值时, 需要声明带形参的构造函数 当数组中每一个对象被删除时, 系统都 要调用一次析构函数 数组23

例 6-3 对象数组应用举例 //Point.h #ifndef _POINT_H #define _POINT_H class Point { // 类的定义 public: // 外部接口 Point(); Point(int x, int y); ~Point(); void move(int newx,int newy); int getx() const { return x; } int gety() const { return y; } private: // 私有数据成员 int x, y; }; #endif //_POINT_H 数组24

//Point.cpp #include <iostream> #include "Point.h" using namespace std; Point::Point() { x = y = 0; cout << "Default Constructor called." << endl; } Point::Point(int x, int y) : x(x), y(y) { cout << "Constructor called." << endl; } Point::~Point() { cout << "Destructor called." << endl; } void Point::move(int newx,int newy) { cout << "Moving the point to (" << newx << ", " << newy << ")" << endl; x = newx; y = newy; } 25

//6-3.cpp #include "Point.h" #include <iostream> using namespace std; int main() { cout << "Entering main..." << endl; Point a[2]; for(int i = 0; i < 2; i++) a[i].move(i + 10, i + 20); cout << "Exiting main..." << endl; return 0; } 26

运行结果 : Entering main... Default Constructor called. Default Constructor called. Moving the point to (10, 20) Moving the point to (11, 21) Exiting main... Destructor called. Destructor called. 27

关于内存地址 内存空间的访问方式 通过变量名访问 通过地址访问 取地址运算符 :& 例 : int var; 则 &var 表示变量 var 在内存中的起始地址 28

指针变量的概念 内存用户数据区 概念指针 : 内存地址, 用于间接访问内存单元指针变量 : 用于存放地址的变量声明例 :int i; int *ptr = &i; 引用例 1:i = 3; 例 2:*ptr = 3; 指针指向整型变量的指针 2000 2004 3010 ptr *ptr 2000 3 3 6 2000 2000 变量 i 变量 j 变量 ptr i 29

指针变量的初始化 语法形式数据类型 * 指针名 = 初始地址 ; 例 :int *pa = &a; 注意事项 用变量地址作为初值时, 该变量必须在指针初始化之前已声明过, 且变量类型应与指针类型一致 可以用一个已赋初值的指针去初始化另一个指针变量 初始化为零指针 :0 或 NULL 指针30

指针变量的赋值运算 指针名 = 地址 地址 中存放的数据类型与指针类型必须相符 向指针变量赋的值必须是地址常量或变量, 不能是普通整数 但可以赋值为整数 0, 表示空指针 指针的类型是它所指向变量的类型, 而不是指针本身数据值的类型, 任何一个指针本身的数据值都是 unsigned long int 型 允许声明指向 void 类型的指针 该指针可以被赋予任何类型对象的地址 例 : void *general; 指针31

} 指针例 6-5 指针的声明 赋值与使用 #include <iostream> using namespace std; int main() { int i; // 定义 int 型数 i int *ptr = &i; // 取 i 的地址赋给 ptr i = 10; //int 型数赋初值 cout << "i = " << i << endl;// 输出 int 型数的值 cout << "*ptr = " << *ptr << endl;// 输出 int 型指针所指地址的内容 return 0; 32

程序运行的结果是 : i = 10 *ptr = 10 33

例 6-6 void 类型指针的使用 #include <iostream> using namespace std; int main() { //!void voidobject; 错, 不能声明 void 类型的变量 void *pv; // 对, 可以声明 void 类型的指针 int i = 5; pv = &i; //void 类型指针指向整型变量 int *pint = static_cast<int *>(pv); //void 类型指针赋值给 int 类型指针 cout << "*pint = " << *pint << endl; return 0; } 指针34

指向常量的指针 指 不能通过指针来改变所指对象的值, 但指针本身可以改变, 可以指向另外的对象 针 例 int a; const int *p1 = &a; //p1 是指向常量的指针 int b; p1 = &b;// 正确,p1 本身的值可以改变 *p1 = 1; // 编译时出错, 不能通过 p1 改变所指的对象 35

指针类型的常量 若声明指针常量, 则指针本身的值不能被改变 例 : p2 = &b;// 错误,p2 是指针常量, 值不能改变指针int a; int * const p2 = &a; 36

指针变量的算术运算 指 指针与整数的加减运算 指针 p 加上或减去 n, 其意义是指针当前指向针位置的前方或后方第 n 个数据的地址 这种运算的结果值取决于指针指向的数据类型 p1[n1] 等价于 *(p1 + n1) 指针加一, 减一运算 指向下一个或前一个数据 例如 :y=*px++ 相当于 y=*(px++) (* 和 ++ 优先级相同, 自右向左运算 ) 37

short *pa pa-2 pa-1 *(pa-2) 或 pa[-2] *(pa-1) 或 pa[-1] pa *pa 或 pa[0] pa+1 pa+2 pa+3 *(pa+1) 或 pa[1] *(pa+2) 或 pa[2] *(pa+3) 或 pa[3] 38

long *pb pb-1 *(pb-1) 或 pb[-1] pb *pb 或 pb[0] pb+1 *(pb+1) 或 pb[1] pb+2 *(pb+2) 或 pb[2] 39

指针变量的关系运算指针 关系运算 指向相同类型数据的指针之间可以进行各种关系运算 指向不同数据类型的指针, 以及指针与一般整数变量之间的关系运算是无意义的 指针可以和零之间进行等于或不等于的关系运算 例如 :p==0 或 p!=0 赋值运算 向指针变量赋的值必须是地址常量或变量, 不能是普通整数 但可以赋值为整数 0, 表示空指针 40

指向数组元素的指针 声明与赋值 例 :int a[10], *pa; pa=&a[0]; 或 pa=a; 通过指针引用数组元素 不能写 a++, 因为 a 是数组首地址是常量 指针经过上述声明及赋值后 : *pa 就是 a[0],*(pa+1) 就是 a[1],...,*(pa+i) 就是 a[i]. a[i], *(pa+i), *(a+i), pa[i] 都是等效的 41

例 6-7 设有一个 int 型数组 a, 有 10 个元素 用三种方法输出各元素 : 使用指针变量指针 使用数组名和下标 使用数组名和指针运算 42

使用数组名和下标 #include <iostream> using namespace std; int main() { int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; for (int i = 0; i < 10; i++) cout << a[i] << " "; cout << endl; return 0; } 43

使用数组名指针运算 #include <iostream> using namespace std; int main() { int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; for (int i = 0; i < 10; i++) cout << *(a+i) << " "; cout << endl; return 0; } 44

使用指针变量 #include <iostream> using namespace std; int main() { int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; for (int *p = a; p < (a + 10); p++) cout << *p << " "; cout << endl; return 0; } 45

指针数组 数组的元素是指针型 例 :Point *pa[2]; 由 pa[0],pa[1] 两个指针组成指针46

例 6-8 利用指针数组存放单位矩阵 #include <iostream> using namespace std; int main() { int line1[] = { 1, 0, 0 }; // 矩阵的第一行 int line2[] = { 0, 1, 0 }; // 矩阵的第二行 int line3[] = { 0, 0, 1 }; // 矩阵的第三行 int *pline[3] = { line1, line2, line3 }; 指针// 定义整型指针数组并初始化 47

} cout << "Matrix test:" << endl; // 输出单位矩阵 for (int i = 0; i < 3; i++) { } for (int j = 0; j < 3; j++) cout << pline[i][j] << " "; cout << endl; return 0; pline[i][j] 等价于 *(pline[i] + j) 输出结果为 : Matrix test: 1,0,0 0,1,0 0,0,1 48

例 6-9 二维数组举例 #include <iostream> using namespace std; int main() { int array2[3][3]= { { 11, 12, 13 }, { 21, 22, 23 }, { 31, 32, 33 } }; for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) cout << *(*(array2 + i) + j) << " "; // 逐个输出二维数组第 i 行元素值 cout << endl; } return 0; *(*(array2 +i) + j) 等价于 array2[i][j] } 指针49

程序的输出结果为 : 11 12 13 21 22 23 31 32 33 50

指针数组 vs 二维数组 pline[0] pline[1] pline[2] array2 (a) 指针数组 array2[0] array2[1] array2[2] (b) 二维数组 51

指针与函数 void f(int *p); 以指针作为函数参数 以地址方式传递数据, 可以用来返回函数处理结果 实参是数组名时, 形参可以是指针 void f(int p[]); void f(int p[3]); 52

针与函数例 6.10 题目 : 读入三个浮点数, 将整数部分和小数部分分别输出 #include <iostream> using namespace std; void splitfloat(float x, int *intpart, float *fracpart) { // 取 x 的整数部分 *intpart = static_cast<int>(x); // 取 x 的小数部分 *fracpart = x - *intpart; } 指53

int main() { cout << "Enter 3 float point numbers:" << endl; for(int i = 0; i < 3; i++) { float x, f; int n; cin >> x; } splitfloat(x, &n, &f); // 变量地址作为实参 cout << "Integer Part = " << n << " Fraction Part = " << f << endl; } return 0; 54

运行结果 : Enter 3 floating point numbers 4.7 Integer Part = 4 Fraction Part = 0.7 8.913 Integer Part = 8 Fraction Part = 0.913-4.7518 Integer Part = -4 Fraction Part = -0.7518 55

针与函数例 : 输出数组元素的内容和地址 #include <iostream> #include <iomanip> using namespace std; void arrayptr(long *p, int n) { cout << "In func, address of array is " << p << endl; cout << "Accessing array using pointers" << endl; for (int i = 0; i < n; i++) { cout << " Address for index " << i << " is " << p + i; cout << " Value is " << *(p + i) << endl; } } 指56

int main() { long list[5]={50, 60, 70, 80, 90}; cout << "In main, address of array is " << list << endl; cout << endl; arrayptr(list,5); } return 0; 57

运行结果 : In main, address of array is 0012FF50 In func, address of array is 0012FF50 Accessing array using pointers Address for index 0 is 0012FF50 Value is 50 Address for index 1 is 0012FF54 Value is 60 Address for index 2 is 0012FF58 Value is 70 Address for index 3 is 0012FF5C Value is 80 Address for index 4 is 0012FF60 Value is 90 58

指向常量的指针做形参 #include<iostream> using namespace std; const int N = 6; void print(const int *p, int n); int main() { int array[n]; for (int i = 0; i < N; i++) cin>>array[i]; print(array, N); return 0; } 指针59

void print(const int *p, int n) { cout << "{ " << *p; for (int i = 1; i < n; i++) cout << ", " << *(p+i); cout << " }" << endl; } 60

指针与函数指针型函数 当函数的返回值是地址时, 该函数就是指针形函数 声明形式 数据类型 * 函数名 ( 形参表 ) 61

指针与函数指向函数的指针 声明形式 含义 : 数据类型 (* 函数指针名 )( 形参表 ); 数据指针指向数据存储区, 而函数指针指向的是程序代码存储区 62

指针与函数指向函数的指针 赋值 函数指针名 = 函数名 ; 函数指针名 = & 函数名 ; 函数指针名 = 0; // NULL 使用函数指针调用函数 函数指针名 ( 实参表 ); (* 函数指针名 )( 实参表 ); 63

针与函数例 6-11 函数指针 #include <iostream> using namespace std; void printstuff(float) { cout << "This is the print stuff function." << endl; } void printmessage(float data) { cout << "The data to be listed is " << data << endl; } void printfloat(float data) { cout << "The data to be printed is " << data << endl; } 指64

const float PI = 3.14159f; const float TWO_PI = PI * 2.0f; int main() { // 主函数 void (*functionpointer)(float); // 函数指针 printstuff(pi); functionpointer = printstuff; functionpointer(pi); // 函数指针调用 functionpointer = printmessage; functionpointer(two_pi); // 函数指针调用 functionpointer(13.0); // 函数指针调用 functionpointer = &printfloat; (*functionpointer)(pi); // 函数指针调用 printfloat(pi); return 0; } 65

运行结果 : This is the print stuff function. This is the print stuff function. The data to be listed is 6.28318 The data to be listed is 13 The data to be printed is 3.14159 The data to be printed is 3.14159 66

对象指针的一般概念 指 声明形式类名 * 对象指针名 ; 针 例 Point a(5,10); Piont *ptr; ptr=&a; 通过指针访问对象成员对象指针名 -> 成员名 ptr->getx() 相当于 (*ptr).getx(); 67

} 指针对象指针应用举例 int main() { Point a(5, 10); Point *ptr; ptr = &a; int x; x = ptr->getx(); cout << x << endl; return 0; 68

曾经出现过的错误例子 class Fred; // 前向引用声明 class Barney { Fred x; // 错误 : 类 Fred 的声明尚不完善 }; class Fred { Barney y; }; 指针69

正确的程序 class Fred; // 前向引用声明 class Barney { Fred *x; }; class Fred { Barney y; }; 指针70

this 指针 指 隐含于每一个类的成员函数中的特殊指针 针 this 是一个指针常量 对于常成员函数, 是指向常量的指针常量 明确地指出了成员函数当前所操作的数据所属的对象 当通过一个对象调用成员函数时, 系统先将该对象的地址赋给 this 指针, 然后调用成员函数, 成员函数对对象的数据成员进行操作时, 就隐含使用了 this 指针 71

this 指针 例如 :Point 类的 getx 函数中的语句 : return x; 相当于 : return this->x; 或 return (*this).x; 指针72

指向类的非静态成员的指针 通过指向成员的指针只能访问公有成员 声明指向成员的指针 声明指向公有数据成员的指针类型说明符类名 ::* 指针名 ; 声明指向公有函数成员的指针 类型说明符 ( 类名 ::* 指针名 )( 参数表 ); 指针73

指向类的非静态成员的指针 指向数据成员的指针 说明指针应该指向哪个成员指针名 = & 类名 :: 数据成员名 ; 通过对象名 ( 或对象指针 ) 与成员指针结合来访问数据成员 对象指针名 ->* 类成员指针名指针对象名.* 类成员指针名 或 : 74

指向类的非静态成员的指针 指向函数成员的指针 初始化指针名 =& 类名 :: 函数成员名 ; 通过对象名 ( 或对象指针 ) 与成员指针结合来访问函数成员 ( 对象指针名 ->* 类成员指针名 )( 参数表 ) 指针( 对象名.* 类成员指针名 )( 参数表 ) 或 : 和指向普通函数的指针不同, & 和 * 不能省略 75

指向类的非静态成员的指针 例 6-13 访问对象的公有成员函数的不同方式 int main() { // 主函数 Point a(4,5); // 声明对象 A Point *p1 = &a; // 声明对象指针并初始化 // 声明成员函数指针并初始化 int (Point::*funcPtr)() const = &Point::getX; //(1) 使用成员函数指针访问成员函数 cout << (a.*funcptr)() << endl; //(2) 使用成员函数指针和对象指针访问成员函数 cout << (p1->*funcptr)() << endl; //(3) 使用对象名访问成员函数 cout << a.getx() << endl; //(4) 使用对象指针访问成员函数 cout << p1->getx() << endl; return 0; } 指针76

指向类的静态成员的指针 对类的静态成员的访问不依赖于对象 可以用普通的指针来指向和访问静态成员 例 6-14 通过指针访问类的静态数据成员 例 6-15 通过指针访问类的静态函数成员指针77

例 6-14 通过指针访问类的静态数据成员 #include <iostream> using namespace std; class Point { //Point 类定义 public: // 外部接口 Point(int x = 0, int y = 0) : x(x), y(y) { count++; } Point(const Point &p) : x(p.x), y(p.y) { count++; } ~Point() { count--; } int getx() const { return x; } int gety() const { return y; } static int count; private: // 私有数据成员 int x, y; }; int Point::count = 0; 指针78

int main() { // 主函数实现 // 定义一个 int 型指针, 指向类的静态成员 int *ptr = &Point::count; Point a(4, 5); // 定义对象 a cout << "Point A: " << a.getx() << ", " << a.gety(); cout << " Object count = " << *ptr << endl; Point b(a); // 定义对象 b cout << "Point B: " << b.getx() << ", " << b.gety(); cout << " Object count = " << *ptr << endl; } return 0; 79

例 6-15 通过指针访问类的静态函数成员 #include <iostream> using namespace std; class Point { //Point 类定义 public: // 外部接口 Point(int x = 0, int y = 0) : x(x), y(y) { count++; } Point(const Point &p) : x(p.x), y(p.y) { count++; } ~Point() { count--; } int getx() const { return x; } int gety() const { return y; } static void showcount() { cout << " Object count = " << count << endl; } private: // 私有数据成员 int x, y; static int count; }; int Point::count = 0; 指针80

int main() { // 主函数实现 // 定义一个指向函数的指针, 指向类的静态成员函数 void (*funcptr)() = Point::showCount; Point a(4, 5); // 定义对象 A cout << "Point A: " << a.getx() << ", " << a.gety(); funcptr(); // 输出对象个数, 直接通过指针访问静态函数成员 Point b(a); // 定义对象 B cout << "Point B: " << b.getx() << ", " << b.gety(); funcptr(); // 输出对象个数, 直接通过指针访问静态函数成员 } return 0; 81

动态存储分配动态申请内存操作符 new new 类型名 T( 初始化参数列表 ) 功能 : 在程序执行期间, 申请用于存放 T 类型对象的内存空间, 并依初值列表赋以初值 结果值 : 成功 :T 类型的指针, 指向新分配的内存 ; 失败 : 抛出异常 82

动态存储分配基本类型变量初始化 分配内存后, 用初值来初始化 : int *point = new int(2); 不初始化 : int *point = new int; 用 0 初始化 : int *point = new int(); 83

动态存储分配类对象初始化 分配内存后, 调用构造函数初始化 : T *point = new T( 初值列表 ); 有用户定义的默认构造函数时,new T 和 new T() 效果相同, 都调用默认构造函数 若用户未定义默认构造函数 : new T 调用系统生成的默认构造函数 new T() 执行默认构造函数, 并为基本数据类型和指针类型的成员用 0 赋初值, 该过程是递归的 84

动态存储分配释放内存操作符 delete delete 指针 p 功能 : 释放指针 p 所指向的内存 p 必须是 new 操作的返回值 如果被删除的是对象, 该对象的析构函数将被调用 delete 零指针是安全的 : int *ip = 0; delete ip; 最好在 delete 指针后, 把其值置为 0 85

态存储分配例 6-16 动态创建对象举例 #include <iostream> using namespace std; class Point { public: Point() : x(0), y(0) { cout<<"default Constructor called."<<endl; } Point(int x, int y) : x(x), y(y) { cout<< "Constructor called."<<endl; } ~Point() { cout<<"destructor called."<<endl; } int getx() const { return x; } int gety() const { return y; } void move(int newx, int newy) { x = newx; y = newy; } private: int x, y; }; 动86

int main() { cout << "Step one: " << endl; Point *ptr1 = new Point;// 调用缺省构造函数 delete ptr1; // 删除对象, 自动调用析构函数 } cout << "Step two: " << endl; ptr1 = new Point(1,2); delete ptr1; return 0; 运行结果 : Step One: Default Constructor called. Destructor called. Step Two: Constructor called. Destructor called. 87

申请和释放动态数组 分配 :new 类型名 T [ 数组长度 ] 数组长度可以是任何正整数值表达式, 在运行时计算 方括号后可加小括号 (), 但小括号内不能带任何参数 是否加 () 的区别同 new T 和 new T() 初始化时的区别 int *p = new int[10](); 释放 :delete[] 指针名 p 释放指针 p 所指向的数组 p 必须是用 new 分配得到的数组首地址 88

动态存储分配} 例 6-17 动态创建对象数组举例 #include<iostream> using namespace std; class Point { // 类的声明同例 6-16, 略 }; int main() { Point *ptr = new Point[2]; // 创建对象数组 ptr[0].move(5, 10);// 通过指针访问数组元素的成员 ptr[1].move(15, 20);// 通过指针访问数组元素的成员 cout << "Deleting..." << endl; delete[] ptr; // 删除整个对象数组 return 0; 89

运行结果 : Default Constructor called. Default Constructor called. Deleting... Destructor called. Destructor called. 90

将动态数组封装成类 更加简洁, 便于管理 建立和删除数组的过程比较繁琐 封装成类后更加简洁, 便于管理 可以在访问数组元素前检查下标是否越界 用 assert 来检查,assert 只在调试时生效 91

例 6-18 动态数组类 #include <iostream> #include <cassert> using namespace std; class Point { // 类的声明同例 6-16 }; class ArrayOfPoints { // 动态数组类 public: ArrayOfPoints(int size) : size(size) { points = new Point[size]; } ~ArrayOfPoints() { cout << "Deleting..." << endl; delete[] points; } Point &element(int index) { assert(index >= 0 && index < size); return points[index]; } private: Point *points; // 指向动态数组首地址 int size; }; // 数组大小 92

int main() { int count; cout << "Please enter the count of points: "; cin >> count; ArrayOfPoints points(count); // 创建对象数组 // 通过访问数组元素的成员 points.element(0).move(5, 0); // 通过类访问数组元素的成员 points.element(1).move(15, 20); return 0; } 93

运行结果如下 : Please enter the number of points:2 Default Constructor called. Default Constructor called. Deleting... Destructor called. Destructor called. 94

动态存储分配动态创建多维数组 new 类型名 T[ 第 1 维长度 ][ 第 2 维长度 ] ; 如果内存申请成功,new 运算返回一个指向新分配内存首地址的指针, 是一个 T 类型的数组, 数组元素的个数为除最左边一维外各维下标表达式的乘积 例如 : char (*fp)[3]; fp = new char[2][3]; 95

char (*fp)[3]; fp fp[0][0] fp[0][1] fp+1 fp[0][2] fp[1][0] fp[1][1] fp[1][2] 96

动态存储分配例 6-19 动态创建多维数组 #include <iostream> using namespace std; int main() { float (*cp)[9][8] = new float[8][9][8]; for (int i = 0; i < 8; i++) for (int j = 0; j < 9; j++) for (int k = 0; k < 8; k++) // 以指针形式数组元素 *(*(*(cp + i) + j) + k) = static_cast<float>(i * 100 + j * 10 + k); 97

} for (int i = 0; i < 8; i++) { for (int j = 0; j < 9; j++) { for (int k = 0; k < 8; k++) // 将指针 cp 作为数组名使用, 通过数组名和下标访问数组元素 cout << cp[i][j][k] << " "; cout << endl; } cout << endl; } delete[] cp; return 0; 98

用 vector 创建动态数组 vector 动态数组对象 为什么需要 vector? 将动态数组封装, 自动创建和删除 数组下标越界检查 例 6-18 中封装的 ArrayOfPoints 也提供了类似功能, 但只适用于一种类型的数组 vector 动态数组对象的定义 vector< 元素类型 > 数组对象名 ( 数组长度 ); 例 :vector<int> arr(5) 建立大小为 5 的 int 数组 99

vector 数组对象的使用 vector 动态数组对象 对数组元素的引用 与普通数组具有相同形式 : 数组对象名 [ 下标表达式 ] 但 vector 数组对象名不表示数组首地址 获得数组长度 用 size 函数 数组对象名.size() 100

例 6-20 vector 应用举例 vector 动态数组对象#include <iostream> #include <vector> using namespace std; // 计算数组 arr 中元素的平均值 double average(const vector<double> &arr) { double sum = 0; for (unsigned i = 0; i < arr.size(); i++) sum += arr[i]; return sum / arr.size(); } 101

int main() { unsigned n; cout << "n = "; cin >> n; vector<double> arr(n); // 创建数组对象 cout << "Please input " << n << " real numbers:" << endl; for (unsigned i = 0; i < n; i++) cin >> arr[i]; } cout << "Average = " << average(arr) << endl; return 0; 102

浅拷贝与深拷贝浅拷贝与深拷贝 浅拷贝 实现对象间数据元素的一一对应复制 隐含的拷贝构造函数完成浅拷贝 深拷贝 当被复制的对象数据成员是指针类型时, 不是复制该指针成员本身, 而是将指针所指的对象进行复制 103

拷贝与深拷贝例 6-21 对象的浅拷贝 #include <iostream> #include <cassert> using namespace std; class Point { // 类的声明同例 6-16 // }; class ArrayOfPoints { // 类的声明同例 6-18 // }; 浅104

int main() { int count; cout << "Please enter the count of points: "; cin >> count; ArrayOfPoints pointsarray1(count); // 创建对象数组 pointsarray1.element(0).move(5,10); pointsarray1.element(1).move(15,20); ArrayOfPoints pointsarray2 = pointsarray1; // 创建副本 cout << "Copy of pointsarray1:" << endl; cout << "Point_0 of array2: " << pointsarray2.element(0).getx() << ", " << pointsarray2.element(0).gety() << endl; cout << "Point_1 of array2: " << pointsarray2.element(1).getx() << ", " << pointsarray2.element(1).gety() << endl; 105

pointsarray1.element(0).move(25, 30); pointsarray1.element(1).move(35, 40); cout << "After the moving of pointsarray1:" << endl; cout << "Point_0 of array2: " << pointsarray2.element(0).getx() << ", " << pointsarray2.element(0).gety() << endl; cout << "Point_1 of array2: " << pointsarray2.element(1).getx() << ", " << pointsarray2.element(1).gety() << endl; } return 0; 106

运行结果如下 : Please enter the number of points:2 Default Constructor called. Default Constructor called. Copy of pointsarray1: Point_0 of array2: 5, 10 Point_1 of array2: 15, 20 After the moving of pointsarray1: Point_0 of array2: 25, 30 Point_1 of array2: 35, 40 Deleting... Destructor called. Destructor called. Deleting... 接下来程序出现异常, 也就是运行错误 107

pointsarray1 points size pointsarray1 的数组元素占用的内存 拷贝前 pointsarray1 points size pointsarray1 的数组元素占用的内存 pointsarray2 points size 拷贝后 108

拷贝与深拷贝例 6-22 对象的深拷贝 #include <iostream> #include <cassert> using namespace std; class Point { // 类的声明同例 6-16 }; class ArrayOfPoints { public: ArrayOfPoints(const ArrayOfPoints& pointsarray); // 其他成员同例 6-18 }; 浅109

ArrayOfPoints::ArrayOfPoints(const ArrayOfPoints& v) { size = v.size; points = new Point[size]; for (int i = 0; i < size; i++) points[i] = v.points[i]; } int main() { // 同例 6-20 } 110

程序的运行结果如下 : Please enter the number of points:2 Default Constructor called. Default Constructor called. Default Constructor called. Default Constructor called. Copy of pointsarray1: Point_0 of array2: 5, 10 Point_1 of array2: 15, 20 After the moving of pointsarray1: Point_0 of array2: 5, 10 Point_1 of array2: 15, 20 Deleting... Destructor called. Destructor called. Deleting... Destructor called. Destructor called. 111

pointsarray1 points size pointsarray1 的数组元素占用的内存 拷贝前 pointsarray1 points size pointsarray1 的数组元素占用的内存 拷贝后 pointsarray2 points size 112

字符串113 用字符数组存储和处理字符串 字符串常量 ( 例 :"program") 各字符连续 顺序存放, 每个字符占一个字节, 以 \0 结尾, 相当于一个隐含创建的字符常量数组 program 出现在表达式中, 表示这一 char 数组的首地址 首地址可以赋给 char 常量指针 : const char *STRING1 = "program"; 字符串变量 可以显式创建字符数组来表示字符串变量, 例如, 以下三条语句具有等价的作用 : char str[8] = { 'p', 'r', 'o', 'g', 'r', 'a', 'm', '\0' }; char str[8] = "program"; char str[] = "program"; p r o g r a m \0

符串用字符数组表示字符串的缺点 用字符数组表示字符串的缺点 执行连接 拷贝 比较等操作, 都需要显式调用库函数, 很麻烦 当字符串长度很不确定时, 需要用 new 动态创建字符数组, 最后要用 delete 释放, 很繁琐 字符串实际长度大于为它分配的空间时, 会产生数组下标越界的错误 解决方法 使用字符串类 string 表示字符串 string 实际上是对字符数组操作的封装 114

符串string 的用法 (1) 常用构造函数 string(); // 缺省构造函数, 建立一个长度为 0 的串 string(const char *s); // 用指针 s 所指向的字符串常量初始化 string 类的对象 string(const string& rhs); // 拷贝构造函数 例 : string s1; // 建立一个空字符串 string s2 = abc ; // 用常量建立一个初值为 abc 的字符串 string s3 = s2; // 执行拷贝构造函数, 用 s2 的值作为 s3 的初值 返回串的长度 : unsigned int length() const; 115

符串string 的用法 (2) 常用操作符 s + t 将串 s 和 t 连接成一个新串 s = t 用 t 更新 s 字 s == t 判断 s 与 t 是否相等 s!= t 判断 s 与 t 是否不等 s < t 判断 s 是否小于 t( 按字典顺序比较 ) s <= t 判断 s 是否小于或等于 t ( 按字典顺序比较 ) s > t 判断 s 是否大于 t ( 按字典顺序比较 ) s >= t 判断 s 是否大于或等于 t ( 按字典顺序比较 ) s[i] 访问串中下标为 i 的字符 例 : string s1 = abc, s2 = def ; string s3 = s1 + s2; // 结果是 abcdef bool s4 = (s1 < s2); // 结果是 true char s5 = s2[1]; // 结果是 e 116

字符串} 例 6.23 string 类应用举例 #include <string> #include <iostream> using namespace std; // 根据 value 的值输出 true 或 false,title 为提示文字 inline void test(const char *title, bool value) { cout << title << " returns " << (value? "true" : "false") << endl; 117

int main() { string s1 = "DEF"; cout << "s1 is " << s1 << endl; string s2; cout << "Please enter s2: "; cin >> s2; cout << "length of s2: " << s2.length() << endl; } // 比较运算符的测试 test("s1 <= \"ABC\"", s1 <= "ABC"); test("\"def\" <= s1", "DEF" <= s1); // 连接运算符的测试 s2 += s1; cout << "s2 = s2 + s1: " << s2 << endl; cout << "length of s2: " << s2.length() << endl; return 0; 118

符串用 getline 输入整行字符串 输入整行字符串 用 cin 的 >> 操作符输入字符串, 会以空格作为分字隔符, 空格后的内容会在下一回输入时被读取 用 string 头文件中的 getline 可以输入整行字符串, 例如 : getline(cin, s2); 以其它字符作为分隔符输入字符串 输入字符串时, 可以使用其它分隔符作为字符串结束的标志 ( 例如逗号 分号 ) 把分隔符作为 getline 的第 3 个参数即可, 例如 : getline(cin, s2, ','); 119

符串例 6.24 用 getline 输入字符串 include <iostream> #include <string> using namespace std; int main() { for (int i = 0; i < 2; i++) { string city, state; getline(cin, city, ','); getline(cin, state); cout << "City:" << city << State:" << state << endl; } return 0; } 字Beijing,China City: Beijing State: China San Francisco,the United States City: San Francisco State: the United States 120

深度探索121 指针与引用 引用本身占用的内存空间中, 存储的就是被引用变量的地址 引用的功能相当于指针常量 普通指针可以多次被赋值 引用只能在初始化时指定被引用的对象 只有常引用, 没有引用常量 不能用 T & const 作为引用类型

深度探索122 指针常量与引用的比较 操作 T 类型的指针常量对 T 类型的引用 定义并用 v 初始化取 v 的值访问成员 m 读取 v 的地址 T * const p = &v; *p p->m p T &r = v; r r.m &r p 可以再被取地址, 而 &r 则不行 引用本身的地址是不可以获得的 引用实现的功能, 用指针都可以实现

度探索指针与引用的对应关系 // 使用指针常量 void swap(int * const pa, int * const pb) { int temp = *pa; *pa = *pb; *pb = temp; } int main() { int a, b; swap(&a, &b); return 0; } // 使用引用深void swap(int &ra, int &rb) { int temp = ra; ra = rb; rb = temp; } int main() { int a, b; swap(a, b); return 0; } 123

深度探索124 指针与引用的联系 引用在底层通过指针来实现 一个引用变量, 通过存储被引用对象的地址, 来标识它所引用的对象 引用是对指针的包装, 比指针更高级 指针是 C 语言就有的底层概念, 使用起来很灵活, 但用不好容易出错 引用隐藏了指针的 地址 概念, 不能直接对地址操作, 比指针更安全

度探索引用与指针的选择 什么时候用引用? 深 如无需直接对地址进行操作, 指针一般都可用引用代替 用更多的引用代替指针, 更简洁 安全 什么时候用指针? 引用的功能没有指针强大, 有时不得不用指针 : 引用一经初始化, 无法更改被引用对象, 如有这种需求, 必须用指针 ; 没有空引用, 但有空指针, 如果空指针有存在的必要, 必须用指针 ; 函数指针 ; 用 new 动态创建的对象或数组, 用指针存储其地址最自然 ; 函数调用时, 以数组形式传递大量数据时, 需要用指针作为参数 125

度探索指针的地址安全性问题 地址安全性问题深 通过指针, 访问了不该访问的地址, 就会出问题 典型问题 : 数组下标越界 多次 delete 问题的严重性 : 有时会在不知不觉中产生错误, 错误源很难定位, 因此程序调试起来很麻烦 解决方案 指针只有赋了初值才能使用 ( 这一点普通变量也应遵循 ) 指针的算术运算, 一定要限制在通过指向数组中某个元素的指针, 得到指向同一个数组中另一个元素的指针 尽量使用封装的数组 ( 如 vector), 而不直接对指针进行操作 126

度探索指针的类型安全性问题 (1) 基本类型数据的转换是基于内容的 : 深 static_cast: 比较安全的, 基于内容的类型转换 例 : int i = 2; float x = static_cast<float>(i); 目标类型不同的指针间的转换, 会使一种类型数据的二进制序列被当作另一种类型的数据 : reinterpret_cast: 可以将一种类型的指针转换为另一种类型 int i = 2; float *p = reinterpret_cast<float *>(&i); 结论 从一种具体类型指针转换为另一种具体类型指针的 reinterpret_cast 很不安全, 一般情况下不要用 127

度探索指针的类型安全性问题 (2) void 指针与具体类型指针的转换 : 深 具体类型指针可以隐含地转换为 void 指针 : int i = 2; void *vp = &i; void 指针转换为具体类型指针需要用 static_cast, 例如 : int *p = static_cast<int *>(vp); 但这样就出问题了 : float *p2 = static_cast<float *>(vp); 结论 void 指针也容易带来不安全, 尽量不用, 在不得不用的时候, 一定要在将 void 指针转换为具体类型指针时, 确保指针被转换为它最初的类型 128

度探索堆对象的管理 堆对象必须用 delete 删除深 避免 内存泄漏 原则很简单, 但在复杂的程序中, 一个堆对象常常要被多个不同的类 模块访问, 该在哪里删除, 常常引起混乱 如何有条理地管理堆对象 明确每个堆对象的归属 最理想的情况 : 在一个类的成员函数中创建的堆对象, 也在这个类的成员函数中删除 把对象的建立和删除变成了一个类的局部问题 如需在不同类之间转移堆对象的归属, 一定要在注释中注明, 作为类的对外接口约定 例如在一个函数内创建堆对象并将其返回, 则该对象应当由调用该函数的类负责删除 129

度探索const_cast 介绍 const_cast 的用法深 用于将常指针 常引用转换为不带 const 的相关类型 例 : void foo(const int *cp) { int *p = const_cast<int *>(cp); (*p)++; } 这可以通过常指针修改指针发对象的值, 但这是不安全的用法, 因为破坏了接口中 const 的约定 const_cast 不是安全的转换, 但有时可以安全地使用 130

度探索const_cast 的安全使用 (1) 对例 6-18 的改进深 例 6-18 通过下列函数访问数组元素 Point &element(int index); 问题 : 由于不是常成员函数, 无法使用常对象访问数组元素 解决方法 : 重载 element 函数 : const Point &element(int index) const { assert(index >= 0 && index < size); return points[index]; } 新问题 代码的重复 : 这个 element 函数与原先的函数内容完全相同, 两份重复的代码, 不易维护 131

度探索const_cast 的安全使用 (2) 新问题的解决深 修改原先的 element 函数 : Point &element(int index) { return const_cast<point &>( static_cast<const ArrayOfPoints *>(this) ->element(index)); } 执行过程 : 调用常成员函数 element(), 再将其返回结果中的 const 用 const_cast 去除 将 this 用 static_cast 转换为常指针, 是安全的转换 该函数本身不是常成员函数, 确保将最终的结果以普通引用形式返回是安全的 思考 : 如果保留该函数, 而修改常成员函数 element, 使常成员函数 element 调用该函数, 是否合适? 132

小结与复习建议 主要内容 数组 指针 动态存储分配 指针与数组 指针与函数 字符串 达到的目标 理解数组 指针的概念, 掌握定义和使用方法, 掌握动态存储分配技术, 会使用 string 类 133