成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

硬核實戰(zhàn):回調函數到底是個啥?一文帶你從原理到實戰(zhàn)徹底掌握C/C++回調函數

開發(fā)
作為一個在生產環(huán)境中與回調函數打了多年交道的開發(fā)者,今天我想分享一些真正實用的經驗,帶你揭開回調函數的神秘面紗,從理論到實戰(zhàn)全方位掌握這個強大而常見的編程技巧。

網上講回調函數的文章不少,但大多淺嘗輒止、缺少系統(tǒng)性,更別提實戰(zhàn)場景和踩坑指南了。作為一個在生產環(huán)境中與回調函數打了多年交道的開發(fā)者,今天我想分享一些真正實用的經驗,帶你揭開回調函數的神秘面紗,從理論到實戰(zhàn)全方位掌握這個強大而常見的編程技巧。

開篇:那些年,我們被回調函數整懵的日子

還記得我剛開始學編程時,遇到"回調函數"這個詞簡直一臉懵:

  • "回調?是不是打電話回去的意思?"
  • "函數還能回過頭調用?這是什么黑魔法?"
  • "為啥代碼里有個函數指針傳來傳去的?這是在干啥?"

如果你也有這些疑問,那恭喜你,今天這篇文章就是為你量身定做的!

一、什么是回調函數?先來個通俗解釋

回調函數本質上就是:把一個函數當作參數傳給另一個函數,在合適的時機再被"回頭調用"。

這么說太抽象?那我們來個生活中的例子:

想象你去火鍋店吃飯,但發(fā)現需要排隊。有兩種方式等位:

  • 傻等法:站在門口一直盯著前臺,不停問"到我了嗎?到我了嗎?"
  • 回調法:拿個小 buzzer(呼叫器),該干嘛干嘛去,等輪到你時,buzzer 會自動震動提醒你

顯然第二種方式更高效!這就是回調的思想:

  • 小buzzer就是你傳遞的"回調函數"
  • 餐廳前臺就是接收回調的函數
  • buzzer震動就是回調函數被執(zhí)行
  • 你不用一直守著,解放了自己去做其他事

回調函數的核心思想是:"控制反轉"(IoC)—— 把"何時執(zhí)行"的控制權交給了別人,而不是自己一直輪詢檢查。

二、為什么需要回調函數?

在深入代碼前,我們先搞清楚為啥需要這玩意兒?回調函數解決了哪些問題?

  • 解耦合:調用者不需要知道被調用者的具體實現
  • 異步處理:可以在事件發(fā)生時才執(zhí)行相應代碼,不需要一直等待
  • 提高擴展性:同一個函數可以接受不同的回調函數,實現不同的功能
  • 實現事件驅動:GUI編程、網絡編程等領域的基礎

三、回調函數的基本結構:代碼詳解

好了,說了這么多,來看看 C/C++ 中回調函數到底長啥樣:

// 1. 定義回調函數類型(函數指針類型)
typedef void (*CallbackFunc)(int);

// 2. 實際的回調函數
void onTaskCompleted(int result) {
    printf("哇!任務完成了!結果是: %d\n", result);
}

// 3. 接收回調函數的函數
void doSomethingAsync(CallbackFunc callback) {
    printf("開始執(zhí)行任務...\n");
    // 假設這里是一些耗時操作
    int result = 42;
    printf("任務執(zhí)行完畢,準備調用回調函數...\n");
    // 操作完成,調用回調函數
    callback(result);
}

// 4. 主函數
int main() {
    // 把回調函數傳遞過去
    doSomethingAsync(onTaskCompleted);
    return0;
}

上面的代碼中:

  • CallbackFunc 是一個函數指針類型,它定義了回調函數的簽名
  • onTaskCompleted 是實際的回調函數,它會在任務完成時被調用
  • doSomethingAsync 是接收回調函數的函數,它在完成任務后會調用傳入的回調函數
  • 在 main 函數中,我們將 onTaskCompleted 作為參數傳給了 doSomethingAsync

注意函數指針的定義:typedef void (*CallbackFunc)(int);

  • void 表示回調函數不返回值
  • (*CallbackFunc) 表示這是一個函數指針類型,名為 CallbackFunc
  • (int) 表示這個函數接收一個 int 類型的參數

這就是回調函數的基本結構!核心就是把函數的地址當作參數傳遞,然后在合適的時機調用它。

