2010年9月29日 星期三

(轉錄) [Java] Static 的意義與實作方式

Static 的意義與實作方式

「將某 class 產生出一個 instance 之後,此 class 所有的 instance field 都會新增一份,那麼所有的 instance method 是否也會新增一份?」我常常看到網路上有人在討論此問題,所以寫了這篇文章,以為解釋。

Member 的種類
類別(class)的內部組成統稱為成員(member),如果依據成員是「資料」還是「程式」來區分,可以分成:
資料,被稱為 field
程式,被稱為 method
如果再依據有無使用 static 修飾而細分,則成員可細分成四種:

  • class field:有用 static 修飾的 field
  • class method:有用 static 修飾的 method
  • instance field:沒有用 static 修飾的 field
  • instance method:沒有用 static 修飾的 method

顧名思義,class field/method 和 class 本身有密切的關係,而 instance field/method 則與 instance(也就是物件)有密切的關係。請看下面的範例程式。

public class Class1 {
  public static int classField;
  public static void classMethod1() {
    // ...
  }
  public static void classMethod2() {
    // ...
  }

  public int instanceField;
  public void instanceMethod1() {
    // ...
  }
  public void instanceMethod2() {
    // ...
  }
}

public class Class2 {
  public static void classMethod () {
    // ...
  }
  public void instanceMethod() {
    // ...
  }
}

Field
宣告 field 時,如果前面加上 static 的修飾字,就會使得此 field 變成是 class field。一個 class field 永遠只佔用一塊記憶體,而且此記憶體空間是在此 class 一被載入(load)記憶體之後就立刻配置的(allocate),感覺就如同此 field 與該 class 本身相關,而不是與該 class 的 instance 相關。class field 可以被同一個 class 的 class method 內部所直接使用,舉例來說,上述的 classMethod1() 內部可以出現 classField。如果 Class1 的 class method 或 instance method 欲使用到 Class2 的 class field,就必須在前面冠上 Class2,例如:Class2.classField。


宣告field時,如果前面「不」加上 static 的修飾字,就會使得此 field 變成是 instance field。對 instance field 而言,每產生一個instance(物件)就多一塊 instance field 的記憶體,每少一個 instance 就少一塊 instance field 的記憶體。instance field 可以被同一個 instance的 instance method 內部所直接使用,舉例來說,上述的 instanceMethod1() 內部可以出現 instanceField。如果某 class 的class method 或 instance method 欲使用到某 instance 的 instance field,就必須在前面冠上 instance 名稱,例如:obj.classField。



Method
宣告 method 時,如果前面加上 static 的修飾字,就會使得此 method 變成是 class method。對 class method 而言,永遠只佔用一塊記憶體,而且此記憶體空間是在此 class 一被載入進記憶體之後就立刻配置的,就如同此 method 是與該 class 本身相關,而不是與該 class 的 instance 相關。class method 可以被同一個 class 的任何 class method 內部所直接使用,舉例來說,上述的classMethod1() 內部可以出現 classMethod2()。如果 Class1 的 class method 或 instance method 欲使用到 Class2 的 classMethod(),就必須在前面冠上 Class2,也就是 Class2.classMethod()。


宣告 method 時,如果前面「不」加上 static 的修飾字,就會使得此 method 變成是 instance method。對 instance method 而言,每產生一個 instance「並不會」多一塊 instance method 的記憶體。同一個 method 不管被調用(invoke)幾次,也不管被調用時的instance 是何者,每次的程式碼完全都一樣,差別只在每次執行時資料不同,而資料是存放在 call stack 中,所以不會混淆。在 instance method 內,資料的來源包括了參數和 instance field。參數被傳進來變成 call stack 內的 entry,所以不會混淆,這很容易理解,但是 instance field 是如何區隔開來的(前面剛提過:instance field 會隨著 instance 數目增加而增加),這是透過隱匿(implicit)的 this 參數來達到了,稍後會有說明。instance method 可以被同一個 instance 的 instance method 內部所直接使用,舉例來說,上述的 instanceMethod1() 內部可以出現 instanceMethod2()。如果某 class 的 class method 或 instance method 欲使用到某 instance 的某 instance method,就必須在前面冠上此 instance 名稱,例如:obj.classMethod()。



隱匿的 this 參數
綜合上面的敘述來看:

  • class field:共用一塊記憶體
  • class method:共用一塊記憶體
  • instance field:隨著每個 instance 各有一塊記憶體
  • instance method:共用一塊記憶體

instance method 為什麼不是隨著每個 instance 佔有一塊記憶體,反倒是共用一塊記憶體?其實,讓每個 instance method 如同instance field 一樣,隨著每個 instance 佔有一塊記憶體,這麼做當然是可以的,只是 Java 編譯器和 JVM 都不這麼做,因為太浪費記憶體空間了。一個 field 少則佔用一個 byte,多則佔用數百 Byte,但是 method 少則數個 byte,多則數百 Kilo Byte。Mehtod耗費的記憶體是 field 的數百倍,甚至數千倍,當然是能共用就盡量共用,比較不會消耗記憶體。既然 JVM 讓一個 class 的所有instance 共用相同的 instance method,下面兩行程式碼在 instanceMethod() 內部時,如何區分是 instance1 或 instance2?
instance1.instanceMethod();
instance2.instanceMethod();

因為編譯器會幫我們在把 instance1 和 instance2 個別傳入 instanceMethod() 中當作第一個參數。也就是說,任何 instance method 參數的實際個數都會比表面上多一個,這個多出來的參數是由 Java 編譯器幫我們加上去的,用來代表對應的 instance。此參數的變數名稱為 this,也是 Java 的一個關鍵字(keyword)。

當調用某個 instance method 或使用某個 instance field 時,你必須在前面加上該 instance 的名稱,如果該 instance method/field 相關的 instance 和當時程式碼所在的 instance method 的 instance 指的是同一個 instance 時,該 instance 的名稱就是 this,這種情況下,你也可以選擇不在前面加上「this.」。

然而,在某些狀況下,非得在前面加上「this.」不可。例如,當method中的參數或區域變數和 instance field 名稱完全相同時,如果不在前面冠上「this.」,那麼指的是參數或區域變數;如果在前面冠上「this.」,那麼指的才是 instance field。

本文作者:蔡學鏞
張貼日期:09/09/2002
參考自[Java] Static 的意義與實作方式

2010年9月28日 星期二

Java Native Interface (JNI) - Introduction

Why JNI?
  • Native applications are written in native programming languages such as C and C++, compiled into host-specific binary code, and linked with native libraries.
    • Native applications and native libraries are typically dependent on a particular host environment.
    • A C application built for one operating system, for example, typically does not work on other operating systems.
  • Java platforms are commonly deployed on top of a host environment.
    • The Java platform offers a set of features that applications can rely on independent of the underlying host environment.
    JNI allows Java code running in a Java Virtual Machine (JVM) to call and to be called by native applications and libraries written in other languages, such as C, C++.
    主要基於以下三個需求因應而生:
  1. 解決性能問題
    • 採用JNI技術針對一些嚴重影響Java性能的部分程式碼,該部分可能只占原始程式的極少部分,所以幾乎可以不考慮該部分代碼在主流平臺之間移植的工作量。同時,也不必過分擔心類型匹配問題,我們完全可以控制程式碼不出現這種錯誤。此外,也不必擔心安全控制問題,因爲Java安全模型已擴展爲允許非系統類別載入和呼叫本地方法。根據Java規範,從JDK 1. 2開始,FindClass將設法找到與當前的本地方法關聯的類別載入器。如果平臺相關程式碼屬於一個系統類別,則無需涉及任何類別載入器;否則,將呼叫適當的類別載入器來載入和鏈結已命名的類別。換句話說,如果在Java程式中直接呼叫C/C++語言産生的機器碼,該部分代碼的安全性就由Java虛擬機器控制。
  2. 解決本機平臺介面呼叫問題
    • 由於Java跨平臺的特性,使得它與本機的各種內部聯繫變得很少,約束了它的功能。通過JNI呼叫native methods,使Java可以實現和本地機器的緊密聯繫,呼叫系統級的各介面方法。而native methods是以shared library文件的形式存放的(在WINDOWS平臺上是DLL文件形式,在UNIX機器上是SO文件形式)
  3. 嵌入式開發應用
    • Write once, run anywhere (WORA)
    • Java應用程式能夠在帶有JVM的任何硬軟體系統上執行。加上Java語言本身所具有的安全性、可靠性和可攜性等特點,對實現瘦身上網的資訊家電等網路設備十分有利。也正是由於JNI解決了本機平臺介面呼叫問題,於是JNI在嵌入式開發領域也是炙手可熱。

