0 初识C++

C++作为一门兼具高性能与抽象能力的通用编程语言,自1983年正式命名以来,历经多次标准化迭代,已成为系统软件、高性能服务端、游戏引擎等邻域的核心开发语言。

1 第一个C++程序

C++完全兼容C语言语法,因此C风格的程序在C++中仍可正常编译运行。但在标准C++规范中,推荐使用C++特有的输入输出机制。

编译说明:C++源文件后缀通常为.cpp,在Windows平台使用MSVC编译时,编译器会根据后缀自动调用C++编译流程;在Linux环境下需要使用g++而非gcc进行编译。

2 命名空间:解决标识符冲突的机制

在C语言项目中,全局作用域内的标识符(变量、函数、类)极易发生重名冲突,尤其是当引入多个第三方库时。C语言缺乏有效的隔离手段。

C++引入命名空间(namespace)机制,用于创建独立的作用域,将不同标识符封装在不同命名空间中。编译器进行名字查找时,默认按照当前作用域及其外层作用域进行查找,不会自动搜索其他命名空间,因此不同命名空间中的同名标识符不会发生冲突。通过作用域解析运算符::using可以显式访问其他命名空间中的成员。

2.1 定义与嵌套

命名空间通过namespace关键字定义,可包含变量、函数、类型(结构体/类)及嵌套命名空间。

同一命名空间可跨文件合并: 若在多个文件中定义同名的namespace,编译器会自动合并其内容,这便于将模块的声明与实现分离。

命名空间不影响变量生命周期: 与局部域(自动存储期)和全局域(静态存储期)不同,命名空间中的变量具有静态存储期(与全局变量一致),其声明周期从程序启动延续至结束。

2.2 核心特性

① 域独立性:命名空间域与全局域、局部域相互独立,不同域中可定义同名标识符。

② 嵌套定义:命名空间支持嵌套,通过多级作用域::访问嵌套成员。

③ 跨文件合并:不同源文件中定义的同名空间会被编译合并为同一个逻辑命名空间。这是多文件项目中组织代码的基础机制。

④ 作用域分类:C++中共有四种作用域------局部域、全局域、命名空间域、类域。其中局部域和全局域会影响变量的声明周期,而命名空间域和类域仅影响编译器的标识符查找逻辑,不改变变量生命周期。

2.3 使用方式

编译器默认仅在局部域和全局域中查找标识符,访问命名空间成员需通过以下三种方式:

① 指定命名空间访问:项目中推荐,最安全,避免冲突。

② 部分成员展开:仅展开单个高频使用的成员,风险可控。

③ 全量展开:强烈不推荐头文件或大型项目,会导致命名空间污染。

在C++中,访问命名空间中的成员通常有三种通用方式:

① 可以直接使用完整限定名命名空间:: 成员,这种方式最明确,不需要任何using

② 可以使用 using namespace 命名空间;,把整个命名空间中的名字引入当前作用域,从而省略命名空间前缀,但容易产生命名冲突。

③ 可以使用using 命名空间::成员;,只引入某一指定成员,既能简化书写,又能减少冲突。

工程实践:标准库的所有组件均定义在std命名空间中,日常练习可使用using namespace std;简化代码,但在大型项目中应避免全局展开,以防命名污染。标准库std是C++最有价值的命名空间,但绝不应在头文件中使用using namespace std;,否则会污染包含该头文件的源文件,导致不可预期的重载冲突。

3 标准输入输出流

C++通过<iostream>头文件提供标准输入输出功能,核心对象包括:

std::cinistream类对象,负责标准上输入。

std::coutostream类对象,负责标准输出。

std::endl :函数对象。插入换行符并刷新输出缓冲区。

运算符重载: <<为流插入运算符,>>为流提取运算符,C++复用了C语言的位运算符符号,通过运算符重载实现类型自动识别。相比C语言的prinftscanf,C++流输出的核心优势在于无需手动指定格式控制符,编译器会根据变量类型自动匹配输出逻辑,且天然支持类型的输入输出扩展。

4 缺省参数:函数参数的默认值机制

缺省参数允许在函数声明或定义时为参数指定默认值,调用时若未提供对应实参,则使用默认值。根据缺省参数的覆盖范围,可分为:

① 全缺省参数:所有参数均指定默认值。

② 半缺省参数:部分参数指定默认值。

语法规则:

