【C++】异常 -- 详解

28 篇文章 2 订阅
订阅专栏
4 篇文章 0 订阅
订阅专栏

一、C 语言传统的处理错误的方式

传统的错误处理机制:

  1. 终止程序,如 assert,缺陷:用户难以接受。如发生内存错误,除 0 错误时就会终止程序。
  2. 返回错误码,缺陷:需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到 errno 中,表示错误实际中 C 语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。

二、C++ 异常概念

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者来处理这个错误。

  • throw当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch在想要处理问题的地方,通过异常处理程序捕获异常 catch 关键字用于捕获异常,可以有多个 catch 进行捕获
  • trytry 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块
如果有一个块抛出一个异常,捕获异常的方法会使用 try catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码
使用 try/catch 语句的语法如下所示:
try
{
    // 保护的标识代码
}catch( ExceptionName e1 )
{
    // catch 块
}catch( ExceptionName e2 )
{
    // catch 块
}catch( ExceptionName eN )
{
    // catch 块
}

三、异常的使用

1、异常的抛出和捕获

抛异常,异常必须被捕获 ,若没有被捕获就会报错。该图程序中只有抛异常,没有捕获异常存在,所以当 b = 0 时,程序直接报错。


(1)异常的抛出和匹配原则
a. 异常是通过抛出对象而引发的,对象的类型决定了应该激活哪个 catch 的处理代码。
double Division(int a, int b)
{
    // 当b == 0时抛出异常
    if (b == 0)
        throw "Division by zero condition!";//抛异常
    else
        return ((double)a / (double)b);
}
void Func()
{
    int len, time;
    cin >> len >> time;
    cout << Division(len, time) << endl;
}
int main()
{
    try
    {
        Func();
    }
    catch (const char* str)
    {
        cout << str << endl;
    }
    return 0;
}

  • catch 的时候,需要跟 throw 抛出对象的类型进行匹配。上面的 throw 传过来的是字符串,所以 catch 用 const char* 接收。
  • 由于有捕获异常,所以当再次 b = 0 时,就不会报错了,显示的详细信息为 Division by zero condition!
  • try 和 catch 两者是配对的, catch 只能捕获 try 里面的抛的异常。比如,在 main 函数中的 catch 捕获异常只能捕获 Func 函数中抛的异常。

b. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。

抛出异常位置最近的验证:

double Division(int a, int b)
{
    // 当b == 0时抛出异常
    if (b == 0)
        throw "Division by zero condition!";//抛异常
    else
        return ((double)a / (double)b);
}
void Func()
{
    int len, time;
    cin >> len >> time;
    try
    {
        cout << Division(len, time) << endl;
    }
    catch (const char* str)
    {
        cout << str << endl;
    }        
    cout << "void func()" << endl;
}
int main()
{
    try
    {
        Func();
    }
    catch (const char* str)
    {
        cout << str << endl;
    }
    return 0;
}

若在 Func 函数处添加捕获,并且类型与对象类型匹配,则当 b = 0 时,由于 Func 函数处更近,所以在 Func 函数处捕捉异常,而不在 main 函数中捕获异常。

对象类型匹配的验证: 

此时 Func 函数中的捕获异常与对象类型不匹配,当再次输入 b = 0 时,在 main 函数处捕获异常:

double Division(int a, int b)
{
    // 当b == 0时抛出异常
    if (b == 0)
        throw "Division by zero condition!";//抛异常
    else
        return ((double)a / (double)b);
}
void Func()
{
    int len, time;
    cin >> len >> time;
    try
    {
        cout << Division(len, time) << endl;
    }
    catch (char str)
    {
        cout << str << endl;
    }        
    cout << "void func()" << endl;
}
int main()
{
    try
    {
        Func();
    }
    catch (char str)
    {
        cout << str << endl;
    }
    return 0;
}

若 Func 函数和 main 函数的捕获异常与对象类型都不匹配 ,则程序会报错。


c. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被 catch 以后销毁。(这里的处理类似于函数的传值返回)