Role of JNI!
    A two-way interface that allows Java applications to invoke native code and vice versa.
  • Users use the JNI to write native methods that allow Java applications to call functions implemented in native libraries. Java applications call native methods in the same way that they call methods implemented in the Java programming language.
  • The JNI supports an invocation interface that allows users to embed a Java virtual machine implementation into native applications. Native applications can link with a native library that implements the Java virtual machine, and then use the invocation interface to execute software components written in the Java programming language.


When to use JNI?
  1. To access system features not otherwise available in the Java language or an API.
  2. To hook to existing libraries of usable native code.
  3. To improve the performance of time-critical methods.

Implications of Using the JNI
  1. Java applications that depend on the JNI can no longer readily run on multiple host environments.
    • Necessary to recompile the part of the application written in native programming languages.
    • Write once, run anywhere” would be lost.
  2. Java programming language is type-safe and secure, native languages such as C or C++ are not.
    • A misbehaving native method can corrupt the entire application. Java applications are subject to security checks before invoking JNI features.

Referenced from "The Java Native Interface - Programmer's Guide and Specification" Chapter 1

2010年9月27日 星期一

JNI - Arguments Passing: Object Arrays

  • Accessing Object Arrays
    1. (example) ObjectArrayTest.java: calls a native method to create a two-dimensional array of int and then prints the content of the array.
    2. class ObjectArrayTest {
          private static native int[][] initInt2DArray(int size);
          public static void main(String[] args) {
              int len = 5;
              int[][] i2arr = initInt2DArray(len);
              for (int i = 0; i < len; i++) {
                  for (int j = 0; j < len; j++) {
                      System.out.print(" " + i2arr[i][j]);
                  }
                  System.out.println();
              }
          }
          static {
              System.loadLibrary("ObjectArrayTest");
          }
      }
      
    3. Generate C/C++ header file
    4. ObjectArrayTest.h
      /* DO NOT EDIT THIS FILE - it is machine generated */                           
      #include <jni.h>
      /* Header for class ObjectArrayTest */
      
      #ifndef _Included_ObjectArrayTest
      #define _Included_ObjectArrayTest
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
       * Class:     ObjectArrayTest
       * Method:    initInt2DArray
       * Signature: (I)[[I
       */
      JNIEXPORT jobjectArray JNICALL Java_ObjectArrayTest_initInt2DArray
        (JNIEnv *, jclass, jint);
      
      #ifdef __cplusplus
      }
      #endif
      #endif
      
    5. Implementation of ObjectArrayTest.c
    6. ObjectArrayTest.c
      • The static native method initInt2DArray creates a two-dimensional array of the given size.
      JNIEXPORT jobjectArray JNICALL
       Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size)
       {
           jobjectArray result;
           int i;
           jclass intArrCls = (*env)->FindClass(env, "[I");
           if (intArrCls == NULL) {
               return NULL; /* exception thrown */
           }
           result = (*env)->NewObjectArray(env, size, intArrCls,
                                           NULL);
           if (result == NULL) {
               return NULL; /* out of memory error thrown */
           }
           for (i = 0; i < size; i++) {
               jint tmp[256];  /* make sure it is large enough! */
               int j;
               jintArray iarr = (*env)->NewIntArray(env, size);
               if (iarr == NULL) {
                   return NULL; /* out of memory error thrown */
               }
               for (j = 0; j < size; j++) {
                   tmp[j] = i + j;
               }
               (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp);
               (*env)->SetObjectArrayElement(env, result, i, iarr);
               (*env)->DeleteLocalRef(env, iarr);
           }
           return result;
       }
      
      • FindClass(): to obtain a reference of the element class of the two-dimensional int array.
      • FindClass() returns NULL and throws an exception if class loading fails. (for example, a missing class file or an out-of-memory condition)
      • The "[I" argument to FindClass is the JNI class descriptor that corresponds to the int[ ] type in the Java programming language.
      • NewObjectArray(): allocates an array whose element type is denoted by the intArrCls class reference.
      • NewIntArray(): allocates the individual array elements.
      • SetIntArrayRegion(): copies the contents of the tmp[ ] buffer into the newly allocated one-dimensional arrays.
      • SetObjectArrayElement(): sets the jth element of the ith one-dimensional array has value i+j.
      • DeleteLocalRef(): ensures that the virtual machine does not run out of the memory used to hold JNI references such as iarr.
    7. Generate shared library file - libObjectArrayTest.so
    8. cc -shared -I/usr/local/jdk1.6.20_21/include -I/usr/local/jdk1.6.20_21/include/linux ObjectArrayTest.c -o libObjectArrayTest.so
    9. Run ObjectArrayTest output (size=5):
    10. 0 1 2 3 4
      1 2 3 4 5
      2 3 4 5 6
      3 4 5 6 7
      4 5 6 7 8

JNI - Arguments Passing: Basic Types, Strings and Primitive Arrays

