尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
C++ 命名空间、异常处理和动态内存
参考C 命名空间 | 菜鸟教程 (runoob.com)命名空间假设这样一种情况当一个班上有两个名叫 Zara 的学生时为了明确区分它们我们在使用名字之外不得不使用一些额外的信息比如他们的家庭住址或者他们父母的名字等等。同样的情况也出现在 C 应用程序中。例如您可能会写一个名为 xyz() 的函数在另一个可用的库中也存在一个相同的函数 xyz()。这样编译器就无法判断您所使用的是哪一个 xyz() 函数。因此引入了命名空间这个概念专门用于解决上面的问题它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上命名空间就是定义了一个范围。我们举一个计算机系统中的例子一个文件夹(目录)中可以包含多个文件夹每个文件夹中不能有相同的文件名但不同文件夹中的文件可以重名。定义命名空间命名空间的定义使用关键字namespace后跟命名空间的名称如下所示namespace namespace_name { // 代码声明 }为了调用带有命名空间的函数或变量需要在前面加上命名空间的名称如下所示name::code; // code 可以是变量或函数注意这里又看到了范围解析运算符的另一种用法那就是用来调用某个属于对应变量空间的变量或者函数。让我们来看看命名空间如何为变量或函数等实体定义范围#include iostream using namespace std; // 第一个命名空间 namespace first_space{ void func(){ cout Inside first_space endl; } } // 第二个命名空间 namespace second_space{ void func(){ cout Inside second_space endl; } } int main () { // 调用第一个命名空间中的函数 first_space::func(); // 调用第二个命名空间中的函数 second_space::func(); return 0; }当上面的代码被编译和执行时它会产生下列结果Inside first_space Inside second_space可以多个C文件中声明同一个命名空间吗在 C 中可以且常见在多个文件中声明同一个命名空间。命名空间的定义是允许分散在多个源文件.cpp或头文件.h中的编译器最终会将同一命名空间的所有内容合并。这是 C 模块化设计的重要特性之一。基本规则同一命名空间的内容会被自动合并即使分布在不同的文件中。可以多次声明同一命名空间每次声明都是向该命名空间添加新成员。通常做法在头文件.h中声明命名空间及其接口。在源文件.cpp中实现命名空间内的函数/类。注意事项避免重复定义同一命名空间的成员定义如函数实现只能出现在一个源文件中否则链接错误。声明如函数原型可以重复出现在多个头文件中。命名污染风险不同库的命名空间可能冲突如MyLib::Logger和YourLib::Logger。解决方法使用嵌套命名空间或更独特的名称如Company::Project::Module。大型项目的组织推荐按模块划分命名空间在 C 中若类在头文件中被命名空间包裹则实现文件中必须使用相同的命名空间以确保函数定义与声明匹配。以下是详细说明一、必须保持命名空间一致头文件中的类声明位于命名空间内实现文件中必须用相同的命名空间包裹实现否则会导致链接错误Linker Error。示例正确做法// MyClass.h头文件 namespace MyNamespace { class MyClass { public: void func(); }; } // MyClass.cpp实现文件 #include MyClass.h namespace MyNamespace { // 必须使用相同的命名空间 void MyClass::func() { /* 实现 */ } }错误示例// MyClass.cpp错误写法 #include MyClass.h // 未使用命名空间 void MyClass::func() { /* 实现 */ } // 错误定义了全局命名空间的 MyClass::func为什么必须使用命名空间C 编译器将函数签名视为命名空间::类名::函数名(参数)。若实现文件中未指定命名空间编译器会认为你在定义一个全局命名空间中的同名函数而非头文件中声明的那个。using 指令您可以使用using namespace指令这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器后续的代码将使用指定的命名空间中的名称。#include iostream using namespace std; // 第一个命名空间 namespace first_space{ void func(){ cout Inside first_space endl; } } // 第二个命名空间 namespace second_space{ void func(){ cout Inside second_space endl; } } using namespace first_space; int main () { // 调用第一个命名空间中的函数 func(); return 0; }当上面的代码被编译和执行时它会产生下列结果Inside first_spaceusing 指令也可以用来指定命名空间中的特定项目。例如如果您只打算使用 std 命名空间中的 cout 部分您可以使用如下的语句using std::cout;注意这中间没有namespace不是using namespace std::cout后面的std::cout并不是命名空间别搞混了随后的代码中在使用 cout 时就可以不用加上命名空间名称作为前缀但是std命名空间中的其他项目仍然需要加上命名空间名称作为前缀如下所示实例#include iostream using std::cout; int main () { cout std::endl is used with std! std::endl; return 0; }当上面的代码被编译和执行时它会产生下列结果std::endl is used with std!using指令引入的名称遵循正常的范围规则。名称从使用using指令开始是可见的直到该范围结束。此时在范围以外定义的同名实体是隐藏的。实际开发中需要在开头写上using namespace std;吗一般一个文件中应该会用到很多命名空间的吧先说结论大型工程、正式项目尽量不要写using namespace std;小型 Demo、测试脚本、单文件小程序可以随便写。而且C 里并不会 “用到一大堆命名空间”绝大多数业务代码日常打交道的就一个std其他嵌套子命名空间std::chrono、std::filesystem使用频率低有更优雅的写法。核心矛盾using namespace xxx是把这个命名空间里所有名字全部导入到当前作用域会带来名字污染命名冲突。几种写法对比按推荐优先级排序方案 1全限定名✅ 大型项目首选推荐直接写std::不导入#include iostream #include vector int main() { std::vectorint vec; std::cout hello\n; return 0; }优点完全没有名字冲突风险一眼就能分清这个符号来自标准库别人读代码立刻知道std::cout是标准库不是自己写的函数缺点多敲几个字符。方案 2using 单独导入你需要的符号⭐折中方案最常用只导入你真正要用的那几个而不是整个 std#include iostream #include vector using std::cout; using std::vector; int main() { vectorint vec; cout hello\n; return 0; }✅ 这个是工业界很受欢迎的折中既不用到处写std::又不会把 std 几千个名字全拉进来污染范围很小。如果还要用std::chrono::secondsusing std::chrono::seconds;方案 3using namespace std;⚠️ 慎用using namespace std;坏处举例名字撞车假设你自己写了一个函数 / 类叫count、list、sort、max#include algorithm using namespace std; void sort() { // 完蛋std::sort 也存在名字冲突编译报错/行为诡异 }std 里面包含非常多通用单词list, map, set, sort, find, count, swap, min, max这些词业务代码里太容易起名撞上。什么时候可以放心写using namespace std;刷题、LeetCode、练习 Demo、单文件小测试简单的 main 程序代码很短不会有复杂自定义类型教学示例为了简化代码。什么时候绝对不要写头文件 (.h/.hpp) 里面重点红线如果头文件写了using namespace std;任何#include这个头的文件都会强制被导入 std污染扩散到整个项目灾难级。 头文件里永远禁止using namespace xxx方案 4别名 namespace长命名空间简化适合 std 子空间碰到std::filesystem、std::ranges::views这种很长的嵌套命名空间namespace fs std::filesystem; namespace views std::ranges::views;之后直接用fs::path、views::filter。✅ 这个非常推荐只起别名不导入任何名字没有污染是处理长嵌套命名空间的最佳实践。回答你第二个疑问会不会用到很多命名空间正常业务代码标准侧主体就是std它下面的子命名空间chrono/filesystem/ranges按需单独引入不会一次性一大堆自己项目一般会给自己模块定义专属命名空间比如namespace app {}、namespace net {}第三方库比如boost::xxx、opencv::cv、nlohmann::json也是独立命名空间。对于第三方库同样遵循上面规则优先写cv::Mat或者namespace cv cv;别名尽量避免using namespace cv;OpenCV 里面名字巨多冲突高发嵌套的命名空间命名空间可以嵌套您可以在一个命名空间中定义另一个命名空间如下所示namespace namespace_name1 { // 代码声明 namespace namespace_name2 { // 代码声明 } }您可以通过使用::运算符来访问嵌套的命名空间中的成员// 访问 namespace_name2 中的成员 using namespace namespace_name1::namespace_name2; // 访问 namespace_name1 中的成员 using namespace namespace_name1;在上面的语句中如果使用的是 namespace_name1那么在该范围内 namespace_name2 中的元素也是可用的如下所示#include iostream using namespace std; // 第一个命名空间 namespace first_space{ void func(){ cout Inside first_space endl; } // 第二个命名空间 namespace second_space{ void func(){ cout Inside second_space endl; } } } using namespace first_space::second_space; int main () { // 调用第二个命名空间中的函数 func(); return 0; }更多待补充。std是什么std其实就是个是个命名空间C标准库中的函数或者对象都是在命名空间std中定义的所以我们要使用标准函数库中的函数或对象都要使用std来限定。标准库在名字空间中被指定为std所以在使用标准库中的函数或者对象的时候要加上std::这样编译器就会明白我们调用的函数或者对象是名字空间std中的。一种更简单的方法不需要写std::直接使用using namespace std来代替std::。using namespace std 告诉编辑器我们将要使用空间std中的函数或者对象。如#includeiostream using namespace std; int main() { couthello world; coutendl; return 0; }C 标准只有一个顶层标准命名空间std剩下全是嵌套在 std 里面的子命名空间另外还有一个隐式的全局命名空间::不是写出来的名字是默认的。编译器自带的那些带下划线__xxx的命名空间__gnu_cxx、std::__detail属于实现内部私有空间标准不规定业务代码不要碰GCC。1. 全局命名空间隐式没有名字所有不在任何 namespace 里的符号都在这里用::func显式访问。main()就必须放在全局命名空间。2. std顶层标准命名空间核心所有 C 标准库iostream、vector、string、algorithm都在std下。std::cout; std::vectorint v;3. std 下面常用嵌套标准命名空间重点你写代码会用到命名空间头文件作用示例std::chronochrono时间、时长、时钟C11std::chrono::secondsstd::literals多个头字面量总入口inline 命名空间包含下面几个 xxx_literalsstd::literals::chrono_literalschrono时间字面量后缀10s、5msusing namespace std::literals::chrono_literals; auto t 30s;std::literals::string_literalsstring字符串字面量s生成 std::stringauto str abcs;std::literals::complex_literalscomplex复数字面量istd::placeholdersfunctionalbind 的占位符_1, _2, _3C11std::bind(func, std::placeholders::_1)std::filesystemfilesystem文件系统C17std::filesystem::path别名std::fsstd::rangesranges范围库C20视图、管道std::ranges::views别名std::viewsstd::numbersnumbers数学常量 π、eC20std::numbers::pistd::pmrmemory_resource多态内存分配器C17内存池std::executionexecution并行执行策略C17std::execution::par4. 容易混淆不是标准规定、编译器扩展的命名空间GCC/Clang 特有不要写业务代码依赖__gnu_cxxGNU 扩展__gnu_pbdsGCC policy-based data structure红黑树等扩展容器__gnu_debug调试版本 STLstd::__cxx11、std::__detailSTL 内部实现细节标准没定义版本一变就炸禁止直接使用GCC5. 补充几个误区❌ 没有单独叫iostream、vector的命名空间它们只是类型 / 类名不是命名空间都属于 std。❌namespace std不是关键字内置硬编码是标准库头文件里面定义出来的你不 include 标准头std 里面是空的。inline 命名空间literals 系列它的特点是里面的东西自动可以当成父命名空间成员主要给字面量设计。C 语言库cstdio的函数也放在std老式stdio.h会放到全局命名空间。命名空间可以放哪些内容在C中命名空间namespace确实可以用来组织代码防止命名冲突以下是命名空间的主要特性和限制可以包含的内容变量和常量namespace MyNS { int x 42; const double PI 3.14159; }函数namespace MyNS { void foo() { /* ... */ } }类和结构体namespace MyNS { class MyClass { /* ... */ }; struct MyStruct { /* ... */ }; }其他命名空间嵌套命名空间namespace MyNS { namespace Inner { void bar() { /* ... */ } } }模板namespace MyNS { templatetypename T T max(T a, T b) { return a b ? a : b; } }类型别名typedef/usingnamespace MyNS { typedef std::vectorint IntVec; using Str std::string; }枚举enum/enum classnamespace MyNS { enum Color { Red, Green, Blue }; enum class Status { Ok, Error }; }不能直接包含的内容非声明性语句如直接执行的代码namespace MyNS { int a 10; a; // 错误不能直接写可执行代码 void func() { a; } // 正确函数内可以包含逻辑 }预处理指令如#include、#define#include iostream // 正确 namespace MyNS { #include file.hpp // 极不推荐可能引发混乱 }显式的内存管理如new/delete语句这些必须放在函数或类方法中。总结命名空间可以包含变量、函数、类、模板、嵌套命名空间等声明性内容但不能直接包含可执行代码或预处理指令。它是逻辑上的代码组织工具而非运行时实体。总体来说就是不要放过程性的内容。 所以命名空间后面跟着的可能是类也可能是函数也可能是变量命名空间只是限定对象的范围而不是限定具体是什么内容。异常处理异常是程序在执行期间产生的问题。C 异常是指在程序运行时发生的特殊情况比如尝试除以零的操作。异常提供了一种转移程序控制权的方式。C 异常处理涉及到三个关键字try、catch、throw。throw:当问题出现时程序会抛出一个异常。这是通过使用throw关键字来完成的。catch:在您想要处理问题的地方通过异常处理程序捕获异常。catch关键字用于捕获异常。try:try块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。如果有一个块抛出一个异常捕获异常的方法会使用try和catch关键字。try 块中放置可能抛出异常的代码try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示抛出异常您可以使用throw语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式表达式的结果的类型决定了抛出的异常的类型。以下是尝试除以零时抛出异常的实例double division(int a, int b) { if( b 0 ) { throw Division by zero condition!; } return (a/b); }异常抛出后就可以让调用者去捕获处理。捕获异常catch块跟在try块后面用于捕获异常。您可以指定想要捕捉的异常类型这是由 catch 关键字后的括号内的异常声明决定的。try { // 保护代码 }catch( ExceptionName e ) { // 处理 ExceptionName 异常的代码 }上面的代码会捕获一个类型为ExceptionName的异常。如果您想让 catch 块能够处理 try 块抛出的任何类型的异常则必须在异常声明的括号内使用省略号 ...如下所示try { // 保护代码 }catch(...) { // 能处理任何异常的代码 }下面是一个实例抛出一个除以零的异常并在 catch 块中捕获该异常。#include iostream using namespace std; double division(int a, int b) { if( b 0 ) { throw Division by zero condition!; } return (a/b); } int main () { int x 50; int y 0; double z 0; try { z division(x, y); cout z endl; }catch (const char* msg) { cerr msg endl; } return 0; }由于我们抛出了一个类型为const char*的异常因此当捕获该异常时我们必须在 catch 块中使用 const char*。当上面的代码被编译和执行时它会产生下列结果Division by zero condition!更多待补充动态内存了解动态内存在 C 中是如何工作的是成为一名合格的 C 程序员必不可少的。C 程序中的内存分为两个部分栈在函数内部声明的所有变量都将占用栈内存。堆这是程序中未使用的内存在程序运行时可用于动态分配内存。很多时候您无法提前预知需要多少内存来存储某个定义变量中的特定信息所需内存的大小需要在运行时才能确定。在 C 中您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存这会返回所分配的空间地址。这种运算符即new运算符。如果您不再需要动态分配的内存空间可以使用delete运算符删除之前由 new 运算符分配的内存。new 和 delete 运算符下面是使用 new 运算符来为任意的数据类型动态分配内存的通用语法new data-type;在这里data-type可以是包括数组在内的任意内置的数据类型也可以是包括类或结构在内的用户自定义的任何数据类型。让我们先来看下内置的数据类型。例如我们可以定义一个指向 double 类型的指针然后请求内存该内存在执行时被分配。我们可以按照下面的语句使用new运算符来完成这点double* pvalue NULL; // 初始化为 null 的指针 pvalue new double; // 为变量请求内存如果自由存储区已被用完可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针并采取以下适当的操作double* pvalue NULL; if( !(pvalue new double )) { cout Error: out of memory. endl; exit(1); }malloc()函数在 C 语言中就出现了在 C 中仍然存在但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比其主要的优点是new 不只是分配了内存它还创建了对象。在任何时候当您觉得某个已经动态分配内存的变量不再需要使用时您可以使用 delete 操作符释放它所占用的内存如下所示delete pvalue; // 释放 pvalue 所指向的内存下面的实例中使用了上面的概念演示了如何使用 new 和 delete 运算符#include iostream using namespace std; int main () { double* pvalue NULL; // 初始化为 null 的指针 pvalue new double; // 为变量请求内存 *pvalue 29494.99; // 在分配的地址存储值 cout Value of pvalue : *pvalue endl; delete pvalue; // 释放内存 return 0; }更多待补充。数组的动态内存分配假设我们要为一个字符数组一个有 20 个字符的字符串分配内存我们可以使用上面实例中的语法来为数组动态地分配内存如下所示char* pvalue NULL; // 初始化为 null 的指针 pvalue new char[20]; // 为变量请求内存要删除我们刚才创建的数组语句如下delete [] pvalue; // 删除 pvalue 所指向的数组下面是 new 操作符的通用语法可以为多维数组分配内存如下所示一维数组// 动态分配,数组长度为 m int *arraynew int [m]; //释放内存 delete [] array;二维数组int **array; // 假定数组第一维长度为 m 第二维长度为 n // 动态分配空间 array new int *[m]; for( int i0; im; i ) { array[i] new int [n]; } //释放 for( int i0; im; i ) { delete [] array[i]; } delete [] array;更多待补充。对象的动态内存分配对象与简单的数据类型没有什么不同。例如请看下面的代码我们将使用一个对象数组来理清这一概念#include iostream using namespace std; class Box { public: Box() { cout 调用构造函数 endl; } ~Box() { cout 调用析构函数 endl; } }; int main( ) { Box* myBoxArray new Box[4]; delete [] myBoxArray; // 删除数组 return 0; }如果要为一个包含四个 Box 对象的数组分配内存构造函数将被调用 4 次同样地当删除这些对象时析构函数也将被调用相同的次数4次。当上面的代码被编译和执行时它会产生下列结果调用构造函数 调用构造函数 调用构造函数 调用构造函数 调用析构函数 调用析构函数 调用析构函数 调用析构函数更多待补充。根据以上内容我们不要以为new只能用来创建对象new是一个通用的分配内存空间的机制。new和malloc的区别基本区别特性new(C)malloc(C)语言C 运算符C 标准库函数 (cstdlib)返回类型返回具体类型的指针如int*返回void*需强制类型转换内存大小计算自动计算类型大小如new int需手动计算字节数如malloc(sizeof(int))初始化支持构造函数初始化如new int(5)仅分配内存不初始化内容失败处理抛出std::bad_alloc异常返回NULLC11 后可用aligned_alloc内存来源可重载自定义operator new不可重载关键差异总结类型安全new返回类型化指针无需强制转换malloc返回void*需显式转换。初始化new可初始化对象调用构造函数malloc仅分配原始内存。错误处理new抛异常malloc返回NULL需检查。重载能力new/delete可重载malloc/free不可重载。何时使用用new/deleteC 代码中优先使用尤其是类对象。需要自动构造/析构、类型安全或自定义内存管理时。用malloc/free与 C 代码交互时。需要直接操作原始内存如实现底层数据结构。在 C 中优先使用new/delete除非有特殊需求如兼容 C 或手动管理原始内存。new更安全、更符合面向对象的设计而malloc更接近底层操作。
RELATED

相关推荐

腾讯云轻量服务器部署OpenClaw自动化平台指南

腾讯云轻量服务器部署OpenClaw自动化平台指南

1. 项目概述:OpenClaw与腾讯云的完美结合OpenClaw(又称Clawdbot)作为2026年最受开发者欢迎的自动化工具之一,正在重塑云端开发的工作流程。这个开源项目最初由机器人操作系统(ROS)社区孵化,现已…

📅 2026/10/30 4:04:54
Juggernaut-Z-Image未来展望:v2版本路线图与技术趋势

Juggernaut-Z-Image未来展望:v2版本路线图与技术趋势

Juggernaut-Z-Image未来展望:v2版本路线图与技术趋势 Juggernaut-Z-Image作为RunDiffusion推出的AI绘图模型,凭借出色的图像生成能力在创作者社区中获得广泛关注。本文将深入解析v2版本的核心升级方向、技术突破路径以及对AI创作生态的潜在影响&#xf…

📅 2026/10/30 4:04:54
xiaoai-patch 开源项目教程

xiaoai-patch 开源项目教程

xiaoai-patch 开源项目教程 【免费下载链接】xiaoai-patch Patching for XiaoAi Speakers (小爱音箱), add custom binaries and open source software. Tested on LX06, LX01, LX05, L09A 项目地址: https://gitcode.com/GitHub_Trending/xia/xiaoai-patch 项目介绍 x…

📅 2026/10/30 4:04:54
MORE NEWS

更多资讯

📰

从CPU指令到指针栈:程序员必知的底层硬核知识

简介:《程序员必知的硬核知识大全》面向希望补齐计算机底层认知的程序员、面试备战者与进阶学习者,围绕CPU组成与主频、RAM分类、二/八/十/十六进制转换、操作系统与API、BIOS及MBR/GRUB引导流程、汇编语言、系统调用与硬件交互、调用约定、DMA、RLE与DL…

📰

网络安全技能大赛加固取证与渗透测试实战复盘

简介:《2021年全国职业院校技能大赛网络安全竞赛试题》文档面向职业院校网络安全竞赛选手、指导教师及备赛者,以赛题任务梳理服务器、数据库、流量、监控、服务与防火墙等考点,适合备赛训练、课堂教学和赛前查漏补缺。压缩包仅含1个docx文件&…

📰

网络安全赛题加固题避坑:参数生效与取证流量分析

简介:《2021年全国职业院校技能大赛网络安全竞赛试题》面向职业院校网络安全竞赛选手、指导教师及备赛人员,围绕服务器与Web服务的安全加固、数据库权限管理、流量完整性保护、事件监控和防火墙策略等实战考点组织内容,适合赛前系统梳理与查漏…

📰

S1切换数据转发验证:用TEID与End Marker检查点定位业务中断

简介:围绕S1数据转发测试的docx小结,面向移动通信核心网、无线侧测试工程师及LTE切换学习者,聚焦切换过程中用户面数据连续性验证这一关键问题。内容从数据流向入手,梳理切换前SGW经源eNodeB至终端、切换中经SGW回传再转目标eNode…

📰

Pikachu靶场暴力破解与XSS通关配置复盘

简介:pikachu靶场全通关教程是一份面向网络安全渗透初学者的PDF学习资料,围绕Pikachu靶场常见关卡整理通关思路与练习心得,适合刚接触Web安全、需要按模块巩固实验操作的学习者。压缩包内共1个PDF文件,大小16.9MB,内容…

📰

MDVR机器AD芯片 TP9950 TP9951 TP9963 mipi 芯片有什么区别?

MDVR机器AD芯片 TP9950 TP9951 TP9963 mipi 芯片有什么区别? 品牌:TechPoint(科点),模拟同轴高清(TVI/AHD/CVI/CVBS)转 MIPI CSI2 解码芯片,车载 / 安防常用,QFN40 封装TI E2E sup... 价格为深圳现货 1K 批量、原装新料、未税参考(2026.10 行情);散买、翻新 / 拆机…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