您的足迹: NIM学习笔记

NIM学习笔记

NIM学习笔记

学习资料

关键词(了解)

addr and as asm
bind block break
case cast concept const continue converter
defer discard distinct div do
elif else end enum except export
finally for from func
if import in include interface is isnot iterator
let
macro method mixin mod
nil not notin
object of or out
proc ptr
raise ref return
shl shr static
template try tuple type
using
var
when while
xor
yield
从历史上看,Nim是一种完全 风格不敏感 语言。 这意味着它不区分大小写并且忽略了下划线,如notin 和 notIn 以及 not_in 是相同的,像 foo 和 Foo 之间甚至没有区别。

Nim 模糊变量名称的唯一小缺点出现在使用Grep等工具或编辑器的搜索功能时。为了解决这个问题,Nim 提供了一个名为nimgrep的工具,它可以进行不区分大小写和样式的搜索。您的编辑器也可能支持这种类型的搜索。

您可以通过使用命令行参数–styleCheck:error或–styleCheck:hint调用编译器来强制实施一致的命名方案。

not关键字是一元操作符:

a not b 解析成 a(not b), 不是 (a) not (b)

变量和常量

var 可变量

Nim使用关键字 var 支持可变变量。如果您打算更改变量的值,请使用var关键字。

var                     # 可重复分配的可变变量
  letter: char = 'n'    # 非强制的类型声明
  lang = "N" & "im"
  nLength: int = len(lang)
  boat: float
  truth: bool = false
Nim是严格区分字符和字符串的表示,字符用 ',字符串用 “

let 不可变量

Nim使用关键字 let来设置不能被修改的不可变量名

let            # 变量只能赋值一次
  legs = 400  
  arms = 2_000
  aboutPi = 3.14
  a: int16 = 42
  b = 42'i8
  c = 1'f32
  d = 1.0e19
  let input = readLine(stdin)   # 正确,只是赋值的变量input为不可变量
Nim支持所有整数类型的类型后缀,包括有符号和无符号:
  • 'iX,其中X是有符号整数的大小,'uX,其中X是无符号整数的大小

浮点数有两种类型,可用的浮点类型:

  • 'f32 for float32 和'f64 for float64

Nim支持不需要转义序列的原始字符串文字。原始字符串文字是一个以r开头的字符串文字

var f = openFile(r"C:\texts\text.txt") # 原始字符串, 所以 ``\t`` 不是制表符。

也可以使用三引号字符串文字指定多行字符串:

let multiLine = """foo
  bar
  baz
"""
echo multiLine

const 常量

const 与 let 区别: const 赋值的内容必须在编译时就能获取,性能佳,let 语句只保证变量不会被重新赋值

const  
  debug = true
  compileBadCode = false

代码格式化

nimpretty --indent:2 tst1.nim

数据结构

数据结构非常有用。

常用基础类型

  • 不可变数组 array
  • 可变列表 seq
  • 元组 tuple

高级数据类型

  • 集合 set
  • 字典 talbe
  • 枚举 enum

array 数组

数组是值类型,就像int,float和许多其他类,这意味着它们在堆栈中被分配。数组的大小是静态&不可变,因此一旦声明数组,它就不能改变它的大小。这就是为什么当您尝试访问其边界之外的索引时,编译器可能会给您一个错误。在C中,不会检查索引边界,因此可以访问超出数组边界的内存。

Nim在编译时和运行时执行这些检查,除非关闭–boundsChecks选项。
var
  array0 = [1,3,5,7]
  array1 = ["My", "name", "is", "Dominik"]
  array2 : array[8,int]     # 定长为8个int元素
 
array2[1] = 1
array2[array2.high] = 9 
 
for i in array1:
  stdout.write i," " # 不换行输出
stdout.write "\n"
 
for i in array1.low .. array1.high:
  echo array1[i]
 
for i in 0 .. array1.high:
  echo array1[i]
 
for i in countup(1,array2.high,2):
  echo array2[i]

自定义下标

var list: array[-10 .. -9, int]
list[-10] = 1
list[-9] = 2

seq 序列

因为数组array的大小是静态的,你不能给他们添加更多的东西; 您只能使用新数据覆盖现有项目。

