分类 技术志 下的文章

'寓教于乐'学Ruby

在2005年初曾经写过一篇文章叫'结识Ruby',当时的确是刚刚结识Ruby这种语言,好奇心使然,遗憾的是之后没有坚持学习下去,也就是在这一年Ruby获得了很大的发展,特别是Ruby On Rails的出现让Ruby一下成为新兴语言的代表,甚至有人预言Ruby将会成为Java的替代者成为下一代主流语言。无论如何,Ruby的日益被广大开发人员所接受是个不争的现实,就连Martin Fowler到中国讲'敏捷'时都向中国的开发人员推荐Ruby。大师都开始学习和使用Ruby了,我们还等什么呢?有空儿的时候就多学学吧。

单纯的从学习Ruby语法的角度来看,有一个交互式学习的站点很是不错-TryRuby,感觉通过TryRuby学习Ruby基础语法就像是在玩类似'仙剑奇侠传'那种RPG游戏,有Tutorial一步一步指导你'过关',而在'过关'的过程中,Ruby相关的基础就印在你的大脑中了。

这里我把在'TryRuby'的过关过程记录下来,当然Tutorial中的一些英文说明这里被我粗略翻译为中文了:

try typing some math. Like: 2 + 6
>> 2 + 6
=> 8

Ruby能识别数字和数学符号,try some other math:
>> 4 * 10
=> 40

>> 5 – 12
=> -7

>> 40 / 10
=> 4

计算机处理数学方便快捷,我们继续,我们来看看倒转你的名字,象这样"Jimmy"输入你的名字:
>> "Tony"
=> "Tony"

一个字符串是一个计算机能够处理的字符集合,引号标识字符串的首尾,如果想翻转你的名字,敲入"Jimmy".reverse
>> "Tony".reverse
=> "ynoT"

让我们看看你的名字中到底有多少个字母:"Jimmy".length
>> "Tony".length
=> 4

看这个,我们将你的名字乘以5. "Jimmy" * 5
>> "Tony" * 5
=> "TonyTonyTonyTonyTony"

我们来看看第一分钟学到了什么。
(1) Numbers and strings are Ruby's math and text objects.
(2) Methods. You've used English-language methods like reverse and symbolic methods like * (the multiplication method.)

Methods are action!

让我们做些uncomfortable的事情,尝试翻转一个数字:40.reverse

