跳至主要內容

数据+算法 泛操作

LincZero大约 13 分钟

数据+算法 泛操作

数据书写类

声明、定义、赋值、使用

概念 (相同)

包括 变量、函数、类 等的定义与声明,不过这里特指 变量

  • 声明
    • 简明概念:告诉你有个东西存在
    • 例如:"数学中有个概念是乘法" / "有个浮点数派"
    • 底层区分:标识符分配名称和类型
  • 定义
    • 简明概念:告诉你这个东西具体是什么
    • 例如:"乘法的定义是 n*m 等同m的n相加" / "这个派的值是3.1415"
    • 底层区分:分配内存并填充值 (对于普通变量) 或提供方法体 (对于函数)
  • 使用
    • 简明概念:你去使用这个概念
    • 例如:"用乘法解决 a*b 的问题" / "使用派去计算圆的直径"
    • 底层区分:访问标识符对应的地址或内存值
  • 赋值
    • (变量有此概念,函数与此概念)
    • 底层区分:修改标识符对应的内存值
  • 初始化
    • 底层区分:为标识符分配内存的同时进行填充,体现为第一次定义 (若声明与定义概念合并,则体现为声明时即初始化值)

自动初始化 (声明默认值)

  • 有默认初始化
    • Java / Kotlin / Python / Swift / Go
      • 基本类型:默认初始化为零值
      • 对象引用:默认初始化为空 (java为null,kotlin为null,python为None,swift为nil,go为nil)
    • TypeScript/JavaScript:
      • 默认初始化为 undefined
  • 强制初始化
    • Rust
      • 强制初始化,否则报错,不存在默认初始化的行为
  • 无默认初始化
    • C/C++(这一类就只有C/C++了,hhhh。该设计可以追求极致的性能,但程序员容易写出bug
      • 基本类型:无默认初始化,成员变量也是。使用这些变量是未定义行为,且无物理内存及虚拟内存对应的值是随机的
      • 全局变量/静态变量:默认初始化为零值

合并 定义与声明 的概念 (与有无默认值有关)

定义与声明是否分开:(这个概念与 “有无默认值” 是类似的,详见对应节/上一节。若有默认值或强制初始化,则一般合并这两个概念)

  • 分开
  • 不分开 (因为有默认值)
  • 不分开 (因为强制初始化)

Linc:

其中 Java 有些特殊,我之前在一本书上看过一句话:“Java中不区分定义与声明”,有一定的道理,Java 简化了这些概念。

在变量定义上如此,在函数定义上亦是如此:Java 中的方法不需要向前声明,其向前声明也并不属于声明和定义分开的概念,在Java中,所有的方法声明都隐式包含了向前声明的概念,因为你可以在方法实现之前调用该方法。

但是这里有个例外,完整的说法应该是:

Java 仅在接口 (interface) 中可仅声明,而在类中不可分离

数据存储大小

  • Java

    • Java规范中没有“依赖具体实现”的地方。基本数据类型的大小以及有关运算都做了明确的说明。例如整型的范围与运行Java代码的机器无关。 这就解决了软件从一个平台移植到另一个平台,或者在同一个平台中的不同操作系统之间进行移植给程序员带来的诸多问题
  • C/C++

    • 需要针对不同的处理器选择最为高效的整型,这样就有可能造成一个在32位处理器上运行很好的C程序在16位系统上运行却发生整数溢出 仅规定了int在short和long之间这种规定 当然,选择C++有直接定义int8_t int16_t int32_t int64_t的方法 (数字表示的是bit数),而不使用short、long类型(个人建议) 也可以使用size_t这种在大小会变的类型(32/64位上分别是4/8字节)将性能优先贯彻到底

类型属性

"类型属性" 这个名字是我杜撰的,暂时先将他理解成一个用于方便说明的引入概念

  • 变量的类型:有不同的存储大小和拥有方法(可理解成所属不同的类)
  • 类型的属性:修饰类型的访问权限 (作用域)、修改权限 (是否可变)、生命周期 (程序周期 作用域周期)、单一性/静态性

不可变变量,究竟是常量 or 变量?

这里有一些概念:

  • 从可变性分类

    • 变量:声明后可修改

      • 变量/可变变量:定义(初始化)后可修改。

        大多数语言正常定义的变量 (除了像rust这种默认变量不可变),如cpp的普通变量、rust的let mut、kotlin的var

    • 常量:声明后不可修改,且声明时需要同时进行定义(初始化)

      • 编译期常量:编译期/声明前甚至能知道他的值。

        如c的宏常量,如cpp的constexpr、rust的const、kotlin的const

      • 运行期常量:可能声明时才能知道他的值,如用户输入存到const。

        如cpp中声明和定义一起的const、rust的let、kotlin的val

  • 从生命周期分类

    • 静态变/常量:全局生命周期,声明后不可再声明副本。可以是变量/常量

      如cpp的static

    • 非静态变/常量

特殊补充:

  • 不可变变量:= 常量(主要指运行期常量),其实就是两种说法,但建议不要用这个概念,这种说法容易造成混淆。 而有的资料会说到这个,注意区分即可
  • 默认声明:大多数语言默认声明可变,而rust默认声明不可变

常量折叠,运行期常量 -> 编译期常量 的自动优化

只要给运行期常量赋值的右值是一个在编译期就能确定的数,哪怕使用的是运行期常量的写法,这个运行期常量也会被优化成编译期常量,这个说法正确吗?

GPT4:

是的,你的理解是正确的。在Go语言中,如果一个常量的值在编译期间就能确定,那么这个常量就可以被优化成编译期常量。这是因为编译器有能力在编译期间进行计算,并且在生成的机器码中使用计算出的常量值,而不是运行期间的表达式。这种优化可以帮助提高程序的运行效率。

这个特性在很多编程语言中都存在,被称为常量折叠(Constant Folding)。常量折叠是编译器的一种优化策略,目的是在编译期间就计算出那些可以预计算的表达式的值,从而减少程序运行时的计算量。

一个简单的验证方法:

C++语言中该写法是正确的:const int a = 10; int list[a];,在C中也依然正确吗

GPT4

在C早期版本中 (如C89/90),这种写法是不正确的。因为它要求数组的大小在编译时就能确定,而 const int a = 10; 事实上在运行时才确定。但在C99及以后的版本中,这种写法是正确的。

各语言 "类型属性" 的写法差异

举例 (int类型为例)

语言JavaCppRustGoKotlin
可变变量int a = 10;int a = 10;let mut a = 10;var a int = 10var a: Int = 10
运行期常量 (不可变变量)final int B; B = 20;const int B = 20;let B: i32 = 20;const B int = 20val B: Int = 20
编译期常量 (无类型,宏)#define C 30const C = 30
编译期常量 (带类型)final int D = 40;constexpr int D = 40;const D: i32 = 40;const d int = 40const val D: Int = 40
静态+可变变量static int e = 50;static int e = 50;static mut e: i32 = 50; (伴生对象模拟)

Java

// 可变变量
int a = 10;
// 运行期常量
final int B; B = 20; // 在Java中,final变量的声明和初始化可以分开,只要确保在使用变量之前已经完成了初始化。在Python、Js、C#、Cpp、Go中,这种做法都不被允许
// 编译期常量-无类型
// Java中没有对应的概念
// 编译期常量-带类型
final int C = 40;
// 静态可变变量
static int d = 50;

C++

// 可变变量
int a = 10;
// 运行期常量
const int b = 20;
// 编译期常量-无类型
#define C 30
// 编译期常量-带类型
constexpr int D = 40;
// 静态可变变量
static int e = 50;

Rust

// 可变变量
let mut a = 10;
// 运行期常量
let B: i32 = 20;
// 编译期常量-无类型
// Rust中没有对应的概念
// 编译期常量-带类型
const D: i32 = 40;
// 静态可变变量
static mut e: i32 = 50;

Go

// 可变变量
var a int = 10
// 运行期常量
const B int = 20
// 编译期常量-无类型
const C = 30
// 编译期常量-带类型
const d int = 40
// 静态可变变量
// Go中没有对应的概念

Kotlin

// 可变变量
var a: Int = 10
// 运行期常量
val B: Int = 20
// 编译期常量-无类型
// Kotlin中没有对应的概念
// 编译期常量-带类型
const val D: Int = 40
// 静态可变变量
// Kotlin中没有对应的概念,但可以用伴生对象来实现类似功能
companion object {
    var e: Int = 50
}

常量泛操作

  • const关键字

    • 在C++中用于定义运行期常量
    • 在Java中是保留的关键字,目前并没有使用。在Java中,必须使用final定义常量
    • kotlin和rust的const是编译期常量,而在C++使用constexpr才表示编译期常量
  • final关键字:在Java中用于定义常量。在C++11中被用于禁止继承或禁止虚函数的

变量泛操作

声明类型

  • C、C++、Java:需要先声明
  • Basic、Python、Php、Js:不显示声明(在使用新名称时创建新的变量)

骚操作

语言C/C++JavaPythonGo操作举例
同行批量定义支持支持不支持支持int x, y = 4
解压缩不支持不支持支持,js的es6有类似x,y,z=listtuple=x,y,z
连等号支持支持支持(原理不同)x = y = z = c
表达式广义性支持支持不支持x = (y+1) +1

类型转换

  • C语言(type)var,如(int)'12'
  • Pythontype(var),如str(12)

数字

数字类型

  • C/C++:分有符号数和无符号数
  • Java:没有任何无符号(unsigned)
  • Python3:数字的类型更少(四种:int、float、bool、complex(复数)) 其中int为变长类型,理论可以无限大。甚至没有long这种类型(python2依然有long类型)

布尔

有无布尔类型

  • C语言:没有布尔类型,用1、0表示布尔
  • Python、java:有布尔类型

无布尔类型需要注意的点:

  • 其中在C++中,数值甚至指针可以代替boolean值。值0相当于布尔值false,非0值相当于布尔值true。在Java中则不是这样。 因此,Java程序员不会遇到下述麻烦:if(x=0)

位运算

  • C/C++:不能保证>>是完成算术移位(扩展符号位)还是逻辑移位(填充0)
  • Java:消除了这种不确定性,这与java中没有无符号类型有关。>>用符号位填充高位,>>>用0填充高位

列表比较

  • C语言:叫数组,()声明,需要先声明长度
  • Python:叫列表,[]声明,不需要预定义长度
  • Matlab:叫矩阵,[]声明

Python list与C数组的比较

  • 空间效率

    • C语言中,创建数组时,首先需要指定数组的容量大小,根据大小来分配内存,也就是说,即使要在数组中存储一个元素,也需要为所有的元素预先的分配内存。故,C语言中数组的空间效率不高
    • Python中,列表list属于其的一个高级特性,是对源码中对象(PyListObject)的一个抽象,而因为不同的list存储元素的个数不同,故这里的PyListObject再创立之初就被设置为变长对象。并且,该对象中内置了像插入、删除等方法,可以在运行时动态的调整维护的内存和元素
    • 总结:C语言数组相较Python List空间效率低,灵活性较差
  • 长度

    • C语言中,除了动态数组以外,数组都是不可变的
    • Python中,对象中封装了插入、删除等方法,所以List是可变的
  • 数据类型

    • C语言中,数组中的元素只能是同一种类型,在数组初始化时已经确定
    • Python中,数组中的元素可以是不同的类型

指针

Java和C++最大的不同在于Java采用的指针模型可以消除重写内存和损坏数据的可能性。

布尔

布尔运算的输出结果:

C语言没有bool,过去C++和C一样没有布尔类型,将非零值解释为true,将零解释为false

ANSI/ISO C++标准后C++的新增布尔类型

即在过去是0,1(或非零数),ANSI/ISO C++标准后是true和false

至于其他语言,Java、Python、JavaScript都有bool类型

  • Java和JavaScript用ture和false表示
  • Python用关键字TrueFalse表示

符号

逗号和分号的使用

关于句末的分号这里不再提及,具体见前,这里只提及分割作用

  • C语言:分号和逗号分割for表达式、逗号分割数组(严格意义上分号是终止符而不是分隔符)
  • Python:空格分割for表达式、逗号分割列表元组字典

括号的使用

  • C语言[]是索引,()声明数组,{}用于缩进
  • Python[]是索引,[]声明列表,()声明元组,{}声明字典和集合

逻辑运算符的使用

  • C语言、Java、Js&&、||、!,C语言有位逻辑符
  • Pythonand、or、not

类型判断

普通类型判断

Python - 其他

x is in list
x is None
x == x
type(x) is QDMGraphicsSocket  # type方法

C++

x == x
x == nullptr
type(x).name  // 这个是Qt的,type方法

枚举型

指数枚举值,用&和|来判断(位与、位或)

&表示包含枚举

|表示将这两个枚举相加

各个语言用法都差不多

if hasattr(item, "node") or isinstance(item, QDMGraphicsEdge) or item is None:
    # Shift加左键 -> 伪装事件:按Shift修饰键时同时自动按Ctrl修饰键
    if event.modifiers() & Qt.ShiftModifier:
        event.ignore()
        fake_event = QMouseEvent(QEvent.MouseButtonPress, event.localPos(), event.screenPos()
                                 , Qt.LeftButton, event.buttons() | Qt.LeftButton
                                 , event.modifiers() | Qt.ControlModifier)
        super().mousePressEvent(fake_event)
        return

类判断

Python

# '''
该文件中用了三种判断方法
()
    type(item) is QDMGraphicsSocket  # type方法
    直接判断类型
()    
    isinstance(item, QDMGraphicsEdge)
    可以判断派生类,这里的QDMGraphicsEdge有两个派生类
()    
    hasattr(item, 'node')
    用于判断对象是否包含对应的属性,这里node里有: 
    grNode、socket - grSocket、title_item、content - grContent
# '''

C++ 有点不同

// 一般通过是否能够进行类型转化来判断