如果错误信息只是一个字符串,有些过于简单,所以设置一个类,内部包含错误码和错误描述。由于成员变量是私有的,在类外可能拿不到,所以设置两个函数,通过函数返回值的方式取到错误码和错误描述。由于对象类型为 const Exception,所以想要使用对象取到这两个函数 ,就需要在外部加上 const 修饰。

对比上面,将字符串替换成了对象,对象含有错误码和错误描述两部分。通过抛异常的方式将对象 传递给 catch 的捕获,在将对象的错误码和错误信息打印出来。

抛异常时,并不是把 e1 直接传给 e。因为 e1 是一个局部对象,出了作用域就销毁了,会产生一个临时对象,将 e1 对象的错误码和错误描述拷贝给临时对象,再通过临时对象传给对象 e,在 catch 结束后,临时对象销毁。


d. catch(...) 可以捕获任意类型的异常,问题是不知道异常错误是什么。
double Division(int a, int b)
{
    // 当b == 0时抛出异常
    if (b == 0)
        throw "Division by zero condition!";//抛异常
    else
        return ((double)a / (double)b);
}
void Func()
{
    int len, time;
    cin >> len >> time;
    try
    {
        cout << Division(len, time) << endl;
    }
    catch (char str)
    {
        cout << str << endl;
    }        
    cout << "void func()" << endl;
}
int main()
{
    try
    {
        Func();
    }
    catch (char str)
    {
        cout << str << endl;
    }
    catch(...)
    {
        cout << "未知异常" << endl;
    }
    return 0;
}

此时由于两个捕获异常都与对象类型不匹配,所以进入 catch(…) 中,使用 catch(…),若有匹配的就用匹配的,若没有匹配的,就使用 catch(…),对任意类型异常进行捕获防止一些异常没有捕获(没有对象类型匹配),导致程序终止

实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用,后面会详细讲解。


(2)在函数调用链中异常栈展开匹配原则
  1. 首先检查 throw 本身是否在 try 块内部,如果是再查找匹配的 catch 语句
  2. 如果有匹配的,则调到 catch 的地方进行处理。
  3. 没有匹配的 catch 则退出当前函数栈,继续在调用函数的栈中进行查找匹配的 catch。
  4. 如果到达 main 函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的 catch 子句的过程称为栈展开。所以实际上我们最后都要加一个 catch(...) 捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
  5. 找到匹配的 catch 子句并处理以后,会继续沿着 catch 子句后面继续执行。


2、异常的重新抛出

有可能单个的 catch 不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch 则可以通过重新抛出将异常传递给更上层的函数进行处理。
double Division(int a, int b)
{
    // 当b == 0时抛出异常
    if (b == 0)
    {
        throw "Division by zero condition!";
    }
    return (double)a / (double)b;
}
void Func()
{
    int* array = new int[10];
    int len, time;
    cin >> len >> time;
        cout << Division(len, time) << endl;


        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;
}

若抛异常,则会导致内存泄漏(没有使用 delete 释放)。

若要求在 main 函数将异常处理, 所以可以采用异常的重新抛出。当在 Func 函数中的 catch 要捕获异常时,再将异常抛出,使 main 函数中进行捕获异常 。


3、异常安全

  • 构造函数完成对象的构造和初始化最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化。
  • 析构函数主要完成资源的清理最好不要在析构函数内抛出异常,否则可能导致资源泄漏(存泄漏、句柄未关闭等)。
  • C++ 中异常经常会导致资源泄漏的问题,比如在 new 和 delete 中抛出了异常,导致内存泄漏,在 lock 和 unlock 之间抛出了异常导致死锁,C++ 经常使用 RAII 来解决以上问题,关于 RAII 将在后面的智能指针部分进行详细讲解。

4、异常规范

exception - C++ Reference (cplusplus.com)

  1. 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。可以在函数的后面接 throw(类型),列出这个函数可能抛掷的所有异常类型。
  2. C++98 中,函数的后面接 throw(),表示函数不抛异常
  3. 无异常接口声明,则此函数可以抛掷任何类型的异常

声明可以不给,但是加上会让人更容易理解。这个函数异常声明并不是强制的,并且比较繁琐,就导致很多人不遵循这个规范。

