zoukankan      html  css  js  c++  java
  • C++语言体系设计哲学的一些随想(未完待续)

    对于静态类型语言,其本质目标在于恰当地操作数据,得到期望的值。具体而言,需要:

    (1)定义数据类型

    你定义的数据是什么,是整形还是浮点还是字符。该类型的数据可以包含的值的范围是什么。

    (2)定义操作的含义

    操作是严格数据类型相关的。操作表明了对了一个具有特定类型的数据,执行操作后产生什么样结果。 

    ===========================================

    C++就是一个典型的静态类型语言。在C++中,无论是"数据类型"还是"操作",都分为内置的和自定义的。

    C++的内置数据类型包括:

    (1)基本内置类型

    整形、浮点、布尔、字符....

    (2)STL库定义的类型

    例如常用的iostream、string、迭代器......

    此外C++和定义了复合类型机制,包括所有类型的引用、指针、数组,他们可以作为一个完整数据类型的一部分。

    顺便提一下,顶层/底层const、static、volatile...等修饰符,定义了数据的其他属性,这些属性也可以是一个完整数据类型的组成部分。

    自定义类型,最常用的就是class、struct、union定义,还有函数签名,当然也可以使用复合类型机制定义自己类的引用、指针、数组等。

    ==============================

     重点在于,无论是变量还是常量,必须属于某一特定的数据类型。因为操作只有基于精确的数据类型,其定义才有了确定的含义(在编译原理中叫做“语义”)。也就是说,在一个确定的操作集合中(例如C++语言内置的所有操作),只要给一个变量赋于了数据类型,这个变量可以执行的操作也就确定了。定义变量nVal为int类型,那么nVal就可以参与加减乘除、关系运算、拷贝、转换为double、传递给函数形参、作为数组的下标.........

    C++的“操作”,其含义非常广泛。其实C++语言已经通过成员函数、操作符重载、函数重载、构造函数定义的隐式类型转换...等机制,表明了 C++作为一个静态类型语言的本质:属于特定类型的数据,加上其上的操作。可以这样理解,任何一个操作,本质就是函数,操作符在C++语言内部也是被当作函数来看待的(这也能解释C++提供operator操作符重载机制的动机);类的成员函数、友元函数,也是对类本身这个“数据类型”的操作。

    更进一步,操作本身也是一种特殊的数据类型。可以定义函数的指针、函数的数组,成员访问(->*,.*),只是可以被当作数据类型来使用的机会不多,也被语言本身限制了。

    C++的内置操作不太好理解,实际上我们常用的语言机制都是“操作”,具体包含了:

    (1)各种各样的操作符

    算术操作符、关系操作符、位运算、取地址、单目运算、解引用、数组元素访问..... 

    (2)拷贝操作

    拷贝初始化、列表初始化(C++ 11)、赋值运算、函数传参、函数返回值、类型转换执行的临时变量拷贝......等其他非引用场景

    (3)数据类型转换

    类型转换也是一种操作。对于普通的操作,执行前需先匹配要操作的数据的类型。现实中,不可能总能保证在代码里提供类型严格匹配的数据,因此类型转换也是C++语言非常普遍的操作。

    该如何理解这样的操作呢?举个例子,例如: 

    int nVal = 42;
    
    double fVal = 3.14;
    
    double fValTwo;
    
    fValTwo = fVal + nVal ; // nVal类型提升为double
    

     上述代码最后一行的相加操作将执行类型提升。从编译器的角度看,此时将生成一个匿名的变量,变量的类新和需要匹配的类型(double)相同,之后执行int至double的类型转换操作,操作结果保存在这个匿名变量中。之后才会执行“+”操作。也就是说,如果选定了操作,那么就会期待若干数据类型完全匹配的操作数,为了满足这个条件,系统会执行类型转换。

    对于赋值操作,该操作会期待=右边操作数的数据类型和左边完全匹配,此时也会和上述相同,生成匿名变量,执行类型转换。准备工作完成后,再执行"="操作。

    函数的调用也是基于相同的原理,即实参类型和形参类型的匹配。

    ... ...

    C++语言内部定义了异常复杂的类型转换规则(操作),只不过大多数对使用者是透明的。例如:

    整形提升 - char、short、bool会先转换为int;

    类型提升 - 防止精度损失;

    类型降低 -有精度损失,常见于拷贝操作。拷贝操作是将源对象严格匹配目标对象,因此不会有算术操作里的“整形提升”。拷贝包括了拷贝初始化、赋值运算、函数调用实参赋给形参

    非bool值都可以转换为bool,相反则转换为0/1;

    任意类新指针都可转换为void*;

    数组在不用于decltype、sizeof、typeid、取地址&的情况下,会自动转换为指向第一个元素的指针。

    非底层const向底层const的转换 - 指向常量的引用和指针可以绑定到非常量上,和内置类型的提升与降低不同,底层const向非底层const的转换是非法的

    子类向基类的转换 - 基类指针/引用可以指向子类,这是多态的基础。和底层const一样,相反的转换是非法的

    ... ...

    -

    PS:关于底层const和继承体系类型转换的单向性:

    本质而言,一个数据的数据类型,可以执行的操作的集合越小,该数据可以引用/绑定的对象类型越广。例如:

    数据类型A,可以执行operA - operZ 共26个操作。数据类新B,可以执行的操作是A的子集,比如operH-operN。那么,B的引用/指针可以绑定到A(B的引用/指针可以接受A/A的指针赋值),相反则是非法的。

    const int *不能修改指向的int,而int *可以,也就是说,数据类型const int *的操作范围比int *要小,所以const int *可以绑定到int*指向的对象(本质上是指const int *可以接受int*赋值)。
    在继承体系中,基类的操作范围肯定是小于子类的,所以 基类指针指向/基类引用 子类的合法的。

    造成这一切的原因就在于,对静态类型语言,编译器始终“固执”地、“自以为是”地按照其静态声明类型,来决定一个操作是否合法,而不去管这个对象实际指向的类型。可以想象,编译器“自以为是”地认为通过int *可以改变这个int,而不管这个int*实际指向的是const int,如果允许底层const向非底层const转换,就会带来冲突。

    -


    PS:基于该观点理解重载

    函数重载、操作符重载的本质,是用同一个名字定义了多个操作。结果是在编译阶段引入了一个确定具体操作的过程 - 从候选操作中选出最匹配的操作。而上述“类型转换”操作则是在运行阶段进行的。

    自定义操作,包括我们定义的普通函数、成员函数、重载的操作符、构造函数定义的隐式类型转换、拷贝构造函数定义的拷贝操作...

    未完待续

  • 相关阅读:
    eslint 的 env 配置是干嘛使的?
    cookie httpOnly 打勾
    如何定制 antd 的样式(theme)
    剑指 Offer 66. 构建乘积数组
    剑指 Offer 65. 不用加减乘除做加法
    剑指 Offer 62. 圆圈中最后剩下的数字
    剑指 Offer 61. 扑克牌中的顺子
    剑指 Offer 59
    剑指 Offer 58
    剑指 Offer 58
  • 原文地址:https://www.cnblogs.com/radiolover/p/3960272.html
Copyright © 2011-2022 走看看