这就是Nim seq序列的用武之地。seq的大小是动态的,可以根据需要增长到更多元素(取决于内存大小)。

@符号用于转换成新序列。匿名过程的语法有点麻烦。值得庆幸的是,Nim支持一些用于定义匿名过程和过程类型的语法糖。语法糖不是语言的一部分,而是在标准库中定义,因此要使用它,您必须导入未来的模块。

  import sequtils, future
  let numbers = @[1, 2, 3, 4, 5, 6]
  let odd = filter(numbers, (x: int) -> bool => x mod 2 != 0)
  assert odd == @[1, 3, 5]
 
  echo numbers.filter do(x:int)->bool: x>5

使用序列构造函数语法时,必须注意指定序列的类型。

var list = seq[int]

或者,因为编译器无法知道要定义的序列类型。当你构造一个非空序列时:

var list = @[4,8,15,16,23,42]

newSeq过程提供了预分配序列的另一种方法。newSeq还提供了一个重要的优化 - 建议你在提前了解序列的大小时使用它。

  var drinks = newSeq[string](3)
 
  drinks[0] = "water"
  drinks[1] = "juice"
  drinks[2] = "chocolate"
 
  drinks.add("beer")
  if "beer" in drinks:
    echo "We have water and ", drinks.len - 1, " other drinks"
 
  let myDrink = drinks[2]

tuple元组

元组(tuple)适用于在同一个复合类型中放入不同类型的元素,元组还定义了元素的顺序 。

var
  child: tuple[name: string, age: int] 
  today: tuple[sun: string, temp: float]
 
child = (name: "Rudiger", age: 2)
today.sun = "Overcast"
today.temp = 70.1
type Person = tuple[name: string, age: int]
 
let
  person1: Person = (name: "Peter", age: 30)
  person2: Person = ("Mark", 40)
 
echo person1
echo person2

set 集合

set使用{}构造集合。在大括号内指定值列表,并用逗号分隔项。
集合不关注它存储的项目的顺序,因此您无法通过索引访问其中的项目。

set类型是值类型,因此不需要初始化。

var collection: set[char]

对于要检查集合中特定值的存在的情况,集合非常有用 - 使用 in 关键字

let collection = {'a', 'x', 'r'}
assert 'a' in collection

可以做集合处理:交集,合集,差集

let collection = {'a', 'T', 'z'}
let isAllLowerCase = {'A' .. 'Z'} * collection == {}
assert(not isAllLowerCase)

table字典

enum枚举

type
  Day = enum
    Mon=1, Tue, Wed, Thu, Fri, Sat, Sun=7
 
  Colors {.pure.} = enum
    Red = "FF0000", Green = (1, "00FF00"), Blue = "0000FF"
 
  Signals = enum
    sigOK, sigFail=-1, sigQuit = 3, sigAbort = 6, sigKill = 9

type声明

type 关键字开头的声明包含多个类型定义。像 objects 或 enums 这样的标称类型只能在 type 部分中定义。

type
  Color = enum cRed, cBlue, cGreen
  Direction = enum # 另一种形式的写法
    dNorth
    dWest
    dEast
    dSouth
var
  orient = dNorth
  pixel = cGreen
type
  DieFaces = range[1..20] # 只有1..20之间的数字才是有效值
var
  my_roll: DieFaces = 13
type
  Name = string # 类型定义将类型给个别名
  Age = int     # 但更具描述性
  Person = tuple[name: Name, age: Age] # 使用别名来声明类型
 
var john: Person = (name: "John B.", age: 17)
type
  AnotherSyntax = tuple                # 另一种元组的写法
    fieldOne: string
    secondField: int
 
var test: AnotherSyntax
test.fieldOne = "hello"
test.secondField = 10
let (b,c) = test

类型定义可以是递归的,甚至可以是相互递归的。(相互递归类型只能在单个 type 部分中使用)

type # 演示相互递归类型的示例
  Node = ref object  # 垃圾收集器管理的对象(r​​ef)
    le, ri: Node     # 左右子树
    sym: ref Sym     # 叶节点含有Sym的引用
 
  Sym = object       # 一个符号
    name: string     # 符号名
    line: int        # 声明符号的行
    code: Node       # 符号的抽象语法树

