2008年12月30日 星期二

static vs. shared in VB.Net

在C++中stitic修飾詞有兩個主要功能

1)在fuction中修飾區域變數,讓這個變數的value不會在離開function後就從記憶體中消失
也就是可以保留前一次function留下的"痕跡"
2)在class定義中修飾class member,讓這個member由該class的所有instance共用

但是在VB.Net這兩個功能被拆成兩個修飾詞,分別是"Static"與"Shared"
以下簡述它的特性
Static:
MSDN說明:Normally, a local variable in a procedure ceases to exist as soon as the procedure stops. A static variable continues to exist and retains its most recent value. The next time your code calls the procedure, the variable is not reinitialized, and it still holds the latest value that you assigned to it. A static variable continues to exist for the lifetime of the class or module that it is defined in.
規則:
1.Static只能修飾Local Variable(函式內的變數)
2.不能修飾Struct的成員函式
3.不能與ReadOnly, Shadows, Shared同時使用
舉例:
Function updateSales(ByVal thisSale As Decimal) As Decimal
Static totalSales As Decimal = 0
totalSales += thisSale
Return totalSales
End Function

因為Satic所以只有第一次進來function才會初始化
所以totalSales不會被歸零
Shared:
MSDN說明:Sharing a member of a class or structure makes it available to every instance, rather than nonshared, where each instance keeps its own copy. This is useful, for example, if the value of a variable applies to the entire application. If you declare that variable to be Shared, then all instances access the same storage location, and if one instance changes the variable's value, all instances access the updated value.
Sharing does not alter the access level of a member. For example, a class member can be shared and private (accessible only from within the class), or nonshared and public.

規則:

1.Shared只能修飾Struct or Class的member(方法或變數)
2.不能與 Overrides, Overridable, NotOverridable, MustOverride, Static 同時使用
3.建議以Struct或Class名稱操作Shared變數,如:Class1.SharedVal1 += 3
(如果用instance操作也可以,不過會有warnning! 這個與C++不同)
4.Shared variable只會存有一份,不管物件有多少個,而Shared function的local variable也是!
(也就是Shared function的local variable隱含的是Shared)
5.不能修飾module與interface的成員、Constant變數,但是他們仍是Shared
6.Shared函式不能使用non-shared變數,只能用shared變數。
但是nonshared函式卻可以使用non-shared、shared變數
(因為non-shared變數是每個instance都有一份專屬的,Shared函式根本不知道你要使用誰的!)

舉例:

Sub main()
shareTotal.total = 10
' The preceding line is the preferred way to access total.
Dim instanceVar As New shareTotal
instanceVar.total += 100
' The preceding line generates a compiler warning message and
' accesses total through class shareTotal instead of through
' the variable instanceVar. This works as expected and adds
' 100 to total.
returnClass().total += 1000
' The preceding line generates a compiler warning message and
' accesses total through class shareTotal instead of calling
' returnClass(). This adds 1000 to total but does not work as
' expected, because the MsgBox in returnClass() does not run.
MsgBox("Value of total is " & CStr(shareTotal.total))
End Sub
Public Function returnClass() As shareTotal
MsgBox("Function returnClass() called")
Return New shareTotal
End Function
Public Class shareTotal
Public Shared total As Integer
End Class

注意:即使returnClass()會回傳shareTotal的物件
但是實際上complier遇到這類的shared操作手法
會直接用shareTotal.total+=1000來解讀
也就是他根本不會進去returnClass()!!!!

2008年12月22日 星期一

前端Script呼叫後端事件處理函式

__doPostBack(TargetObjID,EventArgs)是Asp.net轉成html code會自動加進來的script function
目的在於驅動後端處理函式動作
例如
__doPostBack("Btn1","");

就會馬上執行Btn1.Click的事件處理函式
事實上,Asp.Net元件幾乎都是利用這種方式postBack
我們可以這樣做:
拉一個DropDownList到畫面,
然後把AutoPostBack打開
執行網頁後再看看它的原始碼:


<form name="form1" method="post" action="testpage.aspx" id="form1">
<div>
<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
<input type="hidden" name="__LASTFOCUS" id="__LASTFOCUS" value="" />
</div>
<script type="text/javascript">
//<![CDATA[
var theForm = document.forms['form1'];
if (!theForm) {
theForm = document.form1;
}