>> 40.reverse
=> NoMethodError: undefined method `reverse' for 40:Fixnum                        
 from (irb):10                                                                 
 from :0 

你不能翻转一个数字,翻转一个数字没有意义,Ruby抛出一条错误信息,它在告诉你数字没有reverse方法。

也许你可以先将该数字转换成字符串:40.to_s.reverse

>> 40.to_s.reverse
=> "04"

数字和字符串不同。你可以在任何object上使用method,一些methods只能用于特定的type上,但是你可以使用Ruby's "to" method在各种类型之间作转换。

to_s converts things to strings.
to_i converts things to integers (numbers.)
to_a converts things to arrays.

Arrays是什么?它们是lists,敲入:[]
>> []
=> []

那是一个空list,lists按顺序存储things,这里有一个list,彩票号码:[12, 47, 35]
>> [12, 47, 35]
=> [12, 47, 35]

找出彩票号码中的最大值:[12, 47, 35].max
>> [12, 47, 35].max
=> 47

总是重复写[12, 47, 35]这么一个大长串比较麻烦,我们将这些彩票号码放入一个’ticket’里吧,像这样:ticket = [12, 47, 35]
>> ticket = [12, 47, 35]
=> [12, 47, 35]

现在敲入ticket:
>> ticket
=> [12, 47, 35]

彩票号码被挤入一个变量ticket中,我们来给彩票号码排个序,怎么做呢:ticket.sort!
>> ticket.sort!
=> [12, 35, 47]

现在你拥有了一个已经排好序的list了,而且变量ticket被改变了。我们来看看第二分钟我们都学到了什么东西:
(1) Errors. If you try to reverse a number or do anything fishy, Ruby will skip the prompt and tell you so.
(2) Arrays are lists for storing things in order.
(3) Variables save a thing and give it a name. You used the equals sign to do this. Like: ticket = [14, 37, 18].

打印一首诗吧
>> print poem
My toast has flown from my hand                                                
And my toast has gone to the moon.                                             
But when I saw it on television,                                               
Planting our flag on Halley's comet,                                           
More still did I want to eat it.                                               
=> nil                           

试试这样一个操作:poem['toast'] = 'honeydew',之后再print poem看看我们的新诗:
>> poem['toast'] = 'honeydew'                                                  
=> "honeydew"

>> print poem
My honeydew has flown from my hand                                             
And my toast has gone to the moon.                                             
But when I saw it on television,                                               
Planting our flag on Halley's comet,                                           
More still did I want to eat it.                                               
=> nil   

[]意思是"我要找…",这里我们要在poem中找'toast'并将之替换成'honeydew',这里有一个问题,如果我们将整首诗翻转会怎样呢:poem.reverse.

>> poem.reverse                                                                
=> "\n.ti tae ot tnaw I did llits eroM\n,temoc s'yellaH no galf ruo gnitnalP\n,n
oisivelet no ti was I nehw tuB\n.noom eht ot enog sah tsaot ym dnA\ndnah ym morf
 nwolf sah wedyenoh yM"

可以肯定的是整首诗都被一个字母一个字母的翻转了。其实我只是想按行翻转,即最后一行变成第一行,第一行变成最后一行,而不是像现在这样翻转,试试poem.to_a.reverse.
>> poem.to_a.reverse                                                           
=> ["More still did I want to eat it.\n", "Planting our flag on Halley's comet,\
n", "But when I saw it on television,\n", "And my toast has gone to the moon.\n"
, "My honeydew has flown from my hand\n"]   

发生什么了?发生两件事:你将poem转换成array了,当Ruby将字符串转换为array时,它以每一行为一个单位,这就是我们得到上面结果的原因,因为array被翻转了。我们再来看一个method吧:print poem.to_a.reverse.join

>> print poem.to_a.reverse.join                                                
More still did I want to eat it.                                               
Planting our flag on Halley's comet,                                           
But when I saw it on television,                                               
And my toast has gone to the moon.                                             
My honeydew has flown from my hand                                             
=> nil  

join方法将已经按行翻转的list重新组合成一个字符串,当然你也可以使用to_s方法。

Review Time:
(1) Exclamations. Methods may have exclamations (and also question marks) in their name. No big deal. Try: poem.include? "my

hand"
(2) Square brackets. Target and find things. Search and replace.
(3) Chaining methods lets you get a lot more done. Break up a poem, reverse it, reassemble it: poem.to_a.reverse.join

OK,如果你准备好继续了,敲入books = {}
>> books = {}                                                                  
=> {}

你已经建立起一个空的Hash表了,现在我们准备在这个Hash中建立一个book rating系统:

:splendid -> a masterpiece.
:quite_good -> enjoyed, sure, yes.
:mediocre -> equal parts great and terrible.
:quite_not_good -> notably bad.
:abyssmal -> steaming wreck.

如果你要rate一本书,你可以像这样做:books["Gravity's Rainbow"] = :splendid,将书目放入[]中,将rating放在等号后面。
>> books["Gravity's Rainbow"] = :splendid                                       
=> :splendid    

继续填充该Hash,这些级别为the ratings are: :splendid, :quite_good, :mediocre, :quite_not_good, and :abyssmal. 它们不是strings,把一个:放在一个单词的前面,你就将得到一个符号,在内存空间占用上,符号要比string少得多. 符号在内存中只存储一次,但是可以多次使用,这点string是做不到的。如果你已经输入了3、4本书了,你可以敲入books.length来得到书的个数。

>> books["Gravity's Rainbow1"] = :quite_good                                    
=> :quite_good                                                                  
                                                                                
>> books["Gravity's Rainbow2"] = :quite_good                                    
=> :quite_good                                                                  
                                                                                
>> books["Gravity's Rainbow3"] = :quite_not_good                                
=> :quite_not_good                                                              
                                                                                
>> books.length                                                                 
=> 4                      

如果你想查找某本书的rating,只需要将书名放入[]之间:
>> books["Gravity's Rainbow"]                                                   
=> :splendid                                                                    

又到总结时间了,目前你学会了:
(1) Hashes. The little dictionary with the curly pages: {}.
(2) Symbols. Tiny, efficient code words with a colon: :splendid.

我们的'寓教于乐'就到此为止了,其实TryRuby后面还有6节内容,因为篇幅太长,这里仅是抛砖引玉,剩下的大家可以自己到TryRuby去感受去学习。

字符串拷贝密码

在近期的一次工作交接中,在我的代码中发现了很多’安全隐患’,主要是以’字符串拷贝’为主。这种安全漏洞在C编程中是较为常见的,防范起来也较为容易,这里我们就来一起探索一下’字符串拷贝’的’密码’。

在正常情况下,我们在考量目的缓冲区大小时都会以源缓冲区大小作为依据的,一般会适当的比源缓冲区多出一些空间,其中一种’居中’状况:即sizeof(dstbuf) = strlen(srcbuf) + 1。

当sizeof(dstbuf) > strlen(srcbuf) + 1时,使用strcpy, strncpy都不会出现问题(缓冲区溢出问题);
[Ex1.]
int main() {
        /*
         * 测试char *strcpy(char *s1, const char *s2);
         */
        char    dstbuf1[10];
        char    *srcbuf1 = "Hello";

        memset(dstbuf1, 0, sizeof(dstbuf1));
        strcpy(dstbuf1, srcbuf1);

        printf("%s\n", dstbuf1);        /* 输出结果:Hello */

        /*
         * 测试char *strncpy(char *s1, const char *s2, size_t n);
         */
        char    dstbuf2[10];
        char    *srcbuf2 = "Hello";

        memset(dstbuf2, 0, sizeof(dstbuf2));
        strncpy(dstbuf2, srcbuf2, sizeof(dstbuf2)-1);

        printf("%s\n", dstbuf2);        /* 输出结果:Hello */
}

当sizeof(dstbuf) < strlen(srcbuf) + 1时,当然这种情况就是异常情况,是否能很好的处理这样的异常情况恰恰体现了你的程序的健壮性好坏。我们分别讨论一下使用strcpy、strncpy和strlcpy在这种情况下出现的问题:

(1) 使用strcpy
使用strcpy会出现什么问题呢?strcpy会将srcbuf中的所有字符(直到并包括结尾0)拷贝到dstbuf中,即使sizeof(dstbuf)不够大。这样会导致dstbuf缓冲区溢出,看下面例子:

[Ex2.]
int main() {
        /*
         * 测试char *strcpy(char *s1, const char *s2);
         */
        char    dstbuf1[6];
        char    *srcbuf1 = "HelloWorld";

        memset(dstbuf1, 0, sizeof(dstbuf1));
        strcpy(dstbuf1, srcbuf1);

        printf("%s\n", dstbuf1);        /* 缓冲区溢出,输出结果:HelloWorld */
}
strcpy将’HelloWorld’拷贝到了dstbuf中,由于strcpy不检查目的缓冲区大小,所以即使目的缓冲区dstbuf大小不够,strcpy也继续拷贝,直至碰到源缓冲区的结尾0,strcpy同样不会放过源缓冲区的结尾0,该结尾0也被拷贝到目的缓冲区中,这样我们在输出dstbuf时,printf将结尾0之前的字符悉数打印出来。

(2) 使用strncpy
使用strncpy会出现什么问题呢?如果你这样使用(一般都应该这样用)strncpy(dstbuf, srcbuf, sizeof(dstbuf) – 1),则不会出现问题,最后的sizeof(dstbuf)-1就是为了在dstbuf的结尾留出’结尾0′的空间。但是如果你这样用:strncpy(dstbuf, srcbuf, n), n > sizeof(dstbuf) – 1, 则由于目前srcbuf中的数量已经大于dstbuf的长度,一旦n也大于sizeof(dstbuf)-1,那么dstbuf的最终结果就是其没有结尾0,你printf(dstbuf)会得到结尾为乱码的字符串。看下面例子:
[Ex3.]
int main() {
        /*
         * 测试char *strncpy(char *s1, const char *s2, size_t n);
         */
        char    dstbuf2[6];
        char    *srcbuf2 = "HelloWorld";

        memset(dstbuf2, 0, sizeof(dstbuf2));
        strncpy(dstbuf2, srcbuf2, sizeof(dstbuf2));

        printf("%s\n", dstbuf2);        /* dstbuf2的结尾0被覆盖,输出结果:HelloW鯰 */
}

(3) 使用strlcpy
strlcpy会出现什么情况呢?首先strlcpy并不是标准C库函数,不过在大部分Unix/Linux平台下都提供这个接口,它会在适当的时候截断srcbuf并保证dstbuf最后结尾0不被覆盖,保证缓冲区不溢出,也就是说使用strlcpy是安全的,但是并不一定是你期望的结果。

[Ex4.]
int main() {
        /*
         * 测试size_t strlcpy(char *dst, const char *src, size_t dstsize);
         */
        char    dstbuf3[6];
        char    *srcbuf3 = "HelloWorld";

        memset(dstbuf3, 0, sizeof(dstbuf3));
        strncpy(dstbuf3, srcbuf3, sizeof(dstbuf3));
 
       /*
        * strlcpy截断srcbuf, 将srcbuf的前sizeof(dstbuf3)-1个字符拷贝到dstbuf3中,
        * 并在dstbuf3的结尾处添加结尾0,输出结果:Hello
        */
        printf("%s\n", dstbuf3);       
}

通过上面的几个例子的讲解,相信你已经能够找到’字符串拷贝’的’密码’了,拥有这一密码你的程序将会变得更加健壮。^_^

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats