地址端口检测

C/C++, Linux, Unix No Comments »

       有时候我们需要对某些服务器是否可用进行判断,一般可以通过telnet ip  port  的方式来实现,这时如果返回的是Connection refused或是Escape character is ‘^],则表示这个端口是开放的,不过前者拒绝该请求,而后者则可以正常联通。如果我们需要一次性检测一批端口是否开放,这时如果一条条输telnet命令可能就有些累了。下面说一下如何用脚本来实现。

      首先我们需要一个可以探测端口的程序,这个我是用c语言来实现的(参考了网上的代码,如果谁发现可用的命令欢迎告诉我哦),通过编译后就可以运行,不过它只实现扫描一个端口的功能。后面会再介绍个shell脚本来实现多次调用该程序来实现扫描多个地址的目的。

      具体c语言的代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <time.h>
#include <arpa/inet.h>

/* 该代码可以用来测试某个端口是否联通,使用方法 a.out 128.32.171.33 23
*  然后查看$?的值,如果是0,则表示该端口可以联通,否则不可以联通
*  可以和shell脚本结合使用
*  文件名:detect_ip_port.c
*/

int main(int argc, char *argv[])
{
 int fd, retval;
 struct sockaddr_in addr;
 struct timeval timeo = {3, 0};
 char buf[1024];
 int i;
 socklen_t len;
 fd_set set;

 fd = socket(AF_INET, SOCK_STREAM, 0);
 addr.sin_family = AF_INET;
 addr.sin_addr.s_addr = inet_addr(argv[1]);
 addr.sin_port = htons(atoi(argv[2]));
 if ((i=connect(fd, (struct sockaddr*)&addr, sizeof(addr))) == 0)
 {
  close(fd);
  perror("connected success:");
  return 0;
 }
 perror("connected error:");
 close(fd);
 return 1;
}

可以通过执行shell命令,来生成可执行文件:

gcc –o detect_one_addr  detect_ip_port.c

为了实现一次扫描多个地址的目的,我首先采用一个txt文件来保存需要扫描的ip 和port等信息,具体txt的内容大致如下:

192.168.0.1 8000 01 服务器1
192.168.0.2 8000 02 服务器2
192.168.0.3 8000 03 服务器3
192.168.0.4 8000 04 服务器4
192.168.0.5 8000 05 服务器5

其中每一行中包括ip  port  编号  名称,每个字段用空格或tab分隔。在此我们假设该文件名称为:servers.txt

接下来使用shell脚本来实现扫描多个地址的目的:

#!/usr/bin/sh

# file name : detect_all.sh

detect_one_addr()
{
 ip=`echo $line |  awk '{print $1}'`
 port=`echo $line |  awk '{print $2}'`
 entr_no=`echo $line |  awk '{print $3}'`
 entr_name=`echo $line |  awk '{print $4}'`

 ./detect_ip_port $ip $port
 if [ $? == 0 ] ; then
 	echo "$entr_no  $entr_name  is OK"
 else
 	echo "$entr_no $entr_name is Failed"
 fi
}

testfile="servers.txt"
while read -r line
do
    detect_one_addr $line
done < $testfile

然后在命令行中执行detect_all.sh即可实现该扫描的功能。你可能会发现如果某个端口不开放,该程序需要过好长时间才会超时,这个超时时间是由系统来决定的,具体是TCP连接的超时时间,一般不建议修改。我也经常遇到连接一个端口经过好久最后联通的情况,因为首次连接需要进行ARP、或是路由器会帮你建立连接(因为这个连接好久不用可能已经休息了,呵呵),所以耐心等待就好。

PS:经过上述程序可以看出,我们可以通过shell或是其他高级语言,如ruby,perl等来做高级的工作,然后我们用c/c++、java等实现底层的功能,这样可以更有效率的解决问题,充分发挥各个语言的优点

递归与回溯算法

C/C++, 日志 No Comments »

      长久以来一直对递归一知半解。最近看面试方面的书,在数据结构中有很多算法是和递归有关的,另外一些经典问题:打靶问题、8皇后问题、0-1背包问题也和递归有关,在有些算法中递归甚至是必不可少的,因此很有必要把递归算法再重新学一下。

      首先说一下递归和迭代的区别。迭代是反复的意思,有时候也指重复执行和反复执行。迭代一般是通过循环执行一组指令,并且在每次执行后都会从变量的原值推出它的新值。迭代往往需要一个迭代表达式,如f(n+1)=f(n)+f(n-1)。大家可以看出递归中的经典案例N!也可以通过迭代来做,而且更高效(因为迭代相比递归一般要高效很多)。

      下面就来谈递归。递归的基本思想是把规模大的问题转化为规模小的相似的子问题来解决。在函数实现时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况。另外这个解决问题的函数必须有明显的结束条件,这样就不会产生无限递归的情况了。文[1]中对于递归讲解比较细致,大家可以参考。下面展示两段代码[1]:

返回一个二叉树的深度:


int depth(Tree t){
      if(!t) return 0; 

    else { 

        int a=depth(t.right); 

        int b=depth(t.left); 

        return (a>b)?(a+1):(b+1); 

    } 

}

判断一个二叉树是否平衡:


int isB(Tree t){
      if(!t) return 0; 

    int left=isB(t.left); 

    int right=isB(t.right); 

    if( left >=0 && right >=0 && left - right <= 1 || left -right >=-1) 

        return (left<right)? (right +1) : (left + 1); 

    else return -1; 

} 

    第一个算法还是比较好理解的,但第二个就不那么好理解了。第一个算法的思想是:如果这个树是空,则返回0;否则先求左边树的深度,再求右边数的深度,然后对这两个值进行比较哪个大就取哪个值+1。而第二个算法,首先应该明白isB函数的功能,它对于空树返回0,对于平衡树返回树的深度,对于不平衡树返回-1。明白了函数的功能再看代码就明白多了,只要有一个函数返回了-1,则整个函数就会返回-1。(具体过程只要认真看下就明白了)

    对于递归,最好的理解方式便是从函数的功能意义的层面来理解。了解一个问题如何被分解为它的子问题,这样对于递归函数代码也就理解了。这里有一个误区(我也曾深陷其中),就是通过分析堆栈,分析一个一个函数的调用过程、输出结果来分析递归的算法。这是十分要不得的,这样只会把自己弄晕,其实递归本质上也是函数的调用,调用的函数是自己或者不是自己其实没什么区别。在函数调用时总会把一些临时信息保存到堆栈,堆栈只是为了函数能正确的返回,仅此而已。我们只要知道递归会导致大量的函数调用,大量的堆栈操作就可以了。

    下面介绍和递归密切相关的算法:回溯算法。回溯算法是一种搜索算法,它可以通过对问题从一个状态出发,搜索从该状态出发的所有状态,当一条路走到尽头时,再后退一步继续搜索,直到所有路径都试探过。回溯算法通常由递归来实现,因为递归结束时会返回调用处(上一步),回溯结束时也是返回上一步,两者可以很好的契合。文[2]中对于回溯的讲解挺详细的,大家可以参考下。这里引用文中的一段话,我觉得写的挺好的:“通过把递归结束的条件设置到搜索的最后一步,就可以借用递归的特性来回溯了。因为合法的递归调用总是要回到它的上一层调用的,那么在回溯搜索中,回到上一层调用就是回到了前一个步骤。当在当前步骤找不到一种符合条件情况时,那么后续情况也就不用考虑了,所以就让递归调用返回上一层(也就是回到前一个步骤)找一个以前没有尝试过的情况继续进行。当然有时候为了能够正常的进行继续搜索,需要恢复以前的调用环境。”。下面通过一个例子来介绍递归回溯的实现

楼梯问题:假设一个楼梯共有10级台阶,一个人可以选择一次走1级或2级台阶,请问总共有多少种不同走法?

    这个问题是之前一个师兄跟我说的,当时考虑了好久也不得其解,现在通过递归回溯算法来解决它。具体代码如下:


#include <iostream> 

using namespace std; 

const int STAIRS=10;
  const int MAX_STEP=2; 

int result_number=1;
  int steps[STAIRS]; 

void print_result(){
      cout<<"This is the "<<result_number++<<" result: "<<endl;

    for(int i=0;i<STAIRS;i++){

        if(steps[i]==0){

            break;

        }

        cout<<steps[i]<<" ";

    }

    cout<<endl;

} 

void do_step(int stairs,int step_number){
      if(stairs==1){

        steps[step_number]=1;

        print_result();

        steps[step_number]=0;

        steps[step_number-1]=0;

        return;

    }

    else if(stairs==0){

        print_result();

        steps[step_number-1]=0;

        return;

    }

    else{

        for(int i=1;i<=MAX_STEP;i++){

                  if(stairs-i<0){

            break;

            }

            steps[step_number]=i;

            do_step(stairs-i,step_number+1);

        }

     }

} 

int main(){
      cout<<"the program started..."<<endl;

    do_step(STAIRS,0);

    cout<<"there are "<<result_number-1<<" results."<<endl;

}

     do_step函数的功能是:当执行时如果参数stairs为1,或0,则直接输出结果,并且把steps[]恢复到上一步的状态,退出函数。如果stairs大于1,则尝试分别走1步和2步,此后问题转化为原问题的子问题。明白了函数的功能就很容易看懂代码了。该函数中出现了for循环嵌套 递归的情况,大家只要认为是多次执行其中的代码就可以了(那个我们不理解的复杂的函数堆栈调用关系可以正确无误的实现我们要求的函数调用,呵呵)。程序会列出每个结果,总的可行走法共89种。

    大家了解了回溯算法后就可以解决一大类问题了,包括上面说的打靶问题、8皇后问题等。大家也可以尝试通过回溯算法来解决“数独”问题,通过搜索来查找可行的数独解(也算是个小小的挑战吧,^_^)。 

参考资料:

[1] 漫谈递归思想 http://www.cnblogs.com/BLoodMaster/archive/2010/03/23/1692641.html

[2] 递归回溯总结 http://hi.baidu.com/sulipol/blog/item/32411851bb9724551138c2eb.html

[3] 《程序员面试宝典》第8章 循环、递归与概率

Linux程序输入、输出重定向

C/C++, Linux No Comments »

对于输出重定向大家应该都比较了解了,一般都是指把输出重定向到一个文件中,而对于输入重定向一般就不是很常用了。暂时的一个应用就是实现程序的脚本控制,比如你用脚本启动另一个程序,然后又需要给这个启动的程序发送命令,这时就需要采用输入重定向了,这对于一些服务类型的程序还是很有用的。要实现真正意义的输入重定向还是比较麻烦的,需要用到管道。

下面用一个简单的示例来实现程序的输入,输出重定向。

//file_name:shi_li.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

#define MAX_LINE 80
#define PIPE_STDIN  0
#define PIPE_STDOUT 1

int main(){
 int child_pid;

 char outPath[30];
 strcpy(outPath,"./out.file");

 int pfds[2];
 if (pipe(pfds)== 0){
  child_pid=fork();
  //in child process the return pid==0
  if ( child_pid == 0 ) {

   //input file,may be used later.
   //char inPath[30];
   //strcpy(inPath,"./in.file");

   //int inFd = open(inPath,O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
   int outFd = open(outPath,O_WRONLY|O_APPEND|O_CREAT,S_IRUSR);

   close(0);
   dup2( pfds[PIPE_STDIN], 0);
   dup2(outFd,1);
   close(pfds[PIPE_STDOUT]);
   execl("./getstring","getstring",NULL);
   printf("start getstring: error!!!");
   exit(1);
          }
  else {

   close(pfds[PIPE_STDIN]);
       char msg[MAX_LINE];
         int b=1;
        for(b=1;b<6;b++){              
           sleep(1);
             sprintf(msg,"2+%d\n",b); 
             write( pfds[PIPE_STDOUT], msg, strlen(msg) );              
                 }
         close(pfds[PIPE_STDOUT]);
         sleep(2);
         printf("all done.\n");
         }
       }
 return 0;
} 

接下来是一个很简单的程序输入:

//file_name: getstring.cpp

#include <unistd.h>
#include <iostream>
using namespace std;

int main(){ 
 string tmp;

 while(cin >> tmp,!cin.eof()){
  if(cin.bad()){  
   cerr<<"io stream error"<<endl;
   exit(1); 
  }
  if(cin.fail()){
   cerr<<"io stream error"<<endl;
   cin.clear(istream::failbit);
   continue;
  }
  //ok to process 
  cout<<"all done well:"<<tmp<<endl;
 }
}

编译并运行:

#g++ -o getstring getstring.cpp
#g++ -o shi_li shi_li.cpp
#./shi_li

上述代码包括两个小程序,getstring简单的接收输入的字符串并在终端上回显。shi_li程序负责启动getstring并把输入重定向到管道,输出重定向到out.file文件。执行代码后我们可以查看out.file文件来获得程序执行的结果。

上述小程序可以用在脚本时对向子程序发送命令,从而完成程序测试的自动化。上述代码只是个demo,如果想在实际测试中可用还需要根据自己的情况进行些许修改。

参考资料:

[1] http://blog.chinaunix.net/u/19573/showart_1225848.html

[2]http://www.opengroup.org/onlinepubs/009695399/

ubuntu下成功安装sfslite

C/C++, Linux No Comments »

之前曾采用MIT的开源chord来开发P2P的系统,由于chord依赖于sfslite的一些库文件,所以需要安装sfslite-0.8.16。自此带来了很多问题,因为sfslite需要gcc-4.1.2才能编译,用最新的gcc-4.3或4.4都会编译失败,而且它好像还和操作系统有关,在fedora 7下可以正常编译。如果您不想使用fedora 7这种老版本的系统那就麻烦了。

最近新安装了ubuntu 10,突然想在ubuntu下尝试一下,这样后续的开发就不用再装fedora 7的虚拟机了,而且也可以方便程序的移植。下面简要介绍安装的过程:

首先安装gcc-4.1.2,具体安装过程参考“在ubuntu中编译、安装gcc 4.1.1过程以及遇到的问题”,上面讲的很详细。主要有一个地方需要注意,gcc-4.1.2依赖texinfo库,默然configure不支持texinfo 4.10+,需要修改configure文件。在其中找到’texinfo[^0-9]*([1-3][0-9]|4\.[2-9]|[5-9])’编辑成’texinfo[^0-9]*([1-3][0-9]|4\.[2-9]|4\.[1-9][0-9]*|[5-9])’后保存,编译通过。[注] 无需修改LD_LIBRARY_PATH变量,在ubuntu下没有这个变量,因为已经被废除了,直接安装好后就可以使用了。

[tips]可以使用ubuntu中自带的 update-alternatives 命令来方便的进行多个gcc版本之间的切换(具体命令使用可以在google上搜)。

接下来就是使用gcc 4.1.2来编译sfslite,首先下载sfslite-0.8.16的tar包。(不要使用sfslite-0.8.17,编译会出错)

然后解压,进入源文件根目录,输入 ./configure –with-sfsmisc (with前是两个-) 只要这一个选项就可以了,不用输–with-dmalloc

接下来如果直接make的话会出错,会提示 unknown sizeof  ucred 。在网上找了很久,终于发现只有在编译时加上-D_GNU_SOURCE才可以。在gcc编译选项中增加 -D_GNU_SOURCE,这需要改makefile文件。具体为async和arpc目录下的Makefile文件中找到“ECFLAGS =” 改为:“ECFLAGS = -D_GNU_SOURCE”

然后编译安装即可。

C++静态工厂模式(采用shared_ptr)

C/C++ 3 Comments »

    今天介绍另一个比较常用的设计模式:工厂模式。工厂模式是指通过声明一个工厂的接口,然后定义该接口的各个实现类来具体实现不同的工厂。这里不打算写这种一般的方式,而只是写一种简单的工厂模式:静态工厂模式。这种模式的扩展性不如一般工厂模式那么好,但对于我们一般的应用已经足够了(而且系统设计的太复杂也不是好事,呵呵),静态工厂在实际中还是很常用的。

    值得说明的是这次没有像一般工厂模式那样采用普通的指针,而是采用之前介绍的shared_ptr,这是因为采用智能指针就不必担心工厂中创建的对象的释放问题,因为智能指针会帮你做好这一切,^_^

       下面是示例代码,代码中每个类按照不同文件进行放置,这样也可以了解工厂模式的一个好处,减小使用者和创建的产品类之间的依赖关系,这会在后面说明。

FileName: product.h

#ifndef _PRODUCT_H_
#define _PRODUCT_H_

class Product{
 public:
  virtual void myFunc()=0;
};

#endif

FileName: product_one.h

#ifndef _PRODUCT_ONE_H_
#define _PRODUCT_ONE_H_

#include "product.h"

class ProductOne:public Product{
 public:
  ProductOne();
  ~ProductOne();
  void myFunc();
};
#endif

FileName: product_one.cpp

#include "product_one.h"
#include <iostream>

using namespace std;

ProductOne::ProductOne(){
 cout<<"product one: i have been builded."<<endl;
}

ProductOne::~ProductOne(){
  cout<<"Product one: i have been destroyed."<<endl;

}
void ProductOne::myFunc(){
 cout<<"product one myFunc done!"<<endl;
}

FileName: product_two.h

#ifndef _PRODUCT_TWO_H_
#define _PRODUCT_TWO_H_

#include "product.h"

class ProductTwo:public Product{
 public:
  ProductTwo();
  ~ProductTwo();
  void myFunc();
};

#endif

FileName: product_two.cpp

#include "product_two.h"
#include <iostream>

using namespace std;

ProductTwo::ProductTwo(){
 cout<<"product two: i have been builded."<<endl;
}

ProductTwo::~ProductTwo(){
  cout<<"Product two: i have been destroyed."<<endl;
}

void ProductTwo::myFunc(){
 cout<<"product two myFunc done!"<<endl;
}

FileName: product_factory.h

#ifndef _PRODUCT_FACTORY_H_
#define _PRODUCT_FACTORY_H_

#include "product.h"
#include <tr1/memory>
#include <string>

class ProductFactory{
 public:
  static  std::tr1::shared_ptr<Product>  createProduct(std::string productDescript);
};

#endif

FileName: product_factory.cpp

#include "product_factory.h"
#include "product_one.h"
#include "product_two.h"

using namespace std;
using namespace std::tr1;

shared_ptr<Product> ProductFactory::createProduct(string productDescription){

 if(productDescription=="one"){
  shared_ptr<Product> productOne(new ProductOne());
  return productOne; 
  //you can also do like below.
  //return shared_ptr<Product> (new ProductOne());
 }
 else if(productDescription=="two"){
  shared_ptr<Product> productTwo(new ProductTwo());
  return productTwo; 
  //you can also do like below.
  //return shared_ptr<Product> (new ProductTwo());
 }
}

FileName: test_factory.cpp

#include "product.h"
#include "product_factory.h"
#include <string>
#include <iostream>
#include <tr1/memory>

using namespace std;
using namespace std::tr1;

int main(){
 cout<<"test 1 start..."<<endl;
 shared_ptr<Product> myProduct=ProductFactory::createProduct("one");

 myProduct->myFunc();

 cout<<endl<<"test 2 start..."<<endl;
 myProduct=ProductFactory::createProduct("two");
 myProduct->myFunc();
 { 
  cout<<endl<<"test 3 start..."<<endl;
  shared_ptr<Product> myProduct2=ProductFactory::createProduct("one");
  myProduct2->myFunc(); 
 }
 cout<<"all test done."<<endl<<endl;;
}

编译然后运行:

# g++ -o test_factory  test_factory.cpp  product_one.cpp  product_two.cpp  product_factory.cpp
# ./test_factory

运行结果如下:

test 1 start...
product one: i have been builded.
product one myFunc done!

test 2 start...
product two: i have been builded.
Product one: i have been destroyed.
product two myFunc done!

test 3 start...
product one: i have been builded.
product one myFunc done!
Product one: i have been destroyed.
all test done.

Product two: i have been destroyed.

    从上面结果可以看出通过使用工厂模式,可以使实例对象的生成由工厂来完成,减小使用者test_factory与实例产品product_one、product_two的依赖,这样也有利于后续程序的维护,即使product_one后来取消了,改动的代码也不会很大。大家看下test_factory.cpp的包含文件可以看出使用者不需要知道实际的产品子类(不直接知道),减小了编译的依赖。

    通过采用shared_ptr智能指针,可以避免使用者忘记释放掉new产生的实例类。这也使得代码更加安全,不容易造成内存泄露,也更容易编写异常安全或异常中立的代码。

   [题外话]也可以看出shared_ptr对于继承派生有很好的支持,^_^

单件模式的C++示例

C/C++ No Comments »

     之前花过一段时间学习设计模式,现在对于很多模式只留下一些感性的认识了。最常用的设计模式是策略模式 、工厂模式;最有用的设计原则是封装、组合>继承。
      以前的项目中大家为了简化往往忽视了设计模式的存在,这往往造成了项目维护的困难。现在又要进行新的代码编写了,希望能在设计之初考虑到这些问题,也方便以后代码维护的人。
      今天我记录一个简单的模式:单件模式。单件,毫无疑问就是独一无二的对象,其实在程序中这种对象有很多,例如存储系统配置信息的对象、接受和发送socket或是其他消息的对象、存储一些全局信息的对象,这些对象在系统中都是唯一的,它们建立好之后将不会销毁,直到程序终止(当然独一无二不意味着不销毁)。
      下面还是用代码说话把,下面代码位于三个文件中,分别为:configurator.h(Configurator类的定义头文件)、configurator.cpp(Configurator类的方法实现文件)、test_singleton.cpp(测试文件)。在C++中文件的划分是很重要的,这在大型程序中尤其如此,关于头文件的定义大家可以参考C++ Prime,上面讲的还是挺好的。(曾经又一次错误的在头文件中写下了using namespace std;结果导致各种莫名奇妙的编译错误,汗啊~)

FileName: configurator.h

#ifndef _CONFIGURATOR_H_
#define _CONFIGURATOR_H_

//this class has only one instance.
class Configurator{
	public:
		static Configurator* only();

		void setFlag(int newFlag);
		int getFlag();

		//NOT IMPLEMENTED ,AVOID MISUSE.
		Configurator(const Configurator&);
		Configurator& operator=(Configurator);
	private:
		//if it  has child class, the Constructor should be protected.
		Configurator();

		static Configurator* instance;

		int flag;
};
#endif

FileName: configurator.cpp

#include <iostream>
#include "configurator.h"

using namespace std;

Configurator* Configurator::instance=NULL;

Configurator::Configurator():flag(2){
 cout<<"I have been builded."<<endl;
}

Configurator*  Configurator::only(){
//if you want destory the class after use,you can do define instance here

//static Confiugrator * instance=NULL;

 if(instance==NULL){
  instance= new Configurator;
 }
 return instance;
}

void Configurator::setFlag(int newFlag){ 
 flag = newFlag;
}

int Configurator::getFlag(){ 
 return flag;
}

FileName: test_singleton.cpp

#include <iostream>
#include "configurator.h"

using namespace std;

int main(){

 cout<<"start building Configurator."<<endl;
 Configurator* first = Configurator::only();
 cout<<"first: flag is:"<<first->getFlag()<<endl;
 first->setFlag(7);
 cout<<"first: flag is:"<<first->getFlag()<<endl;

 //the other block.
 {
  Configurator* second=Configurator::only();
  cout<<"second: flag is:"<<second->getFlag()<<endl;
  second->setFlag(9);
  cout<<"second: flag is:"<<second->getFlag()<<endl;
 }

 cout<<"now first: flag is:"<<first->getFlag()<<endl;

}

编译并运行:

 # g++ configurator.cpp test_singleton.cpp
 # ./a.out

代码的运行结果如下:

start building Configurator.
I have been builded.
first: flag is:2
first: flag is:7
second: flag is:7
second: flag is:9
now first: flag is:9

从Configurator类的定义中可以看出它和普通类的定义所不同的有以下几点:

    1 、构造函数被定义为private,(如果有子类可以定义为protected),即对于外界不能通过构造函数来得到实例类型。如果你试图定义一个Configurator myCfg;编译时就会报错;获得实例变量的唯一办法就是调用only类方法,这也就保证了类的唯一性。

      [题外话]调用类的默认构造方法时应该写成Configurator myCfg; 而不是 Configurator myCfg();因为后者编译器会认为是方法的定义。

      2、为了进一步保证类的唯一性,在定义中把“构造拷贝函数”、“赋值函数”只进行声明而没有定义,这样可以防止某些人拷贝此单件类。如果试图拷贝的话可以通过编译,但链接时会报错(因为找不到对应的方法)。

       3、单件类包含有一个private的指向自己类型的指针,并且此指针定义成static类型,即全局的指针,可以用于指向此单件对象,并保证此对象的唯一性。

       众所周知,C++是支持全局变量的(当然也可以是全局的类变量),但是全局变量的出现使得程序文件中充斥了extern等字样,很多人提倡取消全局变量,它有很多缺点,其中包括降低代码的可读性,增加代码的维护难度,但全局方法有一个很大的优点:简单。不过我们还是应该尽量不用全局变量,相比而言,单件模式就是一个很好的解决方案,它可以让代码更易与维护并且避免一些不必要的错误。

      [题外话]很多事情存在都有它存在的理由,在实际中我们需要进行权衡,我们选择的不一定是最好的,但一定应该是最适合自己的。

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS 登录