青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

麒麟子

~~

導航

<2009年5月>
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

統計

常用鏈接

留言簿(12)

隨筆分類

隨筆檔案

Friends

WebSites

積分與排名

最新隨筆

最新評論

閱讀排行榜

評論排行榜

#

游戲中的資源管理――資源高速緩存

《游戲中的資源管理――資源高速緩存》
轉載請注明出處:http://groups.google.com/group/jianguhan


1.什么是資源高速緩存
    資源高速緩存的原理與其它內存高速緩存的工作原理是相似的。在游戲的狀態轉換過程中,有些數據是剛才使用過的,那么直接從資源高速緩存中載入即可。例如,RPG­游戲中主角從大地圖進入一個房間,探索一番后主角退出房間,此時只要直接從緩存中載入大地圖數據即可,節省了從硬盤載入數據的時間,要知道從硬盤載入數據是非常­慢的。當然,如果你的游戲所使用的數據文件很少,那么你可以在游戲運行過程中把這些數據完全儲存在內存中,而不使用資源高速緩存。


2.一個簡單的資源高速緩存管理器
    下面我將向你展示一個比較簡單的資源高速緩存管理器,源代碼來自我上一個游戲,如果你需要知道更多關于資源高速緩存方面的知識,請參考<<Game Coding Complete>>的第八章。
首先,需要一個機制來唯一標識一個資源,我們用下面這個結構來做資源句柄:
struct ResHandle
{
     ResHandle(std::string &resName, void *buffer, int size)
     {
         m_resName = resName;
         m_size   = size;
         m_buffer = buffer;
     }


     ~ResHandle()
     {
         if (m_buffer != 0) delete[] m_buffer;
     }


     std::string   m_resName;    //資源名
     void          *m_buffer;    //資源句柄所標識的資源
     DWORD         m_size;       //資源所占內存大小


};


好了,現在我們可以從資源名來找出這個資源了,接下來實現這個資源高速緩存管理器:
class CacheManager
{
public:
     CacheManager();
     ~CacheManager();

     //載入資源,resName為資源名,若載入成功size被設為該資源的大小
    //注意,管理中的資源不能在管理器外用delete顯示的刪除它
    void*    Load(std::string resName, DWORD *size = 0);
    //設置緩存大小,單位MB
     void      SetCacheSize(int sizeMB)    { m_cacheSize = sizeMB * 1024 * 1024; }
     //得到緩存大小,單位MB
     int      GetCacheSize()              { return m_cacheSize / 1024 /1024; }


private:
     void     Free();                          //釋放lru鏈表中最后一個資源
     void     *Update(ResHandle *res);         //更新lru鏈表
     ResHandle *Find(std::string &resName);     //找出該資源名的資源句柄


private:
     DWORD m_cacheSize;     //緩存大小
     DWORD m_allocated;     //已使用的緩存大小


//lru鏈表,記錄最近被使用過的資源
     std::list<ResHandle*>                m_lru;  
    //資源標識映射
     std::map<std::string, ResHandle*>    m_resources;

 

};


CacheManager:: CacheManager ()
{
     m_cacheSize = 0;
     m_allocated = 0;


}


CacheManager::~ CacheManager ()
{
          while (!m_lru.empty()) Free();   //釋放所有管理中的資源


}


void * CacheManager::Load(std::string resName, DWORD *size)
{
     ResHandle *handle = Find(resName);   //查找該資源是否在緩存中

     if (handle != 0) //如果找到該資源句柄,則返回該資源并更新lru鏈表
     {
         if (size != 0) *size = handle->m_size;
         return Update(handle);
     }
     else
     {
         //先檢測資源大小
         DWORD _size = 資源大小;


         //是否有足夠空間?
         while (_size > (m_cacheSize - m_allocated))
         {
              if (m_lru.empty()) break;
              Free();
         }
         m_allocated += _size;


         buffer = new char[_size];
//在這里用任何你能想到的辦法載入資源文件到buffer
         …
         …


//記錄當前資源
         ResHandle *handle = new ResHandle(resName, buffer, _size);
         m_lru.push_front(handle);
         m_resources[resName] = handle;


         if (size != 0) *size = _size;
         return buffer;
     }


     return 0;

 

}


void CacheManager::Free()
{
     std::list<ResHandle*>::iterator gonner = m_lru.end();
     gonner--;
     ResHandle *handle = *gonner;
     m_lru.pop_back();
     m_resources.erase(handle->m_resName);
     m_allocated -= handle->m_size;
     delete handle;


}


void * CacheManager::Update(ResHandle *res)
{
     m_lru.remove(res);
     m_lru.push_front(res);
     m_size = res->m_size;
     return res->m_buffer;


}

ResHandle * CacheManager::Find(std::string &resName)
{
     std::map<std::string, ResHandle*>::iterator it = m_resources.find(resName);
     if (it == m_resources.end()) return 0;
     return (*it).second;


}

至此,你已經可以在游戲中緩存任何你想緩存的資源了^_^

3. 資源管理進階
    至此你已經可以在游戲中緩存任何你想緩存的資源了,但是你的任務還沒完成,當你請求的資源存在于緩存之外時,那個閃耀的硬盤燈可能就是玩家最感興趣的東西了。
因此你必須根據不同的游戲類型使用不同的載入方式: 
    一次載入所有東西:適用于任何以界面或關卡切換的游戲 
    只在關鍵點載入資源:很多射擊游戲都使用這樣的設計,如“半條命” 
    持續載入:適用于開放型地圖的游戲,如“俠盜獵車手”
    如果有可能的話,你還可以使用緩存預測機制,當CPU有額外時間的時候可以把未來可能用到的資源載入到資源高速緩存。
    最后,盡管在游戲的資源管理中資源打包不是必須的,但仍然建議大家把資源文件按類型分別打包到單一的文件中,這將為你節省磁盤空間,并加快游戲的載入速度。

posted @ 2009-05-17 23:48 麒麟子 閱讀(616) | 評論 (0)編輯 收藏

[轉] extern "C"

前些天,編程序是用到了很久以前寫的C程序,想把里面的函數利用起來,連接發現出現了找不到具體函數的錯誤:

以下是假設舊的C程序庫

C的頭文件

/*-----------c.h--------------*/
#ifndef _C_H_
#define _C_H_
extern int add(int x, int y);
#endif

C的源文件

/*-----------c.c--------------*/
int
add(int x, int y){
return
x+y;
}

C++的調用

