(Boolan) C++面向对象高级编程(五)

2024-04-18 08:08

本文主要是介绍(Boolan) C++面向对象高级编程(五),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

对象模型

虚表和虚指针

  • 关于虚表和虚指针以及内存对其等问题,我之前的文章做了非常详细的测试和整理,如果需要查看,可以查看后面的链接: http://www.jianshu.com/p/46acf45ee795
    再次我对主要的知识点做一些梳理
  • 虚指针

    • 类中如果存在虚函数,就会在内存中存在虚指针
    • 在对象的内存空间中,只需要持有虚函数们的一个指针(实际虚函数们是以个虚函数的数组)
    • 由于虚函数指针,实际持有的实际是一个数组的指针,那么无论class设计有多少个虚函数,对于对象的大小来说都只增加一个指针的大小
    • 继承过程实际是继承了父类的函数的调用权,所以,父类有虚函数,子类也一定会有虚函数

    • 示例

      • UML


        类UML图
      • 虚指针和虚表图


虚指针和虚表

由图可以看出,在虚表中放的全都是虚函数的指针。其中void vfunc2()是在class A中定义的,而他的子类都继承自class A,并且没有被覆写,所以,A、B、C中的虚表都同时具有指针指向A::vfunc2(),而其余的虚函数,都是在各自class中定义的,所以各自会指向自己定义的虚函数(B::vfunc1()、C::vfunc1())

子类调用虚函数:

C* pc = new C;//创建C的对象
pc->vfunc2(); //通过c的指针,调用未被覆写的虚函数
上方的调用过程,实际是相当于 (*(pc-> vptr)[n])(pc); 或 (*pc->vptr[n])(pc);
// 也就是说,调用过程会动态从数组(实际就是虚表)中,取出函数的指针,并把pc作为实参传入该函数,以实现调用

动态绑定调用虚函数:

A* pa = new C; //C的对象向上转型
pa->vfunc2(); //实际会调用C中所实现的vfunc2()
//实际过程,(*(pa->vptr)[n])(pa);或(*pa->vptr)(pa),而pa实际指向的为子类地址,所以看似是父类在调用,实则为子类在调用。所以调用时具体还是需要看指针所指向的对象

通过以上的原理,C++可以实现非常强大的可扩展性,无需判断具体是哪个类来调用相应的函数

实现多态(polymophism)条件:调用者是指针,向上转型(父类指针 = 子类指针),调用虚函数

  • 模版方法(Template Method)

模版方法的UML

父类中的OnFileOpen()函数中调用了自己的虚函数Serialise()。

子类对象再调用OnFileOpen()函数执行到Serialize()函数式,会调用子类所实现的Serialize()函数。

原理:在父类的OnFileOpen()函数中调用自己的虚函数Serialise()时,实际是this->Serialize();的过程,由于Serialize();为虚函数, 并且子类对象符合了向上转型父类* x = new 子类,所以,实际相当于(*(this->vptr)[n])(this);的动态绑定过程。此时子类对象.OnFileOpen()子类调用OnFileOpen()时,相当于父类::OnFileOpen(子类对象的指针);,所以此时的OnFileOpen()中的this指针,实际是子类的指针,所以可以调用到子类所实现父类虚函数的实现函数了。

  • 动态绑定
    • 调用函数的是一个指针,使用对象调用仅仅是静态绑定
    • 子类对象向上转型
    • 调用的函数为虚函数

const关键字

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

问题,常量对象是不能调用非常量函数的, 那么在实际应用者有什么例子吗?