function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
//]]>
</script>
<select name="DropDownList1" onchange="javascript:setTimeout('__doPostBack(\'DropDownList1\',\'\')', 0)" id="DropDownList1"></select>
</form>

提醒一下:doPostBack前面的底線是"兩條",只打一條會出錯的!
還有,如果兩個參數都是空字串""
那PostBack回去就只會做Page_Load()

Button's onClinetClick and onClick attribute

有時候我們需要在Button按下後先彈出一個對話框
詢問user是否確定要執行這個動作
如果user選擇"取消"則回到畫面什麼是也不做
在Asp.Net的Button裡有另外一個attribute===>onClientClick
這個可以指定Script function提供送出表單前的先行作業
例如:


.....


之前一直被一個問題困擾
那就是onClientClick="return false;"可以擋掉畫面postback
為什麼onClientClick="Validate()"
然後function Validate()中只有return false一行卻失敗!!
現在終於曉得了
因為onClientClick="Validate()"
而Validate()又return false時,等於OnClientClick="false"不等於OnClientClick="return false"

2008年12月17日 星期三

VBScript Event Handler

我們知道在Html元素中如果要加入事件
必須在html標籤中加入對應的事件屬性和事件處理函式的名稱
如:

<script language="VBScript">
function btnClick(){
alert("Hello!");
}
</script>

<intput type="button" onClick="btnClick()">
</pre>


但是如果要用VBScript的話,onClick屬性就可以省了!
如:

<script language="VBScript">
Sub btnGreeting_OnClick()
MsgBox("Hello!")
End Sub
</script>

<input type="button" name="btnGreeting">

但是html元素必須加上name屬性!
因為它是用來判斷要呼叫哪個事件處理函式
也就是在VBScript中事件處理函式必須定義成Sub name_event()

參考資料:VBScript vs JavaScript for Event

2008年12月10日 星期三

VB.Net 與 C++的Class 比較 (1)

題目定VB.Net與C++
其實應該說.Net Framework與標準C++ 不過因為大家只用VB所以就下這個標題

C++的Class與.Net的最大差別就是
C++的Class instance預設以Call By Value傳值
也就是說如果傳入一個物件給function
它實際上是"複製一份物件" (這個跟.net的struct變數相同)
但是如果class定的比較大時就會有效能上的問題
幸好c++也有Call By Reference
不過有資料被更改的風險
又幸好我們可以規定傳進去的是Constant
那嚜
就可以兼顧by value與 by reference的優點!!!
例如

void foo3(const String& s);

表示傳入的String s不會被foo3給竄改
這也說明了為什麼c++ 的STL裡面的function那嚜喜歡用const修飾字^^

2008年12月3日 星期三

Word 「樣式與格式」(章節自動編碼)操作心得

1. 樣式與格式工作窗格可由格式工具列最左方的AA圖示啟動,






或是最上方功能表中的格式→樣式與格式啟動。

























2. 在樣式與格式工作窗格中
每個樣式上按右鍵可以看到採用此樣式的地方有幾處,


如果是「目前並未使用」則可刪除,此一情形最容易發生在不是從頭開始寫文件,
或是拿另一份檔案來照其格式寫,通常裡面的樣式又多又雜且一堆無用的樣式。









<===同種階層的標題竟有多種樣式















3.如果你不幸用了一份手動章節編碼的檔案作為出發點,恭喜你,有得搞了, 以下步驟可以幫助你改為自動章節編碼,雖然不是自動的, 但是如果一定要照那份來源檔的格式去寫文件的話,那就乖乖改吧, 下列步驟需要專心與時間,可以泡杯咖啡來準備與word奮戰吧, (A)先將所有的樣式中標題n依階層去統一, 也就是說所有1.的都改為同一個樣式 標題1. 所有1.1.都改為標題2,以此類推,
<=====這才是優質的樣式設定

方法:在樣式與格式的工作窗格中所有名稱中有標題1(假設為1A)的地方按右鍵, 選取相同設定,然後再點選另一個有標題1(假設為1B)的樣式, 這樣會將所有原先使用先選的樣式(1A)的內容套用成後選的那個樣式(1B), 直到只剩一個標題1的樣式, 重複對標題2、3、4...操作以此類推。