在此講述使用JNI在Java與C/C++中傳遞參數的方式,並以簡單的例子做示範。

  • Accessing Strings
    1. (Example) Prompt.javacontains a native method that prints a string, waits for user input, and then returns the line that the user has typed in.
    2. Prompt.java
      class Prompt {
           // native method that prints a prompt and reads a line
           private native String getLine(String prompt);
       
           public static void main(String args[]) {
               Prompt p = new Prompt();
               String input = p.getLine("Type a line: ");
               System.out.println("User typed: " + input);
           }
           static {
               System.loadLibrary("Prompt");
           }
       }
      
      
    3. Generate C/C++ header file by "javah -jni CLASS_NAME (Prompt)"
    4. Prompt.h
      /* DO NOT EDIT THIS FILE - it is machine generated */
      #include <jni.h>
      /* Header for class Prompt */
      
      #ifndef _Included_Prompt
      #define _Included_Prompt
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
       * Class:     Prompt
       * Method:    getLine
       * Signature: (Ljava/lang/String;)Ljava/lang/String;
       */
      JNIEXPORT jstring JNICALL Java_Prompt_getLine
        (JNIEnv *, jobject, jstring);
      
      #ifdef __cplusplus
      }
      #endif
      #endif
      
      The name of the C function is formed by concatenating the "Java_" prefix, the "CLASS_NAME", and the "METHOD_NAME". Parameters of Java_Prompt_getLine():
      • JNIEnv *: The first parameter, the JNIEnv interface pointer, points to a location that contains a pointer to a function table. A JNI interface pointer (JNIEnv*) is passed as an argument for each native function mapped to a Java method, allowing for interaction with the JNI environment within the native method.(from Wiki)
      • jobject: The second argument differs depending on whether the native method is a static or an instance method.
        • The second argument to an instance native method is a reference to the object on which the method is invoked, similar to the "this" pointer in C++.
        • The second argument to a static native method is a reference to the class in which the method is defined.
    5. Implementation of Prompt.c
      • Native method code must use the appropriate JNI functions to convert jstring objects to C/C++ strings.
      (version 1 for JDK1.1)Prompt.c
      #include <stdio.h>
      #include <jni.h>
      #include "Prompt.h"
      
      JNIEXPORT jstring JNICALL 
       Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
       {
           char buf[128];
           const jbyte *str;
           str = (*env)->GetStringUTFChars(env, prompt, NULL);
           if (str == NULL) {
               return NULL; /* OutOfMemoryError already thrown */
           }
           printf("%s", str);
           (*env)->ReleaseStringUTFChars(env, prompt, str);
           /* We assume here that the user does not type more than
            * 127 characters */
           scanf("%s", buf);
           return (*env)->NewStringUTF(env, buf);
       }
      
      • GetStringUTFChars: through the JNIEnv interface pointer to read the contents of the string, and converts the jstring reference from Unicode(JAVA) to UTF-8(C/C++).
      • Do not forget to check the return value of GetStringUTFChars.
        • A chance to fail memory allocation: Because the JVM implementation needs to allocate memory to hold the UTF-8 string. When it happens, GetStringUTFChars returns NULL and throws an OutOfMemoryError exception.
      • ReleaseStringUTFChars: When no longer needs the UTF-8 string returned by GetStringUTFChars; Free the memory taken by the UTF-8 string.
      • New JNI String Functions in Java 2 SDK Release 1.2
        (version 2 for JDK1.2)Prompt2.c
        #include <stdio.h>
        #include <jni.h>
        #include "Prompt.h"
        
        JNIEXPORT jstring JNICALL 
          Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
          {
            /* assume the prompt string and user input has less than 128
               characters */
            char outbuf[128], inbuf[128];
            int len = (*env)->GetStringLength(env, prompt);
            (*env)->GetStringUTFRegion(env, prompt, 0, len, outbuf);                    
            printf("%s", outbuf);
            scanf("%s", inbuf);
            return (*env)->NewStringUTF(env, inbuf);
          }
        
      • GetStringUTFRegion: Copies the content of a string to or from a pre-allocated C buffer in the UTF-8 format. It performs no memory allocation, we need not check for possible out-of-memory conditions.
      • (The above code, however, lacks the necessary checks to ensure that the prompt string contains less than 128 characters.)
    Summary of JNI String Functions

    Choosing among JNI String Functions


  • Accessing Arrays
  • The JNI treats primitive arrays and object arrays differently.
    • Primitive arrays: Primitive arrays contain elements that are of primitive types such as int and boolean. ex. int[ ] iarr; float[ ] farr;
    • Object arrays: Object arrays contain elements that are of reference types such as class instances and other arrays. ex. Object[ ] oarr; int[ ][ ] arr2;
    1. (Example) IntArray.java: calls a native method sumArray that adds up the contents of an int array.
    2. IntArray.java
      class IntArray {                                                                
          private native int sumArray(int[] arr);
          public static void main(String[] args) {
              IntArray p = new IntArray();
              int arr[] = new int[10];
              for (int i = 0; i < 10; i++) {
                  arr[i] = i;
              }
              int sum = p.sumArray(arr);
              System.out.println("sum = " + sum);
          }
          static {
              System.loadLibrary("IntArray");
          }
      }
      
      
      
    3. Generate C/C++ header file by "javah -jni CLASS_NAME (IntArray)"
    4. IntArray.h
      /* DO NOT EDIT THIS FILE - it is machine generated */                           
      #include <jni.h>
      /* Header for class IntArray */
      
      #ifndef _Included_IntArray
      #define _Included_IntArray
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
       * Class:     IntArray
       * Method:    sumArray
       * Signature: ([I)I
       */
      JNIEXPORT jint JNICALL Java_IntArray_sumArray
        (JNIEnv *, jobject, jintArray);
      
      #ifdef __cplusplus
      }
      #endif
      #endif
      
      
      
    5. Implementation of IntArray.c
    6. version 1 using GetIntArrayRegion() - IntArray.c
      #include <jni.h>
      #include "IntArray.h"
      
      JNIEXPORT jint JNICALL 
        Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
        {
           jint buf[10];
           jint i, sum = 0;
           (*env)->GetIntArrayRegion(env, arr, 0, 10, buf);
           for (i = 0; i < 10; i++) {
               sum += buf[i];
           }
           return sum;
        }
      
      • GetIntArrayRegion: to copy all the elements in the integer array into a C buffer (pre-allocated buf).
        • The third argument is the starting index of the elements.
        • The fourth argument is the number of elements to be copied.
      version 2 using GetIntArrayElements() - IntArray2.c
      #include <jni.h>
      #include "IntArray.h"
      
      JNIEXPORT jint JNICALL 
        Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
        {
          jint *carr;
          jint i, sum = 0;
          carr = (*env)->GetIntArrayElements(env, arr, NULL);
          if (carr == NULL) {
              return 0; /* exception occurred */
          }
          for (i=0; i<10; i++) {
              sum += carr[i];
          }
          (*env)->ReleaseIntArrayElements(env, arr, carr, 0);
          return sum;
        }
      
      • The JNI supports a family of Get/Release<type>ArrayElements functions that allow the native code to obtain a direct pointer to the elements of primitive arrays. Because the underlying garbage collector may not support pinning, the virtual machine may return a pointer to a copy of the original primitive array.
    Summary of JNI Primitive Array Functions

    Choosing among JNI Primitive Array Functions

2010年9月23日 星期四

Using Java Native Interface on Ubuntu 10.04

從無到有: to achieve Java Native Interface(JNI) on Ubuntu 10.04
  • How to install Java SDK 1.6 on Ubuntu (manual)
    1. Download the Java JDK 1.6 from Sun's site.
    2. http://java.sun.com/javase/downloads/index.jsp 
    3. Select the Java SE Development Kit (JDK)
    4. This leads to a page allowing us to select the Operating System, usually we just choose "Linux".
    5. Then we came to a page which shows the available packages. I click on "jdk-6u21-linux-i586.bin" because this version is a package resembling to a tar.gz
    6. Once the newly downloaded file is here, we need to extract it to a folder named "jdk1.6.0_21" (for example).
    7. It's better then to move  Jdk1.6.0_21 folder to "/usr/local/" which is the standard folder for programs or GUI console.
    8. We will proceed to add the JAVA_HOME variable to the system. To do this, open bash.bashrc file with the following command:
    9. $sudo vi /etc/bash.bashrc
      At the end of the file put the following instructions.
      export JAVA_HOME=/usr/local/jdk1.6.0_21
      
      export PATH=$JAVA_HOME/bin:$PATH
      This makes sure that the JAVA_HOME variable is always available in the system.
    10. Open a new console and run the command:
    11. $java -version
      To which we assume the following result:
      java version "1.6.0_21"
      Java(TM) SE Runtime Environment (build 1.6.0_21-b06)
      Java HotSpot(TM) Server VM (build 17.0-b16, mixed mode)
  • Achieve a simple JNI implementation on Ubuntu (Java call C/C++)
  • Example: To Pass a string from Java to native method (C++) and prints personalized Hello World.
    1. HelloWorlo.java: main function to call native method
    2. class HelloWorld {
          public native void sayHi(String name);
          static
          {
              System.loadLibrary("HelloWorld");
          }
          public static void main(String[] args)
          {
              HelloWorld h = new HelloWorld();
              h.sayHi("Ryan Cho");
          }
      }
      
      We announce our intention to use native method "sayHi" from "HelloWorld" library -- System.loadLibrary("HelloWorld").
    3. Compile this class and generate class file "HelloWorld.class":
    4. javac HelloWorld.java
    5. Generate the header for native method:
    6. javah -jni HelloWorld
      The header file for native method "HelloWorld.h" is generated.
      HelloWorld.h: The header file for native method (automatic generated)
      /* DO NOT EDIT THIS FILE - it is machine generated */
      #include <jni.h>
      /* Header for class HelloWorld */
      
      #ifndef _Included_HelloWorld
      #define _Included_HelloWorld
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
       * Class:     HelloWorld
       * Method:    sayHi
       * Signature: (Ljava/lang/String;)V
       */
      JNIEXPORT void JNICALL Java_HelloWorld_sayHi
        (JNIEnv *, jobject, jstring);
      
      #ifdef __cplusplus
      }
      #endif
      #endif
      
    7. Write implementation C++ file (HelloWorld.cpp):
    8. HelloWorld.cpp: The implementation file for native method.
      #include <jni.h>
      #include <iostream>
      #include "HelloWorld.h"
      
      using namespace std;
      
      JNIEXPORT void JNICALL Java_HelloWorld_sayHi
        (JNIEnv *env, jobject o, jstring s)
      {
          const char *str = env->GetStringUTFChars(s, 0);
          cout << "Hello! " << str << endl;
          env->ReleaseStringUTFChars(s, str);
      }
      
    9. There is no garbage collector in C++ world, so we need to do some cleanup. We save this as HelloWorld.cpp and compile it to generate shared library:
    10. g++ -shared -I/usr/local/jdk1.6.0_21/include -I/usr/local/jdk1.6.0_21/include/linux HelloWorld.cpp -o libHelloWorld.so
      !Attention: The path to your java include directory may be different.
    11. Run this example as following:
    12. java -Djava.library.path=. HelloWorld
      The result like this:
      Hello! Ryan Cho

Install sun-java6-jdk on Ubuntu 10.04

I just had trouble to install the Sun Java6 JDK after updating to Ubuntu 10.04. The problem was that the system couldn’t find the package sun-java6-sdk and apt-get gave me the message:

