C++----异常
一、C 语言传统的错误处理方式
在 C 语言中,处理错误主要有两种传统方式,每种方式都有其特点和局限性。
1. 终止程序
- 原理:使用类似
assert
这样的断言机制,当程序运行到某个条件不满足时,直接终止程序的执行。 - 示例代码
#include <assert.h> #include <stdio.h>void divide(int a, int b) {assert(b != 0); // 断言b不为0,如果b为0,程序会终止printf("%d / %d = %d\n", a, b, a / b); }int main() {divide(10, 0);return 0; }
- 缺陷:这种方式对用户不太友好,因为一旦出现错误,程序会直接崩溃,用户无法进行一些必要的处理或恢复操作。例如在发生内存错误、除 0 错误等情况时,程序会突然终止,给用户带来不好的体验。
2. 返回错误码
- 原理:函数在执行过程中,如果遇到错误,会返回一个特定的错误码。程序员需要根据这个错误码去查找对应的错误信息。在系统的很多库的接口函数中,会把错误码放到
errno
变量中,表示发生的错误。 - 示例代码
#include <stdio.h> #include <errno.h> #include <string.h>int divide(int a, int b) {if (b == 0) {errno = EINVAL; // 设置错误码为无效参数return -1;}return a / b; }int main() {int result = divide(10, 0);if (result == -1) {printf("Error: %s\n", strerror(errno)); // 根据错误码输出错误信息} else {printf("Result: %d\n", result);}return 0; }
- 缺陷:需要程序员手动去查找和处理错误码对应的错误信息,增加了开发的复杂度。而且不同的库可能使用不同的错误码体系,容易造成混淆。
实际应用情况
在实际的 C 语言编程中,基本都是使用返回错误码的方式处理错误,只有在处理非常严重的错误时,才会使用终止程序的方式。
二、C++ 异常概念
C++ 引入了异常机制,为错误处理提供了一种更加灵活和强大的方式。当一个函数发现自己无法处理的错误时,可以抛出异常,让函数的直接或间接调用者来处理这个错误。
1. 异常处理的关键字
throw
:当问题出现时,程序会抛出一个异常。通过使用throw
关键字来完成,后面可以跟任意类型的数据,如整数、字符串、自定义对象等。catch
:用于捕获异常。可以有多个catch
块,每个catch
块可以捕获不同类型的异常。try
:try
块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个catch
块。try
块中的代码被称为保护代码。
2. try/catch
语句的语法
try
{// 保护的标识代码
}
catch( ExceptionName e1 )
{// catch 块,处理类型为ExceptionName的异常
}
catch( ExceptionName e2 )
{// catch 块,处理类型为ExceptionName的异常
}
catch( ExceptionName eN )
{// catch 块,处理类型为ExceptionName的异常
}
3. 示例代码
#include <iostream>
using namespace std;// 定义一个除法函数,可能会抛出异常
double Division(int a, int b) {// 当b == 0时抛出异常if (b == 0) {throw "Division by zero condition!";} else {return ((double)a / (double)b);}
}// 定义一个函数,调用Division函数并处理异常
void Func() {try {int len, time;cout << "请输入两个整数(用空格分隔): ";cin >> len >> time;cout << "除法结果: " << Division(len, time) << endl;}catch (const char* errmsg) {cout << "捕获到异常: " << errmsg << endl;}}int main() {try {Func();}catch (const char* errmsg) {cout << "捕获到字符串类型异常: " << errmsg << endl;}return 0;
}
三、异常处理基础
1. 异常抛出与捕获原则
-
抛出机制:通过抛出对象触发异常(可抛出任意类型对象)
-
匹配规则:
-
匹配类型相同且位置最近的catch块
#include <iostream>// 函数B抛出整数类型异常 void functionB() {throw 42; }// 函数A调用函数B,并尝试捕获异常 // functionB 抛出一个 int 类型异常。 void functionA() {try {functionB();} catch (double) {std::cout << "在functionA中捕获到double类型异常" << std::endl;} catch (int num) {//与第二个 catch 块匹配,且它离异常抛出点 functionB 最近,//所以会执行该 catch 块,输出“在functionA中捕获到int类型异常,//值为: 42”。std::cout << "在functionA中捕获到int类型异常,值为: " << num << std::endl;} }int main() {try {functionA();} catch (...) {//如果functionA中没有匹配int类型的catch块,异常会传递到main函数,//main函数中的 catch(...) 会捕获所有类型异常。std::cout << "在main中捕获到其他类型异常" << std::endl;}return 0; }
-
会生成异常对象的拷贝(保证异常对象有效性)
// 抛出字符串异常示例 double Division(int a, int b) {if (b == 0) {string s("Division by zero!");throw s; // 抛出拷贝后的临时对象}return static_cast<double>(a)/b; }void func() {int x, y;cin >> x >> y;cout << Division(x, y) << endl; }int main() {while (true) {try {func();}catch (const string& err) { // 捕获引用避免拷贝cout << "Error: " << err << endl;}} }
-
派生类异常可用基类捕获(实际开发常用方式)
-
2. 通用捕获与继承体系
// 通用捕获与继承示例
class BaseException {};
class MathException : public BaseException {};int main() {try {throw MathException();}catch (const BaseException&) { // 基类捕获派生类异常cout << "Base exception caught" << endl; }catch (...) { // 最后防线捕获所有异常cout << "Unknown exception" << endl;}
}
四、异常传播机制
1. 栈展开过程
-
检查throw所在try块
-
逐层回退调用栈查找匹配catch
-
到达main未匹配则程序终止
-
异常处理后继续执行catch块后续代码
2. 异常重新抛出
double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Division by zero condition!";}return (double)a / (double)b;
}
void Func()
{// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再// 重新抛出去。int* array = new int[10];try {int len, time;cin >> len >> time;cout << Division(len, time) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;throw;}// ...cout << "delete []" << array << endl;delete[] array;
}int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}return 0;
}
五、异常安全规范
1. 关键原则
-
构造函数:避免抛出异常(可能导致对象不完整)
-
析构函数:禁止抛出异常(防止资源泄漏)
-
RAII机制:通过智能指针等实现资源自动管理
2.异常规范
- 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的 后面接throw(类型),列出这个函数可能抛掷的所有异常类型。
- 函数的后面接throw(),表示函数不抛异常。
- 若无异常接口声明,则此函数可以抛掷任何类型的异常。
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;
六、自定义异常体系设计
1. 服务器开发典型继承体系
在服务器开发中,为了更好地管理和处理不同类型的异常,通常会设计一个异常继承体系。以下是一个示例代码:
// 异常基类(抽象错误类型)
// Exception 类:作为基类异常,包含异常信息 _errmsg 和异常编号 _id,
// 并定义了虚函数 what() 用于返回异常信息,支持多态。
class Exception {
public:Exception(const string& errmsg, int id): _errmsg(errmsg), _id(id) {}virtual string what() const {return "[" + to_string(_id) + "] " + _errmsg;}virtual ~Exception() = default; // 虚析构保证正确释放protected:string _errmsg; // 错误描述int _id; // 错误编号
};// SQL操作异常(具体错误类型)
// SqlException 类:继承自 Exception 类,添加了 _sql 成员变量,重写了
// what() 函数,返回更详细的 SQL 异常信息。
class SqlException : public Exception {
public:SqlException(const string& errmsg, int id, const string& sql): Exception(errmsg, id), _sql(sql) {}virtual string what() const override {return Exception::what() + "\n[SQL] " + _sql;}private:string _sql; // 错误关联的SQL语句
};// 缓存异常类,继承自 Exception
// CacheException 类:继承自 Exception 类,重写了 what() 函数,返回缓存异常信息
class CacheException : public Exception {
public:CacheException(const std::string& errmsg, int id): Exception(errmsg, id){}// 重写 what 函数,返回缓存异常信息virtual std::string what() const {std::string str = "CacheException:";str += _errmsg;return str;}
};// HTTP服务异常(具体错误类型)
// HttpServerException 类:继承自 Exception 类,添加了 _type 成员变量,
// 重写了 what() 函数,返回 HTTP 服务器异常信息。
class HttpServerException : public Exception {
public:HttpServerException(const string& errmsg, int id, const string& type): Exception(errmsg, id), _type(type) {}virtual string what() const override {return "[HTTP " + _type + " Error] " + Exception::what();}private:string _type; // 请求类型(GET/POST等)
};
what()
函数的作用:what()
函数是一个虚函数,在基类中定义,派生类可以重写该函数。通过基类指针或引用调用what()
函数时,会根据实际对象的类型调用相应派生类的what()
函数,实现多态。这样可以方便地根据不同的异常类型输出不同的异常信息。
2. 异常体系使用示例
// 模拟 SQL 管理函数,可能抛出 SqlException
void SQLMgr() {srand(time(0));if (rand() % 7 == 0) {throw SqlException("权限不足", 100, "select * from name = '张三'");}std::cout << "执行成功" << std::endl;
}// 模拟缓存管理函数,可能抛出 CacheException 或调用 SQLMgr 抛出 SqlException
void CacheMgr() {srand(time(0));if (rand() % 5 == 0) {throw CacheException("权限不足", 100);} else if (rand() % 6 == 0) {throw CacheException("数据不存在", 101);}SQLMgr();
}// 模拟 HTTP 服务器函数,可能抛出 HttpServerException 或调用 CacheMgr 抛出其他异常
void HttpServer() {srand(time(0));if (rand() % 3 == 0) {throw HttpServerException("请求资源不存在", 100, "get");} else if (rand() % 4 == 0) {throw HttpServerException("权限不足", 101, "post");}CacheMgr();
}// 主函数,捕获异常并处理
int main() {while (1) {Sleep(500);try {HttpServer();} catch (const Exception& e) { // 捕获基类异常对象,利用多态处理不同派生类异常std::cout << e.what() << std::endl;} catch (...) {std::cout << "Unkown Exception" << std::endl;}}return 0;
}
3. 常用异常编号
编号 | 异常描述 |
---|---|
1 | 没有权限 |
2 | 服务器挂了 |
3 | 网络错误 |
七、常用标准库异常
bad_alloc
:当使用new
运算符进行动态内存分配失败时,会抛出bad_alloc
异常。out_of_range
:当使用容器(如std::vector
、std::string
等)的成员函数访问越界元素时,会抛出out_of_range
异常。invalid_argument
:当函数接收到无效的参数时,会抛出invalid_argument
异常。
八、异常的优缺点
1. 优点
- 清晰准确的错误信息:异常对象可以定义丰富的信息,相比错误码方式,能更清晰准确地展示错误信息,甚至可以包含堆栈调用信息,有助于更好地定位程序的 bug。
- 简化错误处理流程:在函数调用链中,使用返回错误码的传统方式需要层层返回错误,最外层才能拿到错误信息并处理。而异常体系中,不管是深层函数还是中间层函数出错,抛出的异常会直接跳到合适的
catch
块中,由调用者直接处理错误。//示例代码(错误码方式) #include <iostream> #include <errno.h>int ConnectSql() {// 用户名密码错误if (...) {return 1;}// 权限不足if (...) {return 2;}return 0; }int ServerStart() {if (int ret = ConnectSql() < 0) {return ret;}int fd = socket();if (fd < 0) {return errno;}return 0; }int main() {if (ServerStart() < 0) {// 处理错误}return 0; }
//示例代码(异常方式) #include <iostream> #include <stdexcept>void ConnectSql() {// 用户名密码错误if (...) {throw std::runtime_error("用户名密码错误");}// 权限不足if (...) {throw std::runtime_error("权限不足");} }void ServerStart() {ConnectSql();int fd = socket();if (fd < 0) {throw std::system_error(errno, std::system_category(), "socket 错误");} }int main() {try {ServerStart();} catch (const std::exception& e) {std::cout << "捕获到异常: " << e.what() << std::endl;}return 0; }
- 与第三方库兼容:很多第三方库(如
boost
、gtest
、gmock
等)都使用了异常机制,使用这些库时,我们也需要使用异常来与之配合。 - 适合特定函数:对于一些没有返回值的函数(如构造函数),或者不方便使用返回值表示错误的函数(如
T& operator[]
),使用异常处理错误更加合适。
2. 缺点
- 执行流混乱:异常会导致程序的执行流乱跳,尤其是在运行时出错抛异常时,会使程序的控制流变得复杂,增加了跟踪调试和分析程序的难度。
- 性能开销:异常处理会有一定的性能开销,不过在现代硬件速度较快的情况下,这个影响通常可以忽略不计。
- 异常安全问题:C++ 没有垃圾回收机制,资源需要自己管理。使用异常容易导致内存泄漏、死锁等异常安全问题,需要使用 RAII(资源获取即初始化)技术来管理资源,增加了学习成本。
- 标准库异常体系混乱:C++ 标准库的异常体系定义不够完善,导致不同开发者各自定义自己的异常体系,使得代码的可维护性和兼容性受到影响。
- 异常规范问题:异常需要规范使用,否则会给外层捕获异常的用户带来困扰。异常规范主要包括两点:一是抛出的异常类型都继承自一个基类,二是函数是否抛异常、抛什么异常,都使用
func() throw();
的方式进行规范化。
总结
异常总体而言利大于弊,在工程中鼓励使用异常。而且面向对象的编程语言基本都采用异常处理错误,这也是软件开发的趋势。
相关文章:

C++----异常
一、C 语言传统的错误处理方式 在 C 语言中,处理错误主要有两种传统方式,每种方式都有其特点和局限性。 1. 终止程序 原理:使用类似assert这样的断言机制,当程序运行到某个条件不满足时,直接终止程序的执行。示例代…...
合理规划时间,从容应对水利水电安全员考试
合理规划时间,从容应对水利水电安全员考试 在忙碌的工作与生活节奏中备考水利水电安全员考试,合理规划时间是实现高效备考的核心。科学的时间管理能让你充分利用每一分每一秒,稳步迈向考试成功。 制定详细的学习计划是第一步。依据考试时间…...
(解决) Windows 11使用SetSuspendState睡眠命令但是进入的是休眠
Windows 11 24H2 goes into hibernation mode instead of sleep mode. How can I create a sleep mode shortcut file? 25年3月4号 Win11 23H2 起因 使用网上说的睡眠命令创建bat双击后,电脑风扇会运行一段时间后再停止(应该是在保存进程到硬盘&#…...
Spring Boot 接口 JSON 序列化优化:忽略 Null 值的九种解决方案详解
一、针对特定接口null的处理: 方法一:使用 JsonInclude 注解 1.1 类级别:在接口返回的 DTO 类或字段 上添加 JsonInclude 注解,强制忽略 null 值: 类级别:所有字段为 null 时不返回 JsonInclude(Js…...

计算机毕业设计Python+DeepSeek-R1大模型考研院校推荐系统 考研分数线预测 考研推荐系统 考研(源码+文档+PPT+讲解)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
一、Prometheus架构
Prometheus 云原生十二要素是一套最佳实践和规范,旨在帮助开发人员更好地构建云原生应用 这十二个要素分别是: 单一职责独立部署无状态声明式API服务发现容错处理自适应算法自动化运维响应式编程通信协议服务注册与发现数据持久化一、Prometheus 是什么 Prometheus 是一个…...

火山引擎 DeepSeek R1 API 使用小白教程
一、火山引擎 DeepSeek R1 API 申请 首先需要三个要素: 1)API Key 2)API 地址 3)模型ID 1、首先打开火山引擎的 DeepSeek R1 模型页面 地址:账号登录-火山引擎 2、在页面右下角,找到【推理】按钮&#…...
react+vite+pnpm+ts基础项目搭建
1. 项目初始化 pnpm create vitelatest my-react-app --template react-ts cd my-react-app pnpm install2. 核心依赖安装 # 基础依赖 pnpm add react-router-dom tanstack/react-query zustand axios# UI 组件库 (任选其一) pnpm add mui/material emotion/react emotion/st…...

ArcGIS Pro 经纬网添加全解析:从布局到样式优化
在地理信息系统的广阔领域中,地图的精确性与直观性对于数据的呈现和分析起着至关重要的作用。 经纬网,作为地图上不可或缺的元素之一,能够为用户提供准确的地理坐标参考,帮助用户快速定位和理解地理空间数据的分布。 本文将深入…...

新闻研究导刊杂志社《新闻研究导刊》编辑部2024年第23期目录
研究论文 媒介智能化环境下新闻传播面临的风险及应对策略研究 冶玉娜; AI赋能地方政务新媒体智能化转型策略研究——以佛山政务新媒体为例 温秀妍; 新闻传播在社交媒体影响下的流变与发展展望 李晋; 县级融媒体中心生产优质短视频的路径探索 陈政清; 数字游…...

DDoS攻击的介绍和防治
一.DDoS攻击是什么 DDoS攻击:dos是服务器拒绝提供服务的意思,最前面的D是分布式的意思,所以说这个大概可以理解为分布式的机器攻击服务器,占用服务器资源,使得服务器拒绝提供服务的一种攻击手段,虽然原理简…...
UDP透传程序
UDP透传程序 本脚本用于在 设备 A 和 设备 B 之间建立 UDP 数据转发桥梁,适用于 A 和 B 设备无法直接通信的情况。 流程: A --> 电脑 (中继) --> B B --> 电脑 (中继) --> A 需要修改参数: B_IP “192.168.1.123” # 设备 B 的…...
深度学习pytorch之简单方法自定义9种卷积即插即用
本文详细解析了 PyTorch 中 torch.nn.Conv2d 的核心参数,通过代码示例演示了如何利用这一基础函数实现多种卷积操作。涵盖的卷积类型包括:标准卷积、逐点卷积(1x1 卷积)、非对称卷积(长宽不等的卷积核)、空…...

TMS320F28P550SJ9学习笔记2:Sysconfig 配置与点亮LED
今日学习使用Sysconfig 对引脚进行配置,并点亮开发板上的LED4 与LED5 我的单片机开发板平台是 LAUNCHXL_F28P55x 我是在上文描述的驱动库C2000ware官方例程example的工程基础之上进行添加功能的 该例程路径如下:D:\C2000Ware_5_04_00_00\driverlib\f28p…...

zRAM内存压缩技术:原理与实践初探
zRAM内存压缩技术:原理与实践指南 1. 技术背景与原理 zRAM是Linux内核中的一项内存压缩技术,于2014年进入Linux 3.14内核主线。它的核心思想是利用CPU压缩算法压缩内存数据,在不增加物理内存的情况下扩展系统有效内存容量。 当系统内存紧张…...
Hive 3.1 在 metastore 运行的 remote threads
Remote threads 是仅当 Hive metastore 作为单独的服务运行是启动,请求需要开启 compactor。 有以下几种: 1. AcidOpenTxnsCounterService 统计当前 open 的事务数 从表 TXNS 中统计状态为 open 的事务。此事务数量可以再 hive metrics 中。 2. Acid…...

大语言模型揭秘:从诞生到智能
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)无疑是技术领域最耀眼的明星之一。它们不仅能够理解人类的自然语言,还能生成流畅的文本,甚至在对话、翻译、创作等任务中表现出接近人类的智能…...