与众不同的面向对象

Nim 中的对象类似于C中的结构体。

与Java中的类不同,Nim对象仅包含字段(有时也称为成员变量),但不包含过程、函数或方法,也不包含 cpp 中的初始化程序或析构函数。

在Nim中,我们将数据对象与过程、函数、方法以及与这些数据对象一起使用的可选初始化程序和析构函数分开。

Nim避免了这种严格的类概念,而其通用方法调用语法允许我们对所有数据类型使用类似类的语法。

例如,要获取字符串变量的长度,我们可以用经典的过程符号编写len(myString),或者我们可以使用方法调用语法myString.len()或仅使用myString.len。

方法与类的紧密耦合缺乏灵活性。例如,使用附加方法扩展类可能很困难,在某些情况下甚至是不可能的。Nim的OOP类似于go/rust的impl方法
type User = object
 
proc say(self: User) =
  echo "say hello"
 
proc eat(self: User) =
  echo "eat something "
 
let shaohy = User()
 
shaohy.say()
shaohy.eat()

流程控制

if..elif..else

let ageDesc = if age < 18: "Non-Adult" else: "Adult" # 类似C的三目运算符 max=(a>b)?a:b
if age > 0 and age <= 10:
  echo("You're still a child")
elif age > 10 and age < 18:
  echo("You're a teenager")
else:
  echo("You're an adult")
when 语句几乎和 if 语句相同,但在以下有所差异:
  1. 因为每种情况都会被编译器评估,所以其必须是个常量表达式。
  2. 分支中的语句不会开启新作用域。
  3. 编译器会检查语义并且只为第一个评估为 true 的情况生成代码。

与C语言中的#ifdef相似,when语句在编写针对特定平台的代码时十分有用

when sizeof(int) == 2:
  var intSize = 2
  echo "running on a 16-bit system!"
elif sizeof(int) == 4:
  var intSize = 4
  echo "running on a 32-bit system!"
elif sizeof(int) == 8:
  var intSize = 8
  echo "running on a 64-bit system!"
else:
  echo "cannot happen!"
 
echo intSize # variable is visible here!

while

您还可以嵌套循环语句,您可能想知道如何一次性打破多个循环。这可以通过为break关键字指定标签来解决。标签必须由block关键字定义,并且打破该标签将导致执行突破该块内的每个循环。

block label:
  var i = 0
  while true:
    while i < 5:
      if i > 3: break label
      i.inc

while猜数字

import strutils, rdstdin, random
randomize()
 
let answer = rand(10)
 
while true:
  let guess = parseInt( readLineFromStdin("猜一个0到10之间数字:"))
  if guess < answer:
    echo "小了,again"
  elif guess > answer:
    echo "大了,again"
  else:
    echo "正确!"
    break

for

import os
for i in walkFiles("*.nim"):
  echo i
for i in @[1,2,3,4,5]:
  echo(i)
 
for i,value in @[1,2,3,4,5]:
  echo("idx: " ,i," value: ",value)

case...of

case toLower(readLineFromStdin("are you ok? yes/no ")):
  of "no":
    echo "Sorry."
  of "yes":
    echo "Great."
  else:
    echo "Orz..."
import os
 
case system.hostOS:
of "linux":
  echo "Linux"
of "macosx", "MacOS":
  echo "Macos"
else:
  echo "Unknown"
Nim 区分过程(proc)、函数(func)和方法(method)
在某些方面,Nim的定义比其他语言更迂腐(德国风格)。

func函数更接近于纯数学的函数式编程,本质上,它们是设置了额外限制的过程:

  1. 它们不能访问全局状态(const 除外)且不能产生副作用。
  2. 基本上是用 {.noSideEffects.} 标记的 proc 的别名
  3. 函数仍然可以更改它们被标记为 var 的可变参数,以及任何 ref 对象

method方法与过程不同,它是动态分派的, 是一个与继承和面向对象编程密切相关的概念, 方法依赖于从 RootObj 继承的对象。

过程(proc)