(B)針對每個標題的樣式,逐一修改為想要的格式, 以下列格式作為例子
標題1:18pt 粗體 1.5倍行高 中文:新細明體 英文:Times New Roman
標題2:16pt 粗體 1.5倍行高 中文:新細明體 英文:Times New Roman
標題3:14pt 粗體 1.5倍行高 中文:新細明體 英文:Times New Roman
標題4:12pt 中文:新細明體 英文:Times New Roman

首先在樣式與格式工作窗格中的標題1上按右鍵→選取相同設定 (這一步不做有的時候會只改到部分格式,也不知道是為什麼,總之有選比較安全) 再次右鍵,按下修改,會跳出一個修改樣式的選單,


此選單中最上面的屬性,「供後續段落使用之樣式」請選擇你現在所在修改的樣式名稱,
這效果是某行已經套用A樣式時,行尾按下ENTER後下一行自動以A樣式開始,
若不想要這效果,則可設定「供後續段落使用之樣式」為內文,

接下來的格式設定可以分別設定,此樣式的「標題」文字的格式,
可以針對中英文分別設定,

左下角的格式可以調整各種細部設定,

段落:此選單中的「縮排與行距」標籤頁的「一般」中的「大綱階層」要選擇此樣式所應有的階層
通常還會設定「段落間距」與「行距」,1.5倍行高就是在此設定

定位點:段落左下角也有個定位點的按鈕,與此同,可以設定標題文字跟章節編碼之間的距離


編號方式:最重要的功能,要選「大綱編號」中的樣式,
然後找階層1配標題1,階層2配標題2,階層n配標題n的那個,
如果沒有找到想要的,沒關係,那就就「自訂」吧,
出現「自訂大綱編號清單」後,

按下右方的「更多」會出現較多的進階選項,
通常要檢查下列屬性「數字格式」下的「階層」、「字型」(章節自動編碼的字型),
還有下方的「將階層連結至樣式」、「在此層次之後重新編號」,
假設現在修改的是標題4,那麼「階層」、「將階層連結至樣式」、「在此層次之後重新編號」,
分別應該為4、標題4、階層3,

且要從階層1開始檢視並設定至階層4,
也就是先選階層1,然後「將階層連結至樣式」設為標題1,
選階層2,然後「將階層連結至樣式」設為標題2,「在此層次之後重新編號」設為標題1,
選階層3,然後「將階層連結至樣式」設為標題3,「在此層次之後重新編號」設為標題2,
選階層4,然後「將階層連結至樣式」設為標題4,「在此層次之後重新編號」設為標題3,
如果設定正確便可在右方的預覽視窗中看到下列效果
1.標題1
1.1標題2
1.1.1標題3
1.1.1.1標題4

最後 恭喜你,搞定了,Orz, 因為開新檔案的章節自動編碼竟然怪怪的,
只好跟他耗到底,也才有這篇文章的由來。

2008年11月27日 星期四

Structure VS. Class

昨天看到一篇文章比較.NetFramework的Sructure和class
我把重點整理一下:
(1)struct是value type, class是reference type。所以一樣的data member struct會比class小一點(少消耗了位址空間)
例如:

Class c1
Private Integer a
Private Integer b
End Class

Struct s1
Private Integer a
Private Integer b
End Struct

Dim x as new c1()
Dim y as s1

x會比y大。因為x會比y多佔一個指向x的data member的位址。
(2)struct在CallByValue傳遞時會複製整個結構成員, class則只會copy位址,但是兩個位址會指到相同記憶體區塊
也就是說即使是ByVal對class的instance來說還是同等ByRef!
(3)class因屬reference type,需額外負擔Garbage collection回收、heap管理機制
這一點struct就把class比下去了
(4)成員函式(method member)不影響評估struct與class
對這兩個來說,成員函式都只會存在一副,不管今天有幾個instance
(5)成員變數少(小)時,用struct會比較有利
這點呼應(3),但是如果很大時可以考慮用class
(6)struct不能繼承
要用到繼承只能靠class
(7)對事件處理,struct比class有更多限制
WithEventsHandles 這兩個關鍵字不可套在struct中