① 半缺省参数必须从左向右依次连续缺省,不允许间隔跳跃。

② 函数调用时实参必须从左到右依次传递,不允许跳跃传参。

③ 声明与定义分离时,缺省参数只能在声明中指定。

声明与定义分离时的规则: 当函数声明与定义分开时,缺省参数仅能出现在声明中,定义处不可重复指定。默认参数属于函数接口而非实现细节,其替换发生在编译期的调用点,而调用方通常只能看到头文件中的声明、看不到源文件里的定义;如果允许在定义中指定默认参数,不同源文件可能看到不同的默认值,既违反C++的"单一定义规则(ODR)",又会导致链接期无法校验的一致性错误,因此C++规定默认参数只能在声明中指定,且只能指定一次。

工程应用:缺省参数常用于初始化场景,例如栈初始化时,若已知数据规模,可通过指定缺省参数一次性分配足够内存,避免频繁扩容。

5 函数重载

C++允许在同一作用域内定义多个同名函数,前提是它们的参数列表不同(参数个数、类型或类型顺序不同),这成为函数重载。函数重载是C++实现静态多态的基础。编译器根据调用时提供的实参类型和数量,在编译期决定调用哪个重载版本。

当重载函数与缺省参数结合,可能引发调用歧义:

因此,设计重载时应避免此类模糊组合。

6 内联函数

频繁调用小型函数会引入栈帧建立与销毁的开销,影响性能。C 语言通过宏函数(macro)在预处理阶段展开,但宏存在诸多缺陷:

① 缺少类型检查;

② 容易因运算符优先级问题导致错误;

③ 无法调试(预处理后不可见)。

C++ 引入 inline 关键字,建议编译器将函数体在调用点内联展开,从而消除调用开销,同时保留函数的类型安全和可调试性,但在Debug模式下通常不展开,以便断点。

inline修饰的函数称为内联函数,编译时C++编译器会在调用点展开内联函数,省去函数调用时的栈帧创建、参数压栈、返回地址保存等开销,提升程序执行效率。

6.1 内联函数的特性

① 编译器建议性:inline对编译器仅为建议,对于递归函数、代码量较大的函数,即使添加inline关键字,编译器也可能忽略该建议,不进行展开。

② 替代宏函数:C语言中的宏函数在预处理阶段展开,存在语法复杂、易出错、无法调试等问题,C++引入内联函数作为宏函数的安全替代方案。

③ 声明定义不可分离:内联函数不建议声明与定义分离到不同文件。

内联函数在调用点展开,不会生成独立的函数符号,若分离到不同文件,链接时会因找不到函数地址而报错。通常将内联函数的定义直接放在头文件中,供多个源文件包含。

6.2 宏函数的问题与内联函数的优势

宏正确实现需考虑诸多细节。而内联函数无需额外防范,且支持作用域、类型检查和调试,应作为替代宏的首选方案。

内联函数通常应定义在头文件中,因为内联展开需要在翻译单元内看到完整定义。若将声明与定义分离到不同文件,链接时找不到函数地址(因为未生成符号),会引发链接错误。

7 nullptr

在 C++ 中,传统的 NULL 本质上通常是整数常量 0,因此在函数重载场景下,func(NULL) 更可能匹配 func(int),而不是程序员期望的指针版本 func(int*);如果想调用指针重载,过去往往需要显式强制转换,而 C++11 引入的 nullptr 是专门表示"空指针"的类型,可以正确匹配指针参数,避免 0NULL 在整数与指针重载之间产生歧义,因此现代 C++ 应优先使用 nullptr 表示空指针。

相关推荐
FfHUCisI1 小时前
Go 编译过程全景
开发语言·后端·golang
FfHUCisI1 小时前
Golang 语法分析与 AST:Parser 与 go/ast
开发语言·后端·golang
lemon_sjdk2 小时前
ObjectProperty
java·开发语言·算法
大模型码小白2 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
hh9502 小时前
Agent Plan x DeepSeek Harness:Agent 供应链安全与第三方插件审计
java·开发语言·安全·agent plan·adg成都社区·adg社区
「QT(C++)开发工程师」3 小时前
C++ std::move 详解
开发语言·c++
matlab代码3 小时前
基于matlab的玉米种子计数设计(GUI界面)【源码58期】
开发语言·matlab
c#上位机3 小时前
C#上位机项目实战——C#的dll项目编译时拷贝到指定目录下
开发语言·c#