循环程序跳出(_kbhit()、getch() 、getchar())

本文介绍了在C++编程中如何通过按键来触发循环程序的退出,重点讲解了_kbhit()、_getch()和getchar()三个函数的使用方法和区别。_kbhit()用于检测是否有按键按下,非阻塞;_getch()获取字符但不显示,阻塞;getchar()从缓冲区读取字符,需回车。理解这些函数可以帮助开发者创建响应用户输入的交互式程序。

在构建一个循环程序的时候 程序跳出环节 是构建一个合理的程序重要的一环 在进行程序跳出的过程 如何触发程序跳出条件也是一个重要的环节


按键流触发跳出条件

以下代码为一个完整的按键跳出循环程序

int i = 10,j=0;
while (j < i)
{
	i += 3;
	Sleep(520);
	cout << "正在加油中!! 目前油量" << i << "% \r";
	if (_kbhit())//非阻塞的响应键盘输入时间 (检测是否有按键按下,有按下返回非0值,一般是1没有按下返回0 需要包含 <conio.h>文头件)
	{
		if (_getch() == 27)//从标准输入流(键盘)读取ESC键 (相较于getchar()接收按键后还需按下回车才能返回 _getch()不用按下回车就能返回 返回值为输入的Ascii码  需要包含 <conio.h>头文件)
		{
			cout << "暂停加油!! 当前油量 :" << j << endl;//显示程序跳出提示并换行
			cout << endl;//再次换行
			break;
		}
	}
}

一个完整的按键跳出程序 需要一个while循环 一个_kbhit()非阻塞键盘函数_getch()获取键盘流字符的函数
要使用这两个函数必须包含<conio.h>通用输入输出库


_kbhit()函数

_kbbit()函数的作用是检查控制台窗口的按键是否被按下 其格式为

int _kbhit( void );

如果在调用该函数时,有按键被按下,则返回一个非0值 ,否则该函数的返回值是0 需要注意的是,该函数是一个非阻塞函数不管有没有按键被按下,该函数都会立即返回 _khbit()函数一般与_getch()函数与getche()函数组合使用获取按键信息


为什么需要此函数?
因为_getch()和getchar()等从控制台获取输入字符函数具有阻塞性 只有从控制台读取到了输入的字符才会有返回值(程序运行至此停滞 等待从控制台获取输入字符)导致程序无法正常循环 在获取字符函数外层包裹一个无论有没有输入字符都会返回值的 _kbhit()函数作为判断条件 则能使程序在正常循环的过程中能被随时进行按键中断跳出


_getch()函数

_getch()函数的作用是从控制台中获取输入的字符,在获取输入的字符之后,并不会在控制台中显示该字符 该函数的格式为

int _getch( void );

该函数的返回值是获取到的字符值。需要注意的是,_getch()函数是一个阻塞函数,直到有字符输入时才会返回,所以该函数不会返回错误值

getch();接受一个任意键的输入,不用按回车就返回该函数的返回值是所输入字符的ASCII码,且该函数的输入不会自动显示在屏幕上,需要putchar();函数输出显示 getch();函数常用于中途暂停程序方便调试和查看


getchar()函数

getchar();从键盘读取一个字符并输出,该函数的返回值是输入第一个字符的ASCII码,若用户输入的是一连串字符,函数直到用户输入回车时结束,输入的字符连同回车一起存入键盘缓冲区,若程序中有后继的getchar();函数,则直接从缓冲区逐个读取已输入的字符并输出,直到缓冲区为空时才重新读取用户的键盘输入


_getch()函数与 getchar()函数的区别

_getch()函数的作用是获取按键信息,且该函数在conio.h中定义。_getche()函数与_getch()函数函数类似,其作用也是获取按键信息,并且也是在conio.h中定义。不同之处在于,_getch()函数不会在控制台中显示按键信息,而getche()函数会在控制台中显示按键信息

getchar()函数的作用也是获取按键信息,该函数在stdio.h中定义。_getch()函数和_getche()函数是直接从键盘上获取按键信息,getchar()函数是从控制台输入缓冲区中获取按键信息,所以只有当按下了回车按键后,getchar()函数才会开始获取字符信息


总之

1._getche()和getchar()都是阻塞性函数
2._getche()会在控制台显示按键信息 _getch()不会显示按键信息
3._getch()无需按下回车即可获取按键消息 getchar()需要按回车才会开始获取字符信息
4. _kbhit()函数能检测键盘输入但是没有阻塞性

源自-C++类与对象项目