在 C++11 中,若一个函数明确不抛异常的话,就加 noexcept,可能会抛异常,就什么都不加。


四、自定义异常体系

在实际使用中,很多公司都会自定义自己的异常体系来进行规范的异常管理。因为在一个项目中,如果大家随意抛异常,那么外层的调用者基本就没办法玩了,所以在实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。

// 基类
// 异常
class Exception
{
public:
    Exception(const string& errmsg, int id)
        :_errmsg(errmsg)
        ,_id(id)
    {}

    virtual string what() const
    {
        return _errmsg;
    }
protected:
    string _errmsg; // 错误信息
    int _id; // 错误码
};

// 派生类
// 数据库异常
class SqlException : public Exception
{
public:
    SqlException(const string& errmsg, int id, const string& sql)
        :Exception(errmsg, id)
        , _sql(sql)
    {}

    virtual string what() const
    {
        string str = "SqlException:";
        str += _errmsg;
        str += "->";
        str += _sql;

        return str;
    }
private:
    const string _sql;
};

// 缓存异常
class CacheException : public Exception
{
public:
    CacheException(const string& errmsg, int id)
        :Exception(errmsg, id)
    {}

    virtual string what() const
    {
        string str = "CacheException:";
        str += _errmsg;
        return str;
    }
};

// 网络异常
class HttpServerException : public Exception
{
public:
    HttpServerException(const string& errmsg, int id, const string& type)
        :Exception(errmsg, id)
        , _type(type)
    {}

    virtual string what() const
    {
        string str = "HttpServerException:";
        str += _type;
        str += ":";
        str += _errmsg;
        return str;
    }
private:
    const string _type;
};

void SQLMgr()
{
    srand(time(0));
    if (rand() % 7 == 0)
    {
        throw SqlException("权限不足", 100, "select * from name = '张三'");
    }

    //throw "xxxxxx";
}

void CacheMgr()
{
    srand(time(0));
    if (rand() % 5 == 0)
    {
        throw CacheException("权限不足", 100);
    }
    else if (rand() % 6 == 0)
    {
        throw CacheException("数据不存在", 101);
    }

    SQLMgr();
}

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)
    {
        this_thread::sleep_for(chrono::seconds(1));

        try{
            HttpServer();
        }
        catch (const Exception& e) // 这里捕获父类对象就可以
        {
            // 多态
            cout << e.what() << endl;
        }
        catch (...)
        {
            cout << "Unkown Exception" << endl;
        }
    }

    return 0;
}

五、C++ 标准库的异常体系

C++ 提供了一系列标准的异常,定义在中,我们可以在程序中使用这些标准的异常。它们是以父
子类层次结构组织起来的,如下所示:

实际上,我们可以去继承 exception 类来实现自己的异常类。但是在实际中很多公司会像上面一样,自己去定义一套异常继承体系,因为 C++ 标准库设计的不够好用。
int main()
{
    try{
        vector<int> v(10, 5);
        // 这里如果系统内存不够也会抛异常
        v.reserve(1000000000);

        // 这里越界会抛异常
        v.at(10) = 100; 
    } catch (const exception& e)
    {
        // 这里捕获父类对象就可以
        cout << e.what() << endl;
    } catch (...)
    {
        cout << "Unkown Exception" << endl;
    }

    return 0;
}

六、异常的优缺点

1、C++ 异常的优点

(1)异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的 bug

(2)返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误,具体看下面的详细解释。

  1. 下面这段伪代码我们可以看到 ConnnectSql 中出错了,先返回给 ServerStart,ServerStart 再返回给 main 函数,main 函数再针对问题处理具体的错误。
  2. 如果是异常体系,不管是 ConnnectSql 还是 ServerStart 及调用函数出错,都不用检查,因为抛出的异常异常会直接跳到 main 函数中 catch 捕获的地方,main 函数直接处理错误。
int ConnnectSql()
{
    // 用户名密码错误
    if (...)
        return 1;
 
    // 权限不足
    if (...)
        return 2;
}
  
