[GeekBand]C++高级编程技术(2)
本篇笔记主要分为两个主要部分,第一部分关于对象模型,第二部分是关于new和delete的更加深入的学习。
一、对象模型
关于vptr(虚指针)和vtbl(虚函数表)
只要用到了虚函数,对象中就会多一个指向虚函数表的虚指针。在32位环境下,将占4Bytes的空间。
在vtbl中,每一项都是指向自己类应当调用的虚函数的函数指针。
这里提一下,如果父类定义成虚函数,子类中和父类虚函数相同名称,参数表相同的函数会自动变成虚函数。不管加没有virtual关键字。通常我们还是要加上关键字来确保代码可读性。
2.静态绑定与动态绑定
在C中,对于不同的函数名采用静态绑定的方法,每个函数直接对应了一个地址,存储在相应的位置中。在C++中,非虚的成员函数也用静态绑定的方式被存储。如上图中的A::func1()等成员函数。
不过对于虚函数,C++中采用了动态绑定的方法。在上图中,每个虚函数都存储在虚函数表中。当调用虚函数时,编译器会随着上图中的路径找到正确的函数调用。
由于动态绑定,不管什么地方调用虚函数,总能得到正确的结果这个机制限制了虚函数应该被虚函数覆盖。
虚函数可以由以下的两种方式得到。
一个非常常用的基于虚函数的方式是建立一个指向抽象类的指针链表,这是多态的体现。
关于类的对象模型的内存分配,涉及到对象的位对齐的规则,在博客中的另一篇文章中有简单介绍。
二、再谈new和delete
定位new和delete运算符及其重载
new和delete在使用时可以有一个可选的指针类型的参数,用来指定内存分配的起始地址。如果没有这一参数,则会在堆空间中自动分配一段合适大小的空间。
默认的一个定位new函数是:
void* operator new(size_t size,void *start)
在使用时可以采用例如如下的方法:int *p = new(0x12345678) int;
事实上,我们还可以使用别的参数列进行new操作,我们也可以对operator new和operator delete进行重载。例如下面的两个常用的参数列:
void* operator new(size_t size,long extra)//extra参数用于多申请一段存储空间,专门用来存储一些特别的信息,例如引用计数的信息。
void* operator new(size_t size,long extra,char init)
new和delete操作固有的定义是:
inline void * operator new(size_t size){return malloc(size);}
inline void * operator new[](size_t size){return malloc(size);}//这里在调用时size会自动进行计算。
inline void operator delete(void * ptr){free(ptr);}
inline void operator delete[](void * ptr){free(ptr);}
注意,如果需要进行重载,new第一个参数始终应该是size_t格式的,delete的第一个参数始终是void*类型。此外,通常情况下有参数的delete不会被调用,而是继续采用无参的方法,直接释放内存。
此外,new和delete也可以作为类的成员函数重载,这样重载出来的操作将仅用于这个类。需要注意的是,new和delete操作应该是静态的,其原因在于:
1.创建对象时,先分配内存,此时没有对象,只能是静态的。
2.删除对象,先执行析构,析构后已经没有对象了,所以这里也只能是静态的。
只有当构造函数失败时,才会去寻找匹配的operator delete函数去释放空间;如果没有定义相应的delete函数,就代表放弃处理创建失败的这一异常。
注:
当一对operator new 和operator delete除了第一个参数之外,剩下的参数都一致时,称这两个操作"匹配"。
从new操作显式抛出异常,并不会触发特殊的delete。从new操作中抛出异常,代表内存分配没有进行,因此也就不需要释放内存;只有再分配内存之后,构造时产生异常才会触发特殊的delete。
下面是关于new、delete操作的测试代码:
#ifndef HEADER_H_INCLUDED
#define HEADER_H_INCLUDED
#include <iostream>
#include <stdlib.h>
using namespace std;
class Fruit{
int no;
double weight;
char key;
public:
void print() { cout<<"no =\t"<<no<<"\tweight =\t"<<weight<<"\tkey =\t"<<key<<endl; }
virtual void process(){ }
Fruit(int n = ,double w = ,char k = ):no(n),weight(w),key(k){ cout<<"Ctor of Fruit is called.\nno =\t"<<no<<"\tweight =\t"<<weight<<"\tkey =\t"<<key<<endl;}
virtual ~Fruit(){cout<<"Dtor of Fruit is called.\nno =\t"<<no<<endl;}
}; class Apple: public Fruit{
int size;
char type;
public:
void save() { }
virtual void process(){ }
Apple(int s = ,char t = ,int n = ,double w = ,char k = ):Fruit(n,w,k),size(s),type(t){
cout<<"Ctor of Apple is called.\n";
print();
cout<< "size =\t"<<size<<"\ttype = \t"<<type<<endl;
//注:下面这条语句是为了测试重载版本的delete而特别添加的.
if(s<) throw ;
}
virtual ~Apple(){
cout<<"Dtor of Apple is called."<<endl;
}
static void * operator new(size_t size);
static void * operator new(size_t size,long extra);
static void operator delete(void * ptr);
static void operator delete(void* ptr,long extra);
};
void* operator new(size_t size)
{
cout<<"::operatornew(size_t) is called.\n";
return malloc(size);
}
void operator delete(void* ptr)
{
cout<<"::operatordelete(void*) is called.\n";
free(ptr);
} #endif // HEADER_H_INCLUDED
头文件
#include "header.h" using namespace std; void* Apple::operator new(size_t size)
{
cout<<"void* Apple::operator new(size_t) is called.\n";
return malloc(size);
} void* Apple::operator new(size_t size,long extra)
{
cout<<"void* Apple::operator new(size_t,long) is called.\n";
return malloc(size+extra);
} void Apple::operator delete(void* ptr)
{
cout<<"void Apple::operator delete(void*) is called.\n";
free(ptr);
}
void Apple::operator delete(void* ptr,long extra)
{
cout<<"void Apple::operator delete(void*,long) is called.\n";
free(ptr);
} int main()
{
cout<<"======Fruit new,delete test=======\n";
//这部分的结果体现了:
//1.在没有为类指定单独的new和delete时,会调用全局的new和delete;
//2.造函数先分配内存,再进行构造的特点和析构函数先进行析构,再释放内存的特点;
Fruit * _Fruit = new Fruit(,,'A');
delete _Fruit;
cout<<"======Apple new,delete test======\n";
//这部分的结果体现了:
//1.继承关系先构造基类,再构造自身
//2.析构时先析构自身,再析构基类
//3.类里边有单独的new和delete时,使用类内定义的new和delete。
Apple * _Apple = new Apple(,,,,'B');
delete _Apple;
cout<<"===Apple multi-parameters new,delete test===\n";
//这部分的结果体现了:
//1.显式delete操作总是调用delete(void*),而不管是如何new出来的
_Apple = new(long()) Apple(,,,,'C');
delete _Apple;
cout<<"=Apple new,delete failed test=\n";
//这部分的结果体现了:
//1.当ctor失败时,会去寻找匹配的dtor.
Apple * _Apple_2;
try{
_Apple_2 = new(long()) Apple(-);
}
catch(...)
{
cout<<"Catch Error.";
}
return ;
}
[GeekBand]C++高级编程技术(2)的更多相关文章
- [GeekBand] C++ 高级编程技术 (1)
一.类型转换 class Fraction { public: explicit Fraction(int num, int den=1) : m_numerator(num), m_denomina ...
- C#高级编程技术复习一
从基本的Socket编程进入 (注意:这是转的一篇2011年的文章,有些知识可能该更新了!) 这一篇文章,我将图文并茂地介绍Socket编程的基础知识,我相信,如果你按照步骤做完实验,一定可以对Soc ...
- Qt高级编程 高清PDF+源|网盘下载地址附提取码|
书籍作者:Mark Summerfield(马克 . 萨默菲尔德)(英) 书籍译者:闫锋欣内容简介:本书是一本阐述Qt高级编程技术的书籍.本书以工程实践为主旨,是对Qt现有的700多个类和上百万字 ...
- 【进阶技术】一篇文章搞掂:Spring高级编程
本文篇幅较长,建议合理利用右上角目录进行查看(如果没有目录请刷新). 本文基于<Spring5高级编程>一书进行总结和扩展,大家也可以自行研读此书. 十一.任务调度 任务调度主要由三部分组 ...
- 读《C#高级编程》第1章问题
读<C#高级编程>第1章 .Net机构体系笔记 网红的话:爸爸说我将来会是一个牛逼的程序员,因为我有一个梦,虽然脑壳笨但是做事情很能坚持. 本章主要是了解.Net的结构,都是一些概念,并没 ...
- MVC高级编程+C#高级编程
本人今年的目标是学习MVC高级编程和C#高级编程,把自己的基础打的扎实,本文中值是一个开到,定期会在上面记录学习的技术点和心得就,加油吧!!!!!
- jquery插件开发继承了jQuery高级编程思路
要说jQuery 最成功的地方,我认为是它的可扩展性吸引了众多开发者为其开发插件,从而建立起了一个生态系统.这好比大公司们争相做平台一样,得平台者得天下.苹果,微软,谷歌等巨头,都有各自的平台及生态圈 ...
- 转载--提高C++性能的编程技术
读书笔记:提高C++性能的编程技术 第1章 跟踪范例 1.1 关注点 本章引入的实际问题为:定义一个简单的Trace类,将当前函数名输出到日志文件中.Trace对象会带来一定的开销,因此在默认情况 ...
- Shell高级编程视频教程-跟着老男孩一步步学习Shell高级编程实战视频教程
Shell高级编程视频教程-跟着老男孩一步步学习Shell高级编程实战视频教程 教程简介: 本教程共71节,主要介绍了shell的相关知识教程,如shell编程需要的基础知识储备.shell脚本概念介 ...
随机推荐
- oracle视图总结
视图简介: 视图是基于一个表或多个表或视图的逻辑表,本身不包含数据,通过它可以对表里面的数据进行查询和修改.视图基于的表称为基表.视图是存储在数据字典里的一条select语句. 通过创建视图可以提取数 ...
- linux中mail函数不能发送邮件怎么办
原因分析: 1.检查下本机25端口是否有开放 2.mail是利用linux自带的sendmail服务来发送邮件的,service sendmail status 查看下该服务的状态 发送命令:mail ...
- Spring MVC @RequestMapping Annotation Example with Controller, Methods, Headers, Params, @RequestParam, @PathVariable--转载
原文地址: @RequestMapping is one of the most widely used Spring MVC annotation.org.springframework.web.b ...
- Mac上如何修改默认打开方式
1.找到要打开的文件 2.右键显示简介 3.选择默认程序
- html、css、js的命名规范
最佳原则 坚持制定好的代码规范. 无论团队人数多少,代码应该同出一门. 项目命名 全部采用小写方式, 以下划线分隔. 例:my_project_name 目录命名 参照项目命名规则: 有复数结构时,要 ...
- solr默认查询设置
在搜索过程中,如果我们每次请求中都传入很多固定的参数,会很繁琐,这里再solrconfig.xml中初始化定义一些不经常改动的搜索参数: <requestHandler name="/ ...
- 5. Android框架和工具之 ZXing(二维码)
Android进阶笔记06:Android 实现扫描二维码实现网页登录
- http错误码大全
响应码由三位十进制数字组成,它们出现在由HTTP服务器发送的响应的第一行.响应码分五种类型,由它们的第一位数字表示:1.1xx:信息,请求收到,继续处理2.2xx:成功,行为被成功地接受.理解和采纳3 ...
- Linux Centos 7 使用yum安装 mysql5.7 (实验成功)
第一部分:安装Mysql5.7 1.下载YUM库 shell > wget http://dev.mysql.com/get/mysql57-community-release-el7-7.no ...
- CF 208E - Blood Cousins dfs序+倍增
208E - Blood Cousins 题目:给出一棵树,问与节点v的第k个祖先相同的节点数有多少个. 分析: 寻找节点v的第k个祖先,这不就是qtree2简化版吗,但是怎么统计该祖先拥有多少个深度 ...