发布网友 发布时间:2022-04-21 07:55
共10个回答
懂视网 时间:2022-04-08 03:31
这里的框架指的是让软件运行起来的那种框架,因为我需要让成品不依赖NewLisp的东西,自己就能运行,也就是说编译成可执行程序。
这在主流静态语言中易如反掌的事,在Lisp系列语言中简直比登天还难,只有极少的一星半点的Lisp方言实现能支持,而且效果还不好。以至于新的Lisp方言实现干脆直接挂上JVM平台了,比如Clojure。
而NewLisp是我遇到的最强大的Lisp方言实现,因为我可以像喝水一样极其轻易的编译可执行程序,而且没有任何负面的问题存在。NewLisp编译的原理非常简单,就是复制自身,然后在后面加上代码,这样一个可执行程序就出来了。
; hello.lisp (print "hello world! ") (exit) >newlisp -x hello.lisp hello.exe >hello.exe hello world! >_
当然直接在发行版中附带newlisp.exe,然后用newlisp main.lisp一样可以,但这并不好。编译后的程序不需要额外的命令启动,而且不会被newlisp原本的参数所干扰,这是很重要的一点。
但是这有一个缺点,就是NewLisp只能编译单个lisp文件为可执行程序,至少我目前没有发现编译多个文件的方法。不过这个缺点很容易破解,而且其并不算缺点。我们只需要建立一个框架就可以了:
; main.lisp (load "a.lisp") (load "b.lisp") ... (exit (app:run (rest (main-args))))
这样就做出了一个框架,启动时加载模块,然后用app:run来开始执行,app:run定义在某个模块中,作为程序入口。这样,我们就只需要编译main.lisp:newlisp -x main.lisp demo.exe,然而将所有模块放在demo.exe的目录中,这些模块可随时修改。
但是这样就行了吗?当然不行,因为这是硬编码,所以,我们需要考虑更好的设计。我最终决定使用二级指针来完成,于是整个框架只需要两行代码:
; main.lisp (load "app.lisp") ; 加载一级指针,通过这个模块加载其他模块 (exit (app:run (rest (main-args))))
其中app.lisp就是一级指针,也可以说是模块加载器:
; app.lisp (load "a.lisp") (load "b.lisp") (load "c.lisp") ...
在app.lisp中加载需要的模块,也可以同时做一些事务,或者是定义一些东西等等。这样,除了app.lisp这个文件的文件名不能修改,其他的都可以随意修改,想加模块随意加,想修改模块随意改。
首先我对sdb的定义是一个简单的Key-Value数据库系统,而且是面向本地的。所以我决定像SQLite一样,一个文件对应一个数据库。既然是Key-Value数据库,那么就需要定义好模型。
一个数据库中,平淡的存储Pair就完事了吗,不,我需要个性一点。所以我在Pair的上面加了一层:空间。
一个数据库中可以用多个空间,Pair存储在空间之中。这看起来就像是所有表都只有2列的SQL数据库。
然后是如何操作数据库,我决定采用最简单的方法,因为这本来就只是练手之作。使用一个REPL客户端,通过执行用户的命令来处理事务。当然这个不会有SQL的那些东西,采用最简单的命令结构:
// 设想 >get a.p // 获取空间a中key为p的Pair的值 ... >set a.p 100 // 更新空间a中key为p的Pair的值或建立新的Pair ...
最核心的两个命令:get、set。作为一个Key-Value数据库来说,是核心的东西了。
这几乎就说明了需要做什么了,提供一个REPL客户端,用户通过命令来处理事务,那么设计好对这些命令的处理就差不多是做好sdb了。
以下是目前定义的所有命令:
get <spacename>.<key> // 获取值 set <spacename>.<key> <value> // 更新值或建立Pair list <spacename> // 列出一个空间中的所有Pair erase <spacename>.<key> ... // 删除所有指定的Pair eraseall <spacename> ... // 删除所有指定的空间中的所有Pair spaces // 列出所有空间 dup <source-spacename> <new-spacename> // 复制一个空间到一个新的空间 new <spacename> ... // 建立一至多个空间 delete <spacename> ... // 删除所有指定的空间 link <file> // 连接到一个数据库文件 unlink // 断开当前连接并保存更新 relink // 断开当前连接,仅重置 h or help <command> // 显示命令帮助信息 q or quit // 退出sdb,自动保存更新 q! or quit! // 仅退出sdb
然后是sdb的程序参数,目前倒没什么可定义的,所以只有两个:
link:<file> 启动时连接到一个数据库文件 help 显示使用帮助信息
例子:
sdb // 直接进入REPL sdb help // 显示使用帮助信息,虽然你已经知道了 sdb link:db.txt // 启动时连接数据库文件db.txt
sdb本身是一个Key-Value数据库系统,同时提供REPL客户端,那么我们就划分两个模块,一个是客户端模块,一个是数据库模块。客户端模块处理用户输入,调用数据库模块来处理事务并输出结果,数据库模块负责数据模型和文件存储。
于是sdb需要2个模块,总共4个代码文件:
sdb/ app.lisp ; 一级指针,模块加载器 client.lisp ; 客户端模块 db.lisp ; 数据库模块 main.lisp ; 软件框架
; main.lisp (load "app.lisp") (exit (app:run (rest (main-args)))) ; app.lisp (load "db.lisp") (load "client.lisp") ; db.lisp (context ‘db) ... (context ‘MAIN) ; client.lisp (context ‘app) ... (define run (lambda (.args) )) (context ‘MAIN)
在NewLisp中定义模块很简单,使用(context)函数就行了,(context)会建立一个模块,或者切换到已有的模块,访问一个模块的东西时,需要用<模块名>:<符号名>,就比如app:run,就是访问app模块中的run,从而调用该函数。每个模块开始和结尾都调用了一次(context)函数,这是一种模块设计方式。第一次是建立模块,第二次是切换到顶层作用域。在这里,main.lisp是框架,app.lisp是加载器,所以不需要定义成模块。
client.lisp主要负责处理用户的输入,并调用db.lisp来处理事务,然后输出结果,不断的循环,直到用户砸了机器为止。当然肯定不需要砸机器,这是一个标准的Read->Exec->Print->Loop循环,我们已经提供了quit/quit!两个命令让用户能退出。在框架中,已经预先限定了,需要app模块中有个run函数,这将作为程序的入口。简单起见,直接在run中做好REPL循环,顺带检查参数。
(define run (lambda (.args) ; 如果有参数,调用do-options解析并处理 (unless (empty? .args) (do-options .args)) ; 显示软件标题 (show-title) ; 是否要继续循环,是就继续 (while (nextrun?) ; 显示当前上下文环境 (连接的数据库) (echo-context) ; 等待用户输入然后处理 (query (read-line))) 0))
从代码已经可以清晰的看出整个流程了,但是你可能觉得这个(nextrun?)是不是多余呢,其实不多余。因为整个调用链不小,为了确保正确性,所以使用了一个全局变量。当然这是安全的,因为读写是通过专用函数操作的:
(define _isnextrun true) ; 一种命名风格,前面加_表示为模块内部符号 (define nextrun? (lambda () _isnextrun)) (define stoprun (lambda () (setf _isnextrun false)))
query函数主要负责解析命令、检查命令,执行命令:
(define query (lambda (txt) (letn ((tokens (get-tokens txt)) ; 解析命令文本,转换成单词列表 (cmd (cmd-prehead (first tokens))) ; 是什么命令 (cmdargs (rest tokens))) ; 这个命令的参数列表 (case cmd ; 由于细节较多,所以部分命令的处理过程省略 ("get" ...) ("set" ...) ("list" ...) ("erase" ...) ("eraseall" ...) ("spaces" (map println (db:list-spaces))) ("dup" ...) ("new" ...) ("delete" ...) ("link" ...) ("unlink" (cmd-request (db:unlink))) ("relink" (cmd-request (db:relink))) ("help" (cmd-help cmdargs)) ("quit" (begin (db:unlink) (stoprun))) ("quit!" (stoprun)) (true (unless (empty? cmd) (println "Unknow command, enter h or help see more Information.")))))))
(cmd-request)是对一次数据库操作的过程封装:
(define cmd-request (lambda (result) (if result (println "ok") ; 操作成功,显示ok (println (db:get-last-error))))) ; 失败,显示错误信息
(cmd-prehead)是对命令的预处理:
(define cmd-prehead (lambda (txt) (let ((cmd (lower-case txt))) ; 转换成小写 (case cmd ("h" "help") ; h替换成help ("q" "quit") ; q替换成quit ("q!" "quit!") ; q!替换成quit! (true cmd))))) ; 其他的返回自身
基本上这就是client.lisp的全部了,其他未列出来的东西无关紧要。
在db.lisp中,最核心的问题就是,如何表示数据模型,使用什么结构来构造数据,也可以说是,怎么设计get/set函数。只要get/set设计好了,其他的全部就不是问题了。
如果只是纯粹的Key-Values,那么直接用一个列表就行了,NewLisp有着原生的支持来构造Key-Value表:
> (setf x ‘()) () > (push ‘("a" 1) x) // 建立键值对 (("a" 1)) > (push ‘("b" 2) x) -1) (("a" 1) ("b" 2)) > (assoc "a" x) // 查询键值对 ("a" 1) > (setf (assoc "a" x) ‘("a" "one")) // 更新键值对 ("a" "one") > (pop-assoc "a") // 删除键值对 ("b" 2) > x (("a" "one"))
对于每一个空间,使用原生提供的push/assoc/pop-assoc就可以完成全部的键值对操作了。但是一个数据库有多个空间,那么这些空间怎么放呢?直接嵌套Key-Value表吗?
‘(("s1" // 空间s1,有a=1, b=2两个键值对 (("a" 1) ("b" 2))) ("s2" // 空间s2,有c=1, d=2两个键值对 (("c" 1) ("d" 2))) ("s3" // 空间s3,有e=1, f=2两个键值对 (("e" 1) ("f" 2))))
这样,访问一个键值对就需要如下形式:
(assoc "a" (last (assoc "s1" _spaces))) ; 访问空间s1中的a ... (map (lambda (s) (first s)) _spaces) ; 列出所有空间的名称
对我来说,这样不好,还有一个问题是,更新一个空间中的键值对时,需要(setf (assoc spacename _spaces) newspace)。这意味每一次对空间里键值对的增/删/改操作,都会生成一个新的空间。
在尝试这样设计get/set之后,我决定放弃这种结构,而使用另一种方式。
这种方式很不直接,但很有效。具体的做法是,让每一个空间都对应着一个符号,建立空间时生成一个符号和一个空间对象(Key-Value表),让这个符号指向空间对象,删除一个空间时,直接删除对应的符号就可以了。
这是什么意思呢?就是相当于在运行时定义变量,这个变量的名称都是临时生成的。经过一番设计,最终的结构如下:
(define _spaces ‘())
你没看错,就只是定义一个列表,_spaces中只存放空间的名称。那么如何建立空间呢?建立的空间要怎么访问呢?
(define new-space (lambda (name) (if (have-space? name) ; 这个空间已经存在了吗? (ERROR _SPACE_HAS_EXIST name) ; 已经有了,不能重复建立 (begin ; 如果还未存在 (set (get-space-symbol name) ‘()) ; 定义一个符号,符号的名称根据这个空间的名称来生成,指向一个空的Key-Value表 (push name _spaces))))) ; 加入这个空间的名称
是不是有点困惑,为什么这样就建立了一个空间呢?让我们再看下(get-space-symbol)
(define get-space-symbol (lambda (name) (sym (string "_space." name)))) ; 返回一个符号,这个符号的名称为_space.加上空间的名称
就比如刚才示范的三个空间,一一建立的话,就会在NewLisp的运行时环境中,定义三个新的符号,这些符号指向各自的Key-Value表。
于是,就可以在代码中使用(setf (assoc keyname (get-space-symbol spacename)) (list keyname value))之类的形式来增/删/改一个空间的键值对。
每一次建立空间都会生成一个新的符号,但我们并不能预先知道这个符号是什么名称。上面三个空间在建立时生成_space.s1、_space.s2、_space.s3三个符号,但我们无法直接使用,所以我们需要(eval)来完成对空间的访问。这样的结构,使得get/set也轻松的设计出来:
(define kvs-get (lambda (spacename keyname) (if (have-space? spacename) ; 这个空间存在吗? (let ((kv ; 如果存在 (eval ; 生成[获取键值对的函数调用] 并执行 (list ‘assoc ‘keyname (get-space-symbol spacename))))) (if kv ; 这个键值对存在吗? (last kv) ; 存在,返回值 (ERROR _PAIR_NO_IN_SPACE keyname spacename))) ; 不存在,提供错误信息 (ERROR _SPACE_NO_EXIST spacename)))) ; 这个空间不存在,提供错误信息 (define kvs-set (lambda (spacename keyname value) (if (have-space? spacename) ; 这个空间存在吗? (begin ; 如果存在 (eval (list ; 生成[删除这个键值对的函数调用] 并执行 ‘pop-assoc ‘keyname (get-space-symbol spacename))) (eval (list ; 生成[加入新键值对的函数调用] 并执行 ‘push ‘(list keyname value) (get-space-symbol spacename)))) (ERROR _SPACE_NO_EXIST spacename)))) ; 这个空间不存在,提供错误信息
为什么需要eval呢?因为我们需要访问一个运行时建立的符号,就比如:
var x = 100;
我们可以在源代码中直接对x进行任何操作,但是如果这个x是运行时定义的呢?为什么要运行时定义,因为我们不知道用户会用什么名称来建立空间,我们需要将运行时建立的符号,融合进平常的操作中:
(eval (list assoc ‘keyname (get-space-symbol spacename))) ; 如果spacename传入的是s1 ; 那么就会生成这样的函数调用: (assoc keyname _space.s1) ; 看到这里应该所有人都明白了
为什么这么麻烦?因为NewLisp没有提供指针变量的支持,如果能用指针,就能有更简单好用的设计。这是Lisp的缺点之一,主要是因为很多方言实现都避免指针导致的。就像很多Lisp中易如反掌的事,在C这些语言中需要绕出病来一样,很多在C这些语言中易如反掌的事,同样在Lisp中会绕出病。
就在这时,我发现这样的一个提示:当前已输入9840个字符, 您还可以输入160个字符。于是我只好草草结束。但还好的是,这个sdb最关键的几个地方都讲解了一遍。
当然这里讲解比较浅显,具体的东西,还是要看代码才会明白。文章写的这么多字,远不如直接看代码来的清晰。
NewLisp是一个很有价值的Lisp方言实现,虽然目前还有很多缺点。
您还可以输入3个.
使用NewLisp设计Key-Value数据库系统
标签:
热心网友 时间:2022-04-08 00:39
new的用法
开辟单变量地址空间
使用new运算符时必须已知数据类型,new运算符会向系统堆区申请足够的存储空间,如果申请成功,就返回该内存块的首地址,如果申请不成功,则返回零值。
new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而动态创建的对象本身没有标识符名。
一般使用格式:
格式1:指针变量名=new 类型标识符;
格式2:指针变量名=new 类型标识符(初始值);
格式3:指针变量名=new 类型标识符 [内存单元个数];
说明:格式1和格式2都是申请分配某一数据类型所占字节数的内存空间;但是格式2在内存分配成功后,同时将一初值存放到该内存单元中;而格式3可同时分配若干个内存单元,相当于形成一个动态数组。例如:
1)new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址。int *a = new int 即为将一个int类型的地址赋值给整型指针a
2)int *a = new int(5) 作用同上,但是同时将整数空间赋值为5
2.开辟数组空间
对于数组进行动态分配的格式为:
指针变量名=new 类型名[下标表达式];
delete [ ] 指向该数组的指针变量名;
两式中的方括号是非常重要的,两者必须配对使用,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的指针,会产生回收不彻底的问题(只回收了第一个元素所占空间),加了方括号后就转化为指向数组的指针,回收整个数组。
delete []的方括号中不需要填数组元素数,系统自知。即使写了,编译器也忽略。
请注意“下标表达式”不必是常量表达式,即它的值不必在编译时确定,可以在运行时确定。
一维: int *a = new int[100]; //开辟一个大小为100的整型数组空间
二维: int **a = new int[5][6]
三维及其以上:依此类推.
一般用法: new 类型 (初值)
扩展资料
c语言是一种结构化语言,它有着清晰的层次,可按照模块的方式对程序进行编写,十分有利于程序的调试,且c语言的处理和表现能力都非常的强大,依靠非常全面的运算符和多样的数据类型,可以轻易完成各种数据结构的构建,通过指针类型更可对内存直接寻址以及对硬件进行直接操作。
因此既能够用于开发系统程序,也可用于开发应用软件。通过对C语言进行研究分析,总结出其主要特点如下:
(1)简洁的语言
C语言包含有各种控制语句仅有9种,关键字也只有32 个,程序的编写要求不严格且多以小写字母为主,对许多不必要的部分进行了精简。
实际上,语句构成与硬件有关联的较少,且C语言本身不提供与硬件相关的输入输出、文件管理等功能,如需此类功能,需要通过配合编译系统所支持的各类库进行编程,故c语言拥有非常简洁的编译系统。
(2)具有结构化的控制语句
C语言是一种结构化的语言,提供的控制语句具有结构化特征,如for语句、if⋯else语句和switch语句等。可以用于实现函数的逻辑控制,方便面向过程的程序设计。
(3)丰富的数据类型
C语言包含的数据类型广泛,不仅包含有传统的字符型、整型、浮点型、数组类型等数据类型,还具有其他编程语言所不具备的数据类型,其中以指针类型数据使用最为灵活,可以通过编程对各种数据结构进行计算。
(4)丰富的运算符
c语言包含34个运算符,它将赋值、括号等均视作运算符来操作,使C程序的表达式类型和运算符类型均非常丰富。
(5)可对物理地址进行直接操作
C语言允许对硬件内存地址进行直接读写,以此可以实现汇编语言的主要功能,并可直接操作硬件。c语言不但具备高级语言所具有的良好特性,又包含了许多低级语言的优势,故在系统软件编程领域有着广泛的应用。
(6)代码具有较好的可移植性
c语言是面向过程的编程语言,用户只需要关注所被解决问题的本身,而不需要花费过多的精力去了解相关硬件,且针对不同的硬件环境。
在用C语言实现相同功能时的代码基本一致,不需或仅需进行少量改动便可完成移植,这就意味着,对于一台计算机编写的C程序可以在另一台计算机上轻松地运行,从而极大的减少了程序移植的工作强度。
(7)可生成的高质量目标代码,高执行效率的程序
与其他高级语言相比,C语言可以生成高质量和高效率的目标代码,故通常应用于对代码质量和执行效率要求较高的嵌入式系统程序的编写。
参考资料来源:百度百科-C语言
热心网友 时间:2022-04-08 01:57
C语言中没有new关键字,C++中才有,例如new operator,经常使用的T *ptr = new T(),分配内存,调用构造函数。
1、调用operator new分配内存,operator new (sizeof(A))
2、调用构造函数生成类对象,A::A()
3、返回相应指针
事实上,分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),否则调用全局::operator new(size_t ),后者由C++默认提供。
扩展资料
C++中的placement new介绍:
1、这种new允许在一块已经分配成功的内存上重新构造对象或对象数组。placement new不用担心内存分配失败,因为它根本不分配内存,它做的唯一一件事情就是调用对象的构造函数
2、用定位放置new操作,既可以在栈(stack)上生成对象,也可以在堆(heap)上生成对象。
3、使用语句A* p=new (mem) A;定位生成对象时,指针p和数组名mem指向同一片存储区。 会自动调用类A的构造函数,但是由于对象的空间不会自动释放(对象实际上是借用别人的空间),所以必须显示的调用类的析构函数。
热心网友 时间:2022-04-08 03:32
new的用法热心网友 时间:2022-04-08 05:23
new是C++中用于动态内存分配的运算符,在C语言中一般使用malloc函数。
new有三种用法:new operator、operator new、placement new
1、new operator:
new operator是最常见的用法,如 Emp* e1 = new Emp;(Emp是一个类) 这里new有两种作用:分配空间、初始化对象(调用了构造函数)
2、operator new:
operator new作用是只分配空间,不调用构造函数,如:Emp* e2 = (Emp*)operator new(sizeof(Emp));
3、placement new:
placement new作用是在已分配好的空间上初始化对象,不分配空间,调用拷贝构造函数,如:new ((void *)e2) Emp(*tb1);
示例代码如下:
// Emp.h
热心网友 时间:2022-04-08 07:31
new有三种使用方式:plain new,nothrow new和placement new。热心网友 时间:2022-04-08 09:56
C语言没有new关键词,应该是C++里面编译设计增加了new new就是动态分配一个堆空间. int *p = new int;//使用指针指向新分配的连续空间 也可以是.int *p = new int[10];//数组热心网友 时间:2022-04-08 12:37
C语言没有new关键词,应该是C++里面编译设计增加了new热心网友 时间:2022-04-08 15:35
DataType* DataName = new DataType热心网友 时间:2022-04-08 18:50
哦哦哦🐮牛*