/*-----------cpp.cpp--------------*/
#include "c.h"
void main()
{

add(1, 0);
}

這樣編譯會產生錯誤cpp.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z),原因是找不到add的目標模塊

這才令我想起C++重載的函數命名方式和C函數的命名方式,讓我們回顧一下:C中函數編譯后命名會在函數名前加以"_",比如add函數編譯成obj文件時的實際命名為_add,而c++命名則不同,為了實現函數重載同樣的函數名add因參數的不同會被編譯成不同的名字

例如

int add(int , int)==>add@@YAHHH@Z,

float add(float , float )==>add@@YAMMM@Z,

以上是VC6的命名方式,不同的編譯器會不同,總之不同的參數同樣的函數名將編譯成不同目標名,以便于函數重載是調用具體的函數。

編譯cpp.cpp中編譯器在cpp文件中發現add(1, 0);的調用而函數聲明為extern int add(int x, int y);編譯器就決定去找add@@YAHHH@Z,可惜他找不到,因為C的源文件把extern int add(int x, int y);編譯成_add了;

為了解決這個問題C++采用了extern "C",這就是我們的主題,想要利用以前的C程序庫,那么你就要學會它,我們可以看以下標準頭文件你會發現,很多頭文件都有以下的結構

#ifndef __H
#define __H
#ifdef __cplusplus
extern "C" {
#endif

extern
int f1(int, int);
extern
int f2(int, int);
extern
int f3(int, int);


#ifdef __cplusplus
}
#endif

#endif /*__H*/

如果我們仿制該頭文件可以得到

#ifndef _C_H_
#define _C_H_
#ifdef __cplusplus
extern "C" {
#endif

extern
int add(int, int);

#ifdef __cplusplus
}
#endif

#endif /* _C_H_ */

這樣編譯

/*-----------c.c--------------*/
int
add(int x, int y){
return
x+y;
}

這時源文件為*.c,__cplusplus沒有被定義,extern "C" {}這時沒有生效對于C他看到只是extern int add(int, int);
add函數編譯成_add(int, int);

而編譯c++源文件

/*-----------cpp.cpp--------------*/
#include "c.h"
void main()
{

add(1, 0);
}

這時源文件為*.cpp,__cplusplus被定義,對于C++他看到的是extern "C" {extern int add(int, int);}編譯器就會知道 add(1, 0);調用的C風格的函數,就會知道去c.obj中找_add(int, int)而不是add@@YAHHH@Z

這也就為什么DLL中常看見extern "C" {},windows是采用C語言編制他首先要考慮到C可以正確調用這些DLL,而用戶可能會使用C++而extern "C" {}就會發生作用

posted @ 2009-05-17 23:46 麒麟子 閱讀(325) | 評論 (0)編輯 收藏

[轉]C++中宏的使用

關于## 和 #及#@的用法


## 是連接符號 連接兩個宏,##被稱為連接符(concatenator),用來將兩個Token連接為一個Token。注意這里連接的對象是Token就行,而不一定是宏的變
量。比如你要做一個菜單項命令名和函數指針組成的結構體的數組,并且希望在函數名和菜單項命令名之間有直觀的、名字上的關系。那就可以使用:宏參數##
固定部分。當然還可以n個##符號連接 n+1個Token,這個特性也是#符號所不具備的。
#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d
typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);
// 這里這個語句將展開為:
//      typedef struct _record_type name_company_position_salary;


#@       功能是將其后面的宏參數進行字符化。

#define makechar(x)  #@x
a
= makechar(b);
//a = 'b';