2008年11月10日 星期一

為專案加上Log

應用範圍:
隨時記錄AP的運作狀況,以確定它正常運作。
記錄錯誤發生時的各項資訊,以利之後的Debug作業。
供使用者查詢應用程式運作的記錄。(或是應用程式無法運作時的回報記錄)
錯誤發生時通知相關人員處理問題。

步驟:
1.下載Log4net Library http://logging.apache.org/log4net/download.html
2.專案加入Log4net.dll(檔案於解壓縮資料夾下,bin\net\2.0\release)
3.在你的專案加入應用程式組態檔(App.config)[若是web專案的話,請加入在web.config],並加入下面組態:
  












































PS.Log方式除上面的FileLog及ApplicationEventLog外,還有資料庫(DB2,MS SQL ...),UDP ....
有需要其它Log方式的話,請參照http://logging.apache.org/log4net/release/config-examples.html

基本上,一個logger內會設定一個level;但可以設定多個appender-ref.

4.在專案中,加入Log:
  
Public Class Form1
Private Log As Log4Net.ILog

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Log4Net.Config.XmlConfigurator.Configure()
Log = Log4Net.LogManager.GetLogger("LoggingExample")

Log.Info("INFO Message")
Log.Warn("WARN Message")
Log.Debug("DEBUG Message")
Log.Fatal("FATAL Message")
Log.Error("ERROR Message!")
End Sub
End Class


5. 執行的結果,會寫到文件(rolling-log-20081110.log)及系統事件簿中的應用程式事件中;文件的部份內容如下:
[Header]
2008-11-07 16:23:42,862 [10] INFO LoggingExample [file:D:\Job\VACSS\Program\Server\Log4Net\Log4Net\Form1.vb (line:8)] - INFO Message
2008-11-07 16:23:42,908 [10] WARN LoggingExample [file:D:\Job\VACSS\Program\Server\Log4Net\Log4Net\Form1.vb (line:9)] - WARN Message
2008-11-07 16:23:42,908 [10] DEBUG LoggingExample [file:D:\Job\VACSS\Program\Server\Log4Net\Log4Net\Form1.vb (line:10)] - DEBUG Message
2008-11-07 16:23:42,908 [10] FATAL LoggingExample [file:D:\Job\VACSS\Program\Server\Log4Net\Log4Net\Form1.vb (line:11)] - FATAL Message
2008-11-07 16:23:42,908 [10] ERROR LoggingExample [file:D:\Job\VACSS\Program\Server\Log4Net\Log4Net\Form1.vb (line:12)] - ERROR Message!
[Footer]

你可以在App.config中,宣告多告logger,在不同狀況下使用不同的記錄log方式。

2008年11月9日 星期日

C++ 中的 struct 與 class 的比較


struct a : public b {
int foo_bar() {...;}
};


C++ 中的 struct 與在C語言中的struct不同
差別在於對於所繼承的類別預設存取權限的不同
struct 是 public
class 是 private

感謝秉宏大大提供正解

Reference:C++中的struct專題研究

在C++ primer一書中也有相當清楚的講解

2.8 使用關鍵字 struct P.66:
C++ 支援的另一個關鍵字也可以用來定義class型別。
...
以關鍵字class或struct定義出來的class,彼此之間唯一的差異是其預設存取層級:
struct的預設層級是public,class的預設層級是private。

15.2.5 繼承保護類別(Default Inheritance Protection Level) P.574:
如果你認為「以關鍵字struct定義」和「以關鍵字class定義」的classes另有更深層的差異,
不對,唯一差異就是成員的預設保護級別,以及衍生動作的預設保護級別。

2008年11月5日 星期三

在Web環境下快速彈出新視窗

傳統彈出新視窗的作法
都是用Javascript的window.open()method
但是它反應時間還蠻慢的
其實如果客戶的標準瀏覽器是IE
我們可以改用window.showModalDialog() or window.showModelessDialog()
兩個function的差別是showModalDialog() 彈出的視窗沒關掉前不能操作主畫面(類似對話框alert)

