一些鸡肋般的小容量U盘,用起来嫌容量太少,丢了好像又觉得太可惜。不过现在将它进行一番小改造后,配合我们的电脑,就能得到一台强大的路由器,不仅省了买路由的钱,而且这台路由器在市面上基本买不到! D ...
一些鸡肋般的小容量U盘,用起来嫌容量太少,丢了好像又觉得太可惜。不过现在将它进行一番小改造后,配合我们的电脑,就能得到一台强大的路由器,不仅省了买路由的钱,而且这台路由器在市面上基本买不到!
DD-WRT简介
我们平常使用的每一台路由器设备,都有一个自己的操作系统OS,用于对路由设备进行各种功能设置和管理。其实我们平常所说的升级路由器固件,就是升级刷新路由设备中的操作系统,以使路由设备的功能更加强大。DD-WRT算是一个Linux操作系统,但实质上就是一个第三方的路由器固件。它的路由和网络管理功能非常的强大,被广泛的应用于Linksys WRT54G/GS/GL,或其他基于Broadcom参考设计的802.11g无线路由器上。可以将DD-WRT刷新到所支持的路由器设备上,以便让路由器的功能更加强大。同样的,我们也可以让DD-WRT独立运行于一台电脑上,只要提供相应的硬件,就可以让电脑实现路由器的功能!
硬件准备
只要一个闲置不用的小优盘就足够了,当然这个优盘是无法单独作为路由器使用的。不过它可以作为一个超小巧的服务器,随身携带,配合各种不同的电脑来使用。同时,为整机追加一张网卡或是一个集线器也是必要的。
安装DD-WRT x86版
现在,要为U盘“刷”上路由固件DD-WRT了。一般来说,要刷固件当然得有硬件设备了,这里要刷路由固件,按常理来说,是要将固件刷在路由器上的。不过这里我们是直接将固件刷新到U盘里。DD-WRT的容量只有11MB左右,老旧的U盘大多拥有16MB到128MB左右的空间,绝对够用了,这里我们以在一个64MB的U盘上刷固件为例:
DD-WRT X86版的“dd-wrt-public-vga.image(硬盘/存储卡安装镜像文件)”和“physdiskwrite 0.5.1(路由器IMG写盘工具)”,并解压到同一文件夹中,打开CMD命令提示符窗口,进入程序所在文件夹中。执行如下命令:“physdiskwrite.exe-u dd-wrt_public_vga.image”,程序会自动检测当前系统中的硬盘及移动存储设备,并显示出所有存储设备的序号及相关参数。详细查看“Information for\\.\.PhysicalDrive*”信息,确认老机U盘的序号,其中“PhysicalDrive*”就是指定显示硬盘和U盘设备序号的。一般来说,如果机器上只有一个硬盘,那么接上的U盘序号就为“PhysicalDrive1”;如果新机有两块硬盘,则老机U盘序号就为“PhysicalDrire2”,以此类推。
程序提示“Which disk do you want to write?”,即是要求输入目标U盘的序号,注意不要选错了,否则U盘里所数据都将被覆盖。输入U盘序号后回车,屏幕提示,确认是否正确,是否真的写入,当然选Y,几秒钟后,路由器固件就被写入老机的U盘中了,最后提示“11534336/11534336 bytes written in total”,表示写入成功。
小提示:“physdiskwrite.exe-u dd-wrt_public.vga.image”命令中的“-u”参数,主要是在目标硬盘或U盘大于800MB时使用,如果老盘比较小,低于800MB,或者使用的是CF卡之类的,可以去掉此参数。
设置路由器
将U盘接到电脑上,并为两张网卡都插上网线,开机启动,用刚才刷入DD-WRT的U盘引导系统。启动后,可以看到刚才刷入的DD-WRT接管了系统引导,引导入Linux系统。系统引导后,DD-WRT会自动检测网络环境和硬盘设备,并自动进行网络设置,开始提供路由服务。在电脑上打开浏览器,在浏览器的地址栏中输入路由器的默认IP地址“http://192.168.1.1”,回车后要求输入用户名和密码。登录路由器的默认用户名为“root”,密码为“admin”。登录后默认界面为英文界面,可以调整为中文,点击页面上方的“administrator”,进入页面中后,将“LanguageSelection”设置为“ChineseSimplified”即可。
小提示:这里我们介绍的是直接将DD-WRT剧到U盘上,其实同样可以刷到CF卡、DOM等存储介质上。另外,还可以直接下载“DD-WRT X86”的光盘镜像文件,直接进行刻录,免去安装的过程。光盘镜像还可以直接量产到U盘上,这样就得到了一个带“路由”与存储双功能的U盘了!
现在,要设置ADSL路由器上网了,普通网络接入方式可能是ADSL拨号、固定IP或动态IP,这里以ADSL拨号设置为例,其它的可参照进行。点击页面上方“设置/基本设置”,在“Internet连接类型”中选择“PPOE”,输入ADSL拨号上网的账号及密码。在下方的“网络设置/路由器IP”中,设置本地的DNS域名解析服务器地址,保存设置即可开始提供路由上网功能了。在下方的“网络地址服务器设置(DHCP)”中,可启用DHCP功能。
另外,有许多ADSL MODEM也支持WEB方式管理,它们的IP地址的出厂默认值也常常是“192.168.1.1”,可能因ADSL MODEM与路由器lP地址冲突,造成无法安装和使用路由器。因此,我们必须更改路由器的局域网IP地址,在刚才的“网络设置/路由器IP”中,将“本地IP地址”修改为其它IP地址,例如“192.168.1.254”。当设置生效后,路由器的局域网IP地址即变为“192.168.1.254”,以后设置路由器,就要通过IP连接访问了。
路由上网
在其它电脑上,将网关设置为主机路由器IP地址,设置DNS或使用DHCP,即可自由的上网了。U盘路由器的功能非常强大,可以支持3322ORG的DDNS,支持花生壳的DDNS,可以利用QoS、限速、限连接数等设置来优化带宽,并且支持VPN的PPTP客户端和PPTP服务端设置,可以提供VPN接入,还可以通过配置轻松管理各种网络应用,例如彻底封杀BT、电驴、迅雷等。没想到一个面临淘汰的老U盘还能干这么多事情吧?
本篇文章来源于 黑基网-中国最大的网络安全站点 原文链接:http://www.hackbase.com/tech/2012-01-31/65857.html