#   是把名字代替成字符串,宏體中,#的功能是將其后面的宏參數進行字符串化操作(Stringfication),簡單說就是在對它所引用的宏變量通過替換后在其左右各加上一個
雙引號。
#define WARN_IF(EXP)        \
        do{ if (EXP)        \
                fprintf(stderr, "Warning: " #EXP "\n"); }       \
        while(0)
那么實際使用中會出現下面所示的替換過程:
WARN_IF (divider == 0);
被替換為
do {
        if (divider == 0)
fprintf(stderr, "Warning" "divider == 0" "\n");
} while(0);
這樣每次divider(除數)為0的時候便會在標準錯誤流上輸出一個提示信息。


!IF constantexpression

如果 constantexpression 計算結果為非零值,則處理 !IF 和下一個 !ELSE!ENDIF 之間的語句。
!ENDIF

標記 !IF!IFDEF!IFNDEF 塊的結尾。同一行上 !ENDIF 后面的所有文本被忽略。

posted @ 2009-05-17 23:41 麒麟子 閱讀(335) | 評論 (0)編輯 收藏

游戲開發中常用的設計模式

出自http://blog.csdn.net/duzhi5368/archive/2008/04/22/2314232.aspx

使用設計模式來提高程序庫的重復利用性是大型程序項目開發必須的。但是在“四人幫”的設計模式概述中提到了23種標準設計模式,不但難以記住,而且有些設計模式更多的適用于應用程序開發,對游戲項目引擎設計并沒有很多的利用價值。根據經驗,精挑細選后,篤志在這里記錄一些自認為有利用價值的設計模式,以便之后自己設計時使用。

 

一:觀察者Observer

 

觀察者的設計意圖和作用是 它將對象與對象之間創建一種依賴關系,當其中一個對象發生變化時,它會將這個變化通知給與其創建關系的對象中,實現自動化的通知更新。

 

       游戲中觀察者的適用環境有

1UI控件管理類。當我們的GUI控件都使用觀察者模式后,那么用戶的任何界面相關操作和改變都將會通知其關聯對象-----我們的UI事件機。

2:動畫管理器。很多時候我們在播放一個動畫楨的時候,對其Frame有很大興趣,此時我們設置一個FrameLister對象對其進行監視,獲得我們關心的事件進行處理是必須的。

 

觀察者偽代碼

//-------------------------------------------------------------------------------------------------------

// 被觀察對象目標類

Class Subject

{

       // 對本目標綁定一個觀察者 Attach( Observer );

       // 解除一個觀察者的綁定   DeleteAttach( Observer );

       // 本目標發生改變了,通知所有的觀察者,但沒有傳遞改動了什么

       Notity()

       {

              For ( …遍歷整個ObserverList …)

              { pObserver ->Update(); }

}

// 對觀察者暴露的接口,讓觀察者可獲得本類有什么變動GetState();

}

//-------------------------------------------------------------------------------------------------------

// 觀察者/監聽者類

Class Observer

{

       // 暴露給對象目標類的函數,當監聽的對象發生了變動,則它會調用本函數通知觀察者

Void Update ()

{

pSubject ->GetState(); // 獲取監聽對象發生了什么變化

TODODisposeFun(); // 根據狀態不同,給予不同的處理

}

}

//-------------------------------------------------------------------------------------------------------

 

非程序語言描述

AB的好朋友,對B的行為非常關心。B要出門,此時A給了B一個警報器,告訴B說:“如果你有事,立刻按這個警報器告訴我。”。結果B在外面遇上了麻煩,按下警報器(Update()),B就知道A出了事,于是就調查一下B到底遇到了什么麻煩(GetState()),當知道B原來是因為被人打了,于是立刻進行處理DisposeFun(),派了一群手下幫B打架。

當然關心A的人可以不止一個,CD可能也對A很關心,于是A這里保存一個所有關心它的人的鏈表,當遇到麻煩的時候,輪流給每個人一份通知。

 

二:單件模式Singleton

單件模式的設計意圖和作用是    保證一個類僅有一個實例,并且,僅提供一個訪問它的全局訪問點。

 

游戲中適用于單件模式的有

1:所有的Manger。在大部分的流行引擎中都存在著它的影子,例如SoundManager, ParticeManager等。

2:大部分的工廠基類。這一點在大部分引擎中還是見不到的,實際上,我們的父類工廠采用唯一實例的話,我們子類進行擴展時也會有很大方便。

 

單件模式偽代碼

//-------------------------------------------------------------------------------------------------------

Class Singleton

{

       Static MySingleton;       // 單件對象,全局唯一的。

       Static Instance(){ return MySingleton;}              // 對外暴露接口

}

//-------------------------------------------------------------------------------------------------------

 

三:迭代器Iterator

      

       迭代器設計意圖和作用是    提供一個方法,對一個組合聚合對象內各個元素進行訪問,同時又不暴露該對象類的內部表示。

 

       游戲中適用于迭代器模式的有    因為STL的流行,這個設計已經廣為人知了,我們對任何形式的資源通一管理時,不免會將其聚合起來,或者List,或者Vector,我們都需要一個對其進行訪問的工具,迭代器無疑是一個利器。

 

       迭代器偽代碼

//-------------------------------------------------------------------------------------------------------

// 迭代器基類

       Class Iterator

{

              Virtual First();              

              Virtual Next();

              Virtual End();

              Virtual CurrentItem();    // 返回當前Item信息

}

//-------------------------------------------------------------------------------------------------------

// 聚合體的基類

       Class ItemAggregate

{

              Virtual CreateIterator(); // 創建訪問自身的一個迭代器

}

//-------------------------------------------------------------------------------------------------------

// 實例化的項目聚合體

       Class InstanceItemAggregate : public ItemAggregate

       {

              CreateIterator(){ return new InstanceIterator(this); }

}

//-------------------------------------------------------------------------------------------------------

 

四:訪問者模式Visitor

 

       訪問者設計意圖和作用是    當我們希望對一個結構對象添加一個功能時,我們能夠在不影響結構的前提下,定義一個新的對其元素的操作。(實際上,我們只是把對該元素的操作分割給每個元素自身類中實現了而已)

 

       游戲中適用于訪問者模式的有    任何一個比較靜態的復雜結構類中都適合采用一份訪問者。這里的“比較靜態的復雜結構類”意思是,該結構類中元素繁多且種類復雜,且對應的操作較多,但類很少進行變化,我們就能夠將,對這個結構類元素的操作獨立出來,避免污染這些元素對象。

       1:例如場景管理器中管理的場景節點,是非常繁多的,而且種類不一,例如有Ogre中的Root, Irrchit中就把攝象機,燈光,Mesh,公告版,聲音都做為一種場景節點,每個節點類型是不同的,雖然大家都有共通的Paint(),Hide()等方法,但方法的實現形式是不同的,當我們外界調用時需要統一接口,那么我們很可能需要需要這樣的代碼

       Hide( Object )

       { if (Object == Mesh) HideMesh(); if (Object == Light) HideLight(); … }

此時若我們需要增加一個Object新的類型對象,我們就不得不對該函數進行修正。而我們可以這樣做,讓Mesh,Light他們都繼承于Object,他們都實現一個函數Hide(),那么就變成

       Mesh::Hide( Visitor ) { Visitor.Hide (Mesh); }

       Light::Hide(Visitor ){ Visitor.Hide (Light); }

我們在調用時只需要Object.Hide(Visitor){ return Visitor.Hide(Object); }

 

這樣做的好處,我們免去了對重要函數的修正,Object.Hide(Visitor){}函數我們可以永久不變,但是壞處也是很明顯的,因為將方法從對象集合結構中抽離出來,就意味著我們每增加一個元素,它必須繼承于一個抽象的被訪問者類,實現其全部函數,這個工作量很大。

所以,訪問者是僅適合于一個裝載不同對象的大容器,但同時又要求這個容器的元素節點不應當有大的變動時才使用。另外,廢話一句,訪問者破壞了OO思想的。

 

       訪問者偽代碼

//-------------------------------------------------------------------------------------------------------

// 訪問者基類

       Class Visitor

{

              Virtual VisitElement( A ){ … };             // 訪問的每個對象都要寫這樣一個方法

              Virtual VisitElement( B ){ … };

}

 

// 訪問者實例A

Class VisitorA

{

       VisitElement( A ){ … };         // 實際的處理函數

VisitElement( B ){ … };        // 實際的處理函數

}

 

// 訪問者實例B

Class VisitorB

{

       VisitElement( A ){ … };         // 實際的處理函數

VisitElement( B ){ … };        // 實際的處理函數

}

 

 

// 被訪問者基類

Class Element

{

       Virtual Accept( Visitor );        // 接受訪問者

}

 

// 被訪問者實例A

Class ElementA

{

       Accecpt( Visitor v ){ v-> VisitElement(this); };    // 調用注冊到訪問者中的處理函數

}

 

// 被訪問者實例B

Class ElementB

{

       Accecpt( Visitor v ){ v-> VisitElement(this); };    // 調用注冊到訪問者中的處理函數

}

 

//-------------------------------------------------------------------------------------------------------

 

五:外觀模式Façade

      

       外觀模式的設計意圖和作用是  將用戶接觸的表層和內部子集的實現分離開發。實際上,這個模式是個紙老虎,之后我們看偽代碼立刻就會發現,這個模式實在用的太頻繁了。

 

       游戲中需要使用外觀模式的地方是   這個非常多了,舉幾個比較重要的。

       1:實現平臺無關性。跨平臺跨庫的函數調用。

       2:同一個接口去讀取不同的資源。

       3:硬件自動識別處理系統。

 

       外觀模式偽代碼

//-------------------------------------------------------------------------------------------------------

       // 用戶使用的接口類

       Class Interface

{

// 暴露出來的函數接口函數,有且僅有一個,但內部實現是調用了兩個類

       Void InterfaceFun()

{

       // 根據某種條件,底層自主的選擇使用AB的方法。用戶無須關心底層實現

       If ( XXX )

       {

              ActualA->Fun();

}

Else

{

       ActualB->Fun();

}

};   

}

 

// 實際的實現,不暴露給用戶知道

Class ActualA

{

       Void Fun();

}

 

// 實際的實現,不暴露給用戶知道

Class ActualB

{

       Void Fun();

}

 

怎么樣,紙老虎吧,看起來很高深摸測的命名而已。

//-------------------------------------------------------------------------------------------------------

 

六:抽象工廠模式AbstractFactory

      

       抽象工廠的設計意圖和作用是    封裝出一個接口,這個接口負責創建一系列互相關聯的對象,但用戶在使用接口時不需要指定對象所在的具體的類。從中文命名也很容易明白它是進行批量生產的一個生產工廠的作用。

 

       游戲中使用抽象工廠的地方有    基本上任何有批量的同類形式的子件地方就會有工廠的存在。(補充一句:下面代碼中的ConcreteFactory1實例工廠就是工廠,而抽象工廠僅僅是工廠的一個抽象層而已。

1:例如,在音頻方面,一個音頻的抽象工廠派生出不同的工廠,有音樂工廠,音效工廠。音效工廠中又有一個創建3D音效節點的方法,一個創建普通音效節點的方法。最終用戶只需要SoundFactory->Create3DNode( pFileName );就可以創建一個節點了。

2:場景對象。

3:渲染對象。

4:等等……

工廠與單件,管理器Manager關系一定是非常緊密的。

 

       抽象工廠偽代碼

//-------------------------------------------------------------------------------------------------------

       class AbstractProductA {}; // 抽象的產品A基類
  class AbstractProductB {}; //抽象的產品B基類

 

// 抽象工廠基類
  class AbstractFactory
  
{
  
public:
   virtual AbstractProductA* CreateProductA() = 0 ;// 創建
ProductA
   virtual AbstractProductB* CreateProductB() = 0 ;// 創建
ProductB
       } ;

 

  class ProductA1 : public AbstractProductA {};    // 產品A的實例1
  class ProductA2 : public AbstractProductA {};    // 產品A的實例2

  class ProductB1 : public AbstractProductB {};    // 產品B的實例1
  class ProductB2 : public AbstractProductB {};    // 產品B的實例2

 

// 實例工廠1

     class ConcreteFactory1 : public AbstractFactory
  
{
    
virtual AbstractProductA* CreateProductA() { return new ProductA1() ; }
    
virtual AbstractProductB* CreateProductB() { return new ProductB1() ; }
       static ConcreteFactory1* Instance() { }        // 實例工廠盡量使用單件模式

  } ;

 

// 實例工廠2

class ConcreteFactory2 : public AbstractFactory
  {
    
virtual AbstractProductA* CreateProductA() { return new ProductA2() ; }
    
virtual AbstractProductB* CreateProductB() { return new ProductB2() ; }
    static ConcreteFactory2* Instance() {} 
       // 實例工廠盡量使用單件模式
  } ;

}

//-------------------------------------------------------------------------------------------------------

客戶端代碼

Void main()
{
  AbstractFactory *pFactory1 = ConcreteFactory1::Instance() ;
  
AbstractProductA *pProductA1 = pFactory1->CreateProductA() ;
  
AbstractProductB *pProductB1 = pFactory1->CreateProductB() ;
  
AbstractFactory *pFactory2 = ConcreteFactory2::Instance() ;
  
AbstractProductA *pProductA2 = pFactory2->CreateProductA() ;
  
AbstractProductB *pProductB2 = pFactory2->CreateProductB() ;
}

//-------------------------------------------------------------------------------------------------------

posted @ 2009-05-17 23:36 麒麟子 閱讀(894) | 評論 (0)編輯 收藏

[轉]通過例子學習Lua

據說本文作者是OGDEV的HACK達人

通過例子學習Lua(1) ---- Hello World

1.前言
游戲中少不了用到腳本語言. Lua是一種和C/C++結合非常緊密的腳本語言,效率極高。
一般是對時間要求比較高的地方用C++寫,而經常需要改動的地方用Lua寫。

偶最近在學習Lua, 所以寫出心得和大家共享. Lua是一種完全免費的腳本語言,
它的官方網站在http://www.lua.org.在網站上可以下載到lua的源碼, 沒有可
執行版本, 不過不用擔心, 因為lua源碼可以在任何一種C/C++的編譯器上編譯.

如果要學習Lua, 官方網站上的Reference是必備的,上面有每個命令的用法,非常詳
細。
參考手冊 http://www.lua.org/manual/5.0/
作者寫的Programming in Lua http://www.lua.org/pil/

2.編譯
如果用的VC, 可以下載所需的project文件,地址在
http://sourceforge.net/project/showfiles.php?group_id=32250&package_id=115604
偶用的是cygwin和linux, 打入以下命令即可,
tar -zxvf lua-5.0.2.tar.gz
cd lua-5.0.2
sh ./configure
make
這樣就OK了。
為了以后使用方便,最好把bin目錄加入到path里面。

3."Hello, world!"
現在開始偶們的第一個小程序"Hello, world!"
把以下程序打入文件e01.lua

例1:e01.lua
-- Hello World in Lua
print("Hello World.")

Lua有兩種執行方式,一種是嵌入到C程序中執行,還有一種是直接從命令行方式下執
行。
這里為了調試方便,采用第二種方式,執行命令 lua e01.lua

輸出結果應該是:
Hello World.

4.程序說明
第一行 -- Hello World in Lua
這句是注釋,其中--和C++中的//意思是一樣的
第二行 print("Hello World.")
調用lua內部命令print,輸出"Hello World."字符串到屏幕,Lua中的字符串全部是
由"括起來的。
這個命令是一個函數的調用,print是lua的一個函數,而"Hello World."是print的參
數。

5.試試看
在Lua中有不少字符串的處理操作,本次的課后試試看的內容就是,找出連接兩個字符串
的操作,
并且print出來。
--

通過例子學習Lua(2) --- Lua基礎

1. 函數的使用
以下程序演示了如何在Lua中使用函數, 及局部變量
例e02.lua
-- functions
function pythagorean(a, b)   
       local c2 = a^2 + b^2   
       return sqrt(c2)
end
print(pythagorean(3,4))

運行結果
5

程序說明
在Lua中函數的定義格式為:
function 函數名(參數)
       ...
end
與Pascal語言不同, end不需要與begin配對, 只需要在函數結束后打個end就可以了.
本例函數的作用是已知直角三角形直角邊, 求斜邊長度. 參數a,b分別表示直角邊長,
在函數內定義了local形變量用于存儲斜邊的平方. 與C語言相同, 定義在函數內的代
碼不會被直接執行, 只有主程序調用時才會被執行.
local表示定義一個局部變量, 如果不加local剛表示c2為一個全局變量, local的作用域
是在最里層的end和其配對的關鍵字之間, 如if ... end, while ... end等。全局變量

作用域是整個程序。

2. 循環語句
例e03.lua
-- Loops
for i=1,5 do   
       print("i is now " .. i)
end

運行結果
i is now 1
i is now 2
i is now 3
i is now 4
i is now 5

程序說明
這里偶們用到了for語句
for 變量 = 參數1, 參數2, 參數3 do
       循環體
end
變量將以參數3為步長, 由參數1變化到參數2
例如:   
for i=1,f(x) do print(i) end
for i=10,1,-1 do print(i) end

這里print("i is now " .. i)中,偶們用到了..,這是用來連接兩個字符串的,
偶在(1)的試試看中提到的,不知道你們答對了沒有。
雖然這里i是一個整型量,Lua在處理的時候會自動轉成字符串型,不需偶們費心。

3. 條件分支語句
例e04.lua
-- Loops and conditionals
for i=1,5 do
       print(“i is now “ .. i)
         if i < 2 then       
               print(“small”)   
         elseif i < 4 then       
               print(“medium”)   
         else       
               print(“big”)   
         end
end

運行結果
i is now 1
small
i is now 2
medium
i is now 3
medium
i is now 4
big
i is now 5
big

程序說明
if else用法比較簡單, 類似于C語言, 不過此處需要注意的是整個if只需要一個end,
哪怕用了多個elseif, 也是一個end.
例如
    if op == "+" then
      r = a + b
    elseif op == "-" then
      r = a - b
    elseif op == "*" then
      r = a*b
    elseif op == "/" then
      r = a/b
    else
      error("invalid operation")
    end


4.試試看
Lua中除了for循環以外, 還支持多種循環, 請用while...do和repeat...until改寫本文
中的for程序

--
通過例子學習Lua(3) ---- 數據結構

1.簡介
Lua語言只有一種基本數據結構, 那就是table, 所有其他數據結構如數組啦,
類啦, 都可以由table實現.

2.table的下標
例e05.lua
-- Arrays
myData = {}
myData[0] = “foo”
myData[1] = 42

-- Hash tables
myData[“bar”] = “baz”

-- Iterate through the
-- structure
for key, value in myData do
print(key .. “=“ .. value)
end

輸出結果
0=foo
1=42
bar=baz

程序說明
首先定義了一個table myData={}, 然后用數字作為下標賦了兩個值給它. 這種
定義方法類似于C中的數組, 但與數組不同的是, 每個數組元素不需要為相同類型,
就像本例中一個為整型, 一個為字符串.

程序第二部分, 以字符串做為下標, 又向table內增加了一個元素. 這種table非常
像STL里面的map. table下標可以為Lua所支持的任意基本類型, 除了nil值以外.

Lua對Table占用內存的處理是自動的, 如下面這段代碼
a = {}
a["x"] = 10
b = a -- `b' refers to the same table as `a'
print(b["x"]) --> 10
b["x"] = 20
print(a["x"]) --> 20
a = nil -- now only `b' still refers to the table
b = nil -- now there are no references left to the table
b和a都指向相同的table, 只占用一塊內存, 當執行到a = nil時, b仍然指向table,
而當執行到b=nil時, 因為沒有指向table的變量了, 所以Lua會自動釋放table所占內存

3.Table的嵌套
Table的使用還可以嵌套,如下例
例e06.lua
-- Table ‘constructor’
myPolygon = {
color=“blue”,
thickness=2,
npoints=4;
{x=0, y=0},
{x=-10, y=0},
{x=-5, y=4},
{x=0, y=4}
}

-- Print the color
print(myPolygon[“color”])

-- Print it again using dot
-- notation
print(myPolygon.color)

-- The points are accessible
-- in myPolygon[1] to myPolygon[4]

-- Print the second point’s x
-- coordinate
print(myPolygon[2].x)

程序說明
首先建立一個table, 與上一例不同的是,在table的constructor里面有{x=0,y=0},
這是什么意思呢? 這其實就是一個小table, 定義在了大table之內, 小table的
table名省略了.
最后一行myPolygon[2].x,就是大table里面小table的訪問方式.

--
通過例子學習Lua(4) -- 函數的調用

1.不定參數
例e07.lua
-- Functions can take a
-- variable number of
-- arguments.
function funky_print (...)
for i=1, arg.n do
print("FuNkY: " .. arg)
end
end

funky_print("one", "two")

運行結果
FuNkY: one
FuNkY: two

程序說明
* 如果以...為參數, 則表示參數的數量不定.
* 參數將會自動存儲到一個叫arg的table中.
* arg.n中存放參數的個數. arg[]加下標就可以遍歷所有的參數.


2.以table做為參數
例e08.lua
-- Functions with table
-- parameters
function print_contents(t)
for k,v in t do
print(k .. "=" .. v)
end
end
print_contents{x=10, y=20}

運行結果
x=10
y=20

程序說明
* print_contents{x=10, y=20}這句參數沒加圓括號, 因為以單個table為參數的時候,
不需要加圓括號
* for k,v in t do 這個語句是對table中的所有值遍歷, k中存放名稱, v中存放值


3.把Lua變成類似XML的數據描述語言
例e09.lua
function contact(t)
-- add the contact ‘t’, which is
-- stored as a table, to a database
end

contact {
name = "Game Developer",
email = "hack@ogdev.net",
url = "http://www.ogdev.net,
quote = [[
There are
10 types of people
who can understand binary.]]
}

contact {
-- some other contact
}

程序說明
* 把function和table結合, 可以使Lua成為一種類似XML的數據描述語言
* e09中contact{...}, 是一種函數的調用方法, 不要弄混了
* [[...]]是表示多行字符串的方法
* 當使用C API時此種方式的優勢更明顯, 其中contact{..}部分可以另外存成一配置文


4.試試看
想想看哪些地方可以用到例e09中提到的配置方法呢?

--

通過例子學習Lua(5) ---- Lua與C交互入門

1.簡介

Lua與C/C++結合是很緊密的, Lua與C++交互是建立在Lua與C的基礎上的, 所
以偶先從Lua與C講起.

正如第一講所說, 運行Lua程序或者說調用Lua主要有兩種方式:
* 通過命令行執行"Lua"命令
* 通過Lua的C庫
雖然此前偶們一直用第一種方式, 但偶要告訴你, 通過Lua的C庫執行才是游戲中
常用的方式.

2.Lua的C庫

Lua的C庫可以做為Shared Library調用, 但一般開發游戲時會把Lua的所有源程序
都包含在內, 并不把Lua編譯成共享庫的形式. 因為Lua程序只有100多K, 而且幾乎
可以在任何編譯器下Clean Compile. 帶Lua源程序的另一個好處時, 可以隨時對Lua
本身進行擴充, 增加偶們所需的功能.

Lua的C庫提供一系列API:
* 管理全局變量
* 管理tables
* 調用函數
* 定義新函數, 這也可以完全由C實現
* 垃圾收集器Garbage collector, 雖然Lua可以自動進行, 但往往不是立即執行的,
所以對實時性要求比較高的程序, 會自己調用垃圾收集器
* 載入并執行Lua程序, 這也可以由Lua自身實現
* 任何Lua可以實現的功能, 都可以通過Lua的C API實現, 這對于優化程序的運行速度
有幫助. 經常調用的共用的Lua程序片斷可以轉成C程序, 以提高效率. 連Lua都是C寫的
還有什么C不能實現呢?

3.Lua與C集成的例子
例e10.c
/* A simple Lua interpreter. */
#include <stdio.h>
#include <lua.h>
int main(int argc, char *argv[]) {
char line[BUFSIZ];
lua_State *L = lua_open(0);
while (fgets(line, sizeof(line), stdin) != 0)
lua_dostring(L, line);
lua_close(L);
return 0;
}

編譯
Linux/Cygwin
* 先編譯Lua, 并把頭文件放入include路徑
* gcc e10.c -llua -llualib -o e10

VC6/VC2003
* 先編譯Lua, 在Option中設置頭文件和庫文件路徑
* 新建工程,在工程配置中加入附加庫lua.lib和lualib.lib
* 編譯成exe

運行結果
本程序的功能是實現一個Lua解釋器, 輸入的每行字符都會被解釋成Lua并執行.

程序說明
* #include <lua.h> 包含lua頭文件, 然后才可以使用API
* lua_State *L = lua_open(0) 打開一個Lua執行器
* fgets(line, sizeof(line), stdin) 從標準輸入里讀入一行
* lua_dostring(L, line) 執行此行
* lua_close(L) 關閉Lua執行器


例e11.c
/* Another simple Lua interpreter. */
#include <stdio.h>
#include <lua.h>
#include <lualib.h>
int main(int argc, char *argv[]) {
char line[BUFSIZ];
lua_State *L = lua_open(0);
lua_baselibopen(L);
lua_iolibopen(L);
lua_strlibopen(L);
lua_mathlibopen(L);
while (fgets(line, sizeof(line), stdin) != 0)
lua_dostring(L, line);
lua_close(L);
return 0;
}

運行結果
本程序的功能是實現一個Lua解釋器, 輸入的每行字符都會被解釋成Lua并執行.
與上例不同的是, 本例調用了Lua的一些標準庫.

程序說明
* #include <lualib.h> 包含Lua的標準庫
* 以下這幾行是用來讀入Lua的一些庫, 這樣偶們的Lua程序就可以有更多的功能.
lua_baselibopen(L);
lua_iolibopen(L);
lua_strlibopen(L);
lua_mathlibopen(L);

4.試試看
把上面兩個小例子在你熟悉的編譯器中編譯執行, 并試試能否與Lua源碼樹一起編譯

--
通過例子學習Lua(6) ---- C/C++中用Lua函數

參考英文文檔http://tonyandpaige.com/tutorials/lua2.html

1.簡介
偶們這次主要說說怎么由Lua定義函數, 然后在C或者C++中調用. 這里偶們
暫不涉及C++的對象問題, 只討論調用函數的參數, 返回值和全局變量的使用.

2.
這里偶們在e12.lua里先定義一個簡單的add(), x,y為加法的兩個參數,
return 直接返回相加后的結果.

例e12.lua
-- add two numbers
function add ( x, y )
return x + y
end

在前一次里, 偶們說到 lua_dofile() 可以直接在C中執行lua文件. 因為偶們
這個程序里只定義了一個add()函數, 所以程序執行后并不直接結果, 效果相當
于在C中定義了一個函數一樣.

Lua的函數可以有多個參數, 也可以有多個返回值, 這都是由棧(stack)實現的.
需要調用一個函數時, 就把這個函數壓入棧, 然后順序壓入所有參數, 然后用
lua_call()調用這個函數. 函數返回后, 返回值也是存放在棧中. 這個過程和
匯編執行函數調用的過程是一樣的.

例e13.cpp 是一個調用上面的Lua函數的例子
#include <stdio.h>

extern "C" { // 這是個C++程序, 所以要extern "C",
// 因為lua的頭文件都是C格式的
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

/* the Lua interpreter */
lua_State* L;

int luaadd ( int x, int y )
{
int sum;

/* the function name */
lua_getglobal(L, "add");

/* the first argument */
lua_pushnumber(L, x);

/* the second argument */
lua_pushnumber(L, y);

/* call the function with 2
arguments, return 1 result */
lua_call(L, 2, 1);

/* get the result */
sum = (int)lua_tonumber(L, -1);
lua_pop(L, 1);

return sum;
}

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

/* initialize Lua */
L = lua_open();

/* load Lua base libraries */
lua_baselibopen(L);

/* load the script */
lua_dofile(L, "e12.lua");

/* call the add function */
sum = luaadd( 10, 15 );

/* print the result */
printf( "The sum is %d\n", sum );

/* cleanup Lua */
lua_close(L);

return 0;
}

程序說明:
main中過程偶們上次已經說過了, 所以這次只說說luaadd的過程
* 首先用lua_getglobal()把add函數壓棧
* 然后用lua_pushnumber()依次把x,y壓棧
* 然后調用lua_call(), 并且告訴程序偶們有兩個參數一個返回值
* 接著偶們從棧頂取回返回值, 用lua_tonumber()
* 最后偶們用lua_pop()把返回值清掉

運行結果:
The sum is 25

編譯方法
Linux下把程序存成e13.cpp
g++ e13.cpp -llua -llualib -o e13
./e13

VC下編譯方法
* 首先建立一個空的Win32 Console Application Project
* 把e13.cpp加入工程中
* 點project setting,然后設置link選項, 再加上lua.lib lualib.lib兩個額外的庫
* 最后編譯

建立好的project可以在這里下載
VC http://tonyandpaige.com/tutorials/luaadd.zip
Linux http://tonyandpaige.com/tutorials/luaadd.tar.gz

3.全局變量
上面偶們用到了lua_getglobal()但并沒有詳細講, 這里偶們再舉兩個小例子來說下全局
變量
lua_getglobal()的作用就是把lua中全局變量的值壓入棧
lua_getglobal(L, "z");
z = (int)lua_tonumber(L, 1);
lua_pop(L, 1);
假設Lua程序中定義了一個全局變量z, 這段小程序就是把z的值取出放入C的變量z中.

另外Lua中還有一個對應的函數lua_setglobal(), 作用是用棧頂的值填充指定的全局變

lua_pushnumber(L, 10);
lua_setglobal(L, "z");
例如這段小程序就是把lua中的全局變量z設為10, 如果lua中未定義z的話, 就會自動創
建一個
全局變量z并設為10.

4.試試看
自己寫個函數用C/C++來調用下試試

--
通過例子學習Lua(7) ---- Lua中調用C/C++函數

1.前言
上次偶說到從C/C++中調用Lua的函數, 然后就有朋友問從Lua中如何調用C/C++的
函數, 所以偶們這次就來說說這個問題. 首先偶們會在C++中建立一個函數, 然后
告知Lua有這個函數, 最后再執行它. 另外, 由于函數不是在Lua中定義的, 所以
無法確定函數的正確性, 可能在調用過程中會出錯, 因此偶們還會說說Lua出錯處
理的問題.

2.Lua中調用C函數
在lua中是以函數指針的形式調用函數, 并且所有的函數指針都必須滿足如下此種
類型:
typedef int (*lua_CFunction) (lua_State *L);

也就是說, 偶們在C++中定義函數時必須以lua_State為參數, 以int為返回值才能
被Lua所調用. 但是不要忘記了, 偶們的lua_State是支持棧的, 所以通過棧可以
傳遞無窮個參數, 大小只受內存大小限制. 而返回的int值也只是指返回值的個數
真正的返回值都存儲在lua_State的棧中. 偶們通常的做法是做一個wrapper, 把
所有需要調用的函數都wrap一下, 這樣就可以調用任意的函數了.

下面這個例子是一個C++的average()函數, 它將展示如何用多個參數并返回多個值

例e14.cpp
#include <stdio.h>

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

/* the Lua interpreter */
lua_State* L;

static int average(lua_State *L)
{
/* get number of arguments */
int n = lua_gettop(L);
double sum = 0;
int i;

/* loop through each argument */
for (i = 1; i <= n; i++)
{
/* total the arguments */
sum += lua_tonumber(L, i);
}

/* push the average */
lua_pushnumber(L, sum / n);

/* push the sum */
lua_pushnumber(L, sum);

/* return the number of results */
return 2;
}

int main ( int argc, char *argv[] )
{
/* initialize Lua */
L = lua_open();

/* load Lua base libraries */
lua_baselibopen(L);

/* register our function */
lua_register(L, "average", average);

/* run the script */
lua_dofile(L, "e15.lua");

/* cleanup Lua */
lua_close(L);

return 0;
}

例e15.lua
-- call a C++ function

avg, sum = average(10, 20, 30, 40, 50)

print("The average is ", avg)
print("The sum is ", sum)


程序說明:
* lua_gettop()的作用是返回棧頂元素的序號. 由于Lua的棧是從1開始編號的,
所以棧頂元素的序號也相當于棧中的元素個數. 在這里, 棧中元素的個數就
是傳入的參數個數.
* for循環計算所有傳入參數的總和. 這里用到了數值轉換lua_tonumber().
* 然后偶們用lua_pushnumber()把平均值和總和push到棧中.
* 最后, 偶們返回2, 表示有兩個返回值.
* 偶們雖然在C++中定義了average()函數, 但偶們的Lua程序并不知道, 所以需
要在main函數中加入

/* register our function */
lua_register(L, "average", average);

這兩行的作用就是告訴e15.lua有average()這樣一個函數.
* 這個程序可以存成cpp也可以存成c, 如果以.c為擴展名就不需要加extern "C"

編譯的方法偶們上次說過了, 方法相同.
e15.lua執行的方法只能用上例中的C++中執行, 而不能用命令行方式執行.

3.錯誤處理
在上例中, 偶們沒有對傳入的參數是否為數字進行檢測, 這樣做不好. 所以這里偶
們再加上錯誤處理的片斷.

把這段加在for循環之內:
if (!lua_isnumber(L, i)) {
lua_pushstring(L, "Incorrect argument to 'average'");
lua_error(L);
}
這段的作用就是檢測傳入的是否為數字.

加上這段之后, 偶們debug的時候就會簡單許多. 對于結合兩種語言的編程, 它們之
間傳遞數據的正確性檢測是非常重要的.

這里有別人寫好的例子:
VC的 http://tonyandpaige.com/tutorials/luaavg.zip
Linux的 http://tonyandpaige.com/tutorials/luaavg.tar.gz

至此, Lua與C的結合就基本講完了, 下次偶要開始說說Lua與面向對象.
但是偶自己還沒有學完, 所以大家可能要多等兩天了. Sorry!

--

posted @ 2009-05-16 21:37 麒麟子 閱讀(4060) | 評論 (1)編輯 收藏

僅列出標題
共38頁: First 22 23 24 25 26 27 28 29 30 Last 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美一区二区播放| 久久久久久久久久久久久9999| 另类春色校园亚洲| 久久国产精品久久w女人spa| 国产色综合天天综合网| 欧美一区免费| 久久免费国产| 一本色道久久88综合日韩精品| 亚洲美女电影在线| 国产精品你懂的| 久久国产成人| 狼人社综合社区| 一本色道**综合亚洲精品蜜桃冫| 一本一本久久a久久精品牛牛影视| 国产精品国产三级国产aⅴ浪潮| 亚洲影院免费| 久久精品日韩欧美| 亚洲日本黄色| 羞羞视频在线观看欧美| 一区在线免费| 99精品视频一区| 国产色视频一区| 亚洲激情国产精品| 国产精品久久久91| 欧美大片免费观看| 国产精品久久久久久久久久久久久| 欧美一区二区日韩| 欧美成人一品| 久久狠狠一本精品综合网| 欧美岛国在线观看| 久久九九热re6这里有精品| 欧美jizz19性欧美| 久久久久久久久蜜桃| 欧美精品一区二区三区一线天视频| 亚洲欧美日韩国产一区二区| 老司机67194精品线观看| 亚洲欧美精品中文字幕在线| 久久视频精品在线| 欧美在线视频在线播放完整版免费观看| 久久久噜噜噜久噜久久| 亚洲欧美国产一区二区三区| 免费一级欧美片在线观看| 欧美在线观看视频| 欧美日韩在线三区| 亚洲二区在线观看| 尤物精品国产第一福利三区| 亚洲欧美日韩精品久久久久| 一本久道久久久| 久久一区激情| 久久午夜羞羞影院免费观看| 国产精品手机在线| 在线中文字幕不卡| 一区二区精品在线观看| 免费观看30秒视频久久| 久久久久久网址| 国产又爽又黄的激情精品视频| av成人免费在线| 一区二区三区四区五区精品视频 | 一区二区三区国产在线观看| 久久福利毛片| 久久久精品国产一区二区三区| 国产精品家庭影院| 亚洲精品在线观看免费| 亚洲精品综合久久中文字幕| 欧美.日韩.国产.一区.二区| 牛夜精品久久久久久久99黑人 | 欧美精品一区二区精品网| 免费观看成人鲁鲁鲁鲁鲁视频| 国产欧美精品在线观看| 亚洲尤物精选| 欧美主播一区二区三区美女 久久精品人 | 亚洲欧美日本视频在线观看| 亚洲欧美日本日韩| 国产精品视频在线观看| 中日韩视频在线观看| 亚洲欧美一区二区视频| 国产精品嫩草影院av蜜臀| 午夜一级久久| 媚黑女一区二区| 日韩视频三区| 国产精品乱码妇女bbbb| 亚洲免费影视第一页| 久久蜜桃香蕉精品一区二区三区| 国产又爽又黄的激情精品视频| 久久久久久久97| 亚洲精品久久久久久久久久久久久 | 久久亚洲午夜电影| 最近看过的日韩成人| 亚洲视频精品| 国产欧美一区二区三区国产幕精品 | 久久精品视频亚洲| 亚洲高清二区| 99re6这里只有精品| 国产精品露脸自拍| 久久在线免费观看视频| 日韩视频一区二区三区在线播放免费观看 | 一本色道久久综合亚洲精品高清| 亚洲欧美文学| 亚洲电影av| 欧美午夜精品久久久| 亚洲欧美日韩在线综合| 免费日韩av电影| 亚洲影院污污.| 在线观看成人av| 国产精品www网站| 另类成人小视频在线| aa级大片欧美三级| 女同性一区二区三区人了人一 | 亚洲综合丁香| 亚洲成人在线观看视频| 欧美性jizz18性欧美| 久久久久五月天| 亚洲尤物在线视频观看| 久久久人成影片一区二区三区观看 | 国产精品免费观看视频| 久久综合一区二区| 午夜精品视频网站| 亚洲日本一区二区| 久热精品视频在线观看一区| 在线亚洲精品| 最新日韩精品| 激情久久中文字幕| 国产女主播视频一区二区| 欧美精品三级在线观看| 久久久精品日韩欧美| 亚洲一区二区在线视频 | 久久一区二区三区国产精品 | 一区二区三区在线高清| 国产精品白丝jk黑袜喷水| 欧美大片在线观看| 久久亚洲欧美| 久久精品国产亚洲精品| 亚洲性图久久| 亚洲理伦在线| 亚洲精品乱码久久久久久蜜桃麻豆| 久久久久久一区二区三区| 久久国内精品视频| 先锋影音国产精品| 午夜精品久久| 性欧美1819性猛交| 亚洲欧美综合国产精品一区| 中文精品视频| 亚洲特黄一级片| 一本大道av伊人久久综合| 亚洲老司机av| 一本色道久久综合精品竹菊| 亚洲免费电影在线| 一区二区三区日韩在线观看| 9i看片成人免费高清| 日韩午夜电影| 亚洲视频视频在线| 亚洲一区一卡| 欧美中文在线免费| 久久久久久有精品国产| 久久久久久9| 欧美aⅴ99久久黑人专区| 欧美国产欧美亚洲国产日韩mv天天看完整| 久久精品国产一区二区三区免费看 | 正在播放日韩| 亚洲欧美第一页| 欧美有码在线视频| 久久精品一区二区三区不卡牛牛 | 裸体素人女欧美日韩| 久久综合中文色婷婷| 亚洲第一精品福利| 亚洲精品乱码久久久久久黑人| 亚洲美洲欧洲综合国产一区| 99av国产精品欲麻豆| 亚洲一区二区日本| 久久动漫亚洲| 欧美精品偷拍| 国产欧美一二三区| 亚洲国产精品va| 亚洲永久免费视频| 久久影视三级福利片| 亚洲国产另类 国产精品国产免费| 91久久精品国产91久久性色tv| av不卡在线观看| 久久精品99| 欧美日韩一区二区三区| 国产一区二区高清不卡| 亚洲精品一区二区三区四区高清| 亚洲伊人伊色伊影伊综合网| 久久久水蜜桃av免费网站| 亚洲国产精品传媒在线观看 | 欧美国产日韩一区| 亚洲影院在线观看| 欧美69视频| 国产日韩欧美一区二区三区四区| 亚洲国产精品久久久久婷婷老年 | 久久婷婷蜜乳一本欲蜜臀| 亚洲激情图片小说视频| 午夜精品久久久久久久99樱桃| 久久久蜜桃精品| 国产精品一区二区三区久久久| 亚洲激情精品| 久久婷婷丁香| 亚洲综合社区| 欧美日韩一区二区在线观看视频| 在线电影欧美日韩一区二区私密|