int ServerStart() {
    if (int ret = ConnnectSql() < 0)
        return ret;
     int fd = socket() 
     if(fd < 0)
        return errno;
}
  
int main()
{
    if(ServerStart() < 0)
        // ...
  
    return 0;
}
(3)很多的第三方库都包含异常,比如 boost、gtest、gmock 等等常用的库,那么我们使用它们也需要使用异常。
(4)部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如,T& operator 这样的函数,如果 pos 越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

2、C++ 异常的缺点 

(1)异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时比较困难
(2)异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
(3)C++ 没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用 RAII 来处理资源的管理问题。学习成本较高。
(4)C++ 标准库的异常体系定义的不好,导致大家各自定义各自的异常体系,非常的混乱。
(5)异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常
规范有两点:
  1. 抛出异常类型都继承自一个基类。
  2. 函数是否抛异常、抛什么异常,都使用 func() throw(); 的方式规范化。

【总结】

异常总体而言, 利大于弊 ,所以在工程中还是鼓励使用异常的。另外 OO 的语言基本都是用异常处理错误,这也可以看出这是大势所趋。
详解C++ new-handler机制
01-19
当 operator new 不能满足一个内存分配请求时,它抛出一个 exception(异常)。很久以前,他返回一个 null pointer(空指针),而一些比较老的编译器还在这样做。你依然能达到以前的目的(在一定程度上),但是我要...
C++异常处理详解
12-26
程序中的错误分为编译时的错误和运行时的错误。编译时的错误主要是语法错误,比如:句尾没有加分号,括号不匹配,关键字错误等,这类错误比较容易修改,因为编译系统会指出错误在...C++中处理异常的过程是这样的:在执
C++ string 用法详解 - BYVoid1
08-04
在实际应用中,应当对文件打开失败、读取异常等情况进行适当的处理。 7. **其他可能的解决方案**: - 使用`std::map`可以更方便地管理键值对,如用户名和电话号码。但是,`std::map`内部已经排序,因此不能直接与`...
C++-酒店管理系统源码
02-29
C++酒店管理系统源码详解C++酒店管理系统是一个典型的基于命令行界面的软件应用,它主要用于模拟并管理酒店日常运营的各项事务。该系统通常包括预订房间、入住、退房、查询房间状态等功能,旨在提高酒店运营...
TMS Scripter Crack TMS Scripter is a Delphi C++ Builder
q2315702359的博客
09-19 941
  The integrated development environment or the IDE allows the users in process of creating script jobs during the runtime with the help of multiple cross-language scripts such as de Pascal and the Basics.  It has support for the cross-utilization of the s
CCF201903_1
m0_67663419的博客
09-14 261
【代码】CCF201903_1。
在typescript浏览器端中调用C++编写的函数,WebAssembly传递指针类型的参数,以及处理指针类型的返回值。
Xeon_CC的博客
09-17 415
最后是返回一个C++结构体指针,在TypeScript中通过DataView来获取内存中的地址偏移量来取得结构体中的数据,因为em++编译器是以32位来编译.cpp文件的,所以指针占4个字节。DataResult结构体第一个属性dArr是一个指针,那么就是地址偏移量0,然后以32位无符号整型取得dArr指针,通过这个指针去读取wasm中memoryBuffer中的数据,就可以得到dArr属性对应的值了。同理,通过地址偏移量+4来得到propCount属性,以32位整数读取数据即可。
57.【C语言】字符函数和字符串函数(strerror函数)
2401_85828611的博客
09-18 429
翻译:函数错误码解释errnum的值,产生一条描述错误情况的信息的字符串,就像被库函数设置为errno一样这个返回的指针指向一个静态的已分配的且不能被程序修改的字符串,进一步调用此函数可能会覆盖内容(不需要特定的库的实施,避免数据竞争)strerror产生的错误信息的字符串可能特定于每个系统和库实现errnum:错误码一个指向用于描述错误码的错误信息字符串的指针简明扼要:strerror的作用:把错误码翻译成错误信息。
C语言:刷题日志(1)
zm3rttqs9f的博客
09-07 1581
阶乘计算升级版 然后是几点 求整数段和 爬动的蠕虫 龟兔赛跑
C++:线程库
盒马的博客
09-14 1162
讲解C++线程相关的库,包括线程thread,锁mutex,原子库atomic,条件变量condition_variable
C++核心编程和桌面应用开发 第四天(构造/析构函数 浅拷贝/深拷贝 初始化列表 explicit)
m0_53349772的博客
09-17 212
函数名(const 函数名 &)①用已创建好的对象初始化新对象。②值传递的方式给函数参数传值。③以值的方式,返回局部对象。
(c++)线程的创建、互斥锁的使用、线程数组
最新发布
weixin_53112343的博客
09-20 139
但是线程可能在cpu分配的一个时间片中做不完10万次+1的操作,这时候cpu会被其他线程抢占,由于num1++不是一个原子操作(操作过程中可能会发生中断),导致++其实没有做完,所以num1的最终结果会小于100万。2.创建10个线程,每个线程都做10万次全局变量num2++操作,与前者不同的是,这次对num2++操作加入互斥锁,也就是让num2++成为一个“原子操作”(操作过程不会被中断),所以结果符合预期为100万。
【基于C++的产品入库管理系统】
BABA8891的博客
09-17 749
基于C++的产品入库管理系统可以用来跟踪产品的入库、出库和库存情况。这种系统通常包括产品信息的录入、查询、更新以及库存管理等功能。下面是一个简化的产品入库管理系统的设计方案及其代码示例。
STL_vector实现及干货以及vector迭代器失效问题
2301_79127392的博客
09-16 766
你想在pos位置插入一个值如果你用上面的函数用的次数多了,当它扩容的时候,你去调试,会发现pos位置变成随机值了,因为扩容可能会遇到异地扩容,当异地扩容的时候,原本的空间会被释放,把数据挪移到另外的空间,正确的写法应该是下面这种。
C++ char*和char[] 可能指向的内存区域详解(附实验)
iiiiiiimp的博客
09-16 564
C++ char*和char[] 可能指向的内存区域详解(附实验)
C++速通LeetCode简单第11题-对称二叉树
lyx的博客
09-14 323
递归
【鸿蒙OH-v5.0源码分析之 Linux Kernel 部分】005 - Kernel 入口 C 函数 start_kernel() 源码分析
|~~~热爱生活、努力学习的小伙汁~~~|
09-15 729
【鸿蒙OH-v5.0源码分析之 Linux Kernel 部分】005 - Kernel 入口 C 函数 start_kernel() 源码分析
54.【C语言】 字符函数和字符串函数(strncpy,strncat,strncmp函数)
2401_85828611的博客
09-16 1242
和strcpy,strcat,strcmp函数对应的是strncpy,strncat,strncmp函数。
C语言--指针
2401_83334900的博客
09-17 526
"指针",变量的地址,通过只能能够访问以指针为地址的内存单元,例如,我们通过地址1000可以访问变量i的地址,进而读取变量i的值。比如&i表示变量i的内存地址为1000,通过&i就可以访问变量i所指向的内容,进而读取i的内容。“&”,是取地址运算符,用于返回一个操作数的地址,“*”是指针运算符,用于返回指定地址内保存的运算符。指针变量通常指向一个变量的地址,所以将一个变量的地址赋给该变量后,这个指针就指向该变量。我们从上面的内容可以知道,指针是通过先储存变量i的地址再访问变量i的内容。
写文章

热门文章

  • 【MySQL】在 Centos7 环境安装 MySQL -- 详细完整教程 20136
  • 【Python】搭配 Python 环境(超详细教程) 7517
  • 【Linux 系统】Linux 下基本指令 -- 详解 6570
  • 【C语言】基本数据类型 6164
  • 【ProtoBuf】在 Windows / Linux 安装 ProtoBuf 编译器(超详细教程) 4722

分类专栏

  • C语言 19篇
  • 数据结构初阶(C语言) 9篇
  • C++ 28篇
  • STL 8篇
  • C++学习(转载) 4篇
  • 数据结构高阶(C++) 9篇
  • 高阶数据结构学习(转载) 4篇
  • Python 7篇
  • Linux 27篇
  • 系统 1篇
  • 网络 8篇
  • Linux学习(转载) 7篇
  • MySQL初阶 17篇
  • MySQL进阶 1篇
  • MySQL学习(转载) 3篇
  • 软件测试 7篇
  • Redis初阶 6篇
  • Redis进阶 7篇
  • Docker 13篇
  • QT 6篇
  • ProtoBuf(C++) 5篇
  • Git 7篇
  • 算法 1篇
  • 剑指Offer 48篇
  • 错题【笔试训练编程题】 65篇
  • 项目准备和总结
  • 日志系统
  • 大厂笔试真题合集
  • 面试

最新评论

  • 【QT】QT 窗口(菜单栏、工具栏、状态栏、浮动窗口、对话框)

    你又来看我啦???: 好细的分享,又学到东西,感谢分享

  • 【ProtoBuf】在 Windows / Linux 安装 ProtoBuf 编译器(超详细教程)

    漏洞百出: 看到了,多谢

  • 【ProtoBuf】在 Windows / Linux 安装 ProtoBuf 编译器(超详细教程)

    油炸自行车: 像博主这么安装也是可以的,博主下载的是免编译版本,readme.txt中有写: This package contains a precompiled binary version of the protocol buffer compiler (protoc). This binary is intended for users who want to use Protocol Buffers in languages other than C++ but do not want to compile protoc themselves. To install, simply place this binary somewhere in your PATH. 翻译过来就是: 这个包包含了协议缓冲区编译器(protoc)的预编译二进制版本。这个二进制文件是为那些希望在除 C++ 之外的其他语言中使用协议缓冲区,但又不想自己编译 protoc 的用户准备的。要安装,只需将这个二进制文件放置在你的 PATH 路径中的某个位置即可。

  • 【Python】搭配 Python 环境(超详细教程)

    xingyumengya: python真诡,vs可用,vscode可用,但都有问题,教程也不好找,现在下这个pycharm试试...

  • 【ProtoBuf】在 Windows / Linux 安装 ProtoBuf 编译器(超详细教程)

    漏洞百出: 需要下载源码protobuf,里面有CMakeList.txt文件,配置完了之后make 和 install

大家在看

  • 【前端学习】HTML基础学习 59
  • 统信软件根社区deepin推出中国首款信创生态自研IDE
  • 【华为OD机试】真题E卷-跳马(Java) 149
  • (开题)flask框架的交通管理系统的设计与实现(程序+论文+python)

最新文章

  • C++11:lock_guard / unique_lock 详解
  • 一致性哈希
  • 字符串哈希算法
2024
09月 6篇
08月 16篇
07月 75篇
06月 21篇
05月 76篇
04月 30篇
03月 13篇
02月 17篇
01月 6篇
2023年63篇

目录

目录

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

炫酷的伊莉娜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值

玻璃钢生产厂家济宁市玻璃钢人物雕塑价格湖州公园玻璃钢雕塑供应商玻璃钢佛像雕塑造价汕尾玻璃钢雕塑工厂特色商场美陈哪里有广东艺术商场美陈生产企业欧式人物玻璃钢雕塑佛像玻璃钢雕塑厂信誉好的玻璃钢雕塑嘉定区玻璃钢雕塑制作福建龙岩玻璃钢花盆上海绿色种植玻璃钢花盆清远党建文化玻璃钢雕塑横县玻璃钢泡沫雕塑厂家迪庆玻璃钢雕塑订购宁波户外玻璃钢雕塑供应商潍坊玻璃钢仿真动物雕塑宣城商场美陈公司1.5米宽玻璃钢花盆苏州玻璃钢雕塑定做玻璃钢雕塑的使用指南广州玻璃钢大型雕塑万安商场美陈楚雄市玻璃钢雕塑费用多少义乌玻璃钢雕塑厂商定制常宁玻璃钢雕塑招牌字批发夏季商场美陈经典案例青海仿真人物玻璃钢雕塑安装商场美陈氛围征集成都公园玻璃钢雕塑香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化