)
目录1. 命名空间1.1 命名空间的定义1.2 基本用法1.2.1 域1.3 域的用法/查找规则1.4 域和生命周期的关系1.5 作用域解析运算符1.6 注意事项1.7 命名空间的使用2. C输入和输出3. 缺省参数3.1 缺省参数的定义3.2 缺省参数的用法4. 总结:二次复习1. 命名空间在上篇文章我们主要详细介绍了C围绕着C的发展历程重要性及应用展开了主要的介绍。今天我们就要学习C的第一个知识点——命名空间在引入命名空间之前小编想先展示一段代码通过这段代码带领我们进入命名空间的世界。#include stdio.h #include stdlib.h int rand 10; int main() { // 编译报错: error C2365: “rand”: 重定义以前的定义是“函数” printf(%d\n, rand); return 0; }如上面的代码所示上面的代码如果放在编译器中是会报错的为什么呢在 C 语言里stdlib.h 头文件中声明了 rand 函数用于生成随机数 而代码里又定义了全局变量 int rand 10; 这就导致命名冲突——编译器看到 rand 时不知道该用变量 rand 还是标准库的 rand 函数所以编译报错 “ rand : 重定义以前的定义是‘函数’” 。C语言项目类似上面程序这样的命名冲突是普遍存在的问题C引入namespace就是为了更好的解决这样的问题。在C/C中变量、函数和后面要学到的类都是大量存在的这些变量、函数和类的名称都将存在于全局作用域中可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化以避免命名冲突或名字污染namespace关键字的出现就是针对这种问题的。1.1 命名空间的定义C的命名空间namespace 是一种用来避免命名冲突的机制简单说就是给变量、函数、类等起的名字“分组”让不同组里的同名标识可以共存。1.2 基本用法- 定义命名空间用 namespace 关键字把相关的代码包起来。 namespace csdn { int rand 10; int Add(int left, int right) { return left right; } struct Node { struct Node* next; int val; }; }-定义命名空间需要使用到 namespace 关键字后面跟命名空间的名字然后接一对 {} 即可{} 中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。1.2.1 域在C中域Scope通常译为作用域是指程序中某个标识符变量、函数、类等的有效范围。它决定了在哪里可以访问某个标识符以及该标识符的生命周期从何时开始到何时结束。namespace本质是定义出一个域这个域跟全局域各自独立不同的域可以定义同名变量所以下面的rand不在冲突了。在C中“命名空间”和“域作用域Scope”都和“名字的可见范围”有关但本质不同关系可以概括为命名空间是一种人为定义的、可扩展的域而域是一个更宽泛的概念包含了命名空间在内的多种范围类型。C中域有函数局部域全局域命名空间域类域域影响的是编译时语法查找一个变量/函数/类型出处(声明或定义)的逻辑所有有了域隔离名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑还会影响变量的生命周期命名空间域和类域不影响变量生命周期。补充:常见的域包括- 局部域比如函数内部的变量只在函数内可见- 类域类的成员变量/函数只在类内部或通过对象访问- 命名空间域由 namespace 定义的范围比如 std 就是标准库的命名空间域- 全局域不在任何类、函数、命名空间内的名字整个程序可见- 命名空间 是C专门为解决命名冲突设计的一种“域”它是人为创建的、可以包含变量、函数、类等的范围本质上是一种用户自定义的域。简单类比域就像“地理范围”比如国家、省份、城市而命名空间就像“人为划分的行政区”比如某个开发区它属于地理范围的一种但更灵活——可以嵌套比如 namespace A { namespace B { ... }} 也可以跨文件扩展专门用来管理名字的可见性。1.3 域的用法/查找规则我们来看下面的代码:1. 关于 x 的打印没有指定作用域时编译器会遵循局部优先的原则优先在当前函数的局部作用域中查找变量。当使用 ::x 时:: 是全局作用域解析符会直接去全局作用域中查找变量。2. 关于 rand 的打印这里的 rand 没有指定作用域编译器会先在 main 函数的局部作用域中查找找不到后再去全局作用域中查找。因为你的代码包含了 stdlib.h 这个头文件中声明了标准库的 rand() 函数它位于全局作用域。所以编译器找到的是全局的 rand 函数而不是 bit 命名空间里的 rand 变量因此 %p 格式符会输出这个函数的地址如 00F090F0 。bit::rand 明确指定了在 bit 这个命名空间中查找所以会找到你定义的变量 rand 10 输出 10 。总结:局部域在这里就是指当前函数的局部作用域。作用域查找规则是局部域 → 全局域 → 命名空间域需显式指定1.4 域和生命周期的关系作用域Scope指的是变量/名字在代码中的可见范围是一个“编译期”的概念决定了编译器在哪里能找到这个名字。生命周期Lifetime指的是变量在内存中存在的时间长度是一个“运行期”的概念决定了这个变量何时分配内存、何时释放。不同作用域对应的生命周期1局部域函数/代码块内作用域仅在定义它的函数或代码块内可见。生命周期进入代码块时分配内存离开代码块时立即释放栈上分配。例如 main 函数里的 int x 1; 它的作用域只在 main 里生命周期也只在 main 函数执行期间。2全局域作用域整个程序的所有文件都可见除非用 static 限制。生命周期程序启动时分配内存程序结束时才释放全局/静态存储区。例如全局变量 int x 0; 它在整个程序运行期间都存在所有函数都能访问。3命名空间域作用域仅在该命名空间内可见外部需要通过 命名空间::名字 来访问。生命周期和全局域完全一致因为命名空间里的变量本质上还是全局存储的。例如 bit::rand 它的作用域被限制在 bit 命名空间里但生命周期是整个程序运行期间。4类域作用域仅在类的内部可见外部需要通过 对象.名字 或 类::名字 静态成员来访问。生命周期非静态成员和对象的生命周期绑定对象创建时分配对象销毁时释放。静态成员和全局变量一样程序启动时分配结束时释放。例如 class A { int a; static int b; }; 其中 a 的生命周期和 A 的对象绑定而 b 的生命周期是全局的。1.5 作用域解析运算符在C中 :: 被称为作用域解析运算符用来指定“某个名字属于哪个域作用域”明确告诉编译器要使用哪个范围里的名字。常见用法有1. 访问命名空间中的成员比如 std::cout 表示 cout 这个对象属于 std 命名空间C标准库的命名空间。2. 访问类中的静态成员或嵌套类型例如在类 Person 中有一个静态函数 getCount() 可以用 Person::getCount() 调用表明这个函数属于 Person 类域。3. 访问全局域中的名字如果局部变量和全局变量重名用 :: 变量名表示使用全局域的那个变量。比如int a 10; // 全局变量 int main() { int a 20; // 局部变量 cout a; // 输出 20局部变量 cout ::a; // 输出 10全局变量 }简单说:: 就像一个“路径指示符”告诉编译器“去哪个范围找这个名字”避免混淆。1.6 注意事项1. namespace只能定义在全局当然他还可以嵌套定义。1. 命名空间的定义位置全局作用域这是命名空间最常见的定义位置。在所有函数、类、块之外定义的命名空间属于全局作用域的一部分例如标准库的 std 命名空间。不允许在局部作用域定义命名空间不能在函数内部、块内部如 if / for 的 {} 内等局部作用域中定义。这是因为命名空间的设计目的是用于全局或大尺度的代码组织而非局部临时使用。2. 命名空间的嵌套定义命名空间支持嵌套即一个命名空间内部可以包含另一个命名空间这是C明确允许的主要用于更细致的代码分类namespace A { // 全局作用域的命名空间A namespace B { // 嵌套在A中的命名空间B int x 10; } } // 使用时需通过嵌套路径访问 int main() { std::cout A::B::x; // 输出10 return 0; }这种嵌套可以避免多层代码的命名冲突比如大型项目中按模块划分一级命名空间再按功能划分二级命名空间。2. 项目工程中多文件中定义的同名namespace会认为是一个namespace不会冲突。C标准规定同一个命名空间可以在多个不同的文件中分散定义编译器会将这些分散的定义合并为一个完整的命名空间。假设项目中有两个文件file1.cpp 中定义namespace MyProject { int a 10; // 命名空间 MyProject的一部分 }file2.cpp 中定义namespace MyProject { void func() //与file1中的MyProject是同一个命名空间 {} }编译器会将两者合并相当于namespace MyProject { int a 10; void func() {} }使用时MyProject::a和MyProject::func()可在整个项目中统一访问不会因定义在不同文件而冲突。3. C标准库都放在一个叫std(standard)的命名空间中。C标准库:C标准库是C语言官方规定的一套现成的工具集合里面包含了大量已经写好的代码程序员不用自己编写直接就能用。它就像一个“工具箱”里面有数据结构比如存放数据的 vector 动态数组、 map 键值对集合等不用自己手动实现链表、哈希表了。输入输出工具比如 cout 输出内容到屏幕、 cin 从键盘读取输入负责程序和用户的交互。字符串处理比如 string 类能方便地拼接、截取字符串比C语言的字符数组好用得多。算法比如排序 sort 、查找 find 等直接调用就能完成复杂操作。其他功能比如时间处理、数学计算、文件操作等。简单说标准库就是C官方提供的“现成代码”目的是让程序员少写重复代码提高开发效率。std 命名空间std 是“standard”标准的缩写它是C标准库专用的命名空间。前面说过命名空间的作用是“隔离代码避免重名冲突”。标准库包含了太多工具比如 vector 、 cout 如果这些工具直接放在全局作用域很可能和你自己写的变量/函数重名比如你可能也想定义一个叫 vector 的函数。所以C规定标准库的所有工具都必须放在 std 这个命名空间里。比如标准库的输出工具 cout 完整名字是 std::cout std:: 表示“来自 std 命名空间”。标准库的动态数组 vector 完整名字是 std::vector 。这样一来即使你自己定义了一个叫 cout 或 vector 的东西也不会和标准库的工具冲突——因为标准库的工具被 std “包起来”了。总结:C标准库官方提供的现成工具集合数据结构、函数等直接用能省时间。std 命名空间专门用来存放标准库工具的“容器”防止这些工具和你写的代码重名冲突。使用标准库时需要用 std:: 开头比如 std::cout 或者用 using namespace std; 简化但不推荐在大型项目中用。C标准库将所有组件如函数、类、模板等都放在 std 命名空间中这是C标准明确规定的设计主要目的和作用如下1. 避免命名冲突C标准库包含大量常用组件如 cout 、 vector 、 string 等如果这些组件直接暴露在全局作用域中很可能与用户自定义的标识符变量、函数名等重名导致冲突。例如若用户自己定义了一个名为 vector 的函数而标准库的 vector 容器也在全局作用域编译器就无法区分两者引发错误。std 命名空间通过将标准库组件“隔离”在独立空间中从根本上避免了这种冲突。2. 明确标识标准库组件std 是“standard”标准的缩写使用 std:: 前缀如 std::cout 、 std::vector 可以清晰区分这是标准库提供的组件而非用户自定义或其他库的内容。这增强了代码的可读性和可维护性尤其是在大型项目中能让开发者快速识别组件的来源。3. 符合命名空间的设计初衷命名空间的核心功能就是“组织代码、隔离作用域”标准库作为C的官方组件自然会遵循这一机制。将所有标准库内容统一放在 std 中是对命名空间功能的典型应用也为用户提供了规范的代码组织范例。总结std 命名空间是C标准库的“专属容器”它通过隔离作用域避免了命名冲突同时明确标识了标准库组件是C为规范代码组织、提升兼容性而设计的重要机制。使用标准库组件时需通过 std:: 前缀访问或通过 using 声明简化。1.7 命名空间的使用编译查找一个变量的声明/定义时默认只会在局部或者全局查找不会到命名空间里面去查找。所以下面程序会编译报错。所以我们要使用命名空间中定义的变量/函数有三种方式指定命名空间访问项目中推荐这种方式。using将命名空间中某个成员展开项目中经常访问的不存在冲突的成员推荐这种方式。展开命名空间中全部成员项目不推荐冲突风险很大日常小练习程序为了方便推荐使用。2. C输入和输出C 的输入输出I/O系统主要依赖 iostream 头文件核心是 cin 输入和 cout 输出它们属于 std 命名空间以下详细拆解其用法、原理和特性1. 基础概念与头文件iostream 是 Input/Output Stream输入输出流的缩写包含了 C 标准输入输出的核心定义比如 cin关联标准输入设备通常是键盘、 cout关联标准输出设备通常是屏幕、 cerr标准错误输出直接输出到屏幕不经过缓冲区等。使用时需 #include iostream 引入。命名空间 std C 标准库的组件包括 cin / cout 都放在 std 命名空间下避免和用户自定义的变量/函数重名。因此直接用 cout 会报错需写成 std::cout 或用 using namespace std; 简化小项目/练习常用但大型项目不推荐避免命名冲突。2. 核心对象 cout 输出cout 是 ostream 类的对象用于向标准输出屏幕打印内容配合流插入运算符 使用语法std::cout 数据1 数据2 ... 操纵符;基础用法自动识别变量类型无需像 printf 那样手动指定格式如 %d / %f 。示例int a 10; double b 3.14; std::cout 整数 a 浮点数 b std::endl; // 输出整数10浮点数3.14常用操纵符std::endl 本质是一个函数作用是插入换行符 \n 刷新输出缓冲区确保内容立刻显示到屏幕。也可用 \n 替代换行但 \n 不会主动刷新缓冲区。3. 核心对象 cin 输入cin 是 istream 类的对象用于从标准输入键盘读取数据配合流提取运算符 使用语法std::cin 变量1 变量2 ...;基础用法自动识别变量类型按空白符空格、换行、制表符分割输入跳过空白符直到遇到有效数据。示例int a; double b; std::cin a b; // 输入10 3.14按回车结束a10b3.14- 注意事项- 遇到无效输入如给 int 变量输入字母时 cin 会进入错误状态后续输入会被阻塞。需用 cin.clear() 重置状态 cin.ignore() 清除缓冲区错误数据。4. 和 C 语言 printf/scanf 的对比特性C cin/cout C printf/scanf 类型支持自动识别类型依赖函数重载手动指定格式 %d / %f 等自定义类型支持 / 支持自定义类输入输出无法直接支持需拆分为基础类型代码简洁性无需记忆格式符写法更简洁需精确匹配格式符易出错效率默认带缓冲区效率稍低可通过代码优化格式化灵活但手动控制格式较繁琐命名空间依赖属于 std 命名空间需处理作用域直接在全局作用域 stdio.h 总结:iostream是 Input Output Stream 的缩写是标准的输入、输出流库定义了标准的输入、输出对象。std::cin 是 istream 类的对象它主要面向窄字符narrow characters (of type char)的标准输入流。std::cout 是 ostream 类的对象它主要面向窄字符的标准输出流。std::endl 是一个函数流插入输出时相当于插入一个换行字符加刷新缓冲区。是流插入运算符是流提取运算符。C语言还用这两个运算符做位运算左移/右移使用C输入输出更方便不需要像printf/scanf输入输出时那样需要手动指定格式C的输入输出可以自动识别变量类型(本质是通过函数重载实现的这个以后会讲到)其实最重要的是C的流能更好的支持自定义类型对象的输入输出。cout/cin/endl等都属于C标准库C标准库都放在一个叫std(standard)的命名空间中所以要通过命名空间的使用方式去用他们。一般日常练习中我们可以 using namespace std实际项目开发中不建议 using namespace std。这里我们没有包含stdio.h也可以使用printf和scanf在包含间接包含了。vs系列编译器是这样的其他编译器可能会报错。3. 缺省参数3.1 缺省参数的定义C 中的缺省参数Default Arguments是一个实用特性允许函数声明或定义时为参数指定默认值。调用函数时若省略带缺省值的参数编译器会自动用默认值填充能简化调用、提升代码灵活性。3.2 缺省参数的用法1. 核心概念给参数“预设值”函数声明/定义时可为右侧参数指定默认值。调用时若传入实参用传入的值若省略用默认值。语法示例声明时指定缺省void func(int a, int b 10, int c 20); // b、c 有默认值调用方式func(1); // a1, b10用默认, c20用默认 func(1, 2); // a1, b2, c20用默认 func(1, 2, 3); // a1, b2, c3全用传入值2. 规则与限制右侧优先原则缺省参数必须从右往左连续设置不能跳过中间参数。错误示例void func(int a 10, int b); // 错误a 设了默认但右侧 b 没设违反“右侧连续”规则正确写法需保证有缺省值的参数都在无缺省值参数的右侧void func(int a, int b 10, int c 20); // 合法b、c 连续在右侧声明/定义只能选一处完整指定缺省参数不能在声明和定义中重复设置否则编译器会认为“默认值冲突”。场景 1头文件声明 源文件定义头文件 .h 声明时给默认值源文件 .cpp 定义时不能重复给// test.h头文件 void func(int a, int b 10); // test.cpp源文件 void func(int a, int b) { // 定义时不写默认值避免冲突 // ... }场景 2仅声明或仅定义如 inline 函数若函数直接定义如 inline 函数写在头文件可直接在定义处给默认值inline void func(int a, int b 10) { // ... }缺省值必须是“编译期可确定的常量”默认值可以是- 字面值 10 、 3.14 、 hello 等- const 常量、 constexpr 变量- 全局/静态变量需注意作用域。错误示例运行时才能确定的值无法作为缺省int getDefault() { return 10; } void func(int a getDefault()); // 错误默认值需编译期确定总结:缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时如果没有指定实参则采用该形参的缺省值否则使用指定的实参缺省参数分为全缺省和半缺省参数。有些地方把缺省参数也叫默认参数全缺省就是全部形参给缺省值半缺省就是部分形参给缺省值。C规定半缺省参数必须从右往左依次连续缺省不能间隔跳跃给缺省值。带缺省参数的函数调用C规定必须从左到右依次给实参不能跳跃给实参。函数声明和定义分离时缺省参数不能在函数声明和定义中同时出现规定必须函数声明给缺省值。缺省参数是 C 简化函数调用、扩展接口兼容性的实用工具核心规则是右侧连续设置、声明/定义只设一次、默认值编译期确定。- 日常开发中用它给高频函数加“默认选项”能减少重复代码- 大型项目里新增接口参数时用缺省值兼容旧代码可降低重构成本- 需注意与函数重载的分工、类继承中静态绑定的坑避免引发意外行为。合理使用缺省参数能让代码更简洁、更易维护是 C 工程师必备的语法糖之一4. 总结:以上便是关于C入门基础的第一部分内容,主要围绕着命名空间C的输入和输出以及缺省参数这三个模块展开详细讲解这三个语法知识对于初学C来说都非常至关重要。一定要了解并熟练掌握它们为后面更难的学习内容打下坚实的基础。最后感谢大家的观看二次复习当我们需要使用其他文件中定义的命名空间时首先要引入存放该命名空间声明的头文件引入头文件后编译器就能识别这个命名空间内部的类、函数等标识此时即便不使用 using 语句我们也可以通过命名空间标识的完整写法正常调用里面的内容。如果在当前文件写下 using namespace 对应的命名空间那么在没有名称冲突的前提下当前文件内使用该命名空间的内容就不用再加命名空间前缀可以直接书写标识名称且这条 using 语句仅作用于当前文件不会对其他源文件产生任何影响。倘若当前文件同时 using 了两个不同的命名空间而这两个命名空间里存在名称完全相同的函数或者类此时直接裸写这个共用名称会触发编译歧义报错编译器无法判断我们想要调用哪一个命名空间内的内容想要解决这种冲突最简单的方式就是调用时手动补全命名空间前缀依靠命名空间标识的写法明确区分除此之外也可以选择不整体展开命名空间仅单独 using 需要用到的单个标识或是直接放弃 using 语句全程使用完整带双冒号的写法从根源避免命名冲突问题。包含头文件的作用是头文件里放了命名空间、类、函数的声明只要 #include 这个头文件编译器就知道这些声明不会报 “未定义标识符”。此时我们完整书写 ::A() 就一定能正常编译运行。