Package sun-java6-jdk is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
E: Package sun-java6-jdk has no installation candidate

What I did to solve this problem was to add a new source

sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner"

After this...

sudo apt-get update
sudo apt-get install sun-java6-jdk

It worked fine for me.

2010年9月17日 星期五

Qt4 學習筆記(九):循序容器 (QVector、QLinkedList、QList) 與迭代器 (iterator)

  • QVector、 QLinkedList與QList是Qt所提供的幾個常用容器類別。
    • QVector將項目(item)儲存在鄰接的記憶體空間之中,提供基於索引(index-based)存取方式的容器類別。
    • QLinkedList以鏈結(Linked)的方式儲存項目,提供基於迭代器(iterator- based)存取方式的容器類別。
    • QList提供基於索引的快速存取容器類別,內部使用指標陣列,可提供快速插入及移除項目。
  • 列出QVector、QLinkedList及QList的使用比較:
    • 如果想要有連續鄰接的記憶體空間來存放元件,則使用QVector。
    • 如果需要真正的鏈結資料結構,並使用基於迭代器的存取方式,則使用QLinkedList。
    • 在大部份情況下,QList可以滿足快速存取、插入、移除的需求,並可提供基於索引的存取方式。
  • QVector
  • QVector的基本使用方式,建立一個可容納兩個元素的QVector,並使用索引方式存取元素值:
    #include <iostream>
    #include <vector>
    using namespace std;
    
    QVector<double> vect(2);
    vect[0] = 1.0;
    vect[1] = 2.0;
    
    for (int i = 0; i < vect.count(); ++i) {
        // 使用 [] 運算子指定索引存取
        cout << vect[i] << endl;
    }
    
    for (int i = 0; i < vect.count(); ++i) {
        // 使用 at() 方法指定索引存取
        cout << vect.at(i) << endl;
    }
    
    可以使用QVector的append()方法來加入元素,使用remove()方法來移除元素,使用insert()方法來插入元素,例如append()的使用如下:
    vect.append(3.0);
    vect.append(4.0);
    
    或者是使用<<運算子附加元素:
    vect << 5.0 << 6.0;
    
    QVector提供的是鄰接的記憶體空間以存取物件,所以對於循序存取或使用索引,效率較高,但如果要插入或移除元素時,效率就會低落。QVector的子類別QStack提供了push()、pop()、top()等方法,方便進行堆疊結構的物件管理。
  • QList
  • QList提供的是基於索引的存取方式,其內部實作使用了指標陣列,陣列中每個指標指向所要儲存的元素,結合了QVector與QLinkedList的優點,提供快速存取與插入、移除,其索引存取方式或可用的方法與QVector是類似的,也可以使用<<運算子來附加元素。
    QList<QString> list;
    list << "caterpillar" << "momor" << "bush";
    
    for(int i = 0; i < list.size(); ++i) {
        cout << list[i].toAscii().data() << endl;
    }
    cout << endl;
        
    for(int i = 0; i < list.size(); ++i) {
        cout << list.at(i).toAscii().data() << endl;
    }    
    cout << endl;
    
    QList的子類別QStringList為Qt中應用很廣的類別,可以讓您儲存QString物件,QList的子類別QQueue則提供了佇列結構的容器管理。
  • QIterator
    • 關於迭代器於容器類別的使用,Qt提供兩種風格的迭代器:Java風格與STL風格。Java風格的迭代器使用上就如同Java的迭代器,使用這種迭代器對於Java開發人員較為容易,然而STL風格的迭代器則提供更有彈性的操作。
    • Java-Style iterator
    簡單示範在QList上使用Java風格迭代器:
    // Java-Style Iterator
    QList<QString> list;
    list << "caterpillar" << "momor" << "bush";
        
    QListIterator<QString> iterator(list);
    while (iterator.hasNext()) {
        cout << iterator.next().toAscii().data() << endl;
    }
    
    與Java迭代器類似的,hasNext()測試是否有下一個元素,next()傳回下一個元素,其它還有hasPrevious()、previous()等方法可以使用。Java風格的迭代器有兩種:唯讀與可讀寫。QListIterator是唯讀迭代器,對於可讀寫迭代器,命名上會加上Mutable,例如QMutableListIterator,除了next()、previous()等方法之外,還提供了insert()、remove()等方法可以使用,以QLinkedList使用Java風格迭代器為例:
    QLinkedList<QString> list;
    list << "caterpillar" << "momor" << "bush";
    
    QMutableLinkedListIterator<QString> rwIterator(list);
    while (rwIterator.hasNext()) {
        if(rwIterator.next() == "momor") {
            rwIterator.insert("bee");
            break;
        }
    }
    
    QLinkedListIterator<QString> rIterator(list);
    while (rIterator.hasNext()) {
        cout << rIterator.next().toAscii().data() << endl;
    }
    
    • STL-Style iterator
    • 關於使用STL風格迭代器,可以使用容器類別的begin()方法傳回基於STL的迭代器,它指向容器的第一個元素位址,end()方法則傳回指向容器最後一個元素之後的位址。您可以如下使用基於STL的迭代器:
    // STL-Style Iterator
    QList<QString> list;
    list << "caterpillar" << "momor" << "bush";
    
    QList<QString>::const_iterator i = list.begin();
    while (i != list.end()) {
        cout << (*i).toAscii().data() << endl;
        ++i;
    }
    
    STL風格的迭代器一樣有兩種,C<T>::const_iterator形式的迭代器宣告為唯讀,可以讀取資料,但無法修改資料,C<T>::iterator形式的迭代器則可以修改資料,例如:
    QList<QString> list;
    list << "caterpillar" << "momor" << "bush";
        
    QList<QString>::iterator i = list.begin();
    while (i != list.end()) {
        (*i) = (*i) + ".onlyfun";
        cout << (*i).toAscii().data() << endl;
        ++i;
    }
    
    對於簡單的循序存取,Qt提供了foreach虛擬關鍵字(pseudo-keyword),以標準的for迴圈實作,例如您可以如下循序取出QList中的元素:
    QList<QString> list;
    list << "caterpillar" << "momor" << "bush";
        
    foreach(QString str, list) {
        cout << str.toAscii().data() << endl;
    }
    

[C++] STL idiom Vector

  • 基本操作
Vector的STL型式,其實就是以物件導向的方式來操作vector,以物件的方式來操作vector是比較被鼓勵的方式,以下將介紹幾個vector的基本操作。
  • 建構一個元素為空的vector物件:
  • vector<int> ivector;
    
  • 將元素放入vector中,可以使用push_back()
  • for(int i = 0; i < 5; i++) {
        ivector.push_back(i);
    }
    
  • 將元素循序取出,則可以begin()與end()方法分別傳回起始位置的iterator與結束位置的iterator
  • for(vector<int>::iterator it = ivector.begin(); it != ivector.end(); it++) {
    
        cout << *it << " ";
    }
    cout << endl;
    
iterator是標準函式庫定義類別(Class),它是一個指標,指向iterator物件的真正位址,對它進行++的動作,表示移動至 iterator的下一個元素,對它使用*運算子(Dereferences operator),表示提取出iterator目前位址的值,如果iterator走訪至結束位置的iterator的位址,表示元素走訪完畢。

可以使用下標運算子[ ]來存取vector的元素,但實際上要知道vector與陣列本質上是不相同的,如最上頭那樣宣告一個空的vector物件時,其容量(capacity)為0,長度(size)也為0,所以此時不能使用ivector[0]來取得第一個元素值,因為實際上ivector中還沒有任何的元素。

當使用push_back()將元素加入vector時,vector的長度會自動增長,由於每次增長度都要配置記憶體過於沒有效率,所以vector會自動先增加足夠的容量,當元素的長度超過容量時,才會再重新配置新的容量,可以使用capacity()取得vector容量,使用size()取得元素長度。
#include <iostream>
#include <vector>
using namespace std; 

int main() { 
    vector<int> ivector;
 
    for(int i = 0; i < 10; i++) {

        ivector.push_back(i);
    }
 
    for(vector<int>::iterator it = ivector.begin(); it != ivector.end(); it++) {
 
        cout << *it << " ";
    }
    cout << endl;
 
    cout << "capacity: " << ivector.capacity() << endl
         << "size: " << ivector.size() << endl;
 
    return 0; 
}
執行結果
0 1 2 3 4 5 6 7 8 9
capacity: 16
size: 10
  • Vector演算
