笔记参考

本文参考的一些学习笔记:

学习目标

学习目标如下:

  • 在培养正规、大气的编程素养上继续探讨更多的技巧
  • 泛型编程也是C++的技术主线,探讨template(模板)
  • 深入探索面向对象之继承关系所形成的对象模型,包括this指针虚指针虚表以及虚机制造成的多态效果。

转换函数与explicit

设计一个类Fraction表示分数,包含分子和分母,能自动转换为double类型并参与运算,代码如下:

class Fraction
{
public:
Fraction(int numerator, int denominator = 1)
:m_numerator(numerator), m_denominator(denominator)
{
}
//转换函数
operator double() const
{
return (double)m_numerator / m_denominator;
}
//加操作符重载
Fraction oprateor + (const Fraction& f)
{
int nNumerator = m_numerator*f.m_denominator + this->m_denominator* f.m_numerator;
int nDenominator = m_denominator*m_denominator;
return Fraction(nNumerator, nDenominator);
} private:
int m_numerator; //分子
int m_denominator; //分母
}; int main()
{
Fraction f(3,5);
double sum = 4 + f;
std::cout << "sum = " << sum << std::endl; //sum = 4.6
return 0;
}

考虑将Fraction转化为其string类型,则转换函数的代码如下:

//转换函数
operator string() const
{
return to_string(m_numerator) + "/" + to_string(m_denominator);
} //调用转换函数
string str = f ;
std::cout << "Result is: " << str << std::endl; //Result is: 3/5

在c++中,explicit只能用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换

上面的类Fraction如果换一种方法使用就会报错:

int main()
{
Fraction f(3,5);
Fraction sum = f + 4; //Error ambiguous 编译器不知道调用哪个 f可以转为double 4也可以通过构造函数隐式转为Fraction对象 return 0;
}

如果为类Fraction的构造函数添加explicit关键字:

explicit Fraction(int numerator, int denominator = 1)
:m_numerator(numerator), m_denominator(denominator)
{
} Fraction sum = f + 4; //Error ambiguous +操作符重载函数中4无法转为Fraction

pointer-like classes

pointer-like classes , 类的行为看起来像指针,有智能指针迭代器两大类。

注:C++里操作指针的操作符有 -> 和 * ,实质上就是类重载了这两个操作符,使得类的行为看起来像指针

智能指针的经典实现如下:

template<class T>
class share_ptr
{
public:
T& operator*() const
{return *px;}
T* operator->() const
{return px;}
shared_ptr(T* p): px(p){}
private:
T* px;
long* pn;
......
}; //使用share_ptr
struct Foo
{
......
void method(void) {......}
};
shared_ptr<Foo> sp(new Foo);
Foo f(*sp);
sp->method();

sp->method(); 智能指针调用method()方法。操作符 -> 属于调用操作符重载,这句就相当于px->method()。操作符->作用在指针对象sp上,得到指针对象px,此时符号->并没有消耗掉可以继续使用。

迭代器的经典实现如下,需要重载 ++--符号:

template <class T, class Ref, class Ptr>
struct __list_iterator { //这是一个链表
typedef __list_iterator<T, Ref, Ptr> self;
typedef Ptr pointer;
typedef Ref pointer;
typedef __list_node<T>* link_type;
link_type node;
bool operator==(const self& x) const { return node == x.node; }
bool operator==(const self& x) const { return node != x.node; }
reference operator*() const { return (*node).data; }
pointer operator-> const { return &(operator*())}
self& operator++() { node = (link_type)((*node).next); return *this }
self& operator++(int) { self tmp = *this; ++*this; return tmp; }
self& operator--() { node = (link_type)((*node).prep); return *this }
self& operator--(int) { self tmp = *this; --*this; return tmp; }
};

function-like classes

函数由返回类型函数名称参数(小括号,作用在函数名上)和函数主体组成,小括号内含参数这部分也称作函数调用操作符

如果有个东西能接受小括号操作符,那它就是函数function,或者仿函数function-like

先认识一下std::pair,它主要的作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型,代码如下:

template <class T1, class T2>
struct pair
{
T1 first;
T2 second; //first和second类型不需要一样
pair() : first(T1()), second(T2()) {}
pair(const T1& a, const T2& b)
: first(T1()), second(T2())
......
};

标准库中与pair有关的仿函数如下:

template<class Pair>
struct select1st : public unary_function<Pair, typename Pair::first_type>
{
const typename Pair::first_type&
operator() (const Pair& x) const { return x.first; }
}; template<class Pair>
struct select2nd : public unary_function<Pair, typename Pair::second_type>
{
const typename Pair::second_type&
operator() (const Pair& x) const { return x.second; }
};

上面的两个类中都包括操作符重载operator(),叫做function-like classes,那么由类创建的对象就叫做函数对象functor

这两个类各自继承类unary_function,类似的基类还有binary_function,两者的详细内容参考 unary_function和binary_function详解

总的来说,unary_function这些基类的作用是记录仿函数的参数类型,在例如bind2nd中这很有用处,参考 C++ bind2nd用法

bind2nd代码如下:

template<typename _Operation, typename _Tp>
inline binder2nd<_Operation>
bind2nd(const _Operation& __fn, const _Tp& __x)
{
//如果不继承binary_function,就无法获取第二参数
typedef typename _Operation::second_argument_type _Arg2_type;
return binder2nd<_Operation>(__fn, _Arg2_type(__x));
}

模板template

C++中的模板可以分为三类:class templatefunction templatemember template,示例代码如下:

//class template
template<class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type; T1 first;
T2 second; pair()
: first(T1()), second(T2()) {}
pair(const T1& a, const T2& b)
: first(a), second(b) {} //member template
template<class U1, class U2>
pair(const pair<U1, U2>& p)
: first(p.first), second(p.second) {}
};

上面的示例可以这样使用:



模板特化与偏特化

C++中的模板特化不同于模板的实例化,模板参数在某种特定类型下的具体实现称为模板的特化。模板特化有时也称为模板的具体化或全特化,分别有函数模板特化类模板特化,类模板特化示例如下:

template<class Key>
struct hash {}; template<>
struct hash<char>{
size_t operator() (char x) const { return x; }
}; template<>
struct hash<int>{
size_t operator() (int x) const { return x; }
}; template<>
struct hash<long>{
size_t operator() (long x) const { return x; }
}; //调用
cout << hash<long>() (100) << endl;

模板的偏特化包括两种:个数的偏范围的偏

个数的偏:如果模板参数有两个,你想绑定其中一个,示例代码如下:

template<typename T, typename Alloc=...>
class vector
{
...
}; template<typename Alloc=...>
class vector<bool, Alloc> //绑定了T
{
...
};

*注:要按顺序从左到右绑定模板参数,不能跳跃式绑定第一三五个模板参数。

范围的偏:如果接收任意类型的T,你想缩小接收范围为指向任意类型T的指针,示例代码如下:

template<typename T>
class C
{
...
};
//调用
C<string> obj1 template<typename T>
class C<T*>
{
...
};
//调用
C<string*> obj2

模板模板参数

关于模板模板参数,网上有很多文章但都存在一些错误。这里给出我自己的解释,模板模板参数是指模板的参数是一个模板(该模板的模板参数由之前的模板参数指定),后面会专门用一篇文章解释。

一个模板模板参数的示例:

template<typename T, tamplate<typename T> class SmartPtr> //第二个模板参数是一个智能指针
class XCls
{
private:
SmartPtr<T> sp;
public:
XCls()
: sp(new T) {}
}; XCls<string, shared_ptr> p1; //编译通过
XCls<double, unique_ptr> p2; //编译出错
XCls<int, weak_ptr> p3; //编译出错
XCls<long, auto_ptr> p4; //编译通过

一个非模板模板参数的示例:

template<class T, class Sequence = deque<T>>
class stack
{
friend bool operator== <> (const stack&, const stack&);
friend bool operator< <> (const stack&, const stack&); protected:
Sequence c;
...
}; stack<int> s1;
stack<int, list<int>> s2;

引用(reference)

按我的理解,引用就是漂亮的指针,底层是用指针实现的,但它的实际大小与所引用的数据的大小相同(编译器制造的假象)

关于虚指针(vptr)和虚表(vtbl)

设置了3个class,A,B,C之间是继承的关系,内存布局如下:

通过new c可以得到p,通过p要调用vfunc1(),因为vfunc1是虚函数,所以编译器不能通过老式的call一个地址(静态绑定),就只能通过动态绑定去寻找这个函数,实际调用过程等价于:

(* (p-vptr)[n])(p);
//或
(* p->vptr[n])(p);

符合下列3个条件则会通过动态绑定调用:

  • 是指针
  • 向上转型(子类对象用父类指针表示)
  • 调用的是虚函数