語法:
vReturnValue = window.showModalDialog(sURL [, vArguments] [, sFeatures])
(1)vReturnValue: 子視窗的傳回值
(2)sURL: 子視窗的URL (必要項)
(3)vArguments: 傳入子視窗的參數(非必要)
(4)sFeatures: 視窗外觀屬性(字串型別)

舉個例子:
在主畫面TextBox填入值,按下送出按鈕把值傳到彈出的子畫面的TextBox中,同樣的,在子畫面也可以傳回值給主畫面。

主畫面的script:

function OpenNewWindow() {
var returnObj
var passingValue=document.getElementById('PareText1').value;

//開啟子畫面傳入本身視窗
returnObj=window.showModalDialog("PopWindow.aspx",self,'dialogHeight:100px;');
//讀取子畫面的回傳並填入母畫面
document.getElementById('PareText2').value=returnObj.returnValue;
}

子畫面的Script:

//來自母畫面的參數
var Parent=window.dialogArguments;
//要回傳的參數
var returnObj=Object();

//讀取母畫面的參數並塞值
document.getElementById('ChilText1').value=Parent.document.getElementById('PareText1').value;

function ReturnToParent() {
//把值腮入欲傳回的Object
//把Object傳回母畫面
returnObj.returnValue=document.getElementById('ChilText2').value;
window.returnValue=returnObj;
window.close();
}

說明:
1. window.dialogArguments是當呼叫showModalDialog()有傳入參數時才有值..(即"self")
2. var returnObj=Object();是Javascript宣告物件的語法,宣告之後就可以指定屬性值
如:returnObj.returnValue=document.getElementById('ChilText2').value;
3. window.returnValue就是指定showModalDialog()的傳回值

2008年11月3日 星期一

socket => address already in use

當 client 仍與 server 連接時,server 若重新啟動,
如果出現 address already in user 的 error 表示,
由於沒有正常 close socket ,
所以 socket 仍在使用該條 socket 並等待 client 的 FIN 封包,
解決辦法是將該 socket 設定為可重複使用,
需在建立 socket 後且使用 bind 前設定,


int opt = 1;
setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));


Reference: http://www.diybl.com/course/6_system/linux/Linuxjs/2008630/129368.html

2008年10月30日 星期四

物件陣列與初始化

物件陣列與初始化 筆記
若想要以非物件的預設建構子去初始化一個物件陣列時
要怎麼做呢
請參考底下的程式碼


#include
#include
#include
#include

using namespace std;

class car {
int price;
string name;
public:
//car():price(100), name("ford"){}
car();
car(int, string);
void show();
};

car::car()
{
price = 100;
name = "ford";
}

car::car(int i, string str)
{
price = i;
name = str;
}

void car::show()
{
cout << price << " " << name << endl;
}

int main()
{
car car1, car2(120, "bmw");

//default constructor
car1.show();
//initial by manual
car2.show();
//object array and initial by manual
car car3[5] = {car(1, "bmw z1"), car(2, "bmw z2"), car(3, "bmw z3"),
car(4, "bmw z4"), car(5, "bmw z5")};
//using vector to create 101 objects with the same value
vector car4;


for(int i = 0; i != 5; i++) {
car3[i].show();
}
for(int i = 0; i != 10; ++i) {
car4.push_back(car2);
car4[i].show();
}

//The following two ways are wrong!
//car *ptr = new car(111, "benz")[3];
//car *ptr = new car[3](111, "benz");

return 0;
}


輸出結果如下:
100 ford
120 bmw
1 bmw z1
2 bmw z2
3 bmw z3
4 bmw z4
5 bmw z5
120 bmw
120 bmw
120 bmw
120 bmw
120 bmw
120 bmw
120 bmw
120 bmw
120 bmw
120 bmw

結論是用 car array_car[5](1, "xx")
或是 car array_car(1,"xx")[5] 都是錯的 Orz

2008年10月29日 星期三

大家好原來這裡可以不只一個作者!!

只要送出邀請給對方
然後就可以加入成為blog的作者!
最多可以加到100位....這樣以後就不會有幽靈文章了

getElementsByTagName vs. getElementsByName

