C++ Primer(第4版)-学习笔记-第4部分:面向对象编程与泛型编程
面向对象编程基于三个基本概念:数据抽象、继承和动态绑定。
的函数。
- private  成员
 • 通过类对象无法访问类的private成员。
 • 在派生类中不能访问基类的private成员。
 • private 成员只能在当前类的作用域内访问,类的友元也可以访问类的private 成员。例如,在成员函数中可以访问private 成员,在成员函数中还可以通过自己类的对象来访问类的private 成员。类的作用域包括:类定义的{}之内,类定义之外的成员函数的函数体,形参列表等。
- protected  成员
 • 通过类对象无法访问protected 成员。
 • protected 成员可被public派生类(包括派生类的派生类,向下传递)访问,也就是说在派生类中可以使用基类的protected 成员。
 • 派生类只能通过派生类对象访问其基类的 protected 成员,派生类无法访问其基类类型对象的 protected 成员。
- 派生类和虚函数
 派生类一般会重定义所继承的虚函数。如果派生类没有重定义某个虚函数,则使用基类中定义的版本。
 一般情况下,派生类中虚函数的声明必须与基类中的定义方式完全匹配,例外:返回对基类型A的引用(或指针)的虚函数。派生类中的虚函数可以返回类A的派生类的引用(或指针)。
 一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用 virtual 保留字,也可以不使用。
- 在运行时确定  virtual  函数的调用
 将基类类型的引用或指针绑定到派生类对象,那么在调用虚函数时,如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本。只有通过引用或指针调用,虚函数才在运行时确定。只有在这些情况下,直到运行时才知道对象的动态类型。
- 虚函数与默认实参 
 虚函数也可以有默认实参。如果一个调用省略了具有默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。
- 友元关系与继承
 友元关系不能继承:
 (1)基类的友元对派生类的成员没有特殊访问权限。
 (2)友元类的派生类不能访问授予友元关系的类。
- 继承与静态成员
 如果基类定义 static 成员,则整个继承层次中只有一个这样的成员。无论从基类派生出多少个派生类,每个 static 成员只有一个实例。
 static 成员遵循常规访问控制:如果成员在基类中为 private,则派生类不能访问它。如果可以访问成员,则既可以通过基类访问 static 成员,也可以通过派生类访问 static 成员。一般而言,既可以使用作用域操作符也可以使用点或箭头成员访问操作符。
- 派生类到基类的转换
 指针或引用:(1)派生类型引用到基类类型引用,(2)派生类型指针到基类类型指针。反之是不行的。
 对象:一般可以使用派生类型的对象对基类类型的对象进行初始化或赋值,但没有从派生类型对象到基类类型对象的直接转换,编译器不会自动将派生类型对象转换为基类类型
 对象。
- 可以使用派生类型对象对基类对象进行赋值或初始化。
 将派生类型的对象传给希望接受基类引用的函数,实际上实参是该对象的引用,对象本身未被复制。
 将派生类对象传给希望接受基类类型对象(而不是引用)的函数时,该派生类对象的基类部分被复制到形参。
- 派生类构造函数
 构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。
 派生类的合成默认构造函数,除了初始化派生类的数据成员之外,它还初始化派生类对象的基类部分。基类部分由基类的默认构造函数初始化。
 派生类构造函数的初始化列表只能初始化派生类的成员,不能直接初始化继承成员。派生类构造函数只能通过将基类包含在构造函数初始化列表中来间接初始化继承成员。
 class Bulk_item : public Item_base {
 public:
 Bulk_item(const std::string& book, double sales_price,std::size_t qty = 0, double disc_rate = 0.0):
 Item_base(book, sales_price), min_qty(qty), discount(disc_rate) { }
 // as before
 };
 构造函数初始化列表为类的基类和成员提供初始值,它并不指定初始化的执行次序。首先初始化基类,然后根据声明次序初始化派生类的成员。
 一个类只能初始化自己的直接基类(通过将基类包含在构造函数初始化列表中来间接初始化基类)。
- 复制控制和继承
 只包含类类型或内置类型数据成员、不含指针的类一般可以使用合成操作,复制、赋值或撤销这样的成员不需要特殊控制。具有指针成员的类一般需要定义自己的复制控制来管理这些成员。
- 派生类的复制构造函数
 如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分:
 class Base { /* ... */ };
 class Derived: public Base
 {
 public:
 Derived(const Derived& d):Base(d) { /*... */ }
 };
- 派生类赋值操作符
 赋值操作符通常与复制构造函数类似:如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。
 //Base::operator=(const Base&)
 Derived &Derived::operator=(const Derived &rhs)
 {
 if (this != &rhs) //防止给自己赋值
 {
 Base::operator=(rhs); // 调用 Base 类的赋值操作符给基类部分赋值
 ……//为派生类Derived 的成员赋值
 }
 return *this;
 }