基于模糊PID控制的供热控制系统设计Simulink仿真
1.模型简介 本仿真模型基于MATLAB/Simulink(版本MATLAB 2017Ra)软件。建议采用matlab2017 Ra及以上版本打开。(若需要其他版本可联系店主代为转换) 换热站干扰因素多导致传统PID控制无法满足控制要求的问题,提出利用…...

宝塔找不到php扩展swoole,服务器编译安装
1. 在php7.4中安装swoole,但找不到这个扩展安装 2. 服务器下载源码解压安装 http://pecl.php.net/package/swoole 下载4.8.0版本 解压到/www/server/php/74/下 3. 发现报错问题; 更新一下依赖 yum update yum -y install gcc gcc-c autoconf libjpe…...
LeetCode 1745.分割回文串 IV:动态规划(用III或II能直接秒)
【LetMeFly】1745.分割回文串 IV:动态规划(用III或II能直接秒) 力扣题目链接:https://leetcode.cn/problems/palindrome-partitioning-iv/ 给你一个字符串 s ,如果可以将它分割成三个 非空 回文子字符串,…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙
Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...

聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇
根据 QYResearch 发布的市场报告显示,全球市场规模预计在 2031 年达到 9848 万美元,2025 - 2031 年期间年复合增长率(CAGR)为 3.7%。在竞争格局上,市场集中度较高,2024 年全球前十强厂商占据约 74.0% 的市场…...