上一篇"多個RadioButtonList設為相同Group"裡面有用到getElementsByTagName
有人會說既然有把name(即paramOldName)傳入,何不直接使用getElementsByName
就不需要多這個判斷式
 
if(ObjArray[i].type == "radio" && ObjArray[i].name == paramOldName)


這裡說一下理由 (這部份要感謝昌琰)
因為getElementsByName找到的Object只能改name以外的屬性!!
我試過了連ID也可以改,就是只有name不能被修改
所以只好用getElementsByTagName收集大範圍的物件,再透過if判斷式過濾
By Felix

2008年10月28日 星期二

Code posting

若要在此貼上程式碼,
請依下列方式:



Dim felix_liu as String
felix_liu = "blue"
felix_liu += " underwear"



目前支援
C++:cpp, c, c++
Java :java
Sql :sql
VB :vb, vb.net
XML: xml

P.S. Google pages 不給用了,Google sites 聽說也不能放 js 檔,
找了 boxstor 空間上傳,但是用來放 js 不太穩定,會時有時無,
最後就只好用 derrick 的,並且放棄 easy read more 的功能 Orz 。

參考網址及js來源:http://sharedderrick.blogspot.com/2007/12/blogger-syntaxhighlighter.html

把多個RadioButtonList設為相同Group

問題: 如果頁面上有多組RadioButtonList,要做到這幾組RadioButtonList間都只能單選,例如:
50cc機車 125cc機車 150cc以上機車

休旅車 跑車 房車

如果上下兩排各是一個RadioButtonList如何讓他們都屬與同一group
當然,我們可以只用html的radio button做到,只要name屬性設為一樣。但是如果想透過程式動態產生那些選項,RadioButtonList還蠻好用的。可惜它沒有name or groupname這個property可用!

我的作法是做一個含RadioButtonList的UserControl:

Partial Class UCRadioBtnList
Inherits System.Web.UI.UserControl

Private _GroupName As String

'提供設定RadioButton項目
Public WriteOnly Property Items() As String()
Set(ByVal value As String())
Dim ItemTemp As ListItem

For Each v As String In value
ItemTemp = New ListItem(v)
Me.RadioButtonList.Items.Add(ItemTemp)
Next
End Set End Property

'提供設定RadioButtonList的RroupName
Public Property GroupName() As String
Get
Return Me._GroupName
End Get
Set(ByVal value As String)
Me._GroupName = value
'把GropuName存在ViewState中,預防postback時消失
If IsNothing(Me.ViewState("GroupName")) Then
Me.ViewState("GroupName") = value
End If
End Set
End Property

'註冊一行會呼叫 function SetSameGroup( )的script到畫面
Private Sub SetSameGroup()
Dim StrScript As String

StrScript = "SetSameGroup('" & Me.ClientID & "$RadioButtonList','" &
Me.ViewState ("GroupName") & "');" & vbNewLine
ScriptManager.RegisterStartupScript(Me.Page, Me.Page.GetType(), Me.RadioButtonList.ClientID, StrScript, True)
End Sub

'畫面載入時都在註冊一次script(因為script在postback後就會自動消失)
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Me.SetSameGroup()
End Sub

End Class


//javascript


function SetSameGroup(paramOldName, paramGroupName) {
var ObjArray = document.getElementsByTagName("Input");
var StrTemp;
for (i = 0; i < ObjArray.length; i++) {
if(ObjArray[i].type == "radio" && ObjArray[i].name == paramOldName) {
StrTemp = ObjArray[i].parentNode.innerHTML.replace(paramOldName, paramGroupName);
ObjArray[i].parentNode.innerHTML = StrTemp;
}
}
}


使用這個UserControl的畫面的.vb檔

Dim ArrayListSource1 As String() = {"aa", "bb", "cc", "dd"}
Dim ArrayListSource2 As String() = {"ee", "ff", "gg", "hh"}

Me.UCRadioBtnList1.Items = ArrayListSource1 '加入選項
Me.UCRadioBtnList2.Items = ArrayListSource2
Me.UCRadioBtnList1.GroupName = "sss" '設定GroupName
Me.UCRadioBtnList2.GroupName = "sss"

By Felix

2008年10月27日 星期一

k32討論版開張!