/** * @file Menu.h * @brief 菜单导航系统头文件 * @author huang huan tao * @date 2025/09/28 */ #ifndef MENU_SYSTEM_H #define MENU_SYSTEM_H #include <vector> #include <string> #include <iostream> #ifdef _WIN32 #include <conio.h> #include <windows.h> #else #include <termios.h> #include <unistd.h> #include <sys/select.h> #endif /** * @class Menu * @brief 表示一个菜单及其子菜单项的类 */ class Menu { private: std::string m_mainName; // 主菜单名称 std::vector<std::string> m_subMenus; // 子菜单项列表 public: /** * @brief 构造函数 * @param mainName 主菜单名称 * @param subs 子菜单项列表 */ Menu(const std::string& mainName, const std::vector<std::string>& subs); /// @brief 获取主菜单名称 const std::string& getMainName() const; /// @brief 获取子菜单项列表 const std::vector<std::string>& getSubMenus() const; /// @brief 获取子菜单项数量 size_t getSubMenuCount() const; /** * @brief 获取指定索引的子菜单项 * @param index 子菜单索引 * @return 子菜单项名称 * @note 索引越界时返回最后一个子菜单 */ const std::string& getSubMenu(size_t index) const; }; /** * @class MenuManager * @brief 管理菜单导航和用户交互的类 */ class MenuManager { private: std::vector<Menu> m_menus; // 菜单列表 size_t m_currentMainIndex = 0; // 当前选择的主菜单索引 size_t m_currentSubIndex = 1; // 当前选择的子菜单索引 size_t m_targetMainIndex; // 目标主菜单索引 size_t m_targetSubIndex; // 目标子菜单索引 /** * @brief 清空控制台屏幕(跨平台) * @note Windows使用cls,Linux/Mac使用clear */ void clearScreen() const; /** * @brief 设置非阻塞输入模式 * @note Linux/Mac平台关闭行缓冲和回显 */ void setupNonBlockingInput(); /** * @brief 根据名称查找主菜单索引 * @param name 主菜单名称 * @return 主菜单索引,未找到时返回0 */ size_t findMainIndex(const std::string& name) const; /** * @brief 根据名称查找子菜单索引 * @param mainIdx 主菜单索引 * @param name 子菜单名称 * @return 子菜单索引,未找到时返回0 */ size_t findSubIndex(size_t mainIdx, const std::string& name) const; /** * @brief 调整当前子菜单索引 * @note 当主菜单变更时确保子菜单索引有效 */ void adjustSubIndex(); public: /** * @brief 构造函数 * @throw std::invalid_argument 如果目标菜单项无效 */ MenuManager(); /** * @brief 显示当前菜单状态 * @note 清屏后显示主菜单和当前选中的子菜单 */ void displayMenu() const; /** * @brief 检查是否到达目标菜单项 * @return 到达目标返回true,否则false */ bool isTargetReached() const; /** * @brief 获取用户输入 * @param[out] input 存储输入字符 * @return 有输入返回true,无输入返回false * @note Windows使用_kbhit和_getch,Linux/Mac使用select */ bool getInput(char& input); /** * @brief 处理用户输入 * @param input 用户输入字符 */ void handleInput(char input); /** * @brief 运行菜单导航系统 * @note 主循环,显示菜单并处理输入 */ void run(); }; #endif // MENU_SYSTEM_H #include "Menu.h" int main() { MenuManager manager; manager.run(); return 0; } /** * @file Menu.cpp * @brief 菜单导航系统实现文件 * @author huang huan tao * @date 2025/9/28 */ #include "Menu.h" #include <cctype> #include <cmath> // for pow in Linux/Mac // Menu类实现 ===================================================== Menu::Menu(const std::string& mainName, const std::vector<std::string>& subs) : m_mainName(mainName), m_subMenus(subs) { } const std::string& Menu::getMainName() const { return m_mainName; } const std::vector<std::string>& Menu::getSubMenus() const { return m_subMenus; } size_t Menu::getSubMenuCount() const { return m_subMenus.size(); } const std::string& Menu::getSubMenu(size_t index) const { return m_subMenus.at(index < m_subMenus.size() ? index : m_subMenus.size() - 1); } // MenuManager类实现 ============================================== MenuManager::MenuManager() : m_currentMainIndex(0), m_currentSubIndex(1) { m_menus.emplace_back("A", std::vector<std::string>{"A1", "A2", "A3", "A4"}); m_menus.emplace_back("B", std::vector<std::string>{"B1", "B2", "B3"}); m_menus.emplace_back("C", std::vector<std::string>{"C1", "C2", "C3"}); m_menus.emplace_back("D", std::vector<std::string>{"D1", "D2", "D3"}); m_targetMainIndex = findMainIndex("C"); m_targetSubIndex = findSubIndex(m_targetMainIndex, "C3"); // 验证目标菜单项 if (m_targetMainIndex >= m_menus.size() || m_targetSubIndex >= m_menus[m_targetMainIndex].getSubMenuCount()) { } setupNonBlockingInput(); } void MenuManager::clearScreen() const { #ifdef _WIN32 system("cls"); #else system("clear"); #endif } void MenuManager::setupNonBlockingInput() { #ifndef _WIN32 struct termios term; tcgetattr(STDIN_FILENO, &term); term.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &term); #endif } size_t MenuManager::findMainIndex(const std::string& name) const { for (size_t i = 0; i < m_menus.size(); ++i) { if (m_menus[i].getMainName() == name) return i; } return 0; } size_t MenuManager::findSubIndex(size_t mainIdx, const std::string& name) const { if (mainIdx >= m_menus.size()) return 0; const auto& subs = m_menus[mainIdx].getSubMenus(); for (size_t i = 0; i < subs.size(); ++i) { if (subs[i] == name) return i; } return 0; } void MenuManager::adjustSubIndex() { if (m_currentSubIndex >= m_menus[m_currentMainIndex].getSubMenuCount()) { m_currentSubIndex = m_menus[m_currentMainIndex].getSubMenuCount() - 1; } } void MenuManager::displayMenu() const { clearScreen(); std::cout << "=== 菜单导航系统 ===" << std::endl; std::cout << "操作指南: a/d-切换主菜单 w/s-切换子菜单\n\n"; // 一级菜单 std::cout << "主菜单: "; for (size_t i = 0; i < m_menus.size(); ++i) { if (i == m_currentMainIndex) { std::cout << "[" << m_menus[i].getMainName() << "] "; } else { std::cout << m_menus[i].getMainName() << " "; } } std::cout << "\n\n"; // 二级菜单 const Menu& currentMenu = m_menus[m_currentMainIndex]; std::cout << currentMenu.getMainName() << "的子菜单: "; for (size_t i = 0; i < currentMenu.getSubMenuCount(); ++i) { if (i == m_currentSubIndex) { std::cout << "[" << currentMenu.getSubMenu(i) << "] "; } else { std::cout << currentMenu.getSubMenu(i) << " "; } } // 目标提示 std::cout << "\n\n目标: 选择 " << m_menus[m_targetMainIndex].getMainName() << m_menus[m_targetMainIndex].getSubMenu(m_targetSubIndex) << std::endl; } bool MenuManager::isTargetReached() const { return m_currentMainIndex == m_targetMainIndex && m_currentSubIndex == m_targetSubIndex; } bool MenuManager::getInput(char& input) { #ifdef _WIN32 if (_kbhit()) { input = _getch(); return true; } #else fd_set set; struct timeval tv; FD_ZERO(&set); FD_SET(STDIN_FILENO, &set); tv.tv_sec = 0; tv.tv_usec = 10000; // 10ms超时 if (select(STDIN_FILENO + 1, &set, NULL, NULL, &tv) > 0) { input = getchar(); return true; } #endif return false; } void MenuManager::handleInput(char input) { input = std::tolower(static_cast<unsigned char>(input)); const size_t subCount = m_menus[m_currentMainIndex].getSubMenuCount(); switch (input) { case 'a': // 切换到上一个主菜单 m_currentMainIndex = (m_currentMainIndex + m_menus.size() - 1) % m_menus.size(); adjustSubIndex(); break; case 'd': // 切换到下一个主菜单 m_currentMainIndex = (m_currentMainIndex + 1) % m_menus.size(); adjustSubIndex(); break; case 'w': // 切换到上一个子菜单 m_currentSubIndex = (m_currentSubIndex + subCount - 1) % subCount; break; case 's': // 切换到下一个子菜单 m_currentSubIndex = (m_currentSubIndex + 1) % subCount; break; } } void MenuManager::run() { while (true) { displayMenu(); if (isTargetReached()) { std::cout << "\n成功选择目标菜单! 程序结束" << std::endl; break; } char input = 0; if (getInput(input)) { handleInput(input); } // 降低CPU占用率 #ifdef _WIN32 Sleep(50); #else usleep(50000); #endif } } 解决上述代码中出现的闪屏问题
10-11
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <Windows.h> #include <process.h> #include <conio.h> #include <iostream> #include <vector> #include <set> #include <random> #include <chrono> #include <fstream> #include <iomanip> extern "C" { #include "MvCameraControl.h" } // ==================== 全局变量 ==================== bool g_bExit = false; // 控制程序整体退出 bool g_bStopCycle = false; // 控制流程循环停止 void* g_hCamHandle = nullptr; std::ofstream g_logFile; // 性能统计结构体 struct TimingStats { size_t count = 0; long long total_us = 0; long long max_us = 0; long long min_us = INT64_MAX; void record(long long duration) { ++count; total_us += duration; if (duration > max_us) max_us = duration; if (duration < min_us) min_us = duration; } void log(const char* name) const { if (count == 0) return; double avg = static_cast<double>(total_us) / count; Log("%s 统计: 调用 %zu 次, 平均 %.2f μs, 最大 %lld μs, 最小 %lld μs", name, count, avg, max_us, min_us); } }; // 各函数耗时统计器 TimingStats g_statsSetExposureAuto; TimingStats g_statsSetExposureTime; TimingStats g_statsStartGrabbing; TimingStats g_statsStopGrabbing; TimingStats g_statsTrigger; TimingStats g_statsOpenDevice; TimingStats g_statsCloseDevice; // ==================== 日志函数 ==================== template<typename... Args> void Log(const char* format, Args... args) { auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); struct tm tm_snapshot; localtime_s(&tm_snapshot, &time_t); char tmbuf[64]; strftime(tmbuf, sizeof(tmbuf), "%Y-%m-%d %H:%M:%S", &tm_snapshot); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000; char buffer[4096]; int len = snprintf(buffer, sizeof(buffer), format, args...); if (len < 0 || len >= (int)sizeof(buffer)) len = sizeof(buffer) - 1; buffer[len] = '\0'; printf("[%s.%03d] %s\n", tmbuf, (int)ms.count(), buffer); if (g_logFile.is_open()) { g_logFile << "[" << tmbuf << "." << std::setfill('0') << std::setw(3) << (int)ms.count() << "] " << buffer << std::endl; g_logFile.flush(); } } // ==================== 清理资源函数 ==================== void CleanupResources() { Log("开始执行资源清理..."); if (g_hCamHandle != nullptr) { MV_CC_StopGrabbing(g_hCamHandle); Log("已停止图像采集"); auto start = std::chrono::steady_clock::now(); int nRet = MV_CC_CloseDevice(g_hCamHandle); auto end = std::chrono::steady_clock::now(); long long duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); g_statsCloseDevice.record(duration); if (MV_OK == nRet || nRet == MV_E_CALLORDER) { Log("设备关闭成功,耗时 %lld μs", duration); } else { Log("设备关闭失败: 0x%x,耗时 %lld μs", nRet, duration); } MV_CC_DestroyHandle(g_hCamHandle); g_hCamHandle = nullptr; Log("已销毁相机句柄"); } // 输出性能统计报告 Log("=== 性能统计汇总 ==="); g_statsOpenDevice.log("OpenDevice"); g_statsCloseDevice.log("CloseDevice"); g_statsStartGrabbing.log("StartGrabbing"); g_statsStopGrabbing.log("StopGrabbing"); g_statsSetExposureAuto.log("SetExposureAuto"); g_statsSetExposureTime.log("SetExposureTime"); g_statsTrigger.log("SoftwareTrigger"); if (g_logFile.is_open()) { g_logFile.close(); Log("日志文件已关闭"); } fflush(stdout); fflush(stderr); } // ==================== 控制台事件处理器 ==================== BOOL WINAPI ConsoleHandler(DWORD dwType) { switch (dwType) { case CTRL_C_EVENT: case CTRL_BREAK_EVENT: case CTRL_CLOSE_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: Log("接收到系统终止信号 (Event: %lu),正在安全退出...", dwType); g_bExit = true; g_bStopCycle = true; Sleep(100); CleanupResources(); ExitProcess(0); return TRUE; default: return FALSE; } } // ==================== 打印设备信息 ==================== bool PrintDeviceInfo(MV_CC_DEVICE_INFO* pstMVDevInfo) { if (NULL == pstMVDevInfo) { printf("The Pointer of pstMVDevInfo is NULL!\n"); return false; } if (pstMVDevInfo->nTLayerType == MV_GIGE_DEVICE) { int nIp1 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24); int nIp2 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16); int nIp3 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8); int nIp4 = (pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff); printf(" CurrentIp: %d.%d.%d.%d\n", nIp1, nIp2, nIp3, nIp4); printf(" UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName); } else if (pstMVDevInfo->nTLayerType == MV_USB_DEVICE) { printf(" UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName); printf(" Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chSerialNumber); printf(" Device Number: %d\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.nDeviceNumber); } else { printf(" Unknown device type: 0x%x\n", pstMVDevInfo->nTLayerType); } return true; } // ==================== 带耗时统计的核心函数 ==================== int SetExposureAuto(unsigned int nValue) { const char* modes[] = { "关闭", "单次", "连续" }; auto start = std::chrono::steady_clock::now(); int nRet = MV_CC_SetEnumValue(g_hCamHandle, "ExposureAuto", nValue); auto end = std::chrono::steady_clock::now(); long long duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); g_statsSetExposureAuto.record(duration); if (MV_OK == nRet) { Log("设置 ExposureAuto 为 [%s] 成功,耗时 %lld μs", modes[nValue], duration); } else { Log("设置 ExposureAuto 失败: 0x%x,耗时 %lld μs", nRet, duration); } return nRet; } int SetExposureTime(double fValue) { auto start = std::chrono::steady_clock::now(); int nRet = MV_CC_SetFloatValue(g_hCamHandle, "ExposureTime", fValue); auto end = std::chrono::steady_clock::now(); long long duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); g_statsSetExposureTime.record(duration); if (MV_OK == nRet) { Log("设置 ExposureTime 为 %.2f μs 成功,耗时 %lld μs", fValue, duration); } else { Log("设置 ExposureTime 失败: 0x%x,耗时 %lld μs", nRet, duration); } return nRet; } int StartGrabbing() { auto start = std::chrono::steady_clock::now(); int nRet = MV_CC_StartGrabbing(g_hCamHandle); auto end = std::chrono::steady_clock::now(); long long duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); g_statsStartGrabbing.record(duration); if (MV_OK == nRet) { Log("开始采集成功,耗时 %lld μs", duration); } else { Log("启动采集失败: 0x%x,耗时 %lld μs", nRet, duration); } return nRet; } int StopGrabbing() { auto start = std::chrono::steady_clock::now(); int nRet = MV_CC_StopGrabbing(g_hCamHandle); auto end = std::chrono::steady_clock::now(); long long duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); g_statsStopGrabbing.record(duration); if (MV_OK == nRet || nRet == MV_E_CALLORDER) { Log("停止采集成功,耗时 %lld μs", duration); } else { Log("停止采集失败: 0x%x,耗时 %lld μs", nRet, duration); } return nRet; } int SoftwareTriggerOnce() { auto start = std::chrono::steady_clock::now(); int nRet = MV_CC_SetCommandValue(g_hCamHandle, "TriggerSoftware"); auto end = std::chrono::steady_clock::now(); long long duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); g_statsTrigger.record(duration); if (MV_OK == nRet) { Log("发送软触发命令成功,耗时 %lld μs", duration); } else { Log("发送软触发命令失败: 0x%x,耗时 %lld μs", nRet, duration); } return nRet; } // 打开设备(封装以统计时间) int OpenDevice() { auto start = std::chrono::steady_clock::now(); int nRet = MV_CC_OpenDevice(g_hCamHandle); auto end = std::chrono::steady_clock::now(); long long duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); g_statsOpenDevice.record(duration); if (MV_OK == nRet) { Log("设备打开成功,耗时 %lld μs", duration); } else { Log("打开设备失败: 0x%x,耗时 %lld μs", nRet, duration); } return nRet; } // ==================== 生成唯一曝光时间序列 ==================== std::vector<double> GenerateUniqueExposureTimes(int count) { std::set<int> used; std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(1000, 30000); // 1ms ~ 30ms while (used.size() < count) { used.insert(dis(gen)); } return std::vector<double>(used.begin(), used.end()); } // ==================== 键盘监听线程 ==================== unsigned int __stdcall KeyboardThread(void* pParam) { printf("\n--- 按 [ESC] 键可随时停止循环 ---\n"); while (!g_bStopCycle && !g_bExit) { if (_kbhit()) { int key = _getch(); if (key == 27) // ESC { g_bStopCycle = true; Log("用户按下 ESC,准备停止循环..."); break; } } Sleep(50); } return 0; } // ==================== 主函数 ==================== int main() { int nRet = MV_OK; g_hCamHandle = nullptr; g_bStopCycle = false; // 注册控制台事件处理器 if (!SetConsoleCtrlHandler(ConsoleHandler, TRUE)) { printf("警告:无法注册控制台事件处理器!\n"); } // 打开日志文件 g_logFile.open("AutoToManualExposure.log", std::ios::out | std::ios::app); if (!g_logFile.is_open()) { printf("无法打开日志文件 AutoToManualExposure.log\n"); return -1; } Log("程序启动"); do { // 枚举设备 MV_CC_DEVICE_INFO_LIST stDeviceList = { 0 }; nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList); if (MV_OK != nRet) { Log("枚举设备失败: 0x%x", nRet); break; } if (stDeviceList.nDeviceNum == 0) { Log("未找到任何相机设备"); break; } printf("检测到 %d 台相机:\n\n", stDeviceList.nDeviceNum); for (unsigned int i = 0; i < stDeviceList.nDeviceNum; ++i) { printf("[设备 %d]:\n", i); PrintDeviceInfo(stDeviceList.pDeviceInfo[i]); printf("\n"); } unsigned int nIndex = 0; printf("请输入相机索引 (0-%d): ", stDeviceList.nDeviceNum - 1); scanf_s("%d", &nIndex); if (nIndex >= stDeviceList.nDeviceNum) { Log("输入无效"); break; } nRet = MV_CC_CreateHandle(&g_hCamHandle, stDeviceList.pDeviceInfo[nIndex]); if (MV_OK != nRet) { Log("创建句柄失败: 0x%x", nRet); break; } Log("句柄创建成功"); nRet = OpenDevice(); // 使用封装函数 if (MV_OK != nRet) { break; } if (stDeviceList.pDeviceInfo[nIndex]->nTLayerType == MV_GIGE_DEVICE) { int nPacketSize = MV_CC_GetOptimalPacketSize(g_hCamHandle); if (nPacketSize > 0) { nRet = MV_CC_SetIntValueEx(g_hCamHandle, "GevSCPSPacketSize", nPacketSize); if (nRet == MV_OK) { Log("设置数据包大小为 %d 字节", nPacketSize); } } } nRet = MV_CC_SetEnumValue(g_hCamHandle, "TriggerMode", 1); // On if (MV_OK != nRet) { Log("设置 TriggerMode 失败: 0x%x", nRet); break; } nRet = MV_CC_SetEnumValue(g_hCamHandle, "TriggerSource", 7); // Software if (MV_OK != nRet) { Log("设置 TriggerSource 失败: 0x%x", nRet); break; } Log("已配置为软触发模式"); std::vector<double> expTimes = GenerateUniqueExposureTimes(100); size_t expIndex = 0; char ch; printf("是否开始执行流程?(y/n): "); ch = getchar(); while (ch == '\n') ch = getchar(); if (ch != 'y' && ch != 'Y') { Log("用户取消操作"); break; } unsigned int threadId = 0; void* hKeyboardThread = (void*)_beginthreadex(nullptr, 0, KeyboardThread, nullptr, 0, &threadId); if (!hKeyboardThread) { Log("无法创建键盘监听线程"); break; } Log("开始自动循环执行,按 [ESC] 键退出..."); int cycle = 0; while (!g_bStopCycle) { auto totalStart = std::chrono::steady_clock::now(); Log("=== 第 %d 次流程执行 ===", ++cycle); // Step 1: 自动曝光 + 触发 StopGrabbing(); SetExposureAuto(2); StartGrabbing(); auto triggerStart = std::chrono::steady_clock::now(); SoftwareTriggerOnce(); Sleep(100); long long dur1 = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now() - triggerStart).count(); Log("第一次软触发完成,耗时 %lld ms", dur1); // Step 2: 切换到手动曝光 StopGrabbing(); SetExposureAuto(0); double expTime = expTimes[expIndex++ % expTimes.size()]; SetExposureTime(expTime); StartGrabbing(); triggerStart = std::chrono::steady_clock::now(); SoftwareTriggerOnce(); Sleep(100); long long dur2 = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now() - triggerStart).count(); Log("第二次软触发完成,耗时 %lld ms", dur2); StopGrabbing(); long long totalDur = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now() - totalStart).count(); Log("=== 流程第 %d 次执行完成,总耗时 %lld ms ===", cycle, totalDur); Sleep(200); } if (hKeyboardThread) { WaitForSingleObject(hKeyboardThread, 1000); CloseHandle(hKeyboardThread); } } while (false); CleanupResources(); printf("流程已停止。按任意键退出...\n"); _getch(); return 0; } 修改流程逻辑: 第一次打开相机后,输入Y开始流程后,第一次流程直接直接设置参数,开始取图,执行软触发,停止取图,修改参数,开始取图,执行软触发,停止取图;第二次流程直接开始设置参数,开始取图执行软触发,停止取图,修改参数,开始取图,执行软触发,停止取图;依次循环,打印各个接口的耗时
11-28
#include<thread> #include<iostream> #include<string.h> #include<windows.h> #include<conio.h> #include<mmsystem.h> #include<stdlib.h> using namespace std; #pragma comment(lib,"winmm.lib") //[1] #define MAXLEN 127 #define XSTART 16 #define YSTART 5 #define LENOFPAGE 146 #define DEPTHOFPAGE 20 short piano_type = 1; //2表示亮音钢琴,1是原声钢琴 typedef union { int i; char c[10]; }CwithI; typedef struct { char name[MAXLEN]; CwithI qusu; char ver[20]; }HEAD; void play_sound(char keyboard_key);//播放音频 void play_song(FILE* puzi, short* ystart, HEAD mus_info, short* flag);//播放音乐 void decoding_func(char keyboard_key, char* sound_name, short piano_type);//解码 void gotoxy(int x, int y);//移动光标至指定位置 void print_kuang();//打印框框 void print_pkeys();//打印手动模式下的静态钢琴键盘 void cls_kuang(short, short);//清除框框内指定行的信息 HEAD readhead(FILE*);//读取谱子文件的信息头 void HideCursor();//隐藏光标 void ShowCursor();//显示光标 char begin_page();//初始界面 char exit_page();//退出界面 char manual_page();//手动挡 char auto_page();//自动挡 int main() { system("mode con cols=180 lines=38"); //[8] short mode = 0; while (1) { //所有页面的中转站 if (mode == 0) { mode = begin_page(); } if (mode == 1) { mode = manual_page(); } if (mode == 2) { mode = auto_page(); } if (mode == 3) { exit_page(); break; } fflush(stdin); } return 0; } char exit_page() { HideCursor(); char sentences[][MAXLEN] = { "PROGRAMME DESIGN",\ "名字 from 院系 in 学校",\ "MUSIC RESOURCE",\ "autopiano.cn",\ "SPECIAL THANKS TO",\ "Professor 老师",\ "T.A. 助教1",\ "T.A. 助教2",\ "室友",\ "github.com/WarpPrism/AutoPiano",\ "runoob.com/cprogramming/c-tutorial.html",\ " ",\ "Thank You for Playing My Game!"\ }; print_kuang(); short ystart = YSTART + DEPTHOFPAGE / 2 - 11, line = 0, sigofstr = 0; int speed = 500; for (line = YSTART + DEPTHOFPAGE - 2; line + (sizeof(sentences) / MAXLEN - 2) * 2 > YSTART; line--) { for (sigofstr = 0; sigofstr < sizeof(sentences) / MAXLEN - 1; sigofstr++) { if (kbhit()) { speed = 0; //快速跳过制作人员名单 } if ((line + sigofstr * 2) <= YSTART + DEPTHOFPAGE - 3 && (line + sigofstr * 2) >= YSTART + 2) { cls_kuang(line + sigofstr * 2, -1); cls_kuang(line + sigofstr * 2, 1); //上下两行都清除一下 gotoxy(XSTART + LENOFPAGE / 2 - strlen(sentences[sigofstr]) / 2, line + sigofstr * 2); cout << sentences[sigofstr]; } } Sleep(speed); //滚动字幕的效果 } for (line = YSTART + DEPTHOFPAGE - 3; line >= YSTART + DEPTHOFPAGE / 2 - 1; line--) { cls_kuang(line, 1); gotoxy(XSTART + LENOFPAGE / 2 - strlen(sentences[sizeof(sentences) / MAXLEN - 1]) / 2, line); cout << sentences[sizeof(sentences) / MAXLEN - 1]; Sleep(speed); //同上一条注释 } gotoxy(XSTART + LENOFPAGE / 2 - strlen("请按任意键继续. . .") / 2, YSTART + DEPTHOFPAGE - 2); system("PAUSE"); return 0; } char begin_page() { char line_one[] = "Welcome to elec-piano!"; char line_two[] = "choose your mode:1.manual 2.auto 3.exit"; print_kuang(); gotoxy(XSTART + LENOFPAGE / 2 - strlen(line_one) / 2, YSTART + DEPTHOFPAGE / 2 - 3); //将光标移到方框的正中央并打印文字 cout << line_one; gotoxy(XSTART + LENOFPAGE / 2 - strlen(line_two) / 2, YSTART + DEPTHOFPAGE / 2 - 1); cout << line_two; gotoxy(XSTART + LENOFPAGE / 2, YSTART + DEPTHOFPAGE / 2 + 1); //将光标移至中央 char mode[127] = { 0 }; while (1) { cin >> mode; //输入检测 while (getchar() != '\n') { continue; } if (mode[0] >= '1' && mode[0] <= '3' && strlen(mode) == 1) { break; } else { cls_kuang(YSTART + DEPTHOFPAGE / 2 + 3, 0); gotoxy(XSTART + LENOFPAGE / 2 - strlen("Input error!please try again:") / 2, YSTART + DEPTHOFPAGE / 2 + 1); cout << "Input error!please try again:" << endl; gotoxy(XSTART + LENOFPAGE / 2, YSTART + DEPTHOFPAGE / 2 + 3); } } system("CLS"); return (mode[0] - '0'); } char auto_page() { FILE* dir, * puzi, * puzi_hx; char sys_dir_msg[MAXLEN]; char mus_namelist[50][MAXLEN] = { 0 }; short numofsong = 0, ystart = YSTART + 1, xstart = XSTART + 1, lenofsongname = 0; HEAD mus_info, hx_info; print_kuang(); gotoxy(XSTART + 1, ystart); cout << "Choose your music:"; gotoxy(XSTART + 1, ++ystart); dir = _popen("dir .\\songs", "r"); while (!feof(dir)) { fscanf(dir, "%s", sys_dir_msg); if (strstr(sys_dir_msg, ".dat") != NULL) {//列出songs文件夹中的dat文件 strcpy(mus_namelist[numofsong], sys_dir_msg); cout << ++numofsong << "." << sys_dir_msg; if (strlen(sys_dir_msg) > lenofsongname) { lenofsongname = strlen(sys_dir_msg); } gotoxy(xstart, ++ystart); if (ystart == DEPTHOFPAGE + YSTART - 1) { //文件过多时换个行继续 xstart += lenofsongname + 5; ystart = YSTART + 2; lenofsongname = 0; gotoxy(xstart, ystart); } } } fclose(dir); CwithI tempci; while (1) { scanf("%9s", tempci.c);//输入检测 while (getchar() != '\n') { continue; } tempci.c[9] = '\0'; tempci.i = atoi(tempci.c); if (tempci.i == 0 || tempci.i > 50 || tempci.i < 0) { cls_kuang(ystart, 1); cout << "Input Error!please try again:"; } else { break; } } thread t(play_sound, '+'); //播放一段无声的MP3以加载播放器相关dll t.detach(); gotoxy(XSTART + 1, ++ystart); char path_mode[50] = "songs\\%s"; char path[50] = { 0 }; char path_hx[50] = { 0 }; sprintf(path, path_mode, mus_namelist[tempci.i - 1]); strcpy(path_hx, path); path_hx[strlen(path) - 1] = 'x'; path_hx[strlen(path) - 2] = 'h'; puzi = fopen(path, "r"); if (puzi == NULL) { cout << "File does not exist! Check your input"; Sleep(2500); system("cls"); return 0; } short hx_flag = 1; puzi_hx = fopen(path_hx, "r"); if (puzi_hx == NULL) { hx_flag = 0; } else { hx_info = readhead(puzi_hx); } mus_info = readhead(puzi); system("cls"); print_kuang(); ystart = YSTART + 1; gotoxy(XSTART + 1, ystart); cout << "now play: " << mus_info.name; gotoxy(XSTART + 1, ++ystart); short flag = 0; if (hx_flag == 1) {//有和弦时进入此分支 while (1) { thread t1(play_song, puzi, &ystart, mus_info, &flag); thread t2(play_song, puzi_hx, &ystart, hx_info, &hx_flag); t2.detach(); t1.join(); if (feof(puzi) || feof(puzi_hx) || flag == 2 || hx_flag == 2) {//其中一个谱子放完了自动跳出整个循环 break; } } } else { while (1) { thread t1(play_song, puzi, &ystart, mus_info, &flag); t1.join(); if (feof(puzi) || flag == 2) { break; } } } fclose(puzi); if (hx_flag != 0) { fclose(puzi_hx); } gotoxy(XSTART + 1, ++ystart); cls_kuang(ystart, 1); cout << "end" << endl; cls_kuang(++ystart, 1); system("PAUSE"); system("CLS"); return 0; } char manual_page() { HideCursor(); print_kuang(); print_pkeys(); gotoxy(XSTART + 1, YSTART + DEPTHOFPAGE - 2); cout << "press Enter to quit the manual mode!"; thread t(play_sound, '+'); //播放一段无声的MP3以加载播放器相关dll t.detach(); int i = 0; while (1) { char keyboard_key; keyboard_key = getch(); //[2] if (keyboard_key == '\r') { break; //按回车退出 } thread t(play_sound, keyboard_key); //[3] t.detach(); FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE)); //[4] } gotoxy(XSTART + LENOFPAGE / 2 - strlen("exit") / 2, YSTART + DEPTHOFPAGE / 2); cout << "exit!"; ShowCursor(); system("CLS"); return 0; } void decoding_func(char keyboard_key, char* sound_name, short piano_type) { //在plays函数中被引用,可有效降低创建线程时传参的复杂程度 char jian[] = { '1','!','2','@','3','4','$','5','%','6','^','7','8','*','9','(','0','q','Q',\ 'w','W','e','E','r','t','T','y','Y','u','i','I','o','O','p','P','a','s','S','d','D',\ 'f','g','G','h','H','j','J','k','l','L','z','Z','x','c','C','v','V','b','B','n','m' }; //所有音名对应的按键 char zhi_zimu[][3] = { "C","Cs","D","Ds","E","F","Fs","G","Gs","A","As","B" }; //音名字母部分 char zhi_shuzi[][2] = { "0","1","2","3","4","5","6","7" }; //音名数字部分 for (short i = 0; i < sizeof(jian); i++) { if (keyboard_key == jian[i]) { short zimu_bianhao = i % 12; //字母部分12个一轮回 short shuzi_bianhao = i / 12; //数字部分每十二个音+1 char ss_zimu[5], ss_shuzi[2]; strcpy(ss_zimu, zhi_zimu[zimu_bianhao]); strcpy(ss_shuzi, zhi_shuzi[shuzi_bianhao + piano_type]); //带一个piano_type补正可以拓展音域 strcat(ss_zimu, ss_shuzi); strcpy(sound_name, ss_zimu); } else { continue; } } } void play_sound(char keyboard_key) { char sound_name[5] = { 0 }; //音名 char temp_command[MAXLEN] = { 0 }; //mciSendString的命令 if (keyboard_key == '+') { strcpy(sound_name, "No_sound"); //播放一段无声的声音,用于加载与播放器有关的DLL文件 } else { decoding_func(keyboard_key, sound_name, piano_type); //将键盘上的键对应地解码成音名 } sprintf(temp_command, "open piano\\%s.mp3 alias %s", sound_name, sound_name); mciSendStringA(temp_command, 0, 0, 0); //[5] //打开音名.mp3 sprintf(temp_command, "play %s", sound_name); mciSendStringA(temp_command, 0, 0, 0); //播放 Sleep(10000); sprintf(temp_command, "close %s", sound_name); //关闭 mciSendStringA(temp_command, 0, 0, 0); return; } void play_song(FILE* puzi, short* ystart, HEAD mus_info, short* flag) { char keyboard_key; keyboard_key = fgetc(puzi); if (*flag == 0) { cout << keyboard_key; } if (keyboard_key == '|' || keyboard_key == '\n') {//遇到|和换行符时,由于不对乐曲本身发挥任何作用,因此需要特殊处理 if (keyboard_key == '\n' && (*flag) == 0) { if ((*ystart) + 1 == DEPTHOFPAGE + YSTART - 1) { *ystart = YSTART + 2; cls_kuang(*ystart, 1); } else { if ((*ystart) + 2 == DEPTHOFPAGE + YSTART - 1) { (*ystart)++; gotoxy(XSTART + 1, *ystart); } else { (*ystart)++; cls_kuang(*ystart, 1); } } } return; } else { Sleep(mus_info.qusu.i);//此处控制曲速,注意:与一般音乐软件使用的曲速单位bpm不同,这里只是单纯的停顿一定毫秒 } if (kbhit()) { char kbout = '\0'; kbout = _getch(); if (kbout == '\r') { //播放中途按回车键退出 *flag = 2; return; } } thread t(play_sound, keyboard_key); //单独分出线程来播放声音 t.detach(); return; } void gotoxy(int x, int y) { //[6] _COORD pos; pos.X = x; pos.Y = y; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); //设置鼠标位置 } void print_kuang() { short start_x = XSTART, start_y = YSTART; short page_x = LENOFPAGE, page_y = DEPTHOFPAGE; gotoxy(start_x, start_y); //将鼠标移动到起始点 for (short y = 0; y < page_y; y++) { if (y == 0 || y == page_y - 1) { gotoxy(start_x, start_y + y); for (short x = 0; x <= page_x; x++) { cout << "="; //若为第一行或最后一行,则打印page_x个= } } else { gotoxy(start_x, start_y + y); cout << '|'; gotoxy(start_x + page_x, start_y + y); cout << '|'; //每行开头和末尾打印| } } } void print_pkeys() { short ystart = YSTART + 1; gotoxy(XSTART + 1, ystart);//普通的打印(很蠢) cout << "_________________________________________________________________________________________________________________________________________________"; for (short i = 0; i < 6; i++) { gotoxy(XSTART + 1, ++ystart); cout << "| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |"; } gotoxy(XSTART + 1, ++ystart); cout << "| |!| |@| | |$| |%| |^| | |*| |(| | |Q| |W| |E| | |T| |Y| | |I| |O| |P| | |S| |D| | |G| |H| |J| | |L| |Z| | |C| |V| |B| | |"; for (short i = 0; i < 2; i++) { gotoxy(XSTART + 1, ++ystart); cout << "| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |"; } gotoxy(XSTART + 1, ++ystart); cout << "| |_| |_| | |_| |_| |_| | |_| |_| | |_| |_| |_| | |_| |_| | |_| |_| |_| | |_| |_| | |_| |_| |_| | |_| |_| | |_| |_| |_| | |"; for (short i = 0; i < 2; i++) { gotoxy(XSTART + 1, ++ystart); cout << "| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |"; } gotoxy(XSTART + 1, ++ystart); cout << "| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | q | w | e | r | t | y | u | i | o | p | a | s | d | f | g | h | j | k | l | z | x | c | v | b | n | m |"; gotoxy(XSTART + 1, ++ystart); cout << "| C2| D2| E2| F2| G2| A2| B2| C3| D3| E3| F3| G3| A3| B3| C4| D4| E4| F4| G4| A4| B4| C5| D5| E5| F5| G5| A5| B5| C6| D6| E6| F6| G6| A6| B6| C7|"; gotoxy(XSTART + 1, ++ystart); cout << "|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|"; } void cls_kuang(short line, short mode) { gotoxy(XSTART + 1, line);//清除框框内的文字,比system(“cls”)更快 for (short c = 0; c < LENOFPAGE - 1; c++) { printf(" "); } gotoxy(XSTART + 1, line + mode);//清除当前行数之外的另一行 for (short c = 0; c < LENOFPAGE - 1; c++) { printf(" "); } gotoxy(XSTART + 1, line);//光标回到初始位置 } void HideCursor() { //[7] CONSOLE_CURSOR_INFO cursor; //隐藏光标 cursor.bVisible = FALSE; cursor.dwSize = sizeof(cursor); HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorInfo(handle, &cursor); } void ShowCursor() { CONSOLE_CURSOR_INFO cursor; //显示光标 cursor.bVisible = TRUE; cursor.dwSize = sizeof(cursor); HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorInfo(handle, &cursor); } HEAD readhead(FILE* puzi) { //读取曲谱文件的标头,获取曲速,曲名等信息 char flag = 0, keyboard_key, count = 0; char tempstr[MAXLEN] = { 0 }; HEAD temphead; while (!feof(puzi)) { keyboard_key = fgetc(puzi); if (keyboard_key == '<') { flag++; continue; } if (keyboard_key == '>') { count = 0; flag++; //使得变量flag兼具计数和条件判断的功能 if (flag == 2) { strcpy(temphead.name, tempstr); } if (flag == 4) { strcpy(temphead.qusu.c, tempstr); temphead.qusu.i = atoi(temphead.qusu.c); } if (flag == 6) { strcpy(temphead.ver, tempstr); keyboard_key = fgetc(puzi); //将信息头最后一个换行符给吃掉 break; } memset(tempstr, '\0', sizeof(tempstr)); } if (flag % 2 == 1) { tempstr[count++] = keyboard_key; } } return temphead; } 我用visual studio运行报错,我一会给你发送错误,请你修改
最新发布
12-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值