在本节中,我们将探讨Nim中的procedures(过程)。在其他编程语言中,过程可以被称为函数,方法或子例程。每种编程语言都对这些术语有不同的含义,Nim也不例外。可以使用proc关键字定义Nim中的过程,然后是过程的名称,参数,可选的返回类型,=和过程体。

图2.1显示了Nim过程定义的语法。 nim中的procedure

也可以让编译器为您推断出程序的返回类型。为此,您需要使用auto自动类型:

proc genHello(name: string, surname = "Doe"): auto =
  "Hello " & name & " " & surname
assert genHello("Peter") == "Hello Peter Doe"
assert genHello("Peter", "Smith") == "Hello Peter Smith"

虽然这很方便,但您应尽可能明确指定类型。这样做可以让您和其他人更容易确定过程的返回类型,而无需了解过程的主体。

警告:auto 类型推理在Nim中,程序的类型推断仍然有点实验,您可能会发现它在某些情况下是无能为力的。

快速排序

import strutils, strformat
var arrNum = @[3,1,4,2,7,5,6,9,8,0]
 
proc quickSort(arr: var seq[int], st, en: int) =
  if st == en: return
  var sep = st
  for i in st+1..<en:
    echo &"st: {st} i : {i}, sep: {sep} "
    if arr[i] > arr[st]:
      inc sep
      stdout.write fmt" {arr[i]} <-> {arr[sep]} => "
      swap(arr[i],arr[sep])
      stdout.write arr
    echo ""
  swap(arr[st], arr[sep])
  quickSort(arr, st, sep)
  quickSort(arr, sep+1, en)
 
quick_sort(arrNum,0,arrNum.len)
echo arrNum.join(" ")
fmt 跟 & 同样效果

斐波那契数列

递归函数

proc fib(n: int): int =
  if n < 2:
    result = n
  else:
    result = fib(n-1) + fib(n-2)
 
echo fib(6)

自构建数列

proc fib2(n: int) : int {. noSideEffect .}=
  var
    a = 0
    b = 1
    c: int
    m = 1
  while m < n:
    c = a
    a = b
    b = c + b
    inc m
  return b
 
echo fib2(6)

迭代器

自定义一个浮点数半增的计数器

iterator countTo(n: float): float =
  var i:float = 0
  while i <= n:
    yield i
    i += 0.5
 
for i in countTo(5):
  echo i

泛型函数

泛型函数就像CPP的模板一样,并允许与模板相同的静态检查

import math
 
proc `**`(a,b : float):float = 
  pow(a,b)
 
echo 2 ** 5
 
 
proc `*`[T](a: T, b: int): T =
  result = ""
  for i in 0..<b:
    result &= a 
 
echo "a"*10

调用系统命令

import osproc
 
for line in lines "./ip.txt":
  var cmd = "ping -f -c3 " & line
  echo execCmd(cmd)
import osproc
import streams
 
for line in lines "./ip.txt":
  var cmd = "ping -f -c3 " & line
  let p = startProcess(cmd, options={poEvalCommand})
  let s = p.outputStream()
  echo s.readAll()

引用和指针

使用 ref 关键字声明追踪引用,使用 ptr 关键字声明未追踪引用。 通常, ptr T 可以隐式转换为 pointer 类型。

多线程和线程池

nim中的『multi-thread』线程模块是隐式的导入的一部分系统模块,因此您无需显式导入它。

#import locks # not necessary
import osproc
 
proc Ping(ip: string) {.thread.} =
  var cmd = "ping -f -c3 " & ip
  discard execCmd(cmd)
 
for line in lines "./ip.txt":
  var thread: Thread[string]
  createThread[string](thread, Ping, line)
  joinThread(thread)

nim中的线程池模块需要显式导入它(异步最快)

import threadpool
import osproc
 
proc Ping(ip: string) {.thread.} =
  var cmd = "ping -f -c3 " & ip
  discard execCmd(cmd)
 
for line in lines "./ip.txt":
  spawn Ping(line)
sync()

NIM编程实例

最简单webserver

import asynchttpserver,asyncdispatch
 
proc conn(req: Request) {.async} =
  await req.respond(Http200,"Hello world, Nim!")
 