在单一的继承中,对象的内存布局如下(参考 C++ 对象的内存布局(上)):

  • 虚函数表在最前面的位置
  • 成员变量根据其继承和声明顺序依次放在后面
  • 在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新

在多重继承中,对象的内存布局如下(参考 C++ 对象的内存布局(上)):

  • 每个父类都有自己的虚表
  • 子类的成员函数被放到了第一个父类的表中
  • 内存布局中,其父类布局依次按声明顺序排列
  • 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

关于this

通过一个对象调用函数,这个对象的地址就是this。this指针是“成员函数”的第一个隐藏参数,由编译器自动给出。

友元函数没有 this 指针,因为友元不是类的成员,只有成员函数才有 this 指针。

动态绑定

在这里重复一下动态绑定调用的条件:

  • 是指针
  • 向上转型(子类对象用父类指针表示)
  • 调用的是虚函数

需要注意,虚函数并不一定都是动态调用,必须符合上面这3个条件。

const成员函数

关于const成员函数记住以下规则即可:

const object
(data members 不得变动)
non-const object
(data members可变动)
const member functions
(保证不更改data members)
non-const member functions
(不保证 data members 不变)
×

常成员函数的const和non-const版本同时存在,const** object 只会(只能)调用const版本,non-const object 只会(只能)调用non-const版本。**

例如 class template std::basic_string<...> 有如下两个member functions:

charT
operator[] (size_type pos) const
{……/*不必考虑COW*/} reference
operator[] (size_type pos)
{.../*必须考虑COW*/} //COW:Copy On Write

上面这两个函数是重载关系,同时也验证了const是函数签名的一部分

关于const的更多介绍可以参考以下文章:

重载new、delete

newoperator new、**placement new是三个不同概念,区别如下:

  • new是一个关键字,主要做三件事:分配空间(调用operator new分配空间)、初始化对象、返回指针。
  • operator new是一个操作符,和 + - 操作符一样,作用是分配空间。
  • placement new是operator new的一种重载形式

一个完整的重载new、delete示例,来自 详解C++重载new, delete

class Foo {
public:
Foo() { std::cout << "Foo()" << std::endl; }
virtual ~Foo() { std::cout << "~Foo()" << std::endl; } void* operator new(std::size_t size)
{
std::cout << "operator new" << std::endl;
return std::malloc(size);
} void* operator new(std::size_t size, int num)
{
std::cout << "operator new" << std::endl;
std::cout << "num is " << num << std::endl;
return std::malloc(size);
} void* operator new (std::size_t size, void* p)
{
std::cout << "placement new" << std::endl;
return p;
} void operator delete(void* ptr)
{
std::cout << "operator delete" << std::endl;
std::free(ptr);
} }; int main()
{
Foo* m = new(100) Foo;
Foo* m2 = new(m) Foo;
std::cout << sizeof(m) << std::endl;
//delete m2;
//::delete m;//强制调用全局的delete函数
delete m;
return 0; }