- 派生类析构函数
 派生类析构函数不负责撤销基类对象的成员。每个析构函数只负责清除自己的成员。
 class Derived: public Base
 {
 public:
 ~Derived() { /* do what it takes to clean up derived members
 */ }
 };
- 虚析构函数
 如果层次中基类的析构函数为虚函数,则派生类析构函数也将是虚函数,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。
 建议:即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。
- 构造函数和赋值操作符不是虚函数
 在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数。
- 名字冲突与继承
 与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。
 可以使用作用域操作符访问被屏蔽的基类成员:
 struct Derived : Base
 {
 int get_base_mem() { return Base::mem; }
 };
 设计派生类时,只要可能,最好避免与基类成员的名字冲突。
- 作用域与成员函数
 在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽。
- 重载函数
 如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。
 如果派生类想要通过自身类型来使用重载的版本,那么派生类必须重定义所有的重载版本,但这样会繁琐,可以通过给重载成员提供using 声明来达到简化的目的。
 using Base::Func;//注意,将所有基类Base中的Func函数在本类中可见。
- 名字查找与继承
 (1)首先确定进行函数调用的对象、引用或指针的静态类型。
 (2)在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。如果不能在类或其相关基类中找到该名字,则调用是错误的。
 (3)一旦找到了该名字,就进行常规类型检查,查看如果给定找到的定义,该函数调用是否合法。
 (4)假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。
- 纯虚函数
 在函数形参表后面写上 = 0 以指定纯虚函数。该函数为后代类型提供了可以覆盖的接口,但是这个类中的版本决不会调用。
 含有(或继承)一个或多个纯虚函数的类是抽象基类。除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象。
泛型编程就是以独立于任何特定类型的方式编写代码。模板是泛型编程的基础。在泛型编程中,我们所编写的类和函数能够多态地用于跨越编译时不相关的类型。一个类或一个函数可以用来操纵多种类型的对象。
- 定义函数模板
 函数模板是一个独立于类型的函数,可作为一种方式,产生函数的特定类型版本。下面是 compare 的模板版本:
 // implement strcmp-like generic compare function
 // returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
 template <typename T>
 int compare(const T &v1, const T &v2)
 {
 if (v1 < v2) return -1;
 if (v2 < v1) return 1;
 return 0;
 }
 模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。模板形参表不能为空。
- inline 函数模板
 函数模板可以用与非模板函数一样的方式声明为 inline。说明符放在模板形参表之后、返回类型之前,不能放在关键字 template 之前。
 template <typename T> inline T min(const T&, const T&);// ok: inline specifier follows template parameter list
 inline template <typename T> T min(const T&, const T&);// error: incorrect placement of inline specifier
- 类模版
 类模版是一个类定义,用来定义一组特定类型的类,类模板用template关键字后接用尖括号<>括住,以逗号分隔的一个或多个模板形参的列表来定义。
 template <class Type> class Queue {
 public:
 Queue (); // default constructor
 Type &front (); // return element from head of Queue
 const Type &front () const;
 void push (const Type &); // add element to back of Queue
 void pop(); // remove element from head of Queue
 bool empty() const; // true if no elements in the Queue
 private:
 // ...
 };
 与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参:
 Queue<int> qi; // Queue that holds ints
 Queue< vector<double> > qc; // Queue that holds vectors of doubles
 Queue<string> qs; // Queue that holds strings
- 模板形参
 模板形参作用域:模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。
 模板形参遵循常规名字屏蔽规则。与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字。
- 限制
 模板形参的名字不能在模板内部重用。
 模板形参的名字只能在同一模板形参表中使用一次。
 模板形参的名字可以在不同模板中重用。
- 模板声明
 像其他任意函数或类一样,对于模板可以只声明而不定义。
 同一模板的声明和定义中,模板形参的名字可以不相同。
- 模板类型形参
 类型形参由关键字 class 或 typename 后接说明符构成。在模板形参表中,这两个关键字具有相同的含义,都指出后面所接的名字表示一个类型。
 它可以用于指定返回类型或函数形参类型,以及在函数体中用于变量声明或强制类型转换。
- typename  与  class  的区别 
 在函数模板形参表中,关键字 typename 和 class 具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用。
 关键字 typename 是作为标准 C++ 的组成部分加入到 C++ 中的,因此旧的程序更有可能只用关键字 class。
- 在模板定义内部指定类型 
 通过在成员名前加上关键字 typename 作为前缀,可以告诉编译器将成员当作类型。
 template <class Parm, class U>
 Parm fcn(Parm* array, U value)
 {
 typename Parm::size_type * p; // ok: declares p to be a pointer
 }
- 非类型模板形参
 模板形参不必都是类型。
 template <class T, size_t N>
 void array_init(T (&parm)[N])
 {
 for (size_t i = 0; i != N; ++i) {
 parm[i] = 0;
 }
 }
