Qi HE (何奇) Home Research Publication Software Technology Coding Life Miscellaneous

 

C++

什么是虚函数?CObject类中的析构函数为什么是虚函数?

Thursday, March 13, 2008 08:54:32 PM 于新加坡 (部分内容直接转载)

C++用关键字virtual来声明一个函数为虚函数。虚函数是指一个类中我们希望被派生类重载(override)的成员函数。当我们用一个基类指针或者引用指向一个继承类对象的时候,我们调用一个虚函数,实际调用的是继承类的版本。所以说,虚函数主要用于实现多态性。

示例程序如下:

class ABase                                                          
{                                                                    
    public:                                                          
    virtual ~ABase() { ... }                                         
    virtual void Func(void) { cout << "This is ABase" << endl }      
};                                                                   
void Test(A *a)                                                      
{                                                                    
    a->Func();                                                       
}                                                                    
class BChild : public ABase                                          
{                                                                    
    public:                                                          
    ~BChild() { ... }                                                
    virtual void Func(void) { cout << "This is BChild" << endl }     
};                                                                   
                                                                     
// Example                                                           
main()                                                               
{                                                                    
    ABase *a;                                                        
    BChild b;                                                        
    Test(a); // output: This is ABase                                
    Test(&b); // output: This is BChild                              
    a=&b;                                                            
    Test(a); // output: This is BChild                               
}                                                                    
由此可见,虚函数在实现“多态性”时有很明显的优点:
  • 应用程序不必为每一个派生类编写函数,只需要对抽象基类进行处理即可,大大提高了接口设计的复用性;
  • 派生类的功能可以被基类指针引用,以实现向后兼容。以前写的程序能被后来写的程序调用不足为奇,后来写的程序能被以前写的程序调用,那可了不起。

如果抽象基类的析构函数不被定义为虚函数,当a指针被撤销时,根据静态联编,调用的是ABase自己的析构函数。由于a指针在程序中被引用到了派生类对象b,如果BChild类的构造函数在堆中分配了内存,那么撤销a指针将不会调用BChild::~BChild(),从而不会释放BChild::BChild分配的内存,造成内存泄漏。而将ABase的析构函数设为虚函数,则其派生类BChild的析构函数也将自动变为virtual类型,这保证了a指针撤销时,会先调用BChild的析构函数,再调用ABase的析构函数。

注意:析构函数可以是虚函数,但构造函数则不能。

如何处理main函数的标准输入参数

Thursday, March 13, 2008 08:54:32 PM 于新加坡

很多时候我们编写的C++程序都是为了生成独立的exe文件,所以新建project时我们通常选择Win32 Console Application类别。这类程序通常需要处理main函数的标准输入参数(用户在console中手动输入)。由于处理此类参数的代码简单而且标准,我们写程序时常常直接从某处把这段代码copy过来。在本文总结出来主要就是为了日后copy的方便:)

所谓的main函数的标准输入参数就是指如下代码中的argv字符串数组,而其前面的argc存放的是参数的数目。

int main(int argc, char* argv[]) // 假设project名是example

比如,用户输入example.exe -i input1.txt -i input2.txt -o output.txt。这行命令共有7个参数,第一个是example.exe(argv[0]),第二个是-i(argv[1]),...以此类推。我们首先要做的就是根据参数选项( 如-i)分解argv,标准代码如下:

map<string, vector<char*>> io;                                            
for (int i = 1; i < argc - 1; i += 2) io[argv[i]].push_back(argv[i+1]);   
vector<char*>& ins = io["-i"];                                            
vector<char*>& outs = io["-o"];                                           
if (argc % 2 == 0 || io.size() != 2 || ins.empty() || outs.size() != 1)   
                                                                          
{                                                                         
    cerr << "Invalid I/O arguments" << endl;                              
    exit(1);                                                              
}                                                                         

代码限制了两个参数选项(-i/-o),其中-i不能为空,-o只能有一个。可得ins[0]=input1.txt,ins[1]=input2.txt,output[0]=output.txt.

如何重载流(override stream)操作符?

 

Java

编译运行环境变量设置以及与package, jar的关系(终极篇)

Thursday, March 13, 2008 08:54:32 PM 于新加坡

用Java虽说好几年,可是因为长期依赖IDE的缘故,总是忘记如何手动设置classpath。今天把自己的程序移植到Linux下运行,又被这个“麻烦”的小问题折腾了许久。上网查解答,看那些贴子心里实在堵得慌,要么说的都是太简单的原理,要么根本云蒸雾罩不知所云。一气之下,自己决定写一篇号称“终极”的解决方案,其实也只是希望自己下次忘记的时候不用再去看那些骗论坛分数的帖子:)

