第16章 数据的共享和流通 一、浅拷贝和深拷贝 二、只读成员函数 三、友元friend.

Slides:



Advertisements
Similar presentations
Memory Pool ACM Yanqing Peng.
Advertisements

類別與物件 Class & Object.
第八章 类和对象.
C语言程序设计 第十二章 位运算.
高级语言程序设计 主讲人:陈玉华.
走向C++之路 WindyWinter WindyWinter感谢诸位前来捧场。
主讲教师:吴琼 微信群:C语言2016 QQ群: 密码scu2016 昵称:“真名+学号”
物件導向程式設計 (Object-Oriented rogramming)
Chap 18 類別與物件 夫有土者,有大物也。有大物者,不可以物。 物而不物,故能物物。 明乎物物者之非物也,豈獨治天下百姓而已哉!
第四章 函数和递归.
If … else 選擇結構 P27.
第七章 搜索结构 静态搜索结构 二叉搜索树 AVL树.
·线性表的定义及ADT ·线性表的顺序存储结构 ·线性表的链接存储结构 · 单向循环链表 · 双链表、双向循环链表 · 一元多项式的加法
習作2-1 題目+解答 紐約港 紐約中央公園 格陵蘭島.
哈夫曼编码.
程式設計實作.
刘胥影 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院.
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
Object-Oriented Programming in C++ 第一章 C++的初步知识
程序设计期末复习 黎金宁
第三章 C++中的C 面向对象程序设计(C++).
第12章 從C到C++語言 12-1 C++語言的基礎 12-2 C++語言的輸出與輸入 12-3 C++語言的動態記憶體配置
2 C++ 的基本語法和使用環境 親自撰寫和執行程式是學好程式語言的不二法門。本章藉由兩個簡單的程式,介紹C++ 程式的基本結構和開發環境,讓初學者能逐漸建立使用C++ 的信心。
第八章 类与对象 本章要求: 掌握类的含义与定义格式。 掌握类的无参构造函数、带参构造函数、拷贝构造函数和赋值重载函数的定义格式及作用。
第二章 C++对C 在非面向对象方面的改进 更简洁,更安全.
C/C++/Java 哪些值不是头等程序对象
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
$10 可空类型.
第三章 链表 单链表 (Singly Linked List) 循环链表 (Circular List) 多项式及其相加
第三章 链表 单链表 循环链表 多项式及其相加 双向链表 稀疏矩阵.
第1讲 C语言基础 要求: (1) C程序的组成 (2) C语言的标识符是如何定义的。 (3) C语言有哪些基本数据类型?各种基本数
潘爱民 C++ Overview 潘爱民
C语言复习3----指针.
C#程序设计基础 $3 成员、变量和常量.
第10讲 构造函数和析构函数 构造函数 析构函数 This 指针.
C++复习2----类与对象.
第四章 栈和队列 栈 ( Stack ) 队列 ( Queue ) 优先队列 (Priority Queue) 小结.
Java程式初體驗大綱 大綱 在學程式之前及本書常用名詞解釋 Hello Java!程式 在Dos下編譯、執行程式
C语言程序设计 李祥 QQ:
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
Object-Oriented Programming in C++ 第二章 类和对象
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
5431-數位- 課號: 5431 課程名稱:認知與數位教學 (一) 作業名稱:5431-數位-6-解錄-0110 系級:科技與數位學習組 學號: 姓名:王品涵 個人相片 檔案命名規則 5431-數位-學號-姓名-.ppt.
第二章 Java语法基础.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
第7章 程序的结构 四、生存期与存储属性 五、extern关键字与外部连接属性 六、static关键字与内部连接属性.
第一章 C语言概述 目录 什么是语言、程序 C语言的历史与发展 C语言的书写形式与程序结构 运行C语言的步骤与方法
第二章 类型、对象、运算符和表达式.
C++程序设计基础 主讲人:谢昕 华东交通大学信息工程学院 第十~十二讲 多态性和虚函数 2005年春季学期.
第九章 物件導向-進階.
C/C++基礎程式設計班 C++: 物件的使用、參考、重載函式 講師:林業峻 CSIE, NTU 3/28, 2015.
本节内容 函数嵌套调用的内存布局 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
授课老师:龚涛 信息科学与技术学院 2016年3月 教材:《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
#include <iostream.h>
第四章 函数 丘志杰 电子科技大学 计算机学院 软件学院.
第二章 Java基本语法 讲师:复凡.
谭浩强编著 C++面向对象程序设计 授课教师:姬广永 学习网站:
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
第十二章 位运算.
目录 12.1 位运算符 12.2 位域(位段) 1.
JAVA 程式設計與資料結構 第三章 物件的設計.
Introduction to the C Programming Language
習作2-1 題目+解答 紐約港 紐約中央公園 格陵蘭島.
C/C++基礎程式設計班 陣列 講師:林業峻 CSIE, NTU 3/14, 2015.
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
第9章 C++程序设计初步 9.1 C++的特点 9.2 最简单的C++程序 9.3 C++的输入输出 9.4 函数的重载
第三章 流程控制 程序的运行流程 选择结构语句 循环结构语句 主讲:李祥 时间:2015年10月.
第11章 字符和内存处理 一、字符数组、指针和字符串的初始化作用 二、strlen函数确定字符串有效长度 三、strcpy函数拷贝字符串.
C++语言程序设计 C++语言程序设计 第二章 基本数据类型与表达式 第十一组 C++语言程序设计.
Presentation transcript:

第16章 数据的共享和流通 一、浅拷贝和深拷贝 二、只读成员函数 三、友元friend

一、浅拷贝和深拷贝 对象作为数据的集合,其中有些数据需要与变 量进行流通,有些数据需由不同的对象共享。 本章介绍的深拷贝、友元函数、静态成员和指 向成员的指针就是在不同的方面加快信息流动以及 实施对象的保护的。

存在两种形式的类。 一种形式的类中仅存在变量或对象,不具备指 针成员。缺省的拷贝构造函数和赋值运算符函数是 浅拷贝的方式,该方式通过memcpy函数将源实例 的数据复制给目标实例占住的一片内存空间。 对于这样的类,缺省的浅拷贝方式是安全的。 另一种形式的类含有指针成员,浅拷贝不再胜 任这样的类。

从上可见对于存在指针成员的类,系统提供的浅拷贝导 致指针指向的内存为两个对象共享的格局。 考虑如下说明: 从上可见对于存在指针成员的类,系统提供的浅拷贝导 致指针指向的内存为两个对象共享的格局。 class CDeep { int n; int*p; } a, b; 一个CDeep类的声明和对象定义 a.n a.p a.p=new int[a.n] b.n b.p b.p=new int[b.n] 对象a,b的内存和指针成员动态扩展的内存空间b=a导致[ b.p=a.p; b.n=a.n; ]。指针b.p指向a对象的动态内存。 ??=new int[b.n] a.n a.p a.p=new int[a.n] b.n b.p 中间深资源归口两个对象监控

浅拷贝的不良结果是:b.p原先指向的堆空间悬空----既 无法索引也不能收回这片内存,a或b对象的析构函数诱发中 间共享的深资源的流失。 对于凡是具有指针成员的类,应细致提交两个函数: 一.是拷贝构造函数, 二.是赋值运算符函数,以便进行指针成员的动态资源 的深拷贝。 深拷贝的核心思路是: 1. 目标对象与源对象内存空间独立,相应指针成员指 向的内存空间也彼此独立。 2. 全部拷贝源对象的数据到目标对象,包括分别拷贝 指针成员指向的内存数据。

[例] 深拷贝方式(去掉定制的拷贝构造函数和赋值运算符函 数则变成缺省的浅拷贝) #include <stdio.h> #include<string.h> class CDeep { public: int n; int *p; CDeep (int) ; ~CDeep (); CDeep (const CDeep& r) ; CDeep& operator= (const CDeep& r); };