charT operator[] (size_type pos) const {.....//不需要考虑copy on write}
reference operator[] (size_type pos) {....// 必须考虑copy on write}
//对于class template std::basic_string具有以上的两个成员函数。
//原因:对于字符串来说,无论常量还是非常量,都应该可以通过[index]来获取其中的某一个字母,此时无法确认是否是那种调用,必须有两个函数来由编译器自动区分,以便常量对象调用常量成员函数,非常量对象调用非常量成员函数
  • const关键字的初始化
    const修饰的变量必需 初始化

    int i = 41;
    const int c1 = i;  //正确,i的值拷贝给了c1
    int j = c1;  //正确。ci的值拷贝给了i

    拷贝一个对象的,并不会改变它,一旦拷贝完成,心的对象就和原来的对象没什么关系了

  • const对象默认情况下只在文件内有效。
    当多个文件中出现了同名的const变量时,其实等同于不同文件中分别定义了独立的变量。(解决方案是在定义和声明的地方都是用extern关键字修饰,两处则为同一个对象)

new和delete

new和delete的分解动作

  • new:先分配memory,再调用Constructor

    TypeName *pt = new TypeName(params...);的编译器处理过程:

    1. void *mem = operator new (sizeof(TypeName)); //分配所需的内存,operator new的内部调用了malloc(n)来分配空间
    2. pt = static_cast<TypeName *>(mem); //类型强转
    3. pt->TypeName::TypeName(params...); //调用class所对应的构造函数
  • delete:先分配deconstructor,再释放内存

    delete pt; 的编译器处理过程
    1 TypeName::~TypeName(pt); //调用析构函数
    2 operator delete(pt); //释放内存,operator delete内部调用了free(pt)

重载operator new 和operator delete

  • 全局的重载
    • 调用者为编译器
    • 范围为全局
      void* myAlloc(size_t size){return malloc(size);}
      void myFree(void* ptr){return free(ptr); }
      .....
      //痊愈重载,不能放在任何的namespace中
      inline void* operator new (size_t size){
      cout << "global new() \n"; 
      return myAlloc(size);
      }
      inline void* operator new[](size_t size){
      cout << "operator new[]\n";
      return myAlloc(size);
      }
      inline void operator delete(void* ptr){
      cout << "global delete()\n";
      myFree(ptr);
      }
      inline void operator delete[](void* ptr){
      cout << "global delete[]\n";
      myFree(ptr);
      }
  • 重载member operator new/ delete
    class Foo{
    public:void* operator new(size_t);void operator delete(void* , size_t);//第二参数是可选参数,可以不写
    //.......
    }
    • 重载后的调用过程
      • Foo* p = new Foo;
        //Foo* p = new Foo;执行过程
        try{void* mem = operator new (sizeof(Foo));p = static_cast<Foo*> (mem);p->Foo:Foo();
        }
      • delete p;
        //delete p;的执行过程
        p->~Foo();
        operator delete(p);
  • 重载member operator new[]/delete[]
    class Foo{
    public:void* operator new[](size_t);void operator delete[](void* , size_t);//第二参数是可选参数,可以不写
    //.......
    }
    • 重载后的调用过程
      • Foo* p = new Foo[N];
        //Foo* p = new Foo[N];执行过程
        try{void* mem = operator new (sizeof(Foo));p = static_cast<Foo*> (mem);p->Foo:Foo();//调用N次
        }
      • delete [] p;
        //delete [] p;的执行过程
        p->~Foo();  //调用析构函数N次
        operator delete(p);

重载new()和delete()

  • operator new()
    可以重载class member operator new(), 写出多个版本,前提是每一个版本声明都必须有独立的参数列,其中第一参数必须是size_t,(其实就是unsign int),其余参数以new所指定的placement arguments为初值。出现new(....)小括号便是所谓的placement arguments。
    Foo* pf = new(300, 'c')Foo; //设计时参数为三个,size_t, int, char
  • operator delete()
    也可以重载class member operator delete(),写出多个版本。但他们绝对不会被delete调用。只当new锁掉用的ctor抛出异常,才会调用这些重载版本的operator delete()。他们可能这样被调用,主要用来归还未完成创建功能的object所占用的内存。

  • 示例

class Foo{
public:Foo(){ cout << "Foo::Foo() " << endl; }Foo(int){cout << "Foo::Foo(int)" << endl;throw Bad();   //class Bad{};//故意抛出exception,测试placement operater delete}//1.这个就是一般的operater new()的重载
void* operator new(size_t size){return malloc(size); }//2.整个就是标准库已经提供的placement operater new()的重载的形式
//所以也模拟Standard placement new,救治传回pointer
void* operator new(size_t size, void* start){return start;}//3.这个才是placement new
void* operator new (size_t size, long extra){return malloc(size + extra); }//4.也是一个placement new
void* operator new(size_t size, long extra, char int){return malloc(size + extra); }//以下是搭配上述placement new的各个所谓placement delete
//当ctor发出异常,这对应的operator (placement) delete就会被调用
//其用途是释放对应的placement new分配的内存
1. 这是个一般的operater delete()
void operator delete(void*, size_t){cout << "operator delete(void*, size_t)" << endl;}//2. 这是对应void* operator new(size_t, void*)
void operator delete(void*, void*){}//3.这是对应void* operator new(size_t, long)
void operator delete(void*, long){}4.这是对应的void* operater new (size_t, long, char)
void operator delete(void*, long, char){}
//如果placement operater deleter如果不和placement operater new一一对应,程序不会报错private:int m_i;
};

这篇关于(Boolan) C++面向对象高级编程(五)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/914224

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