對vector進行排序、尋找、反轉等操作,可以使用標準函式庫中的泛型演算法,要使用泛型演算法必須先含入表頭檔:
#include <algorithm>
下面這個程式直接示範了排序、尋找、反轉等操作:
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std; 

int main() { 
    int iarr[] = {30, 12, 55, 31, 98, 11, 41, 80, 66, 21};
    vector<int> ivector(iarr, iarr + 10);
 
    // 排序 
    sort(ivector.begin(), ivector.end());
 
    for(vector<int>::iterator it = ivector.begin(); it != ivector.end(); it++) {
 
    cout << *it << " ";
    }
    cout << endl;

    cout << "輸入搜尋值:";
    int search = 0;
    cin >> search;
                              // 搜尋
    vector<int>::iterator it = find(ivector.begin(), ivector.end(), search);
 
    if(it != ivector.end()) {
        cout << "找到搜尋值!" << endl;
    }
    else {
        cout << "找不到搜尋值!" << endl;
    }
 
    // 反轉 
    reverse(ivector.begin(), ivector.end());
 
    for(vector<int>::iterator it = ivector.begin();
        it != ivector.end();
        it++) {
 
        cout << *it << " ";
    }
    cout << endl;
 
    return 0; 
}
11 12 21 30 31 41 55 66 80 98
輸入搜尋值:41
找到搜尋值!
98 80 66 55 41 31 30 21 12 11

2010年9月14日 星期二

[C++] 標準類型轉換:static_cast, dynamic_cast, reinterpret_cast, and const_cast

  • static_cast
    • 可用於轉換基底類別指標為衍生類別指標,也可用於傳統的資料型態轉換。
    舉例來說,在指定的動作時,如果右邊的數值型態比左邊的數值型態型態長度大時,超出可儲存範圍的部份會被自動消去,例如將浮點數指定給整數變數,則小數的部份會被自動消去,例子如下,程式會顯示3而不是3.14:
    int num = 0;
    double number = 3.14;
    num = number;
    cout << num;
    
    由於失去的精度,在編譯時編譯器會提出警訊: [Warning] converting to `int' from `double' 如果確定這個動作是您想要的,那麼您可以使用顯式型態轉換(Explicit type conversion)或稱之為強制轉型(Cast),例如:
    int num = 0; 
    double number = 3.14; 
    num = static_cast(number);
    
    在兩個整數型態相除時,您也可以進行型態轉換,將其中一個型態轉換至double型態再進行運算,下例會顯示3.3333:
    int number = 10;
    cout << static_cast<double>(number) / 3;
    
    static_cast是Standard C++新增加的轉型語法,在這之前顯式型態轉換可以使用以下的舊語法,基於向後相容,Standard C++仍支援這種舊語法,但鼓勵使用新風格的語法
    int number = 10;
    cout << (double) number/ 3;
    
  • dynamic_cast
  • 使用static_cast(甚至是傳統的C轉型方式)將基底類別指標轉換為衍生類別指標,這種轉型方式稱為強制轉型,但是在執行時期使用強制轉型有危險性,因為編譯器無法得知轉型是否正確,執行時期真正指向的物件型態是未知的,透過簡單的檢查是避免錯誤的一種方式:
    if(typeid(*base) == typeid(Derived1)) {
        Derived1 *derived1 = static_cast<derived1*>(base);
        derived1->showOne();
    }
    
    為了支援執行時期的型態轉換動作,C++提供了dynamic_cast用來將一個基底類別的指標轉型至衍生類別指標,稱之為「安全向下轉型」(Safe downcasting),它在執行時期進行型態轉換動作,首先會確定轉換目標與來源是否屬同一個類別階層,接著才真正進行轉換的動作,檢驗動作在執行時期完成,如果是一個指標,則轉換成功時傳回位址,失敗的話會傳回0,如果是參考的話,轉換失敗會丟出bad_cast例外。 可以將執行時期型態資訊(RTTI)中的showWho()函式如下修改:
    void showWho(Base *base) {
        base->foo();
        
        if(Derived1 *derived1 = dynamic_cast>derived1*<(base)) {
            derived1->showOne();
        }
        else if(Derived2 *derived2 = static_cast>derived2*<(base)) {
            derived2->showTwo();    
        } 
    }
    
    由於dynamic_cast轉換失敗的話會傳回0,因而運算的結果不可使用,必須先行對轉換結果作檢查才可以,如上例所示。 如果使用參考的話,dynamic_cast在轉換失敗之後會丟出bad_cast例外,所以您必須使用try...catch來處理例外,例如:
    #include <iostream>
    #include <typeinfo>
    using namespace std;
    
    class Base {
    public:
        virtual void foo() = 0;
    };
    
    class Derived1 : public Base {
    public:
        void foo() {
            cout << "Derived1" << endl;
        }
    
        void showOne() {
            cout << "Yes! It's Derived1." << endl;
        }
    };
    
    class Derived2 : public Base {
    public:
        void foo() {
            cout << "Derived2" << endl;
        }
     
        void showTwo() {
            cout << "Yes! It's Derived2." << endl;
        }
    }; 
    
    void showWho(Base &base) {
        try {
            Derived1 derived1 = dynamic_cast<derived1&>(base);
            derived1.showOne();
        }
        catch(bad_cast) {
            cout << "bad_cast 轉型失敗" << endl;
        }
    }
    
    int main() { 
        Derived1 derived1;
        Derived2 derived2;
    
        showWho(derived1);
        showWho(derived2);
    
        return 0;
    }
    
    執行結果(使用dynamic_cast):
    Yes! It's Derived1.
    bad_cast 轉型失敗
    執行結果(使用static_cast):
    Yes! It's Derived1.
    Yes! It's Derived1.
  • reinterpret_cast
    • 用於將一種型態的指標轉換為另一種型態的指標,例如將char*轉換為int*
    #include <iostream>
    using namespace std;
    
    int main() {
        int* i;
        char* str = "test";
    
        i = reinterpret_cast<int>(str);
        cout << i << endl;
    
        return 0;
    } 
    
    (可能)執行結果:
    134514704
  • const_cast
    • const_cast用於一些特殊場合可以覆寫變數的const屬性,利用cast後的指標就可以更改變數的內部。
    使用格式:
    const_cast<常量(const)型態(指標) / 非常量型態(指標)>( 非常量變數(指標) / 常量變數(指標) );
    
    使用範例:
    #include <iostream>
    using namespace std; 
    
    void foo(const int*);
    
    int main() {
        int var = 10;
    
        cout << var << endl;
    
        foo(&var);
    
        cout << var << endl;
    
        return 0;
    }
    
    void foo(const int* p) {
        int *v = const_cast<int *>(p);
        *v = 20;
    }
    
    執行結果:
    10
    20

Qt4 學習筆記(八):Custom Event and Post Event

  • Custom Event and Post Event
自訂事件類型,最簡單的方式,是透過QEvent::Type指定事件類型的常數值,在建構QCustomEvent時作為建構引數並透過postEvent()傳送事件,例如:
const QEvent::Type MyEvent = (QEvent::Type) 9393;
...
QApplication::postEvent(object, new QCustomEvent(MyEvent));
自訂事件必須定義事件號碼(Event number),自定義的事件號碼必須大於QEvent::Type的列舉值,通常1024以下的值是保留給Qt預先定義的事件類型來使用。object是事件的接受者,使用 postEvent()方法時,事件必須以new的方式建立,在事件處理完畢之後,會自動將事件delete,postEvent()會將事件放至事件佇列的尾端,然後立即返回。若要強迫Qt馬上處理先前postEvent()排到佇列的事件,則可以使用sendPostedEvents()。
您可以使用sendEvent()方法,事件會立即送至接受者,sendEvent()方法的事件不會被delete,所以通常建立在堆疊(Stack)區,例如:
CustomEvent event("Event Message");
QApplication::sendEvent(object, &event);
自訂的事件類型必須是QEvent的子類別,通常繼承QCustomEvent類別,建立自訂事件類別可以獲得更多的型態安全(Type safety)。

  • 要處理自訂事件,可以重新定義customEvent()方法,例如:
void CustomWidget::customEvent(QCustomEvent *event) {
    CustomEvent *customEvent = static_cast<CustomEvent *>(event);
    ....
}
  • 或是重新定義event()方法,將自訂事件分派給其它函式或直接在event()中處理,例如:
bool CustomWidget::event(QEvent *event) {
    if (event->type() == MyCustomEventType) {
        CustomEvent *myEvent = static_cast<CustomEvent *>(event);
        // 對自訂事件的處理,或呼叫其它函式來處理事件
        return true;
    }
    return QWidget::event(event);
}

Qt4 學習筆記(七):Event Filter

  • Event Filter
Qt將事件封裝為QEvent實例之後,會呼叫QObject的event()方法並將QEvent實例傳送給它,在某些情況下,會希望物件在執行event()處理事件之前,先對一些事件進行處理或過濾,然後再決定是否呼叫event()方法,這個時候就可以使用事件過濾器。

舉例來說,對QWidget按鍵事件的Tab鍵處理而言,如果圖形介面中有很多的元件,每個圖型元件都要如當中的範例重新定義event()方法,顯然是非常沒有效率且沒什麼維護性的方法。

可以藉由自定義一個物件繼承QObject(或其子類別),重新定義它的eventFilter()方法,例如自定義了一個FilterObject,希望Tab鍵可以用來將焦點轉移至下一個子元件:
bool FilterObject::eventFilter(QObject *object, QEvent *event) {
    if(event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->key() == Qt::Key_Tab) {
            // 處理Tab鍵
            return true;
        }
    }
    return false;
}
eventFilter()的object參數表示事件發生的來源物件,eventFilter()若傳回false,則安裝該事件過濾器的物件之event()就會繼續執行,若傳回true,則安裝該事件過濾器的物件之event()方法就不會被執行,由此進行事件的攔截處理。

要為指定的物件安裝事件過濾器,可以使用物件的installEventFilter()方法,例如:
QLineEdit *nameEdit = new QLineEdit;
QLineEdit *addressEdit = new QLineEdit;
...
FilterObject filter = new FilterObject;
...
nameEdit->installEventFilter(filter);
addressEdit->installEventFilter(filter);
....

Notes:
  • 也可以將事件過濾器安裝在QApplication,在任何的事件發生後呼叫每個物件的event()方法之前,會先經過事件過濾器,這可提供更多控制應用程式事件的能力。
  • Qt的事件迴圈與sendEvent()方法會呼叫QCoreApplication(QApplication的父類別)的notify()以分派事件,如果想要完全控制Qt應用程式的事件,則可以重新定義notify()方法。
P.S.
由此可以看出Qt事件處理的五個層次:
  1. 重新定義事件處理者
  2. 重新定義event()方法
  3. 為個別物件安裝事件過濾器
  4. 為QApplication安裝事件過濾器
  5. 重新定義QCoreApplication的notify()方法

2010年9月13日 星期一

Qt4 學習筆記(六):Event Accept or Ignore and event() method

  • Event Accept or Ignore and event() method
不同類型的事件,都有對應的事件處理函式,它們接受QEvent的特定子類別作為引數,像是下例中mousePressEvent()事件處理函式上的 QMouseEvent,可以針對事件的不同狀況作特定處理,而其它未指定的狀況,則呼叫父類別對應的的事件處理函式,讓父類別預先定義的事件處理來完成。
void CustomLabel::mousePressEvent(QMouseEvent *event) {
     if (event->button() == Qt::LeftButton) {
         // 指定處理當滑鼠左鍵按下事件
         // ....
     } else {
         // 由父類別所定義的事件處理函式來處理
         QLabel::mousePressEvent(event);
     }
}
void CustomLabel::mouseReleaseEvent(QMouseEvent *event) {
    // 滑鼠放開事件處理....
}

每個可傳播的事件都有accept()與igore()兩個方法,用以告知Qt應用程式,這個事件處理者是否接受或忽略此一事件,如果事件處理者中呼叫事件的accept(),則事件不會再進一步傳播,若呼叫了ignore(),則Qt應用程式會嘗試尋找另一個事件的接受者,您可以藉由isAccepted()方法得知事件是否被接受。

一般來說,除了QCloseEvent之外,很少直接呼叫accept()或ignore(),如果您接受事件,則在事件處理者當中實作對事件的處理(如上例的if陳述句),如果您不接受事件,則直接呼叫父類別的事件實作(如上例的else陳述句),對於QWidget來說,預設的實作如下,由於QWidget預設的實作是呼叫ignore(),這讓事件可以向父元件傳播。

void QWidget::keyPressEvent(QKeyEvent *event) {
    event->ignore();
}
  • QCloseEvent則建議直接呼叫accept()與ignore(),accept()方法會繼續關閉的操作,ignore()則會取消關閉的操作:
void MainWindow::closeEvent(QCloseEvent *event) {
    if (continueToClose()) {
        event->accept();
    } else {
        event->ignore();
    }
}

QObject的event()方法通常用於分派事件,但在某些情況下,您希望在事件分派給其它事件處理者之前,先行作一些處理,則可以重新定義event()方法,例如在視窗程式中,Tab鍵按下時希望其將焦點移至下一個圖型元件,而不是直接讓目前焦點的圖形元件直接處理Tab鍵,則您可以在繼承QWidget子類別時,重新定義其event()方法,例如:

bool CustomWidget::event(QEvent *event) {
    if (event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->key() == Qt::Key_Tab) {
            // 處理Tab鍵
            return true;
        }
    }
    return QWidget::event(event);
}
Notes:
      if (event->type() == QEvent::KeyPress)
    1. 在執行時期想要知道所取得之QEvent類型,可以使用QEvent的type()方法取得常數值,並與QEvent::Type作比對。
    2. QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
    3. 對event作強制形態轉換為QKeyEvent並宣告為keyEvent,新的keyEvent可以使用QKeyEvent的成員。

事件若順利處理完畢,則要傳回true,表示這個事件被接受並處理,QApplication可以繼續事件佇列中的下一個事件處理,若傳回false,則QApplication嘗試尋找下一個可以處理事件的方法。不用透過呼叫事件的accept()或ignore(),這也沒有意義,accept()或ignore()是用來在特定的事件處理者之間作溝通,而event()的true或false,是用來告知QApplication的notify()方法是否處理下一事件,以QWidget的event()實作來說,它是根據事件的isAccepted()來判斷該傳回true或false。

bool QWidget::event(QEvent *event) {
    switch (event->type()) {
    case QEvent::KeyPress:
         keyPressEvent((QKeyEvent *)event);
        if (!((QKeyEvent *)event)->isAccepted())
            return false;
        break;
    case QEvent::KeyRelease:
        keyReleaseEvent((QKeyEvent *)event);
        if (!((QKeyEvent *)event)->isAccepted())
            return false;
        break;
        ...
    }
    return true;
}
  • 當重新定義event()的情況是自訂CustomEvent子類型時,可以將之分派給其它函式或直接在event()中處理,自訂事件必須是CustomEvent的子類別,也可以直接實作customEvent()方法來處理自訂事件,例如:
bool CustomWidget::event(QEvent *event) {
    if (event->type() == MyCustomEventType) {
        CustomEvent *myEvent = static_cast<CustomEvent *>(event);
        // 對自訂事件的處理,或呼叫其它函式來處理事件
        return true;
    }
    return QWidget::event(event);
}

本文參考自"良葛格:Qt4學習筆記"

Qt4 學習筆記(五):Event Class & Event Handler

  • Event Class & Event Handler
    • 當執行QApplication的exec()方法之後,應用程式會進入事件迴圈來傾聽(Listen)應用程式的事件,事件來源通常是視窗系統,例如使用者的滑鼠事件或鍵盤事件,事件來源可以是Qt應用程式事件本身,例如QTimerEvent,事件來源也可以是使用者自定義的事件,透過QApplicaiton的sendEvent()或postEvent()來發送。
    • 當事件發生時,Qt為之建立事件實例,QEvent是Qt中所有事件的基礎類別,Qt所建立的事件實例為QEvent的子類別實例,並將之傳送給 QObject子類別實例的event()函式,event()這個函式本身通常不直接處理事件,而是基於所傳送的事件類型,分派給處理特定類型的事件處理者(Event Handler)。

以下是簡單的事件處理示範,繼承了QLabel並重新定義了相關的事件處理者,當滑鼠移動、按下或放開時,顯示滑鼠游標的所在位置。

#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>

