关于C/C++的一些思考(4)
C++的类型转换规则:
对于数值类型而言:当一个较小数值类型赋值给一个较大数值类型的时候,C++支持隐式的类型转换,不会有任何的损失;
对于数值类型而言,当一个较大数值类型赋值给一个较小数值类型时候,由于较小数值类型内存空间有限,有信息丢失,这种转换被视为不安全,但仍旧可以隐式进行;
对于对象类型而言,派生类对象中的信息肯定包含基类对象,将一个派生类对象赋值给基类对象的时候,肯定也有信息丢失,但是这不会影响我们使用基类对象的信息,所以这种转换被视为安全的,可以隐式进行;
对于对象类型而言,反过来,如果将基类对象赋值非派生类对象,由于没有足够的信息来填充派生类对象的对应空间,这样的转换被视为不安全的;如果是按值传 递,则不能进行显示转换(除非定义转换构造函数或者转换运算符重载函数),如果是指针或者索引类型则可以进行显示转换(强制转换,可行,但无意义);对于对象类型而言,反过来,如果将基类对象赋值非派生类对象,由于没有足够的信息来填充派生类对象的对应空间,这样的转换被视为不安全的;如果是按值传 递,则不能进行显示转换(除非定义转换构造函数或者转换运算符重载函数),如果是指针或者索引类型则可以进行显示转换(强制转换,可行,但无意义);
对象值类型之间的类型转换:
对于不同类型之间的赋值classB=classA而言,如果没有对应的赋值运算符重载函数,则首先通过转换构造函数转换,再使用赋值运算符重载函数赋值;
对于不同类型之间的混合运算classB=classA+classC而言,如果没有正确参数对应的友元函数,则需要首先使用转换构造函数进行类型转换,然后调用友元函数;
对于相同类型之间的初始化classB b=classB而言,调用的是拷贝构造函数,所以对于不同种类型没有作用;
C++对于内部数值类型的类型检查不严,认为这些类型都可以相互隐式转化,从这个角度看C++是弱类型语言;对于基类和派生类而言,派生类可通过隐式转换 到达基类(通过拷贝构造函数或者赋值运算符重载函数实现),但是基类必须通过强制转换才可以到达派生类(这些规则对于类对象类型,引用类型和指针类型都适 用),不相关类型之间的转换只能通过特殊的程序特征设计才能实现,所以从这个角度看C++是强类型语言;
C++中有三种强制类型转换方式:
转换构造函数:当提供非目标对象类型的类型时,将调用转换构造函数创建一个临时对象,此临时对象一般情况下会用于拷贝构造函数(参数传递,初始化)或重载 赋值运算符函数(赋值)的参数。如果在转换构造函数前添加explicit关键字,则需要显示使用强制转换符号才能显示进行转换。
Base::Base(int init) {……}
有一函数void setOther(Base b),则如果传入参数为setOther(5);实际的调用为
setOther(Base(5));
依赖转换构造函数的隐式转换仅限于按值传递参数类型,因为转换构造函数构造一个对象本身,所以对于指针和引用类型的参数不能依赖这个机制;
转换运算符重载函数:不能有在函数头上定义返回值,但是函数体内需要有return返回值,定义:operator const char* () const {return ptr;} 其中的const char*就是最终转到的类型,使用:(char *)typeinst; typeinst 就是定义转换运算符函数的类型,此类函数的功能与static_cast<> ()类似。尽管没有显示声明返回值,但是函数体内也需要使用return语句返回对应函数名的类型;
operator const char*() const {……return const ptr;}
operator char*() {……return ptr;}
指针(引用)之间的转换:隐式转换的规则仅适用于数值类型(和有转换构造函数的类型),任何类型的变量都可以接受强制类型转换的初始化或者赋值,但大多数情况下是使用新的类型解释机制去解释原有的内存数据,尽管没有意义,但是编译器却允许这样的显示转换;
当使用基类对象作为右值赋值给作为左值的派生类对象时,即使使用强制转换也是不合法的;仅有定义针对基类对象的赋值运算符重载函数才可以(派生类对象有部分数据需要额外赋值)
class Base {}; class Derived :public Base {}
Derived d; Base b;
d=b;//错误,一定需要赋值运算符重载函数
d=(Derived)b;//错误,一定需要转换操作符重载函数解决办法是:
之一:为Derived添加赋值运算符重载函数
void Derived::operator=(const Base &b)
void Derived::operator=(const Derived &d)之二:为Derived添加转换构造函数
Derived::Derived(const Base & b)之三:为Base添加转换操作符重载函数
Base::Derived() {……return derived;}
对象类型之间的转换,无论是基类对象转换成派生类对象,还是派生类对象转换成基类对象,都是生成新对象的过程,需要调用对应的构造函数,并且可能会丢失信 息(派生类转换到基类的时候会出现信息丢失)。而指针或者索引之间的转换则只是访问模式的转换,对于对象本身来讲并没有实质上的变化,因此不同类别之间的 指针和索引可以相互转换,并且转换是可逆的;
虚函数机制的使用:
只适用于指针和索引类型
其本身不能是静态函数,不能使用对象变量或者作用域运算符调用
并且一般是基类类型指针(索引)指向派生类类型对象的时候使用
继承模式为public(隐式转换只有在公共派生时才有效)
虚函数不能声明为static,private
派生类中对应的函数需要与基类中的函数的名字,参数列表,返回值都完全相同;
class Base {virtual void func() {……}};
class Derived1 : public Base {void func() {……}};
class Derived2 : public Base {void func() {……}};
Base* comBase=new Derived1(); comBase->func();//调用1
comBase=new Derived2(); comBase->func();//调用2则comBase会根据具体索引不同的派生类类型,调用不同的func()方法
四类函数定义:
1类:基类中定义,派生类中没有定义
2类:基类中没有定义,派生类中进行了定义
3类:基类中,派生类中都进行了定义(相同函数名)
4类:虚函数
三类索引情况:(针对四类函数定义中的四项)
基类指针索引基类对象:1
派生类指针索引派生类对象:某些public继承子基类的1,2和3中的派生类定义(静态绑定);
基类指针索引派生类对象:1,3中的基类定义(静态绑定)和4中的派生类定义(动态绑定);
对C++类的内存模型的理解:
static类函数和static类成员数据是独立于类而言,也就是存储于静态存储区;
非static类函数是针对类而言,也就是所有相同类型的实例都共享同一份非static类函数列表的内存,这份数据在类的第一个实例创建的时候载入内 存,直到最后一个类的实例销毁的时候才销毁,表示一个类的数据;非static类函数以Table的形式出现,每一个slot表示函数的调用地址;如果是 派生类,则Table会以基类的Table作为模板(因此包含private, protected和public的函数指针),然后将派生类的类函数地址存储到新的Table中(顺序查找是否有同名函数,如果有则实现函数覆盖;如果 没有,则加入Table的末尾);由于使用Base::fun2()就可以访问基类的方法,所以可能不是简单的同名覆盖;
非static类成员数据是针对实例而言,也就是相同类型的实例在内存中有一份独立的成员数据拷贝,在非static类函数体内通过*this指针进行成 员数据的访问;如果是派生类,则其内存中会包含所有基类的数据成员(因此包含所有private, protected和public的数据成员);
virtual函数是针对实例而言,含有虚函数的类与不含有虚函数的类在内存中同样具有以Table形式存储指向类函数的指针的列表,派生类的类函数地址 同样会存储于Table中(顺序查找是否有同名函数,如果有则实现函数覆盖;如果没有,则加入Table的末尾);不同的是,类实例的内存的起始位置会使 用4bytes(一个指针大小)存储指向这个Table的指针,因此这个Table更名为Virtual Table;并且表明为virtual的函数会使用运行时查找的方式到VTable中查找函数,其他函数仍旧使用静态绑定的方式确定类函数调用地址;
如果是多继承的情况,则按照继承顺序依次存储每一个基类的VT_Ptr和数据,最后才是自身的数据;
class Base {
private:
int data0;
public:
int data1;
static int data2;
public:
void fun1();
void fun2();
void fun3();
virtual void fun4();
virtual void fun5();
virtual void fun6();
static void fun7();
}; class Derived : public Base{
public :
int data3;
static int data4;
public : void fun2();
void fun3(int); void fun5();
void fun6(int);
void fun8();
static void fun9();
}; class solo {
private:
int data0;
public :
int data1; };
Base类的类函数Table(由于有虚函数,所有也叫VTable): Base::fun1 Base::fun2 Base::fun3 Base::fun4 Base::fun5 Base::fun6 Derived类的类函数Table(由于有虚函数,所有也叫VTable): Base::fun1 Derived::fun2 Derived::fun3 Base::fun4 Derived::fun5 Derived::fun6 Derived::fun8static函数存储于程序静态存储区,所以独立于类的类函数Table;
派生类的Table直接复制自基类的Table,并将自己定义的函数添加到Table的末尾(如fun8),如果有同名函数则直接进行覆盖(这里不管是否参数列表相同,也不管是否为virtual函数,如fun2和fun5,fun3和fun6)
Base类的类实例内存结构:
VT_Ptr1 data0 data1Derived类的类实例内存结构:
VT_Ptr2 data0 data1 data3
static成员数据存储于程序静态存储区,所以独立于类的实例内存;
派生类的类实例包含所有基类的数据成员;并且不会覆盖重名的成员数据,而只是简单的追加
VT_Ptr1表示指向Base类在内存中的类函数Table的指针;VT_Ptr2表示指向指向Derived类在内存中的类函数Table的指针;仅仅virtual函数才是动态动态调用,其他函数则是静态绑定
虚拟继承和虚基类:用于避免多继承环境中的二义性和内存使用效率
- 问题:当B和C拥有共同的基类A,同时D同时继承B和C,这样当D调用A中的方法时产生二义性(不知道选择B继承路线还是C继承路线,在D的内存数据中有两份A的类数据),其实问题在于B和C各自拥有一份A基类的内存拷贝,不仅有二义性且内存耗用大
class Top {};
class Left : virtual public Top {};
class Right : virtual public Top {};
class Bottom : public Left, public Right {};Bottom* b=new Bottom(); b指向Left::Top::a;
Left* bl=b;bl指向Left::Top::a;但是当Right* br=b;时,br需要指向Right::Top::a才逻辑正确,需要手动进行偏移;
当Top* bt=b;时,编译不通过,产生歧义。
虚继承中,每个类实例中仍旧有4bytes的指针指向虚函数表,并且Derived3的实例中有两个4bytes的VT指针;
解决:class B: virtual A{}; class C: virtual A{};这样的定义使得内存中A基类仅有一份数据,所以的子类都使用同一份拷贝
关于C/C++的一些思考(4)的更多相关文章
- 领域驱动和MVVM应用于UWP开发的一些思考
领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中有了心得体会或遇到了问 ...
- 关于面试题 Array.indexof() 方法的实现及思考
这是我在面试大公司时碰到的一个笔试题,当时自己云里雾里的胡写了一番,回头也曾思考过,最终没实现也就不了了之了. 昨天看到有网友说面试中也碰到过这个问题,我就重新思考了这个问题的实现方法. 对于想进大公 ...
- 关于 CSS 反射倒影的研究思考
原文地址:https://css-tricks.com/state-css-reflections 译者:nzbin 友情提示:由于演示 demo 的兼容性,推荐火狐浏览.该文章篇幅较长,内容庞杂,有 ...
- 关于.NET参数传递方式的思考
年关将近,整个人已经没有了工作和写作的激情,估计这个时候很多人跟我差不多,该相亲的相亲,该聚会喝酒的聚会喝酒,总之就是没有了干活的心思(我有很多想法,但就是叫不动我的手脚,所以我只能看着别人在做我想做 ...
- 使用NUnit为游戏项目编写高质量单元测试的思考
0x00 单元测试Pro & Con 最近尝试在我参与的游戏项目中引入TDD(测试驱动开发)的开发模式,因此单元测试便变得十分必要.这篇博客就来聊一聊这段时间的感悟和想法.由于游戏开发和传统软 ...
- OpenGL shader 中关于顶点坐标值的思考
今天工作中需要做一个事情: 在shader内部做一些空间距离上的计算,而且需要对所有的点进行计算,符合条件的显示,不符合条件的点不显示. 思路很简单,在vertex shader内知道顶点坐标,进行计 ...
- 关于领域驱动设计(DDD)中聚合设计的一些思考
关于DDD的理论知识总结,可参考这篇文章. DDD社区官网上一篇关于聚合设计的几个原则的简单讨论: 文章地址:http://dddcommunity.org/library/vernon_2011/, ...
- 关于bug分析与异常处理的一些思考
前言:工作三年了,工作内容主要是嵌入式软件开发和维护,用的语言是C,毕业后先在一家工业自动化控制公司工作两年半,目前在一家医疗仪器公司担任嵌入式软件开发工作.软件开发中,难免不产生bug:产品交付客户 ...
- 【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。
× 缘起---闲逛博客园 前几天的时候,在某一QQ群看到一条消息“XXX酒店开房XXXBTXX迅雷BT下载”,当时是一目十行的心态浏览,目光掠过时, 第一反应我想多了~以为是XX种子(你懂的~ ...
- 对于多个数据库表对应一个Model问题的思考
最近做项目遇到一个场景,就是客户要求为其下属的每一个分支机构建一个表存储相关数据,而这些表的结构都是一样的,只是分属于不同的机构.这个问题抽象一下就是多个数据库表对应一个Model(或者叫实体类).有 ...
随机推荐
- 状压dp之二之三 炮兵阵地/玉米田 By cellur925
一.简单的状压dp 玉米田 题目描述 Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ ...
- API+RESTful
什么是API? API 划分了服务供需方的边界,是协调不同端开发人员的协议/框架.API两端,程序可以用不同的语言.由不同的团队开发,追求不同的目标,有不同的发布节奏.只要在 API 方面达成一致,两 ...
- _bzoj1798 [Ahoi2009]Seq 维护序列seq【线段树 lazy tag】
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1798 注意,应保证当前节点维护的值是正确的,lazy tag只是一个下传标记,在下传时应即时 ...
- 使用VS2015打包winform程序安装包简单方法(不需要InstallShield)
转载自: DGPLM博客 使用VS2015打包winform程序安装包简单方法(不需要InstallShield)
- 题解报告:hdu 1039 Easier Done Than Said?
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1039 Problem Description Password security is a trick ...
- Oracle代码 规则 创建表 表空间 用户等
-----创建表空间----------create tablespace bdccslogging datafile 'D:\oracle\product\10.2.0\oradata\bdccs\ ...
- Object C学习笔记18-SEL,@ selector,Class,@class--转
一. SEL 类型 在上一篇介绍了几个方法,都只是介绍了其使用方式但是没有具体介绍参数: - (id)performSelector:(SEL)aSelector; - (id)performSele ...
- sdut1933WHUgirls(dp)
http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=1933 矩形的dp一般挺类似 大的矩形都是由小 ...
- SpringBoot之旅第七篇-Docker
一.引言 记得上大三时,要给微机房电脑安装系统,除了原生的操作系统外,还要另外安装一些必要的开发软件,如果每台电脑都重新去安装的话工作量就很大了,这个时候就使用了windows镜像系统,我们将要安装的 ...
- 微信小程序flex布局
一.flex布局基础 二.相对定位和绝对定位 flex的容器和元素 主轴(左-右),交叉轴(上-下) flex容器属性详解 flex-direction 决定元素的排列方向(默认row ...