四、回調函數的本質:深入理解函數指針

要真正理解回調函數,必須先搞清楚函數指針。在C/C++中,函數在內存中也有地址,可以用指針指向它們。

// 普通函數
int add(int a, int b) {
    return a + b;
}

int main() {
    // 聲明一個函數指針
    int (*funcPtr)(int, int);
    
    // 讓指針指向add函數
    funcPtr = add;
    
    // 通過函數指針調用函數
    int result = funcPtr(5, 3);
    printf("結果是: %d\n", result);  // 輸出: 結果是: 8
    
    return0;
}

這里的 funcPtr 就是函數指針,它指向了 add 函數。我們可以通過這個指針調用函數,就像通過普通指針訪問變量一樣。

回調函數的本質就是利用函數指針,實現了函數的"延遲調用"或"條件調用"。它讓一個函數可以在未來某個時刻,滿足某個條件時,被另一個函數調用。

五、C與C++中的不同回調方式

C和C++提供了不同的實現回調的方式,讓我們比較一下:

1. C語言中的函數指針

這是最基礎的方式,就像我們前面看到的:

typedef void (*Callback)(int);

void someFunction(Callback cb) {
    // ...
    cb(42);
}

2. C++中的函數對象(Functor)

// 函數對象類
class PrintCallback {
public:
    void operator()(int value) {
        std::cout << "值是: " << value << std::endl;
    }
};

// 接收函數對象的函數
template<typename Func>
void doSomething(Func callback) {
    callback(100);
}

int main() {
    PrintCallback printer;
    doSomething(printer);  // 輸出: 值是: 100
    return0;
}

3. C++11中的 std::function 和 lambda 表達式

這是最現代的方式,也最靈活:

// 使用std::function
void doTask(std::function<void(int)> callback) {
    callback(200);
}

int main() {
    // 使用lambda表達式
    doTask([](int value) {
        std::cout << "Lambda被調用,值是: " << value << std::endl;
    });
    
    // 帶捕獲的lambda
    int factor = 10;
    doTask([factor](int value) {
        std::cout << "結果是: " << value * factor << std::endl;
    });
    
    return0;
}

C++11的std::function和 lambda 表達式讓回調變得更加靈活,特別是 lambda 可以捕獲外部變量,這在 C 語言中很難實現。

六、回調函數的實戰(zhàn)案例

光說不練假把式,來幾個實際案例感受一下回調函數的強大:

案例1:自定義排序

假設我們有一個數組,想按照不同的規(guī)則排序:

// 定義比較函數類型
typedef int (*CompareFunc)(const void*, const void*);