1 //双链表插入函数
2 //把一个新值插入到一个双链表中,rootp是一个指向根节点的指针
3 //value是需要插入的新值
4 //返回值:如果链表原先已经存在这个值,函数返回0
5 //如果为新值分配内存失败,函数返回-1
6 //如果新值成功地插入到链表中,函数返回1
7 #include<stdlib.h>
8 #include<stdio.h>
9 #include"doubly_liked_list_node.h"
10
11 int dll_insert(register Node *rootp,int value)
12 {
13 register Node *this;
14 register Node *Next;
15 register Node *newnode;
16 //查看value是否已经存在于链表中,如果是就返回
17 //否则,为新值创建一个新节点(“newnode"将指向它)
18 //"this"将指向应该在新节点之前的那个节点
19 //"next"将指向应该在新节点之后的那个节点
20
21 for(this=rootp;(next=this->fwd)!=NULL;this=next)
22 {
23 if(next->value==value)
24 return 0;
25 if(next->value>value)
26 break;
27 }
28 newnode=(Node *)malloc(sizeof(Node));
29 if(newnode==NULL)
30 return -1;
31 newnode->value=value;
32 //把新节点添加到链表中
33 newnode->fwd=next;
34 this->fwd=newnode;
35
36 if(this!=rootp)
37 newnode->bwd=this;
38 else
39 newnode->bwd=NULL;
40
41 if(next!=NULL)
42 next->bwd=newnode;
43 else
44 rootp->bwd=newnode;
45 return 1;
46 }
1 //插入到一个有序单链表。函数的参数是一个指向
2 //链表第一个节点的指针,以及一个需要插入的新值
3 #include<stdio.h>
4 #include<stdlib.h>
5 #include"sll_node.h"
6
7 #define FALSE 0
8 #define TRUE 1
9 sill_insert(register Node **linkp,int new_value)
10 {
11 register Node *current;
12 register Node *new;
13 //寻找正确的插入位置,方法是按序访问链表,直到到达一个其值大于或等于新值的节点。
14 while((current=*linkp)!=NULL && current->value<new_value)
15 linkp=¤t->link;
16 //为新节点分配内存,并把新值存储到新节点中,如果内存分配失败,函数返回FALSE
17 new=(Node *)malloc(sizeof(Node));
18 if(new==NULL)
19 return FALSE;
20 new->value=new_value;
21 //在链表中插入新节点,并返回TRUE
22 new->link=current;
23 *Linkp=new;
24 return TRUE;
25 }
在函数原型中,列出了函数期望接受的参数,但原型只能显示固定数目的参数。让一个函数在不同的时候接受不同数目的参数是不是可以呢?答案是肯定的,但存在一些限制。
考虑一个计算一系列值的平均值的函数。如果这些值存储于数组中,这个任务就太简单了,所以为了让问题变得更有趣一些,我们假定它们并不存储于数组中。先来看一个计较差的,也是不太稳定的一个解决方案:
1 //计算指定数目的值的平均值(差的方案)
2 float average(int n_value,int v1,int v2,int v3,int v4,int v5)
3 {
4 float sum=v1;
5 if(n_values>=2)
6 sum+=v2;
7 if(n_values>=3)
8 sum+=v3;
9 if(n_values>=4)
10 sum+=v4;
11 if(n_values>=5)
12 sum+=v5;
13 return sum/n_values;
14 }
这个函数存在几个问题:首先,它不对参数的数量进行测试,无法检测参数过多的这种情况。不过这个问题容易解决,简单加上测试就是了。其次,函数无法处理超过5个的值。要解决这个问题,你只有在已经很臃肿的代码中再增加一些类似的代码。另外还存在一个更为严重的问题:就是实参数与形参的对应不一定准确。
stdarg宏
可变参数列表是通过宏来实现的,这些宏定义stdarg.h头文件,它是标准库的一部分。这个头文件声明了一个类型va_list和
三个宏——va_start、va_arg、va_end。我们可以声明一个类型为va_list的变量,与这几个宏配合使用,访问参数的值。
下面的程序使用三个宏正确的完成计算指定数目的值的平均值的任务。
1 //指定数量的值的平均值
2 #include<stdarg.h>
3 float(int values,...)
4 {
5 va_list var_arg;
6 int count;
7 float sum=0;
8
9 var_start(var_arg,n_values);//准备访问可变参数
10 for(count=0;count<n_values;count+=1) //添加取自可变参数表的值
11 {
12 sum+=var_varg(var_arg,int);
13 }
14 var_end(var_arg); //完成处理可变参数
15 return sum/n_values;
16 }



尾部递归是一种编程技巧。递归函数是指一些会在函数内调用自己的函数,如果在递归函数中,递归调用返回的结果总被直接返回,则称为尾部递归。尾部递归的函数有助将算法转化成函数编程语言,而且从编译器角度来说,亦容易优化成为普通循环。这是因为从电脑的基本面来说,所有的循环都是利用重复移跳到代码的开头来实现的。如果有尾部归递,就只需要叠套一个 stack,因为电脑只需要将函数的 parameter 改变再重新跑一次。例如,可以把上一次函数的输出当作下一次的输入。然而,利用尾部递归最主要的目的,是要优化。例如在 Scheme 语言中,明确规定必须针对尾部递归作优化。[1][2]可见尾部递归的作用,是非常依赖于 implementation 的。
举例说明,最常用作解说递归函数的形式是计算阶乘,如下例
1 function factorial(x)
2 if (x = 1)
3 return 1
4 else
5 return x * factorial(x-1)
6 end if
7 end function
上例中,factorial函数调用了自己,所以这是一递归函数。而且,只在返回指令才调用递归函数,所以这亦是尾部递归(错误,这不是尾部递归,
因为factorial(x-1)算完后还要乘以x)。以上函数很容易可以转化为循环版本:
1 function factorial(x)
2 returnvalue = 1
3 while (x != 1)
4 returnvalue = x * returnvalue
5 x = x - 1
6 end while
7 return returnvalue
8 end function
又例如我们可以替 factorial 设立一个 wrapper function:
1 function fact(i, acc)
2 if (i == 1)
3 return acc
4 return fact(i-1, acc*i)
5 end function
6
7 function factorial(x)
8 return fact(x, 1)
9 end function
但是,下列计算斐波那契数列的函数不是尾部递归:
1 function fibonnacci(x)
2 if (x = 1)
3 return 1
4 else if (x = 2)
5 return 1
6 else
7 return fibonnacci(x-1) + fibonnacci(x-2)
8 end if
9 end function
因为上述函数中,调用了自己两次,亦即一函数的运行会导致两个函数的运行,故此不可能在叫呼的同时退出本来的函数,
换句话说,调用自己的时后并未到达运行的尾部。
对于递归函数的使用,人们所关心的一个问题是栈空间的增长。确实,随着被调用次数的增加,某些种类的递归函数会线性地
增加栈空间的使用——不过,有一类函数,即尾部递归函数,不管递归有多深,栈的大小都保持不变。
函数所做的最后一件事情是一个函数调用(递归的或者非递归的),这被称为尾部调用。使用尾部调用的递归
称为尾部递归。
在尾部调用之后除去栈结构的方法称为尾部调用优化。尾部调用优化就是要在尾部进行函数调用时使用下一个
栈结构覆盖当前的栈结构,同时保持原来的返回地址。
尾部递归和迭代的区别: 这个确实很难区分。迭代中不一定有递归,但递归中一定有迭代,可说递归是迭代的一部分。
递归是简单的重复调用自己,而迭代则必须有新值出现,而且这个新值是由旧值得来的。
递归就是一个函数调用自己。尾递归也是递归(一个函数在最末尾的地方调用自己),只不过编译器可能会做优化。一旦优化成功,除了性能的提升之外,还可以避免堆栈溢出的情形。即使无限递归也不会造成溢出。
迭代应该是指多次计算,每次计算都更加的接近最终结果,因此,计算的次数增多了就能得到足够精确的近似值。迭代一般用循环来做,但没有规定不能用其它方法来做。比如,你可以用递归来做迭代。
递归是分割子问题 迭代是把用子问题推出大问题,就像算法里面的 搜索 和动态规划一样。
消除尾部递归(数据结构知识:排序二叉树的内容)
1 TreeNode *TreeSearch(TreeNode *root,KeyType target)
2 {
3 while(root && target != root->entry.key)
4 if(target < root->entry.key)
5 root=root->left;
6 else
7 root=root->right;
8 return root;
9 }消除尾部递归
关于c语言部分函数的一些总结和注意事项:

递归是一种强有力的技巧,但是和其他技巧一样,它也可能被误用。这里就有一个例子。阶乘的定义往往就是以递归的形式描述的。factorial(n)=1,n<=0; factorial(n)=n*factorial(n-1),n>0;
这个定义同时具备了递归所需要的两个特性:1、存在限制条件,当符合这个条件时递归便不再继续;2、每次递归调用之后越来越接近这个限制条件。用这种方式定义阶乘往往引导人们使用递归来实现阶乘函数。程序如下所示:
1 //用递归方法计算n的阶乘
2
3 long factorial(int n)
4
5 {
6
7 if(n<=0)
8
9 return 1;
10
11 else
12
13 return n*factorial(n-1);
14
15 }
但它不是递归的良好用法。为什么呢?因为递归函数调用将涉及一些运行时开销——参数必须压到堆栈中,为局部变量分配内存空间(所有递归均如此,并非特指这个例子),寄存器的值必须保存等。当递归函数的每次调用返回时,上述这些操作必须还原,恢复成原来的样子。所以,基于这些开销,对于这个程序而言,它并没有简化问题的解决方案。下面这个函数是尾部递归的一个例子,由于函数在递归调用返回后不再执行任何任务,所以尾部递归可以很方便的转换成一个简单循环,完成相同的任务 ,程序效率更为有效。
1 //用迭代的方法计算n的阶乘
2
3 long factorial(int n)
4
5 {
6
7 int result=1;
8
9 while(n>1)
10
11 {
12
13 result*=n;
14
15 n-=1;
16
17 }
18
19 return result;
20
21 }
许多问题是以递归的形式进行解释的,这只是因为它比非递归形式更为清晰。但是,这些问题的迭代往往比递归实现效率更高,代码的可读性可能稍差一些。当一个问题相当复杂,难以用迭代形式实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。当你使用递归时候,一定要考虑它所带来的好处与它的代价之间的比较。
1 //以下以一个斐波那契数列的例子说明:
2
3 //----------------------------------
4
5 //1.迭代方法:
6
7 public class Fab_iterate
8 {
9 public static void main(String[] args)
10 {
11 System.out.println("结果是:"+Fab(8)); //求第八个位置的数
12 }
13 public static long Fab(int index) //斐波那契数列
14 {
15 if(index == 1 || index == 2)
16 {
17 return 1;
18 }
19 else
20 {
21 long f1 = 1L;
22 long f2 = 1L;
23 long f3 = 0;
24 for(int i = 0;i < index-2;i ++) //迭代求值
25 {
26 f3 = f1 + f2;
27 f1 = f2;
28 f2 = f3;
29 }
30 return f3;
31 }
32 }
33 }
34
35
36 //2.递归方法:
37
38 public class Fab_recursion
39 {
40 public static void main(String[] args)
41 {
42 System.out.println("结果是:"+fab(8)); //求第八个位置的数
43 }
44 public static long fab(int index) //斐波那契数列
45 {
46 if(index == 1 || index == 2)
47 {
48 return 1;
49 }
50 else
51 {
52 return fab(index-1)+fab(index-2); //递归求值
53 }
54 }
55 }
/*下面说说递归和迭代在算法上的区别(仅供参考);
所谓递归,简而言之就是应用程序自身调用自身,以实现层次数据结构的查询和访问。 递归的使用可以使代码更简洁清晰,可读性更好(对于初学者到不见得),但由于递归需要系统堆栈,所以空间消耗要比非递归代码要大很多,而且,如果递归深度太大,可能系统资源会不够用。 往往有这样的观点:能不用递归就不用递归,递归都可以用迭代来代替。 诚然,在理论上,递归和迭代在时间复杂度方面是等价的(在不考虑函数调用开销和函数调用产生的堆栈开销),但实际上递归确实效率比迭代低,既然这样,递归没有任何优势,那么是不是就,没有使用递归的必要了,那递归的存在有何意义呢? 万物的存在是需要时间的检验的,递归没有被历史所埋没,即有存在的理由。从理论上说,所有的递归函数都可以转换为迭代函数,反之亦然,然而代价通常都是比较高的。但从算法结构来说,递归声明的结构并不总能够转换为迭代结构,原因在于结构的引申本身属于递归的概念,用迭代的方法在设计初期根本无法实现,这就像动多态的东西并不总是可以用静多态的方法实现一样。这也是为什么在结构设计时,通常采用递归的方式而不是采用迭代的方式的原因,一个极典型的例子类似于链表,使用递归定义及其简单,但对于内存定义(数组方式)其定义及调用处理说明就变得很晦涩,尤其是在遇到环链、图、网格等问题时,使用迭代方式从描述到实现上都变得不现实。 因而可以从实际上说,所有的迭代可以转换为递归,但递归不一定可以转换为迭代。 采用递归算法需要的前提条件是,当且仅当一个存在预期的收敛时,才可采用递归算法,否则,就不能使用递归算法。 递归其实是方便了程序员难为了机器,递归可以通过数学公式很方便的转换为程序。其优点就是易理解,容易编程。但递归是用栈机制实现的,每深入一层,都要占去一块栈数据区域,对嵌套层数深的一些算法,递归会力不从心,空间上会以内存崩溃而告终,而且递归也带来了大量的函数调用,这也有许多额外的时间开销。所以在深度大时,它的时空性就不好了。 而迭代虽然效率高,运行时间只因循环次数增加而增加,没什么额外开销,空间上也没有什么增加,但缺点就是不容易理解,编写复杂问题时困难。 因而,“能不用递归就不用递归,递归都可以用迭代来代替”这样的理解,还是辩证的来看待,不可一棍子打死。*/
1 //输入一个整型值(无符号),把它转换成字符并打印它,前导0被删除
2 #include<stdio.h>
3 #include<stdlib.h>
4 void binary_to_ascii(unsigned int value);
5 int main()
6 {
7 unsigned int i;
8 printf("please input a value:\n");
9 scanf("%d",&i);
10 binary_to_ascii(i);
11 system("pause");
12 return 0;
13 }
14 void binary_to_ascii(unsigned int value)
15 {
16 unsigned int quotient;
17 quotient=value/10;
18 if(quotient!=0)
19 binary_to_ascii(quotient);
20 putchar(value%10+'0');
21 }
此处是递归应用的一个例子,对于数值转换成字符,递归是一个很好的办法。
1 //输入需要转换的数和进制数,将该数按该进制转换成对应进制数,输出结果。
2 #include <stdio.h>
3 #include<stdlib.h>
4 int Conversion(int number,int system,char output[])//要转换的数据、进制、输出数据
5 {
6 int Quotient,Balance,i=0;//商,余数和标记
7 Quotient = number;
8
9 do
10 {
11 Balance = Quotient % system;
12 output[i++]=Balance;
13 } while (Quotient/=system);
14
15 for(int j=0;j<i;j++) // 将十进制数字转换为char型数值
16 if(output[j]>9)
17 output[j]=output[j]+55;
18 else output[j]+=48;
19
20 for(int k=0;k<i/2;k++) // 字符串取反
21 {
22 char tmp;
23 tmp=output[k];
24 output[k]=output[i-k-1];
25 output[i-k-1]=tmp;
26 }
27 return i;
28 }
29
30 int main(void)
31 {
32 char rec[1024]={0};
33 int num, sys;
34
35 printf("任意转换位数测试程序\t\t\tby candy\n");
36 printf("请输入一个数和要转换成的进制数:\t\t(如 12345 16)\n");
37 scanf("%d%d", &num,&sys);
38 int res = Conversion(num,sys,rec);
39
40 printf("结果为:\t%s\n",rec);
41 system("pause");
42 return 0;
43 }
'0'+1='1'
'0'+2='2' .............. '0'+9='9' 字符'0'对应48,48=48+0 字符'A对应十进制65,65=55+10(A)
是不是对指针的任何运算都是合法的呢?答案是它可以执行某些运算,但并非所有的运算都合法。除了加法运算之外,你还可以对指针执行一些其他运算,但并不是很多。
指针加上一个整数的结果是另一个指针。问题是,它指向哪里?如果你将一个字符指针加1,运算结果产生的指针指向内存中的下一个字符。float占据的内存空间不止1个字节,如果你将一个指向float的指针加1,将会发生什么?它会不会指向该float值内部的某个字节呢?
答案是否定的。当一个指针和一个整数量进行算术运算时,整数在执行加法运算前始终会根据合适的大小进行调整。这个“合适的大小”就是指针所指向类型的大小,“调整”就是把整数值和“合适的大小”相乘。为了更好的说明,试想在某台机器上,float占据4个字节。在计算float型指针加3的表达式时候,这个3将根据float类型的大小(此例中为4)进行调整(相乘),这样实际上加到指针上的整型值为12。
把3与指针相加使指针的值增加3个float的大小,而不是3个字节。这个行为较之获得一个指向一个float值内部某个位置的指针更为合理。下表包含了一些加法运算的例子。调整的美感在于指针算法并不依赖于指针的类型。换句话说,如果p是个指向float的指针,那么p+1就指向下一个float,其他类型也是如此。

1.算术运算
C的指针的算术运算只局限于两种形式。第一种形式是: 指针+-整数 标准定义这种形式只能用于指向数组中某个元素的指针,如图所示:

并且这类表达式的结果类型也是指针。这种形式也适用于使用malloc函数动态分配获得的内存。
对一个指针加1使它指向数组中的下一个元素,加5使它向右移动5个元素的位置,依次类推。把一个指针减去3使它向左移动3个元素的位置。
第二种类型的指针运算具有如下的形式: 指针—指针
只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针,如下所示:

两个指针相减的结果的类型是ptrdiff_t,它是一种有符合整数类型。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位),因为减法运算的结果将除以数组元素类型的长度。

如果两个指针所指向的不是同一个数组中的元素,那么它们之间相减的结果是未定义的。程序员无从知道两个数组在内存中的相对位置,如果不知道这一点,两个指针之间的距离就毫无意义。
2.关系运算: < <= > >= 不过前提是它们都指向同一个数组中的元素。根据你所使用的操作符,比较表达式将告诉你哪个指针指向数组中更前或更后的元素。标准并未定义如果两个任意的指针进行比较会产生什么结果。
2011-11-26 10:19:13