本文将从以下几个方面详细阐述如何解决Linux下编译运行Java程序时可能碰到的与classpath相关的问题。

  1. 在Linux下如何安装Java
  2. 如何设置Java编译/运行的环境变量
  3. 在Windows下编译通过的程序为何在Linux下编译不通过?
  4. 如果代码import了第三方jar,如何编译/运行?
  5. 如果代码本身属于一个package,而且还调用了package中其他的类,该如何编译/运行?
  6. IDE下的当前路径与Linux下的当前路径有何区别?

1. 在Linux下如何安装Java

这一步比较简单。先去java.sun.com下载一个最新版的Java SDK,推荐自解压版本(.bin),然户用chmod +x命令赋予安装包可执行权限,最后执行./***.bin即可。安装结束后,可以用java -version命令来确认当前机器使用的java的版本。

假设当前执行解压命令的目录是/student/qihe/experiments/,系统会在当前目录下为安装的Java自动创建一个新的目录,比如/student/qihe/experiments/jdk1.6.0_05

2. 如何设置Java编译/运行的环境变量

如果机器之前安装了旧版的Java,在新版java未配置前,java -version命令显示的是旧版的信息。

配置Java有两种常用的方案。

第一种是在/etc/profile.d/目录下建立一个java.sh的文件(名字可以任意取),如vi /etc/profile.d/java.sh

然后往该文件写入一下内容:

#!/bin/bash                                         
                                                    
JAVA_HOME=/student/qihe/experiments/jdk1.6.0_05/    
JAVA_FONTS=/usr/share/fonts/truetype                
ANT_HOME=/usr/share/ant                             
                                                    
PATH=$JAVA_HOME/bin:$ANT_HOME/bin:$PATH             
                                                    
export PATH JAVA_HOME JAVA_FONTS ANT_HOME           
export CLASSPATH=.                                  

再用chmod 755赋予java.sh可执行权限,最后运行java.sh即可

第二种是手动连续输入三条export命令(ant和font略去)。这样做原因很简单,有时候我们可能没有在/etc下创建新文件的权限。

export JAVA_HOME=/student/qihe/experiments/jdk1.6.0_05/   
export PATH=$JAVA_HOME/bin:$PATH                          
export CLASSPATH=.                                        

配置结束后,应该就可以运行简单的java程序,如果该程序只是import了Java自带的package(如java.io.*)的话。

如果偷懒不想配置Java路径,也可以用如下命令编译程序(运行类似):

/student/qihe/experiments/jdk1.6.0_05/bin/javac example.java

注意:一般jdk1.6.0_05/路径下面还会有jre/bin这样一个子路径,这个bin下面也有一个名为java的文件,但是却没有名为javac的文件。千万不要把这个bin目录和jdk1.6.0_05/bin目录搞混了。后者才是我们真正需要配置的。

3. 在Windows下编译通过的程序为何在Linux下编译不通过?

不用怀疑Java代码的跨平台性。出现这种问题99%的情况下都是因为Java编译的版本不同所致:如在Windows下用JDK 6编译但是在Linux下用JDK 4编译。解决办法就是重复上述第一、二步。

4. 如果代码import了第三方jar,如何编译/运行?

简单地说,可以把第三方jar放入classpath中。如果classpath中还有多个jar,在Linux中用冒号(:)分开,在Windows中用分号(;)分开。由于第三方jar通常都不固定,我们可以直接把classpath写入执行命令中,如下所示:

javac -classpath ../lib/lucene-core-2.0.0.jar example.java  (假设jar在上一级目录的lib下)

5. 如果代码本身属于一个package,而且还调用了package中其他的类,该如何编译/运行?

举个简单的例子,example.java属于package edu.ntu.textproc,而且又调用了edu.ntu.data中的某一个class(如a.class)。假设当前目录是src,下一级目录是edu/ntu/*。

编译命令:javac -classpath .:../lib/lucene-core-2.0.0.jar edu/ntu/textproc/example.java

注意classpath这里多了一个当前目录(.),用于指导在当前目录下寻找edu/ntu/textproc/example.javaedu.ntu.data下面那个被调用的a.class,即src/edu/ntu/data/a.class,如果a.class不存在但是找到了a.java,a.java会在其所属目录下被自动同时编译。如果两者都找不到,当然会编译出错。classpath也可以给出全路径(而非当前目录),这样就不一定要在src目录下执行。

运行命令:java -classpath .:../lib/lucene-core-2.0.0.jar edu.ntu.textproc.example

被运行的类必须写成前缀加package名的形式,然后在classpath(本例是当前目录)下面寻找。

6. IDE下的当前路径与Linux下的当前路径有何区别?

移植并编译成功后,运行时还可能会出现一种错误,就是由IDE下的当前路径与在Linux中运行命令时的当前路径不一致所造成的。举个简单例子,IDE中的当前路径是我们在package-->prosperities下面配置的路径,如C:\project。如果example.java中有一个路径是"./data",指的是"C:\project\data"。 当我们在Linux下面的src目录下运行程序时,同样的代码指的是"./src/data"。所以运行时还要把data这个目录copy到src下面。