asyncCheck newAsynchttpserver().serve(Port(8080),conn)
runForever()
Server Software:        
Server Hostname:        192.168.13.181
Server Port:            8080
 
Document Path:          /
Document Length:        17 bytes
 
Concurrency Level:      100
Time taken for tests:   11.016 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      560000 bytes
HTML transferred:       170000 bytes
Requests per second:    907.80 [#/sec] (mean)
Time per request:       110.157 [ms] (mean)
Time per request:       1.102 [ms] (mean, across all concurrent requests)
Transfer rate:          49.65 [Kbytes/sec] received

NIM读写excel

import xlsx, random
 
proc processExcel(inputFile: string, outputFile: string) =
  # Load the workbook and select the first sheet
  let wb = loadWorkbook(inputFile)
  let ws = wb[0]
 
  # Create a new workbook for output
  var newWb = newWorkbook()
  let newWs = newWb.addSheet("Processed Data")
 
  # Iterate through the rows and columns
  for row in ws.rows:
    var newRow = newWs.addRow()
    for cell in row.cells:
      if cell.hasValue:
        # Check if the cell contains a number
        if cell.value.isNumber:
          # Generate a random coefficient between 0.9 and 0.95
          let coefficient = 0.9 + random.uniform(0.0, 0.05)
          # Multiply the cell value by the coefficient
          let newValue = cell.value.float * coefficient
          newRow.addCell(newValue)
        else:
          newRow.addCell(cell.value)  # Keep non-numeric values unchanged
 
  # Save the new workbook
  newWb.save(outputFile)
 
# Main program
proc main() =
  let inputFile = "input.xlsx"  # Replace with your input file path
  let outputFile = "output.xlsx"  # Replace with your desired output file path
  processExcel(inputFile, outputFile)
 
main()

NIM写jwt认证

import jwt, times, json, tables
 
var secret = "upyun123"
 
proc sign(userId: string): string =
  var token = toJWT(%*{
    "header": {
      "alg": "HS256",
      "typ": "JWT"
    },
    "claims": {
      "userId": userId,
      "exp": (getTime() + 1.days).toUnix()
    }
  })
 
  token.sign(secret)
 
  result = $token
 
proc verify(token: string): bool =
  try:
    let jwtToken = token.toJWT()
    result = jwtToken.verify(secret, HS256)
  except InvalidToken:
    result = false
 
proc decode(token: string): string =
  let jwt = token.toJWT()
  result = $jwt.claims["userId"].node.str
 
echo sign("shaohaiyang@gmail.com")

Nim 写游戏

Nim Blog写博客

NIM 与 python 的天衣无缝

nimpy

import nimpy
 
proc fib(n: int): int {.exportpy.} =
    if n <= 2:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

如果没有nimpy模块,使用

nimble install nimpy

然后导出c库函数,源文件名和库函数名要一致

nim c -d:release --app:lib --threads:on --out:fib_nimpy.so fib_nimpy.nim

nimporter

import nimporter
from test01 import fib  # 直接调用test01.nim中的函数,第一次会编译运行
 
if __name__ == "__main__":
  print(fib(44))

pylib

import pylib
 
var n = int(input("♚ :请输入一个数字: "))
print(f"计算 {n} 的斐波那契数:{fib(n)}")

NIM 与 C 交互

Nim because it natively compiles down to C and has some of the best C FFI I have used.

nim与c的交互

nim与C做数据结构转化(file)

# file /usr/bin/ls
/usr/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, ....
 
#ldd /usr/bin/file
        linux-vdso.so.1 =>  (0x00007ffedefd3000)
        libmagic.so.1 => /usr/lib64/libmagic.so.1 (0x00007f7f1919d000)
        libz.so.1 => /usr/lib64/libz.so.1 (0x00007f7f18f87000)
        libc.so.6 => /usr/lib64/libc.so.6 (0x00007f7f18bb9000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f7f193ba000)
file这个命令依赖 libmagic.so.1这个动态库
from os import fileExists, expandFilename
 
const libName* = "libmagic.so.1"
 
const  MAGIC_NONE*  = 0x000000                 # No flags 
const  MAGIC_DEBUG* = 0x000001                 # Turn on debugging 
const  MAGIC_SYMLINK* = 0x000002                 # Follow symlinks 
const  MAGIC_COMPRESS* = 0x000004                # Check inside compressed files 
const  MAGIC_DEVICES* = 0x000008                 # Look at the contents of devices 
const  MAGIC_MIME_TYPE* = 0x000010            # Return only the MIME type 
const  MAGIC_CONTINUE* = 0x000020             # Return all matches 
const  MAGIC_CHECK* = 0x000040                 # Print warnings to stderr 
const  MAGIC_PRESERVE_ATIME* = 0x000080        # Restore access time on exit 
const  MAGIC_RAW* = 0x000100                    # Don't translate unprint chars 
const  MAGIC_ERROR* = 0x000200                 # Handle ENOENT etc as real errors 
const  MAGIC_MIME_ENCODING* = 0x000400         # Return only the MIME encoding 
const  MAGIC_NO_CHECK_COMPRESS* = 0x001000     # Don't check for compressed files 
const  MAGIC_NO_CHECK_TAR* = 0x002000         # Don't check for tar files 
const  MAGIC_NO_CHECK_SOFT* = 0x004000         # Don't check magic entries 
const  MAGIC_NO_CHECK_APPTYPE* = 0x008000        # Don't check application type 
const  MAGIC_NO_CHECK_ELF* = 0x010000            # Don't check for elf details 
const  MAGIC_NO_CHECK_ASCII* = 0x020000         # Don't check for ascii files 
const  MAGIC_NO_CHECK_TOKENS* = 0x100000         # Don't check ascii/tokens 
 
type Magic = object
type MagicPtr* = ptr Magic 
 
# magic_t magic_open(int);
proc magic_open(i:cint) : MagicPtr {.importc, dynlib:libName.}
 
# void magic_close(magic_t);
proc magic_close(p:MagicPtr): void {.importc,  dynlib:libName.}
 
#int magic_load(magic_t, const char *);
proc magic_load(p:MagicPtr, s:cstring) : cint {.importc, dynlib: libName.}
 
#int magic_errno(magic_t);
proc magic_error(p: MagicPtr) : cstring  {.importc, dynlib:libName.}
 
#const char *magic_file(magic_t, const char *);
proc magic_file(p:MagicPtr, filepath: cstring): cstring {.importc, dynlib: libName.} 
 
proc guessFile*(filepath: string, flags: cint = MAGIC_NONE): string =
    var mt : MagicPtr
    mt = magic_open(flags)
    discard magic_load(mt, nil)
 
    if fileExists(expandFilename(filepath)):
        result = $magic_file(mt, cstring(filepath))
    magic_close(mt)
 
echo guessFile("/usr/bin/ls")

nim编译成c动态库

proc fib*(a: int): int {.exportc, dynlib.} =
  if a <= 2:
    result = 1
  else:
    result = fib(a - 1) + fib(a - 2)
 
[root@ops-shaohy-rds myNim]#  nim c --app:lib nim2_dynlib.nim  -> libnim2_dynlib.so

nim调用c动态库

proc fib(n: int): int{.importc, dynlib: "./libnim2_dynlib.so".}
 
echo fib(10)

nim调用标准库

proc strcmp(a, b: cstring): cint {.importc: "strcmp", nodecl.}
 
echo strcmp("Nim", "Nim")
当过程或函数被声明为 nodecl 时,编译器不会为其生成普通的过程/函数声明。这通常用于以下场景:
  1. 给编译器提供一个声明,但不需要实际的实现。
  2. 实现是通过其他方式提供的,比如外部库函数等

nim调用非标准库

#include <stdio.h>
 
/* gcc -shared -nostartfiles -fPIC fakeimglib.c -o fakeimglib.so */
 
int openimage(const char *s)
{
  static int handle = 100;
  fprintf(stderr, "opening %s\n", s);
  return handle++;
}
 
# nim code
proc openimage(s: cstring): cint {.importc, dynlib: "./fakeimglib.so".}
 
echo openimage("foo")
echo openimage("bar")
如果lib.so是C写的,那nim调用的时候要用cstring,cint,cfloat等数据类型来匹配。
wiki/public/nim学习笔记.txt · 最后更改: 2025/11/21 00:36