候捷-C++程序设计(Ⅱ)兼谈对象模型的更多相关文章

  1. zw版·Halcon与delphi(兼谈opencv)

    zw版·Halcon与delphi(兼谈opencv) QQ群 247994767(delphi与halcon) <Halcon与delphi>系列,早两年就想写,不过一方面,因为Halc ...

  2. [转] Portable Trac 简单介绍 - 兼谈为什么不选择 Redmine

    Portable Trac 简单介绍 - 兼谈为什么不选择 Redmine ​Trac是一个轻量级的软件项目管理环境,如果在工作中涉及一个开发团队的管理并且关心项目管理工具的话,相信都在 ​Trac. ...

  3. 垃圾回收机制GC知识再总结兼谈如何用好GC(转)

    作者:Jeff Wong 出处:http://jeffwongishandsome.cnblogs.com/ 本文版权归作者和博客园共有,欢迎围观转载.转载时请您务必在文章明显位置给出原文链接,谢谢您 ...

  4. TCP的状态兼谈Close_Wait和Time_Wait的状态

    原文链接: http://www.2cto.com/net/201208/147485.html TCP的状态兼谈Close_Wait和Time_Wait的状态   一 TCP的状态: 1).LIST ...

  5. 漫谈 Google 的 Native Client(NaCl) 技术(二)---- 技术篇(兼谈 LLVM)

    转自:http://hzx5.blog.163.com/blog/static/40744388201172531637729/ 漫谈 Google 的 Native Client(NaCl) 技术( ...

  6. spm使用之二兼谈spm的贱格

    上一篇还没写完, 因为我觉得太长了, 影响阅读, 就截断继续写. 因为还没有写到修改 创建模块的模板啊. 之所以想到要修改spm用来创建模块的模板, 是因为, 有一天我突然上不了网了, 发现spm完全 ...

  7. Windows折腾之路 兼谈纯净强迫情节

    早期新鲜感 想当年,终于有了第一台属于自己自由处置的电脑,1.2Ghz的CPU,256兆的内存.这在CPU刚刚上1G的年代,不说顶级,也算主流.操作系统呢,在别人的帮助下,装上新鲜的XP,各种的华丽, ...

  8. C#中StreamWriter与BinaryWriter的区别兼谈编码。

    原文:http://www.cnblogs.com/ybwang/archive/2010/06/12/1757409.html 参考: 1. <C#高级编程>第六版 2.  文件流和数据 ...

  9. 《Word排版艺术》读后感,兼谈LaTeX

    我有两年多的LaTeX使用经验,用它排实验报告.毕业论文和书籍(半本):Word的使用时间长一些,但真正用好也不过是近一两年的事.这两个软件我都 用得很熟,我想我可以一边谈谈读<Word排版艺术 ...

  10. DTV_SI 汇总 & 兼谈LCN

    前言 本章主要对数字广播DVB做一个系统的概况的描述,以及一些spc的相关的内容,虽然流程分析的不多,但是做为后续 章节资料的源泉,也是不可或缺的. 一. ATSC和DVB数字电视系统的比较 本文的主 ...

随机推荐

  1. C/C++ Qt 使用JSON解析库 [修改篇]

    JSON是一种轻量级的数据交换格式,它是基于ECMAScript的一个子集,使用完全独立于编程语言的文本格式来存储和表示数据,简洁清晰的的层次结构使得JSON成为理想的数据交换语言,Qt库为JSON的 ...

  2. C/C++ Qt 基本文件读写方法

    Qt文件操作有两种方式,第一种使用QFile类的IODevice读写功能直接读写,第二种是利用 QFile和QTextStream结合起来,用流的方式进行文件读写. 第一种,利用QFile中的相关函数 ...

  3. 使用Docker单机部署Ceph

    安装Docker过程参考:https://www.cnblogs.com/hackyo/p/9280042.html 1. 创建Ceph专用网络 sudo docker network create ...

  4. 万字手撕AVL树 | 上百行的旋转你真的会了吗?【超用心超详细图文解释 | 一篇学会AVL】

    说在前面 今天这篇博客,是博主今年以来最最用心的一篇博客.我们也很久没有更新数据结构系列了,几个月前博主用心深入的学习了这颗二叉平衡搜索树,博主被它的查找效率深深吸引. AVL树出自1962年中的一篇 ...

  5. 【译】.NET 8 网络改进(二)

    原文 | Máňa,Natalia Kondratyeva 翻译 | 郑子铭 修改 HttpClient 日志记录 自定义(甚至简单地关闭)HttpClientFactory 日志记录是长期请求的功能 ...

  6. Power BI 15 DAY

    业务(表结构)数据分析 1.业务理解 准确 全面 2.数据收集 了解需要用到的数据有哪些 5W2H 结构化数据 SQL.通过查询获取数据库资源 多源表结构数据 企业数据库数据 文本文件数据 Excel ...

  7. SSD 简介—— NAND 芯片介绍

    制作 存储芯片的制作和其他芯片制作大致相同,从沙子中提取单晶硅制作晶圆再封装芯片. 闪存芯片从架构上分为NOR和NAND NOR Flash的source line把每个cell都并联起来,而NAND ...

  8. Laravel入坑指南(4)——数据库(Mysql)

    来来来,新的一节出炉了.这一节大家一起了解,Laravel如何对Mysql进行CURD. 我们回顾一下,PHP操作Mysql无非就是通过五个要素:host(地址).username(用户名).pass ...

  9. OCP试题解析之053-61 RMAN set command id to

    61.You frequently have multiple RMAN sessions running, and you want to be able to easily identify ea ...

  10. Java实现文件下载断点续传(一)

    参考文章:https://www.ibm.com/developerworks/cn/java/joy-down/ 1.原理介绍 想象一下我们下载一个10G的文件,当下载到9.99G的时候断网了... ...