class EventLabel : public QLabel {
protected:
    void mouseMoveEvent(QMouseEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
};

void EventLabel::mouseMoveEvent(QMouseEvent *event) {
    QString msg;
    msg.sprintf("Move: (%d, %d)", event->x(), event->y());
    this->setText(msg);  
}

void EventLabel::mousePressEvent(QMouseEvent *event) {
    QString msg;
    msg.sprintf("Press: (%d, %d)", event->x(), event->y());
    this->setText(msg);
}

void EventLabel::mouseReleaseEvent(QMouseEvent *event) {
    QString msg;
    msg.sprintf("Release: (%d, %d)", event->x(), event->y());
    this->setText(msg);
}

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
 
    EventLabel *label = new EventLabel;
    label->setWindowTitle("MouseEvent Demo");
    label->resize(300, 200);
    label->show();
    
    return app.exec();
}

Notes:
  1. QEvent是Qt中所有事件的基礎類別,最常見的事件類型皆為其子類別,像是滑鼠事件的QMouseEvent、鍵盤事件的QKeyEvent、縮放事件的QResizeEvent等,這些子類別事件皆加入其特定的函式。
    • 滑鼠事件的x()、y():指出發生滑鼠事件時,滑鼠游標的x、y座標。
    • 鍵盤事件的key():可以取得目前所按下的按鍵常數。
  2. 以圖形元件來說,通常會繼承QWidget或其子類別,並重新定義事件處理者,也就是事件處理函式,QWidget定義了:keyPressEvent()、keyReleaseEvent()、mouseDoubleClickEvent()、mouseMoveEvent()、mousePressEvent()、mouseReleaseEvent()等事件處理函式,並接受QEvent的特定子類別實例作為引數,只要根據想要處理的事件重新定義對應的函式即可進行事件處理。
P.S.
Qt的事件跟Signal、Slot機制是不同的。Signal與Slot的機制是同步的(Synchronous),Signal是由物件發出的,使用QObject的connect()連接物件上定義的Slot來立即處理。

Qt的事件可以是非同步的(Asynchronous)的,Qt使用一個事件佇列來維護,新的事件產生時基本上會被排到佇列的尾端,前一個事件處理完成,再從佇列的前端取出下一個佇列來處理,必要的時候,Qt的事件也可以是同步的,而事件還可以使用事件過濾器 進行過濾處理。

本文參考自"良葛格:Qt4學習筆記"

Qt4 學習筆記(四):Custom Signal & Slot

  • Custom Signal & Slot
    • 除了使用Qt現有元件預先定義好的Signal與Slot之外,也可以定義自己物件的Signal與Slot,方式是繼承QObject或它的子類別(例如QWidget)。
    • 現在改以定義一個物件,當拉桿拉動時,必須通知該物件儲存拉桿的游標值,而物件儲存的游標值有變動時,LCD數字顯示也必須更新,這樣的一個物件不是圖形元件,它是個資料模型,用以儲存與圖形介面無關的資料。

定義一個Model Class如下:
Model.h
#ifndef MODEL_H
#define MODEL_H
#include <QObject>

class Model : public QObject {
     Q_OBJECT

 public:
     Model() { m_value = 0; }
     int value() const { return m_value; }

 public slots:
     void setValue(int);

 signals:
     void valueChanged(int);

 private:
     int m_value;
 };

#endif

  • 簡介Qt的Meta-Object System,它基於以下三個部份:
    • QObject類別
    • Q_OBJECT巨集
    • Meta-Object Compiler(moc)

Qt管理的物件必須繼承QObject類別,以提供Qt物件的Meta訊息,若要實作Signal與Slot機制,則必須包括Q_OBJECT巨集,moc會處理Qt的C++擴充(Meta-Object System),使用moc讀取C++標頭檔案,若發現類別定義中包括Q_OBJECT巨集,就會產生Qt meta-object相關的C++程式碼。
若使用qmake來產生Makefile,若必要時,檔案中就會包括moc的使用,程式完成建置之後,會在release或debug目錄中,找到moc_Model.cpp,即為moc所提供的C++程式碼。


public slots:
void setValue(int);

signals:
void valueChanged(int);

在Model中,自訂了Signal與Slot,slots與signals關鍵字其實是巨集,將被展開為相關的程式碼。
Slot:定義setValue(int),將接收Signal傳來的整數資料,如果不想接受資料的話,int可以省去。
Signal:定義valueChanged(int),表示將發出的Signal會帶有一個整數。

Model.cpp
#include "Model.h"

void Model::setValue(int value) {
     if (value != m_value) {
         m_value = value;
         emit valueChanged(m_value);
     }
}

Slot只是一般的函式,可以由程式的其它部份直接呼叫,也可以連接至Signal,若有呼叫setValue(),程式執行到emit時,就會發出valueChanged()的Signal。

main.cpp
#include <QApplication>
#include <QWidget>
#include <QSlider>
#include <QLCDNumber>
#include "Model.h"

int main(int argc, char *argv[]) {
     QApplication app(argc, argv);

     QWidget *parent = new QWidget;
     parent->setWindowTitle("Signal & Slot");
     parent->setMinimumSize(240, 140);
     parent->setMaximumSize(240, 140);
          
     QLCDNumber *lcd = new QLCDNumber(parent);
     lcd->setGeometry(70, 20, 100, 30);
     
     QSlider *slider = new QSlider(Qt::Horizontal, parent);
     slider->setRange(0, 99);
     slider->setValue(0);
     slider->setGeometry(70, 70, 100, 30);
     
     Model model;
     
     QObject::connect(slider, SIGNAL(valueChanged(int)),
                      &model, SLOT(setValue(int)));
     QObject::connect(&model, SIGNAL(valueChanged(int)),
                      lcd, SLOT(display(int)));
     
     parent->show();
     
     return app.exec();
}

QObject::connect(slider, SIGNAL(valueChanged(int)), &model, SLOT(setValue(int)));

QObject::connect(&model, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));

這邊使用connect()連接QSlider SIGNAL:valueChanged()及Model SLOT:setValue(),當拉動拉桿時,Model的m_value就會被設定為QSlider的游標值,Model在執行setValue()過程中會emit valueChanged() signal,由於Model SIGNAL:valueChanged()連接至QLCDNumber SLOT:display(),所以LCD顯示數字也會改變。


Signal與Slot的簽名(參數)基本上要相同,但若Signal的參數多於Slot的參數,則額外的參數會被Slot忽略。
如果要斷開Signal與Slot的連接,則使用disconnect(),例如:
QObject::disconnect(slider, SIGNAL(valueChanged(int)), &model, SLOT(setValue(int)));

本文參考自"良葛格:Qt4學習筆記"

Qt4 學習筆記(三):Signal & Slot with Parameters

  • Signal & Slot with Parameters
    • 運用拉桿改變LCD數字

在實際運用上,Signal在發出時常常是帶參數的,而相對應的Slot也可以接受參數。
以下程式將建立一個LCD數字顯示元件,以及一個拉桿元件,LCD數字將顯示目前拉桿的進度。

#include <QApplication;>
#include <QWidget>
#include <QSlider>
#include <QLCDNumber>

int main(int argc, char *argv[]) {
     QApplication app(argc, argv);

     QWidget *parent = new QWidget;
     parent->setWindowTitle("Signal & Slot");
     parent->setMinimumSize(240, 140);
     parent->setMaximumSize(240, 140);
     // equal to parent->setFixedSize(240, 140);
          
     QLCDNumber *lcd = new QLCDNumber(parent);
     lcd->setGeometry(70, 20, 100, 30);
     
     QSlider *slider = new QSlider(Qt::Horizontal, parent);
     slider->setRange(0, 99);
     slider->setValue(0);
     slider->setGeometry(70, 70, 100, 30);
     
     QObject::connect(slider, SIGNAL(valueChanged(int)), 
                      lcd, SLOT(display(int)));
     
     parent->show();
     
     return app.exec();
}