// 升序比較
int ascendingCompare(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

// 降序比較
int descendingCompare(const void* a, const void* b) {
    return (*(int*)b - *(int*)a);
}

// 自定義排序函數
void customSort(int arr[], int size, CompareFunc compare) {
    qsort(arr, size, sizeof(int), compare);
}

int main() {
    int numbers[] = {-42, 8, -15, 16, -23, 4};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    // 升序排序
    customSort(numbers, size, ascendingCompare);
    
    // 降序排序
    customSort(numbers, size, descendingCompare);
    
    return0;
}

這個例子展示了回調函數最常見的用途之一:通過傳入不同的比較函數,實現不同的排序規(guī)則,而無需修改排序算法本身。

案例2:事件處理系統(tǒng)

GUI編程中,回調函數無處不在。下面我們模擬一個簡單的事件系統(tǒng):

// 事件類型
enum EventType { CLICK, HOVER, KEY_PRESS };

// 事件結構體
struct Event {
    EventType type;
    int x, y;
    char key;
};

// 定義回調函數類型
typedef void (*EventCallback)(const Event*);

// 各種事件處理函數
void onClickCallback(const Event* event) {
    printf("點擊事件觸發(fā)了!坐標: (%d, %d)\n", 
           event->x, event->y);
}

void onKeyPressCallback(const Event* event) {
    printf("按鍵事件觸發(fā)了!按下的鍵是: %c\n", 
           event->key);
}
...

// 事件處理器結構體
struct EventHandler {
    EventCallback callbacks[10];  // 假設最多10種事件類型
};

// 注冊事件回調
void registerCallback(EventHandler* handler, EventType type, EventCallback callback) {
    handler->callbacks[type] = callback;
}

// 事件分發(fā)器
void dispatchEvent(EventHandler* handler, const Event* event) {
    if (handler->callbacks[event->type] != NULL) {
        handler->callbacks[event->type](event);
    }
}

int main() {
    // 創(chuàng)建并初始化事件處理器
    EventHandler handler;
    
    // 注冊回調函數
    registerCallback(&handler, CLICK, onClickCallback);
    
    // 模擬點擊事件
    Event clickEvent = {CLICK, 100, 200};
    dispatchEvent(&handler, &clickEvent);
    
    return0;
}

這個例子模擬了 GUI 程序中的事件處理機制:不同類型的事件發(fā)生時,系統(tǒng)會調用相應的回調函數。這是所有 GUI框架的基礎設計模式。

案例3:帶用戶數據的回調函數

在實際應用中,我們經常需要給回調函數傳遞額外的上下文數據。下面看看幾種實現方式:

使用 void 指針傳遞用戶數據(C語言風格)

// 用戶數據結構體
struct UserData {
    constchar* name;
    int id;
};

// 回調函數類型
typedef void (*Callback)(int result, void* userData);

// 實際的回調函數
void processResult(int result, void* userData) {
    UserData* data = (UserData*)userData;
    printf("用戶 %s (ID: %d) 收到結果: %d\n", 
        data->name, data->id, result);
}

// 執(zhí)行任務的函數
void executeTask(Callback callback, void* userData) {
    int result = 100;
    callback(result, userData);
}

int main() {
    // 創(chuàng)建用戶數據
    UserData user = {"張三", 1001};

    // 執(zhí)行任務
    executeTask(processResult, &user);

    return0;
}

這種方式通過void*類型參數傳遞任意類型的數據,是C語言中最常見的方式。但缺點是缺乏類型安全性,容易出錯。

使用C++11的 std::function 和 lambda 表達式

// 使用std::function定義回調類型
using TaskCallback = std::function<void(int)>;

// 執(zhí)行任務的函數
void executeTask(TaskCallback callback) {
    int result = 300;
    callback(result);
}

int main() {
    // 使用lambda捕獲局部變量
    std::string userName = "用戶1";
    int userId = 2001;

    // lambda捕獲外部變量
    executeTask([userName, userId](int result) {
        std::cout << userName << " (ID: " << userId 
            << ") 收到結果: " << result << std::endl;
    });

    return0;
}

這種方式最靈活,lambda表達式可以直接捕獲周圍環(huán)境中的變量,大大簡化了代碼。

七、回調函數的設計模式

回調函數在各種設計模式中廣泛應用,下面介紹兩個常見的模式:

1. 觀察者模式(Observer Pattern)

觀察者模式中,多個觀察者注冊到被觀察對象,當被觀察對象狀態(tài)變化時,通知所有觀察者:

// 使用C++11的方式實現觀察者模式
class Subject {
private:
    // 存儲觀察者的回調函數
    std::vector<std::function<void(conststd::string&)>> observers;
    
public:
    // 添加觀察者
    void addObserver(std::function<void(const std::string&)> observer) {
        observers.push_back(observer);
    }
    
    // 通知所有觀察者
    void notifyObservers(const std::string& message) {
        for (auto& observer : observers) {
            observer(message);
        }
    }
};

這個模式在GUI編程、消息系統(tǒng)、事件處理中非常常見。

2. 策略模式(Strategy Pattern)

策略模式使用回調函數實現不同的算法策略:

// 定義策略類型(使用回調函數)
using SortStrategy = std::function<void(std::vector<int>&)>;

// 排序上下文類
class Sorter {
private:
    SortStrategy strategy;
    
public:
    Sorter(SortStrategy strategy) : strategy(strategy) {}
    
    void setStrategy(SortStrategy newStrategy) {
        strategy = newStrategy;
    }
    
    void sort(std::vector<int>& data) {
        strategy(data);
    }
};

策略模式允許在運行時切換算法,非常靈活。

八、回調函數的陷阱與最佳實踐

使用回調函數雖然強大,但也存在一些潛在的問題和陷阱。下面總結一些常見的坑和相應的最佳實踐:

1. 生命周期問題

陷阱:回調函數中引用了已經被銷毀的對象。

void dangerousCallback() {
    char* buffer = new char[100];
    
    // 注冊一個在未來執(zhí)行的回調函數
    registerCallback([buffer]() {
        // 危險!此時buffer可能已經被刪除
        strcpy(buffer, "Hello");
    });
    
    // buffer在這里被刪除
    delete[] buffer;
}

最佳實踐:

  • 使用智能指針管理資源
void safeCallback() {
    // 使用智能指針
    auto buffer = std::make_shared<std::vector<char>>(100);
    
    // 智能指針會在所有引用消失時自動釋放
    registerCallback([buffer]() {
        // 安全!即使原始作用域結束,buffer仍然有效
        std::copy_n("Hello", 6, buffer->data());
    });
}
  • 提供取消注冊機制
class CallbackManager {
    std::map<int, std::function<void()>> callbacks;
    int nextId = 0;
    
public:
    // 返回標識符,用于取消注冊
    int registerCallback(std::function<void()> cb) {
        int id = nextId++;
        callbacks[id] = cb;
        return id;
    }
    
    void unregisterCallback(int id) {
        callbacks.erase(id);
    }
};

void safeUsage() {
    CallbackManager manager;
    
    // 保存ID用于取消注冊
    int callbackId = manager.registerCallback([]() { /* ... */ });
    
    // 在合適的時機取消注冊
    manager.unregisterCallback(callbackId);
}

2. 回調地獄(Callback Hell)

陷阱:嵌套太多層回調,導致代碼難以理解和維護。

doTaskA([](int resultA) {
    doTaskB(resultA, [](int resultB) {
        doTaskC(resultB, [](int resultC) {
            // 代碼縮進越來越深,難以閱讀和維護
        });
    });
});

最佳實踐:

  • 使用 std::async 和 std::future(C++11)
// C++11及以上
std::future<int> doTaskAAsync() {
    returnstd::async(std::launch::async, []() {
        return doTaskA();
    });
}

std::future<int> doTaskBAsync(int resultA) {
    returnstd::async(std::launch::async, [resultA]() {
        return doTaskB(resultA);
    });
}

std::future<int> doTaskCAsync(int resultB) {
    returnstd::async(std::launch::async, [resultB]() {
        return doTaskC(resultB);
    });
}

// 真正的異步鏈式調用
void chainedAsyncTasks() {
    try {
        // 啟動任務A
        auto futureA = doTaskAAsync();
        
        // 等待A完成并啟動B
        auto resultA = futureA.get();
        auto futureB = doTaskBAsync(resultA);
        
        // 等待B完成并啟動C
        auto resultB = futureB.get();
        auto futureC = doTaskCAsync(resultB);
        
        // 獲取最終結果
        auto resultC = futureC.get();
        std::cout << "Final result: " << resultC << std::endl;
    }
    catch(conststd::exception& e) {
        std::cerr << "Error in task chain: " << e.what() << std::endl;
    }
}
  • 使用協程 (C++20)
// 使用C++20協程解決回調地獄
#include <coroutine>

// 偽代碼:簡化的任務協程類型
template<typename T>
struct Task {
    struct promise_type {/* 協程必需的接口 */ };
    // 使用自動生成的協程狀態(tài)機
};

// 異步任務A
Task<int> doTaskAAsync() {
    // co_return 返回值并結束協程 (類似return但用于協程)
    co_return doTaskA();
}

// 異步任務B - 接收A的結果作為輸入
Task<int> doTaskBAsync(int resultA) {
    co_return doTaskB(resultA);
}

// 異步任務C - 接收B的結果作為輸入
Task<int> doTaskCAsync(int resultB) {
    co_return doTaskC(resultB);
}

// 主任務 - 協程方式鏈接所有任務
Task<int> processAllTasksAsync() {
    try {
        // co_await 暫停當前協程,等待doTaskAAsync()完成
        // 協程暫停時不會阻塞線程,控制權返回給調用者
        int resultA = co_await doTaskAAsync();
        
        // 當任務A完成后,協程從這里繼續(xù)執(zhí)行
        std::cout << "Task A completed: " << resultA << std::endl;
        
        // 等待任務B完成
        int resultB = co_await doTaskBAsync(resultA);
        std::cout << "Task B completed: " << resultB << std::endl;
        
        // 等待任務C完成
        int resultC = co_await doTaskCAsync(resultB);
        std::cout << "Task C completed: " << resultC << std::endl;
        
        // 返回最終結果
        co_return resultC;
    } 
    catch (conststd::exception& e) {
        std::cerr << "Error in coroutine chain: " << e.what() << std::endl;
        co_return-1;
    }
}

// 啟動協程鏈 (偽代碼)
void runAsyncChain() {
    // 啟動協程并等待完成
    auto task = processAllTasksAsync();
    int finalResult = syncAwait(task);  // 同步等待協程完成
    std::cout << "Final result: " << finalResult << std::endl;
}

3. 異常處理

陷阱:回調函數中拋出的異常無法被調用者捕獲。

void riskyCallback() {
    try {
        executeCallback([]() {
            throw std::runtime_error("回調中的錯誤");  // 這個異常無法被外層捕獲
        });
    } catch (const std::exception& e) {
        // 這里捕獲不到回調中拋出的異常!
        std::cout << "捕獲到異常: " << e.what() << std::endl;
    }
}

最佳實踐:使用錯誤碼代替異常

// 定義錯誤碼
enumclass ErrorCode {
    Success = 0,
    GeneralError = -1,
    NetworkError = -2,
    TimeoutError = -3
    // 更多具體的錯誤類型...
};

// 使用std::function
void executeSafe(std::function<void(int result, ErrorCode code, const std::string& message)> callback) {
    try {
        // 嘗試執(zhí)行操作
        int result = performOperation();
        callback(result, ErrorCode::Success, "操作成功");
    } catch (conststd::exception& e) {
        // 可以根據異常類型設置不同的錯誤碼
        callback(0, ErrorCode::GeneralError, e.what());
    } catch (...) {
        callback(0, ErrorCode::GeneralError, "未知錯誤");
    }
}

4. 線程安全問題

陷阱:回調可能在不同線程中執(zhí)行,導致并發(fā)訪問問題。

class Counter {
    int count = 0;
    
public:
    void registerCallbacks() {
        // 這些回調可能在不同線程中被調用
        registerCallback([this]() { count++; });  // 不是線程安全的
        registerCallback([this]() { count++; });
    }
};

最佳實踐:

  • 使用互斥鎖保護共享數據
class ThreadSafeCounter {
    int count = 0;
    std::mutex mutex;
    
public:
    void registerCallbacks() {
        registerCallback([this]() { 
            std::lock_guard<std::mutex> lock(mutex);
            count++;  // 現在是線程安全的
        });
    }
};
  • 使用原子操作
class AtomicCounter {
    std::atomic<int> count{0};
    
public:
    void registerCallbacks() {
        registerCallback([this]() { 
            count++;  // 原子操作,線程安全
        });
    }
};

5. 循環(huán)引用(內存泄漏)

陷阱:對象間相互持有回調,導致循環(huán)引用無法釋放內存。

class Button {
    std::function<void()> onClick;
    
public:
    void setClickHandler(std::function<void()> handler) {
        onClick = handler;
    }
};

class Dialog {
    std::shared_ptr<Button> button;
    
public:
    Dialog() {
        button = std::make_shared<Button>();
        // 循環(huán)引用: Dialog引用Button,Button的回調引用Dialog
        button->setClickHandler([this]() {
            this->handleClick();  // 捕獲了this指針
        });
    }
};

最佳實踐:使用 enable_shared_from_this

class DialogWithWeakPtr : publicstd::enable_shared_from_this<DialogWithWeakPtr> {
    std::shared_ptr<Button> button;
    
public:
    DialogWithWeakPtr() {
        button = std::make_shared<Button>();
    }
    
    void initialize() {
        // 安全地獲取this的weak_ptr
        std::weak_ptr<DialogWithWeakPtr> weakThis = shared_from_this();
            
        button->setClickHandler([weakThis]() {
            // 嘗試獲取強引用
            if (auto dialog = weakThis.lock()) {
                dialog->handleClick();  // 安全使用
            }
        });
    }
    
    void handleClick() {
        // 處理點擊事件
    }
};

// 使用方式
auto dialog = std::make_shared<DialogWithWeakPtr>();
dialog->initialize();  // 必須在shared_ptr構造后調用

九、回調函數在現代C++中的演化

C++11及以后的版本為回調函數提供了更多現代化的實現方式:

1. std::function 和 std::bind

std::function是一個通用的函數包裝器,可以存儲任何可調用對象:

// 接受任何滿足簽名要求的可調用對象
void performOperation(std::function<int(int, int)> operation, int a, int b) {
    int result = operation(a, b);
    std::cout << "結果: " << result << std::endl;
}

// 使用
performOperation([](int x, int y) { return x + y; }, 5, 3);

2. Lambda表達式

Lambda大大簡化了回調函數的編寫:

std::vector<int> numbers = {5, 3, 1, 4, 2};

// 使用lambda作為排序規(guī)則
std::sort(numbers.begin(), numbers.end(), 
          [](int a, int b) { return a > b; });

// 使用lambda作為遍歷操作
std::for_each(numbers.begin(), numbers.end(),
              [](int n) { std::cout << n << " "; });

3. 協程(C++20)

C++20引入了協程,可以更優(yōu)雅地處理異步操作:

// 注意:需要C++20支持
std::future<int> asyncOperation() {
    // 模擬異步操作
    co_return 42;  // 使用co_return返回結果
}

// 使用co_await等待異步結果
std::future<void> processResult() {
    int result = co_await asyncOperation();
    std::cout << "結果: " << result << std::endl;
}

協程將回調風格的異步代碼轉變?yōu)楦鬃x的同步風格,是解決回調地獄的有效方式。