CDeep::~CDeep() { static int s_num=1; printf ("%d.~CDeep ()\t", s_num++); delete [ ] p; } CDeep::CDeep (int k) { n=k; p=new int [n]; } CDeep& CDeep::operator= (const CDeep& r) { if (n!=r.n) { delete [ ] p; n=r.n; p=new int [n]; } memcpy (p, r.p, n*sizeof (int)); return *this;

CDeep::CDeep (const CDeep& r) { n=r.n; p=new int [n]; memcpy (p, r.p, n*sizeof (int)); } void main () { CDeep a (2), d(3); a.p[0]=1; d.p[0]=666; { CDeep b (d); a.p [0]=88; b=a; printf ("%d;",b.p[0]); printf ("%d; ",d.p[0]); printf ("b fade out away;\t"); printf ("%d; ",a.p[0]);

程序输出: 88;1.~CDeep() 666;b fade out away; 88; 2.~CDeep() 3.~CDeep() 删除上面的拷贝构造函数和等号赋值运算符函数时,程 序运行输出: 88;1.~CDeep() 666;b fade out away; -572662307;2.~CDeep() 3.~CDeep() 之后弹出一个Debug Assertion Failed!的警告对话框。 原因在于b对象退出作用范围后导致析构函数的调用, 析 构函数释放原来由a对象拥有的深部堆中资源,其后对该内 存空间的操作 a.d[0]就等于在没有定义的内存空间进行寻址 访问,因而是运行时的错误。

二、只读成员函数volatile, mutable关键字 1. 只读成员函数 未经const或volatile限制的对象或成员函数是普通的 对象或普通的成员函数,依称为对象或成员函数。 只读对象是关键字const限定的对象。只读成员函数是 const置于成员函数右圆括号之后修饰的成员函数,该成员 函数不修改成员变量的数据状态,即成员函数体中出现的数 据成员仅作为右值。 没有只读的构造函数和析构函数,只读对象和对象都调 用同一构造函数和析构函数。

const用于名称细分,成员函数声明和定义都必须紧跟 只读成员函数的声明和定义格式为:(其中type,t1, t2,tn是已经声明的类型) type f (t1 v1, t2 v2, ...,tn vn) const; type CType:: f(t1 v1, t2 v2, ...,tn vn) const {; ...;} 成员函数可存在两个重载版本: 一个只读版本另一个普通版本。 当两种版本并存时,对象优先调用普通的成员函数,只 读对象或只读的对象引用仅仅调用只读成员函数。

对象可调用只读成员函数。关键字const本质上约束 this形参为只读属性。 只读指针形参可以匹配左值区的地址,普通指针形参不 指向右值区的地址。 只读对象的地址是右值区的地址。 只读对象和只读成员函数不调用普通成员函数。普通的 成员函数可调用只读成员函数。 只读成员函数是只读取对象的数据状态的成员函数。

[例] const对象与只读成员函数。 #include<stdio.h> struct CPoint { long x; long y; } ; class CRect { public: CRect (int l, int t, int r, int b) ; CPoint& TopLeft () ; const CPoint& TopLeft () const ; private: long left; long top; long right; long bottom; }; inline CRect::CRect (int l, int t, int r, int b) { left = l; top = t; right = r; bottom = b; }

inline CPoint& CRect::TopLeft() { return *((CPoint*)this); } inline const CPoint& CRect::TopLeft () const { return *((CPoint*)this); } void main() { CRect r (10,20,30,40); CPoint& rtl = r.TopLeft (); const CRect d (rtl.x+5, rtl.y+5, 35, 45); CPoint dtl (d.TopLeft()); printf ("left=%d, top=%d\t", rtl.x, rtl.y); printf ("left=%d, top=%d\n", dtl.x, dtl.y); } //输出:left=10,top=20 left=15,top=25

成员函数TopLeft存在只读的和非只读的版本,其中的 语句是一样的。 返回引用的函数可以作为左值,本来只读成员函数不改 变对象的数据状态,如果不在返回类型上加const限制将导 致对象外部调用对数据的改变。 于是返回引用的只读成员函数在返回类型上由const前 置限制。 d.TopLeft ()为只读对象d调用只读成员函数。 r.TopLeft ()为对象r调用普通的成员函数。

volatile关键字表示内存数据的变更。 volatile关键字和const关键字的语法是一致的。 const修饰的变量维持恒定或函数不改变相关成员的数 据状态。 与const相反,volatile关键字限定的对象或成员函数 可以有效的变动。 这种变动可以来自其它的外部进程。

[例] volatile关键字的用法 #include <stdio.h> class B { volatile int m_n; public: B (int v=1) { m_n=v; } void Set (int n) volatile { m_n=n; } void Show () const { printf ("Show()const; n=%d\n",m_n); } void Show() volatile { printf ("Show() volatile; n=%d\n",m_n); } void Show() { printf ("Show() ;n=%d\n",m_n); } };

void main() { const B c; c.Show(); volatile B v(2); v.Show(); v.Set(3); B x(4); x.Show(); } /*程序输出结果: */ /*Show() const; n=1 */ /*Show() volatile; n=2*/ /*Show() volatile; n=3 */ /*Show() ; n=4 */

volatile对象操作volatile成员函数, const对象操作 const 成员函数。不妨认为volatile关键字和const关键字是 一对含意相反的语法修饰词,它们常称为c-v限定词。 对象既可操作const成员函数也可操作volatile成员函 数,如果这两个成员函数都存在但不存在普通的版本则导致 调用的歧义。 volatile关键字使用的场合在一般程序中不 多,主要用在系统的程序设计中。 volatile关键字可以和const同时出现。如下: volatile const int g_n=1; 这表示程序不能改变变量g_n的值,但允许系统改变它。

3. mutable关键字 关键字mutable可以局部松动const对象的不变属性。 如果一个对象前加上const关键字限制,则这个对象所 有的成员就冻结为右值。 但有时候对于这种约束期望有所放松,此时只需在相关 的成员前冠以mutable修饰,通知编译器如此成员不受 const的制约。

[例] mutable关键字注册绝对可变的成员 #include <stdio.h> class CType { public: mutable long v; long n; }; void main() { const CType cobj={1,2}; printf ("cobj={%d,%d}; \t",cobj.v,cobj.n); cobj.v=8; printf ("cobj={%d,%d};\n",cobj.v,cobj.n); //cobj.n=2; error : l-value specifies const object } //输出结果:cobj={1,2}; cobj={8,2};

上面的代码中v是一个绝对可变的成员。 n是一个相对可变的成员,n受const对象限制时而间接 的成为右值表达式。 对于本题cobj是一个不变对象,因而cobj.n不能成为左 值,v由于有了mutable的特殊关照,cobj.v则构成左值表达 式。

三、友元friend friend声明的函数称为友元函数,friend 可用于声明一 个类,这个类称为友元类。 友元函数细分为全局友元函数和成员友元函数。友元函 数或友元类在所访问类或当前类中由friend声明。格式为: class CN { private: friend T f (T1, CN&, Tn); friend type A::f (CN*,T2,Tn); protected: friend class B; friend void funct (T v, CN& r) {...} }; //类型名称type,T, T1, T2, …,Tn, A, B在使用点之前 应首先说明

friend关键字无视protected,private的制约,放置在类 声明的任何地方是等效的。 友元可访问当前类的所有成员。友元类所有的成员函数 都是友元函数。 友元类或友元函数一般通过三个途径访问当前类所有的 成员,包括公共属性的保护属性的和私有属性的成员: a. 友元函数的入口形参具有当前类的类名。 b. 友元函数具有当前类的对象作为局部对象或对象指针 作为局部指针。 c. 友元类具有当前类的对象作为其嵌入成员对象。

1. 友元函数 友元函数是一种定义在当前类外部的普通函数,通过 friend在当前类内部声明,但友元函数不是当前类的成员函 数。 在当前类的友元函数获得如此特权: 定义在友元函数形参列表或函数体中的当前类对象、对 象引用或对象指针等可以访问该类所有控制属性的成员包括 私有成员,就好像当前类三个访问控制属性全部是公共的访 问控制属性。

友元是一种解除访问控制约束的机制。 此种破除访问控制约束的友元关系不传递不反身不继 承,即如果类A是类B的友元, 类B是类C的友元,A不会自动 称为类C的友元; 类A是类B的友元但不一定类B是类A的友元; 类A是类B的友元, 类A的派生类D不会自动成为类B的 友元。 友元函数是某个类的特权函数,这个访问当前类所有成 员的特权在该类中由friend声明获得,未由friend声明的函 数操作当前类的公共成员。

[例] 友元函数将类回归到经典的公共访问性质的结构 #include <stdio.h> struct S {int e;}; class B; class A { private: int a; public: void add(B& ); friend void swap(A*,B& ); int& Ea () { return a; } };

class B { friend void swap (A *p,B& r); private: int b; friend void A::add (B& r); public: int& Eb () { return b; } }; void PublicB (S *p,B& r) { r.Eb()=1; //p->e=r.b; //error 'b' : cannot access private member }

void swap (A *p,B& r) { int t=p->a; p->a=r.b; r.b=t; } void A::add (B& r) { a+=r.b; } void main () { A x; x.Ea ()=1; B y; y.Eb ()=2; x.add (y); swap (&x,y); printf ("%d, %d\n", x.Ea(), y.Eb()); //输出2,3

说明:类B的友元函数的声明格式为: class B{ friend void swap(A *p,B& r); //全局函数swap是B类和A类的的友元函数 friend void A:: add(B& r); }; //A类的成员函数add是B类的友元函数 其中值得关注的是当前类的类名B出现在友元函数的形 参类型中,形参r可通过圆点运算符访问B类中的任一成员。 同理全局函数swap (A *p,B& r)是类A的友元函数,这 个函数的A*型形参p可以通过箭头运算符->访问A类的所有 成员包括私有成员。

友元函数或友元类由friend声明,但友元函数或友元类 友元函数的定义应位于当前类的描述之后,以便编译器 必须有足够的信息操作当前的成员。 友元函数的调用维持原来的格式,即全局友元函数按照 全局函数的虚实结合匹配,成员友元函数则根据自身类的对 象外部调用或自身类的this指针内部调用。 另一个全局函数PublicB (S *p,B& r)由于不是类B的友 元函数,B&型的引用形参r只能访问公共的成员,妨碍了数 据的流通。 为解决这种现象可以声明这个全局函数为B类的友元函 数,通知编译器跳过成员属性访问控制检查的机制,抹平精 细封装的界限。或者将B类的数据处理为公共的属性。

2. 友元类 前面说明了友元函数在某一个类中的特权地 位,在友元函数中当前类不过是一个毫不设防的公 共世界,关键字protected和private形如虚设。 一个类可以声明另一个类为其友元,由此形成 友元类。 友元类中的所有成员函数可以访问当前类所有 的成员。

#include <stdio.h> [例] 友元类B将当前类A视为一个公共访问控制属性的结构 #include <stdio.h> class B; class A { friend class B; private: int m_a; A (int k=0) {m_a=k;} public: friend void g_f(); void Show() { printf ("m_a=%d\t", m_a); } };

class B { int m_b; A a; public:B (int k=1) { m_b=k; a.m_a=1+k; } A& Add(A* ); A& B::Add (A* p) { p->m_a+= a.m_a+m_b; return *p; } A* pa; void g_f () { A a; a.m_a=1; a.Show(); static A d(100); pa=&d; } void main() { g_f (); B b; b.Add (pa).Show (); } //输出:m_a=1,m_a=103

说明: 嵌入对象成员a的行为由于类B是类A的友元类而好像类 A回归到传统的公共的结构,访问控制属性这些与解决具体 问题无关的桎梏消失不起作用。 全局函数g_f()是类A的友元函数,在这个友元函数里调 用私有的构造函数,完成对象的定义。 通过一个全局指针pa与静态局部对象d的关联完成数据 封装与共享。

请打开"第16章(2).ppt"