Notes:
QWidget *parent = new QWidget;
parent->setWindowTitle("Signal & Slot");
parent->setMinimumSize(240, 140);
parent->setMaximumSize(240, 140);

  1. QWidget是Qt中所有使用者圖形介面元件的父類別,可在螢幕上繪製自身,可接受滑鼠、鍵盤等介面操作,一個QWidget可以指定它的parent為哪個元件,而這也表示child可顯示的範圍將是在parent之內,parent沒有顯示的話,子元件也不會顯示。
  2. 沒有指定parent的QWidget是一個獨立視窗(window),例如先前的幾個範例,無論是QLabel或QPushButton都沒有指定parent,它們可獨立的顯示在畫面之中,只要呼叫其show()方法。
  3. QLCDNumber *lcd = new QLCDNumber(parent); QSlider *slider = new QSlider(Qt::Horizontal, parent);
  4. QLCDNumber與QSlider實例建立時,指定了這個QWidget為它的parent,所以QLCDNumber與QSlider被置入了QWidget之中成為child。
  5. QObject::connect(slider, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));
  6. 當拉動QSlider的游標,造成游標值變動時會發出valueChanged(int) Signal,參數int表示Signal帶有一個整數值,在這表示QSlider的游標值一併被發出,QLCDNumber的display(int) Slot接受Signal所帶來的整數值,可以在QLCDNumber顯示數字。

本文參考自"良葛格:Qt4學習筆記"

Qt4 學習筆記(二):Signal & Slot

  • use Signal & Slot
    • 運用按鈕關閉視窗

在Qt之中,當某個元件發生狀態改變,而另一個元件想得知其狀態改變時,作出一些相對應行為的話,可以使用Signal與Slot的機制來達到。
Signal與Slot之間,可透過QObject的靜態方法connect來連結,Signal與Slot之間的處理是同步的(Synchronized)。

#include <QApplication>
#include <QPushButton>
#include <QFont>

int main(int argc, char *argv[]) {
     QApplication app(argc, argv);

     QPushButton *btn = new QPushButton("Close");
     btn->setWindowTitle("Signal & Slot");
     btn->setFont(QFont("Courier", 18, QFont::Bold));
     btn->resize(200, 50);
     
     QObject::connect(btn, SIGNAL(clicked()), &app, SLOT(quit()));
     
     btn->show();
     
     return app.exec();
}

Notes:
QObject::connect(btn, SIGNAL(clicked()), &app, SLOT(quit()));
  1. connect()方法的第一個參數是發出Signal的物件之位址,第三個參數是對Signal有反應動作的物件之位址。
  2. SIGNAL()與SLOT()為巨集函式,是語法的一部份,所傳入的Signal或Slot為沒有參數名稱的函式簽名(function signature)。
  3. 程式中btn與app並不知道彼此的存在,而是藉由connect()連接Signal與Slot,這降低了物件之間的耦合度。

本文參考自"良葛格:Qt4學習筆記"

Qt4 學習筆記(一):First Qt Application!

  • First Qt Application!
HelloWorld.cpp
#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[]) {
     QApplication app(argc, argv);

     QLabel *label = new QLabel("Hello~World!");
     label->setWindowTitle("First Qt Application!");
     label->resize(150, 50);
     label->show();

     return app.exec();
}

Notes:
  1. 每個Qt視窗程式,都必須有且只能有一個QApplication物件,它管理了整個應用程式所需的資源〈負責程式的初始、結束及處理事件(event)的迴圈等,並提供基本的視窗外觀〉。
  2. QApplication :: exec(),這將程式的控制權交給了QApplication,exec()方法會提供一個事件處理迴圈,視窗顯示之後會不斷傾聽(listen)事件,像是鍵盤、滑鼠等動作所引發的事件。
  3. Qt的元件預設是不可視的,所以要使用show()方法將之顯示出來。
  4. Qt對於顯示字串可支援HTML格式。

2010年9月10日 星期五

C++:箭號(->) 、雙冒號(::)、點(.) 之各自用法說明

  • 箭號(->)與點(.)

    • Used in C++ Struct
    struct MyStruct {
    int member_a;
    };
    

    如果有變數 MyStruct s,那麼對於其中成員的取用為:
    MyStruct s;
    s.member_a = 1;

    如果是採用指標方法存取,譬如 MyStruct * ps,那麼相對的存取方式必須使用箭號:
    MyStruct * ps;
    ps->member_a = 1;
    • Used in C++ Class
    class MyClass { 
    public: 
    int ca_var; 
    int add(int a); 
    };
    

    當宣告為一般型態時 MyClass CA,就使用點(.)來存取Class中的成員:
    MyClass CA;
    CA.add(0);
    左邊為 Class變數

    當宣告為指標型態時 MyClass * CA,就使用箭號(->)來存取Class中的成員:
    MyClass * pCA;
    pCA->add(0);
    左邊為 Class指標


  • 雙冒號(::)


  • 雙冒號(::)只用在Class成員函式或Class成員變數中:

    class MyClass { 
    public: 
    int ca_var; 
    int add(int a);
    int add(int a, int b);
    };
    

    在實作這個add()這個函式時必須這樣描述:
    int MyClass::add(int a, int b) 
    { 
    return a + b; 
    }
    

    此外,雙冒號也常常使用在當前Class內部,對目前Class內部變數元素進行表示:
    int MyClass::add(int a) 
    { 
    return a + ::ca_var; 
    }
    
    用於表示目前Class的內部變量ca_var。

    2010年9月1日 星期三

    [轉錄] 下班本來就是應該的

    企管專家認為,不管辦公室有多少工作,時間到了最好就離開,不僅如此,最好五點就下班。
    「怎麼可能?那麼事情更做不完」你心裡不以為然的想。先別急,聽聽專家的理由是什麼。

    理由1. 讓你更有效率
    多數的辦公室工作十分繁瑣,沒有明確的開始與結束。正由於事情千頭萬緒,你很容易這個做一點、那個進行一半,結果沒有一件有結果,迫使你以加班來趕工,一方 面也安慰自己的心理。但是,如果你的下班時間是五點,那麼你就得盤算一下,在一天有限的時數內,該先做那些事?少和同事聊天,多用點時間思考都好!一味埋頭長時間工作而不思考,容易做虛工,而且會失去看事情的整體觀。

    理由2-對你的上司有教育作用不要過度擴大上司對你的期望
    如果你常常讓上司看到你留下來加班,他會開始認為你很願意加班,久了就變成你應該加班不要讓上司以工作時間的長短來評估你的表現。

    理由3-對你的屬下有教育作用
    讓你的屬下學著在有限的時間內,分配工作的優先次序。明確的表示你下班就會離開,到時候他們應該完成的工作或報告,就應該交到你桌上。

    理由4-迫使你釐清價值觀
    想清楚你生命中最重要的是家庭或是工作?當然不加班、不拼命工作,可能讓你失去許多表現機會,錯過加薪與升遷。但是你不會辛苦工作像條狗似的,到了四、五十歲,突然覺得愧對家人、愧對自己的生命。

    理由5-讓你走在時代尖端
    企管顧問觀察到一個趨勢,這兩年愈來愈多的人認為,生命中比工作重要的東西還有許多;工作時間長的人不再被視為英雄,反而被看成不懂生命的人。
    現在懂得拒絕長時間工作的人,將是未來的領導人物。

    理由6-讓偶一為之的加班變得有趣
    如果大家平常準時下班,碰到緊急狀況或工作時,大夥晚上一起留在辦公室;有人從外面提了便當走進來,一邊吃飯、一邊討論,這時候很容易顯出團隊合作的革命情感。常常加班,同事之間會生膩,合作的興奮感也全無。

    理由7-讓你免於枯竭的惡性循環
    你愈加班,愈覺得事情做不完;愈覺得事情做不完,工作就拖的愈長。這樣的惡性循環遲早會讓你崩潰。

    理由8-讓你善用休閒時間
    工作之餘的時間不應只是休息、睡覺,以便讓你第二天有精力繼續工作。
    何不培養些興趣?
    ●如果你五點下班,你可以有時間去學外語、去彈吉他、參加才藝活動
    ●這會讓你成為一個更活潑、更有能力、更有趣的人。

    理由9-會讓你更健康
    並不是抽空去打球、上健身房、跳韻律操才叫使身體健康,人的身體也需要其他的方式來維持活力。
    ●比方說,好整以暇的喝杯茶、慢慢的品味一個甜美多汁的水蜜桃、靜靜擁抱你喜愛的人等等。而這些都不是每週工作五、六十個小時的人所能做的。

    理由10-讓你更懂得去愛
    你是不是很久沒有和三、五好友一起說笑狂歡了?你是不是每天都和另一半、和孩子或父母匆匆打個照面?你是不是難得和所愛的人交換生活的心情?五點一到,放下你的工作,多接近那些對你很重要的人。
    以後他們記得的不是你的升遷、你的成就、而是和他們共處的時光。

    資料來源:天下雜誌