- 编写泛型程序 
 在函数模板内部完成的操作限制了可用于实例化该函数的类型。所以,应该保证用作函数实参的类型实际上支持所用的任意操作,以及保证在模板使用哪些操作的环境中那些操作运行正常。
 编写模板代码时,重要的原则是:实参类型的要求尽可能少。
- 编写泛型代码的两个重要原则(对实参类型的要求尽可能少):
 • 模板的形参是 const 引用。
 • 函数体中的测试只用 < 比较。
- 类的实例化
 类模板的每次实例化都会产生一个独立的类类型。
 想要使用类模板,就必须显式指定模板实参。
- 函数模板实例化 
 使用函数模板时,编译器通常会为我们推断模板实参。
- 类型形参的实参的受限转换,编译器只会执行两种转换:
 • const 转换:接受 const 引用或 const 指针的函数可以分别用非 const 对象的引用或指针来调用,无须产生新的实例化。
 • 数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。
- 类定义和函数声明放在头文件中,而函数定义和成员定义放在源文件中。
- 包含编译模型
 在包含编译模型中,编译器必须看到用到的所有模板的定义。一般而言,可以通过在声明函数模板或类模板的头文件中添加一条 #include 指示使定义可用,该 #include 引入了包含相关定义的源文件。
- 分别编译模型
 在分别编译模型中,编译器会为我们跟踪相关的模板定义。但是,我们必须让编译器记住给定的模板定义,可以使用 export 关键字来做这件事。
 在一个程序中,一个模板只能定义为导出一次。
 (1)函数模板
 在函数模板的定义中指明函数模板为导出的,在关键字template 之前包含 export 关键字:
 export template <typename Type>
 Type sum(Type t1, Type t2) /* ...*/
 这个函数模板的声明像通常一样应放在头文件中,声明不必指定 export。
 (2)类模版
 类声明必须放在头文件中,头文件中的类定义体不应该使用关键字 export,应该在类的实现文件中使用 export。
 template <class Type> class Queue { ... };
 export template <class Type> class Queue;
 #include "Queue.h"
- 通常,当使用类模板的名字的时候,必须指定模板形参。这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名。例如,在默认构造函数和复制构造函数的声明中,名字 Queue 是 Queue<Type> 缩写表示。
 编译器不会为类中使用的其他模板的模板形参进行这样的推断,因此,在声明伙伴类 QueueItem 的指针时,必须指定类型形参: QueueItem<Type> *head; // pointer to first element in Queue
- 类模板成员函数
 • 必须以关键字 template 开关,后接类的模板形参表。
 • 必须指出它是哪个类的成员。
 • 类名必须包含其模板形参。例如:
 template <class Type> void Queue<Type>::destroy()
 {
 while (!empty())
 pop();
 }
- 将类模板设为友元
 template <class Type> class Queue;
 template <class Type> class QueueItem {
 friend class Queue<Type>;
 // ...
 };
 这个声明建立了想要一对一映射,只将与 QueueItem 类用同样类型实例化的
 Queue 类设为友元。
- 将函数模板设为友元
 template <class T>
 std::ostream& operator<<(std::ostream&, const Queue<T>&);
 template <class Type> class QueueItem {
 friend class Queue<Type>;
 // needs access to item and next
 friend std::ostream&
 operator<< <Type> (std::ostream&, const Queue<Type>&);
 // ...
 };
 template <class Type> class Queue {
 // needs access to head
 friend std::ostream&
 operator<< <Type> (std::ostream&, const Queue<Type>&);
 };
- 成员模板 
 任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板,成员模板不能为虚。
- 在类外部定义成员模板 
 当成员模板是类模板的成员时,它的定义必须包含类模板形参以及自己的模板形参。首先是类模板形参表,后面接着成员自己的模板形参表。
 template <class T> template <class Iter> //第一个模板形参表 template<class T> 是类模板的,第二个模板形参表 template<class Iter> 是成员模板的。
 void Queue<T>::assign(Iter beg, Iter end)
 {
 destroy(); // remove existing elements in this Queue
 copy_elems(beg, end); // copy elements from the input range
 }
- 类模板的  static  成员
 类模板可以像任意其他类一样声明 static 成员
C++ Primer(第4版)-学习笔记-第4部分:面向对象编程与泛型编程的更多相关文章
- <C++Primer>第四版 阅读笔记 第四部分 “面向对象编程与泛型编程”
		继承和动态绑定与数据抽象一起成为面向对象编程的基础. 模板使我们能够编写独立于具体类型的泛型类和泛型函数. 第15章 面向对象编程 面向对象编程基于三个基本概念:数据抽象.继承和动态绑定.在C++中, ... 