十、總結:回調函數的本質與價值

經過這一路的學習,我們可以總結回調函數的本質:

  • 控制反轉(IoC) - 把"何時執(zhí)行"的控制權交給調用者
  • 延遲執(zhí)行 - 在特定條件滿足時才執(zhí)行代碼
  • 解耦合 - 分離"做什么"和"怎么做"
  • 行為參數化 - 將行為作為參數傳遞

回調函數的最大價值在于它實現了"控制反轉",這使得代碼更加靈活、可擴展、可維護。這也是為什么它在GUI編程、事件驅動系統(tǒng)、異步編程等領域如此重要。

最后用一句話總結回調函數:把"怎么做"的權力交給別人,自己只負責"做什么"的一種編程技巧。

責任編輯:趙寧寧 來源: 跟著小康學編程
相關推薦

2010-02-04 16:07:39

C++回調函數

2018-11-29 08:00:20

JavaScript異步Promise

2023-04-18 08:10:10

2011-06-15 11:05:14

C語言回調函數

2009-08-12 10:11:18

C# 回調函數

2009-08-19 17:10:09

C#回調函數

2009-07-31 16:25:29

C#回調函數API應用

2009-08-19 16:40:35

C#回調

2018-10-22 08:14:04

2022-04-12 08:30:52

回調函數代碼調試

2020-11-03 10:32:48

回調函數模塊

2012-02-01 10:33:59

Java

2017-03-16 15:28:20

人工智能視覺識別

2021-04-07 13:28:21

函數程序員異步

2011-07-25 14:27:10

Objective-C 協議 函數

2023-12-15 09:45:21

阻塞接口

2018-01-12 14:49:18

區(qū)塊鏈分布式數據庫

2023-01-26 23:44:41

C++代碼生命周期

2011-05-20 17:19:25

回調函數

2019-11-05 10:03:08

callback回調函數javascript
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久亚洲精品国产精品紫薇 | 日本不卡一区二区三区在线观看 | 久久久精品网 | 精品视频在线观看 | 欧美日韩在线观看一区 | 欧美日韩综合视频 | 波多野吉衣久久 | 91.com视频| 国产视频久久 | 91在线看片| 三级av在线 | 亚洲精品一区二区三区丝袜 | 一区二区三区四区av | 亚洲视频免费观看 | 精品国产一区二区三区久久久久久 | 91精品国产一区 | www.久| 浴室洗澡偷拍一区二区 | 成年免费大片黄在线观看岛国 | www国产成人免费观看视频,深夜成人网 | 久久精品欧美电影 | 一区二区三区高清 | 久久精品国产精品青草 | 超碰电影| 欧美日韩国产在线 | 国产三级精品三级在线观看四季网 | 国产一级在线视频 | 欧美精品一区二区三 | 成人精品在线观看 | 日本欧美国产 | 亚洲第一av网站 | 国产精品成人在线播放 | 天天操夜夜操 | 日韩精品一区中文字幕 | 久久久久国产精品一区二区 | 亚洲国产成人精品女人久久久野战 | 亚洲国产精品一区二区第一页 | 卡通动漫第一页 | 久久手机视频 | 九久久| 国产精品福利一区二区三区 |