- C++Primer第5版学习笔记(四)
		C++Primer第5版学习笔记(四) 第六章的重难点内容 你可以点击这里回顾第四/五章的内容 第六章是和函数有关的知识,函数就是命名了的代码块,可以处理不同的情况,本章内 ... 
- C++Primer第5版学习笔记(三)
		C++Primer第5版学习笔记(三) 第四/五章的重难点内容 你可以点击这里回顾第三章内容 因为第五章的内容比较少,因此和第四章的笔记内容合并. 第四章是 ... 
- C++Primer第5版学习笔记(二)
		C++Primer第5版学习笔记(二) 第三章的重难点内容 这篇笔记记录了我在学习C++常用基本语法的学习过程,基本只记录一些重难点,对概念的描述不是一开始就详尽和准确的,而是层层深入 ... 
- C++Primer第5版学习笔记(一)
		C++Primer第5版学习笔记(一) 第一.二章的重难点内容 这个笔记本主要记录了我在学习C++Primer(第5版,中文版)的过程中遇到的重难点及其分析.因为第一.二章都比较简单,因 ... 
- C#学习笔记(六)——面向对象编程简介
		一.面向对象编程的含义 * 是一种模块化编程方法,使代码的重用性大大的增加. * oop技术使得项目的设计阶段需要的精力大大的增加,但是一旦对某种类型的数据表达方式达成一致,这种表达方式就可以 ... 
- C++Primer第五版学习笔记
		<C++ Primer>Learning Note 程序实例下载地址:http://www.informit.com/title/0321714113 第一章 开始 ... 
- C++ Primer(第4版)-学习笔记-第5部分:高级主题
		第17章 用于大型程序的工具 异常处理 不存在数组或函数类型的异常.相反,如果抛出一个数组,被抛出的对象转换为指向数组首元素的指针,类似地,如果抛出一个函数,函数被转换为指向该函数的指针. 不要抛出 ... 
- C++ Primer(第4版)-学习笔记-第3部分:类和数据抽象
		第12章 类 每个类可以没有成员,也可以定义多个成员,成员可以是数据.函数或类型别名. 成员函数必须在类内部声明,可以在类内部定义,也可以在类外部定义.如果在类内部定义,就默认是内联函数. ... 
随机推荐
- PHP博客项目-gai
			XX科技还是米有电话过来,看样子真的是黄了.这段时间都没有好好学习,经历了两次稀里糊涂的面试,特别是第二次,让我感觉自己之前学的东西都已经忘了,本来就学的不多,也不扎实,还一忘...看了是真的要开始着 ... 
- where 1=1影响效率以及having和where的区别
			低效的“WHERE 1=1” 网上有不少人提出过类似的问题:“看到有人写了WHERE 1=1这样的SQL,到底是什么意 思?”. 其实使用这种用法的开发人员一般都是在使用动态组装的SQL. 让我们想像 ... 
- Apache Ignite——集合分布式缓存、计算、存储的分布式框架
			Apache Ignite内存数据组织平台是一个高性能.集成化.混合式的企业级分布式架构解决方案,核心价值在于可以帮助我们实现分布式架构透明化,开发人员根本不知道分布式技术的存在,可以使分布式缓存.计 ... 
- Codeforces Round #498 (Div. 3) 简要题解
			[比赛链接] https://codeforces.com/contest/1006 [题解] Problem A. Adjacent Replacements [算法] 将序列中的所有 ... 
- QRCoder生成二维码
			现在二维码支付越来越流行,二维码使用的地方越来越多,项目中也需要一个二维码生成工具,QRCoder是一个简单的生成二维码的库,用C#.NET编写,他是开源的MIT-license. 二维码简介 二维条 ... 
- 乐搏讲自动化测试- Python自动化测试前景怎么样(4)
			Python言语还能够写爬虫,但仅仅只是爬虫的入门罢了.通过Python入门爬虫比较简略易学,不需要在一开始把握太多太根底太底层的常识就能够很快上手,而且很快能够做出成果,十分合适小白一开始想做出点看 ... 
- Notepad++安装插件
			https://sourceforge.net/projects/npp-plugins/files/ 下载完插件后,通过notepad++的设置>>>>导入>>& ... 
- java  io 文件下载
			/** * 文件下载 * @param response * @param downloadPath * @param docName */ public void downLoadFile( Htt ... 
- python自动化测试学习笔记-6excel操作xlwt、xlrd、xlutils模块
			python中通过xlwt.xlrd和xlutils操作xls xlwt模块用于在内存中生成一个xls/xlsx对象,增加表格数据,并把内存中的xls对象保存为本地磁盘xls文件; xlrd模块用于把 ... 
- PHP 小tip .(@)符号和 php if 赋值
			tip 1: 下面介绍一下它的用法. 例如: 复制代码代码如下: function db_connect()//连接数据库 { @$db =mysql_connect('localhost','roo ... 
