C++笔记

C++知识笔记

一、C++概述

C++是一种通用编程语言,它在C语言的基础上扩展而来,支持面向对象编程、泛型编程和过程化编程等多种编程范式。C++具有高效、灵活、接近硬件等特点,广泛应用于系统软件、应用软件、嵌入式系统、游戏开发等领域。其强大的性能和丰富的功能使得它在软件开发中占据着重要地位。

(一)C++的发展历程

C++最初由贝尔实验室的本贾尼·斯特劳斯特卢普(Bjarne Stroustrup)在20世纪80年代初设计并实现,最初被称为"C with Classes",即带有类的C语言。随着时间的推移,C++不断发展,引入了虚函数、运算符重载、模板等重要特性,逐渐形成了完整的面向对象编程体系。1998年,C++标准(C++98)正式发布,之后又陆续发布了C++03、C++11、C++14、C++17、C++20等标准,不断完善语言特性和库功能,使C++更加适应现代软件开发的需求。

(二)C++的特点

  1. 面向对象编程(OOP):C++支持封装、继承、多态等面向对象的核心概念,能够将数据和操作数据的函数封装成类,通过继承实现代码的复用,利用多态实现灵活的动态绑定,提高软件的可维护性和可扩展性。
  2. 泛型编程:模板是C++泛型编程的核心机制,通过模板可以编写通用的算法和数据结构,使其能够适用于不同的数据类型,提高代码的复用性和灵活性。
  3. 与C语言的兼容性:C++完全兼容C语言,这意味着C语言的代码可以在C++环境中编译运行,同时C++也可以调用C语言的库函数,使得开发者可以在C语言的基础上逐步过渡到C++编程,充分利用已有的C语言资源。
  4. 高效性:C++具有接近汇编语言的执行效率,能够直接操作硬件资源,对于对性能要求较高的应用场景,如实时系统、高性能计算等,C++是首选的编程语言之一。

二、C++编译器

(一)编译器的工作流程

C++编译器将源代码转换为可执行程序的过程通常包括以下几个阶段:

1. 预处理(Preprocessing)

预处理阶段是编译器的第一个阶段,主要处理以"#"开头的预处理指令,如#include、#define、#ifdef等。

  • 头文件包含(#include):将指定的头文件内容插入到当前源文件中。例如,#include 会将iostream头文件的内容替换到该指令的位置。
  • 宏定义(#define):将代码中的宏标识符替换为对应的宏体。例如,#define PI 3.14159 会在预处理阶段将代码中所有的PI替换为3.14159。
  • 条件编译(#ifdef、#else、#endif等):根据定义的宏或条件表达式,决定是否编译某部分代码。这在跨平台开发或调试代码时非常有用,可以根据不同的编译环境选择不同的代码分支。

预处理完成后,会生成一个经过处理的中间文件,通常称为预处理后的源文件。

2. 编译(Compilation)

编译阶段将预处理后的源文件转换为汇编语言代码。编译器对源代码进行语法分析、语义分析和中间代码生成等操作,检查代码中的语法错误和语义错误。如果代码存在错误,编译器会给出相应的错误提示和警告信息。在这个阶段,编译器会将高级语言的语句转换为与目标架构相关的汇编语言指令。

3. 汇编(Assembly)

汇编阶段将汇编语言代码转换为机器语言的目标文件(二进制文件)。汇编器(Assembler)将每条汇编指令转换为对应的机器码,生成以.obj(Windows)或.o(Linux)为扩展名的目标文件。目标文件中包含了机器码和符号表等信息,但此时还不能直接运行,因为目标文件可能引用了其他文件中的函数或变量。

4. 链接(Linking)

链接阶段将多个目标文件和库文件链接成一个可执行程序。链接器(Linker)处理目标文件中的外部符号引用,将各个目标文件中的代码和数据合并,并解析符号的地址,确保程序在运行时能够正确访问外部函数和变量。链接可以分为静态链接和动态链接两种方式:

  • 静态链接:将库文件中的代码直接复制到可执行程序中,生成的可执行程序较大,但在运行时不需要依赖外部库文件。
  • 动态链接:在可执行程序中只保留对库文件的引用,程序运行时动态加载所需的库文件,生成的可执行程序较小,节省磁盘空间,但需要在运行环境中存在相应的库文件。

(二)常见的C++编译器

  1. GCC(GNU Compiler Collection)
    • 简介:GCC是GNU项目的核心组件之一,是一款功能强大、跨平台的编译器,支持多种编程语言,包括C、C++、Java、Fortran等。GCC在Linux和Unix系统中广泛使用,同时也可以在Windows系统上通过MinGW或Cygwin等工具链进行安装和使用。
    • 特点
      • 支持最新的C++标准,包括C++11、C++14、C++17、C++20等,能够充分利用C++的新特性。
      • 提供了丰富的编译选项,可以对代码进行优化、调试信息生成、警告级别设置等操作。
      • 具有良好的可扩展性和兼容性,能够与其他工具和库良好集成。
  2. Clang
    • 简介:Clang是一个基于LLVM(Low-Level Virtual Machine)的编译器前端,主要用于C、C++、Objective-C等语言的编译。Clang以其快速的编译速度、清晰的错误提示和良好的模块化设计而受到广泛关注。
    • 特点
      • 编译速度快,在处理大型项目时能够显著提高编译效率。
      • 错误提示信息更加友好和详细,能够帮助开发者更快地定位和解决问题。
      • 支持与GCC兼容的编译选项,方便开发者从GCC迁移到Clang。
  3. Visual C++ Compiler(MSVC)
    • 简介:MSVC是微软公司开发的C++编译器,集成在Visual Studio集成开发环境(IDE)中,主要用于Windows平台的软件开发。
    • 特点
      • 与Windows操作系统和Visual Studio IDE深度集成,提供了强大的调试、性能分析等工具。
      • 支持Windows特定的API和特性,如COM组件、MFC框架等。
      • 对C++标准的支持逐渐完善,特别是在C++11及以后的标准支持上不断进步。
  4. Intel C++ Compiler(ICC)
    • 简介:ICC是英特尔公司开发的C++编译器,主要针对英特尔处理器进行优化,能够生成高效的代码,提高程序在英特尔平台上的性能。
    • 特点
      • 提供了针对英特尔处理器的特殊优化选项,如向量化指令、多线程优化等,能够充分发挥硬件性能。
      • 支持与GCC和MSVC兼容的编译选项,方便开发者在不同平台上使用。

三、头文件

(一)头文件的作用

头文件在C++编程中起着重要的作用,主要包括以下几个方面:

  1. 声明函数和类:头文件中通常包含函数的声明、类的定义、结构体的定义等。其他源文件在使用这些函数或类时,只需要包含对应的头文件,就可以知道函数的参数类型、返回值类型以及类的成员函数和数据成员等信息,而不需要在每个源文件中重复声明。
  2. 定义宏和常量:头文件可以用于定义宏常量、宏函数等,通过#include指令将这些定义引入到多个源文件中,确保各个源文件中使用的宏和常量的一致性。
  3. 声明模板和命名空间:模板的定义通常需要放在头文件中,因为模板的实例化需要在编译时看到模板的完整定义。此外,命名空间的声明和定义也常常放在头文件中,以便在多个源文件中统一使用命名空间。
  4. 提供类型定义和extern声明:头文件可以使用typedef或using关键字为复杂的类型定义别名,方便代码的编写和阅读。同时,头文件中可以使用extern关键字声明全局变量,使这些变量能够在多个源文件中共享。

(二)如何使用头文件

  1. 包含标准头文件:C++标准库提供了大量的头文件,如iostream(输入输出流)、vector(动态数组)、string(字符串)等。包含标准头文件时,使用#include <头文件名>的形式,例如#include 。标准头文件通常位于编译器的安装目录下的include文件夹中,编译器会自动搜索这些头文件。
  2. 包含自定义头文件:如果开发者自己编写了头文件,需要使用#include "头文件名"的形式,例如#include "myheader.h"。此时,编译器会首先在当前源文件所在的目录中搜索头文件,如果找不到,再到标准头文件目录中搜索。
  3. 头文件的包含顺序:为了避免头文件之间的依赖问题和编译错误,头文件的包含顺序通常遵循一定的规则。一般来说,应该先包含标准头文件,再包含自定义头文件。对于自定义头文件,按照项目中的模块依赖关系,从最基础的头文件开始包含。

(三)头文件的内容

头文件中可以包含以下内容:

  1. 函数声明 :声明函数的名称、参数列表和返回值类型,例如:

    cpp 复制代码
    int add(int a, int b);
  2. 类定义 :定义类的成员函数和数据成员,例如:

    cpp 复制代码
    class MyClass {
    public:
        void myFunction();
    private:
        int myVariable;
    };
  3. 结构体和联合体定义 :定义结构体和联合体的类型,例如:

    cpp 复制代码
    struct Student {
        char name[20];
        int age;
    };
  4. 枚举类型定义 :定义枚举类型,例如:

    cpp 复制代码
    enum Color { RED, GREEN, BLUE };
  5. 宏定义 :使用#define指令定义宏,例如:

    cpp 复制代码
    #define MAX_SIZE 100
  6. 模板定义 :定义函数模板和类模板,例如:

    cpp 复制代码
    template <typename T>
    T max(T a, T b) {
        return (a > b) ? a : b;
    }
  7. 命名空间声明和定义 :声明和定义命名空间,例如:

    cpp 复制代码
    namespace MyNamespace {
        int myVariable;
        void myFunction();
    }
  8. extern变量声明 :声明全局变量,例如:

    cpp 复制代码
    extern int globalVariable;

(四)头文件保护(防止重复包含)

在C++编程中,头文件可能会被多个源文件包含,或者在同一个源文件中被多次包含,这会导致编译错误,如函数重复声明、类重复定义等。为了避免这种情况,需要使用头文件保护机制,确保头文件的内容在编译过程中只被处理一次。

1. 使用#ifndef、#define、#endif预处理指令

这是最常用的头文件保护方法,其原理是通过宏定义来判断头文件是否已经被包含过。具体步骤如下:

  • 在头文件的开头,定义一个唯一的宏名称,通常使用头文件名的大写形式,并加上下划线或其他分隔符,例如:

    cpp 复制代码
    #ifndef MYHEADER_H
    #define MYHEADER_H
  • 在头文件的结尾,使用#endif指令结束条件编译,例如:

    cpp 复制代码
    #endif // MYHEADER_H

这样,当第一次包含该头文件时,MYHEADER_H宏未被定义,预处理指令会执行中间的头文件内容,并定义MYHEADER_H宏。当再次包含该头文件时,由于MYHEADER_H宏已经被定义,预处理指令会跳过中间的头文件内容,从而避免重复包含。

2. 使用#pragma once指令

#pragma once是一种现代的头文件保护方法,它告诉编译器该头文件只需被包含一次。其使用方法非常简单,只需在头文件的开头添加#pragma once指令即可,例如:

cpp 复制代码
#pragma once
// 头文件内容

#pragma once指令的优点是简单易用,不需要手动定义宏名称,避免了宏名称冲突的问题。但需要注意的是,#pragma once并不是C++标准的一部分,而是编译器的扩展功能,虽然大多数现代编译器都支持该指令,但为了确保代码的跨平台兼容性,通常建议同时使用#ifndef和#pragma once,或者根据项目的具体情况选择合适的头文件保护方法。

3. 头文件保护的重要性

头文件保护是C++编程中非常重要的一个环节,它可以避免因头文件重复包含而导致的编译错误,提高代码的可靠性和可维护性。特别是在大型项目中,头文件之间的依赖关系复杂,头文件保护机制能够有效地防止命名冲突和重复定义问题,确保项目的顺利编译和运行。

四、命名空间

(一)定义命名空间

命名空间是C++中用来解决命名冲突问题的一种机制,它将全局作用域划分为不同的命名空间,每个命名空间中的名称都是独立的,不会与其他命名空间中的名称发生冲突。

1. 命名空间的定义语法
cpp 复制代码
namespace 命名空间名称 {
    // 命名空间内的声明和定义
    int variable;
    void function();
    class MyClass { /* ... */ };
}

命名空间名称可以是任意合法的标识符,通常使用具有描述性的名称,以便区分不同的命名空间。

2. 嵌套命名空间

命名空间可以嵌套定义,即在一个命名空间中定义另一个命名空间,例如:

cpp 复制代码
namespace OuterNamespace {
    int outerVariable;
    void outerFunction();

    namespace InnerNamespace {
        int innerVariable;
        void innerFunction();
    }
}

嵌套命名空间的使用需要通过作用域解析运算符(::)来指定路径,例如OuterNamespace::InnerNamespace::innerVariable。

(二)使用命名空间中的成员

1. 直接使用作用域解析运算符

通过作用域解析运算符(::)可以直接访问命名空间中的成员,例如:

cpp 复制代码
namespace MyNamespace {
    int myVariable = 10;
    void myFunction() { /* ... */ }
}

int main() {
    // 访问命名空间中的变量
    int x = MyNamespace::myVariable;
    // 调用命名空间中的函数
    MyNamespace::myFunction();
    return 0;
}
2. 使用using指令

using指令可以将整个命名空间引入到当前作用域中,使得在当前作用域中可以直接使用命名空间中的成员,而不需要每次都指定命名空间名称,例如:

cpp 复制代码
using namespace MyNamespace;

int main() {
    int x = myVariable;
    myFunction();
    return 0;
}

需要注意的是,使用using namespace指令引入整个命名空间可能会导致命名冲突,如果当前作用域中已经存在与命名空间中同名的标识符,就会产生编译错误。

3. 使用using声明

using声明可以将命名空间中的某个特定成员引入到当前作用域中,例如:

cpp 复制代码
using MyNamespace::myVariable;
using MyNamespace::myFunction;

int main() {
    int x = myVariable;
    myFunction();
    return 0;
}

using声明比using namespace指令更加灵活和安全,它只引入需要使用的成员,避免了命名空间中其他成员可能带来的命名冲突问题。

(三)命名空间的作用

  1. 解决命名冲突:在大型项目中,不同的模块或库可能会定义相同名称的变量、函数或类,使用命名空间可以将这些名称封装在不同的命名空间中,避免全局作用域中的命名冲突。例如,标准库中的所有名称都定义在std命名空间中,当我们使用std::cout时,就不会与我们自己定义的cout名称发生冲突。
  2. 组织代码结构:命名空间可以将相关的类、函数和变量组织在一起,形成一个逻辑上的模块,使代码结构更加清晰,便于维护和管理。例如,在开发一个图形库时,可以将所有与图形绘制相关的类和函数定义在一个名为Graph的命名空间中,方便用户识别和使用。
  3. 支持库的开发和复用:库开发者可以使用命名空间来封装库的接口,避免与用户代码中的名称冲突,同时也方便用户根据需要选择引入库中的特定成员或整个命名空间。例如,第三方库通常会定义自己的命名空间,用户在使用时只需按照库的文档要求引入相应的命名空间或成员即可。

(四)标准命名空间std

C++标准库中的所有名称都定义在std命名空间中,例如cout、cin、endl、vector、string等。为了使用标准库中的成员,我们需要通过以下方式之一来引入std命名空间:

  1. 直接使用std::前缀:例如std::cout << "Hello, World!" << std::endl;
  2. 使用using namespace std:在文件开头添加using namespace std;,然后在代码中直接使用标准库成员,如cout << "Hello, World!" << endl;
  3. 使用using声明引入特定成员:例如using std::cout; using std::endl;,然后在代码中使用cout和endl。

需要注意的是,在大型项目中,使用using namespace std可能会引入大量的名称,增加命名冲突的风险,因此推荐使用using声明或直接使用std::前缀来访问标准库成员,以提高代码的可读性和安全性。

五、C++与办公工具及技能的关联思考(行业顾问视角)

(一)从C++开发工具看办公工具换代

在C++开发领域,集成开发环境(IDE)的发展体现了办公工具的不断换代。早期的C++开发可能依赖于简单的文本编辑器和命令行编译器,如Vi/Vim、Emacs搭配GCC,开发者需要手动编写Makefile来管理项目,效率较低。随着技术的进步,现代IDE如Visual Studio、CLion、Qt Creator等应运而生,它们集成了代码编辑、编译、调试、版本控制、项目管理等功能,提供了直观的图形界面和丰富的插件支持,大大提高了开发效率。

这类似于医药流通行业中办公工具的换代,从早期的纸质文档、Excel表格手动处理数据,到现在使用专业的企业资源计划(ERP)系统、供应链管理系统等,实现了数据的自动化处理和流程的规范化管理。开发工具的换代不仅提高了开发人员的工作效率,还降低了出错率,使项目管理更加便捷。

(二)C++编程技能对办公技能的重构启示

  1. 模块化思维:C++中通过头文件和命名空间将代码划分为不同的模块,实现了代码的封装和复用。在办公场景中,也可以运用模块化思维,将复杂的业务流程分解为多个独立的模块,每个模块负责特定的功能,如采购管理、库存管理、销售管理等,通过标准化的接口实现模块之间的交互,提高工作效率和系统的可维护性。
  2. 逻辑思维和问题解决能力:C++编程需要严谨的逻辑思维,开发者需要分析问题、设计算法、编写代码并调试解决错误。这种逻辑思维能力在办公场景中同样重要,例如在处理数据报表、制定工作计划、优化业务流程时,需要有条理地分析问题,找出关键因素,制定合理的解决方案。
  3. 学习新技能的能力:C++不断发展,新的标准和特性不断推出,开发者需要持续学习以跟上技术的步伐。在医药流通行业中,随着信息技术的发展,新的办公软件、管理系统和技术手段不断涌现,如大数据分析、人工智能在供应链中的应用等,员工需要具备学习新技能的能力,不断提升自己的办公技能,以适应行业的发展需求。

(三)C++在医药流通行业软件开发中的应用

作为医药流通行业的传统IT行业顾问,了解C++在行业软件开发中的应用至关重要。C++可以用于开发高性能的供应链管理系统,处理大量的库存数据、订单数据和物流数据,确保系统的高效运行和实时响应。在嵌入式设备开发中,如医药仓储中的智能货架、物流中的手持终端设备等,C++的高效性和对硬件的直接操作能力使其成为理想的选择。

同时,在开发行业专用的数据分析工具时,C++可以结合算法和数据结构,对海量的销售数据、库存数据进行快速处理和分析,为企业的决策提供有力支持。通过合理运用C++的特性和开发工具,能够提高软件开发的质量和效率,满足医药流通行业对信息化管理的需求。

总之,C++作为一种强大的编程语言,其知识体系不仅在软件开发领域具有重要意义,对于医药流通行业的办公工具换代和办公技能重构也具有一定的启示作用。通过借鉴C++中的模块化思维、逻辑思维和持续学习的理念,能够帮助企业更好地应对信息化时代的挑战,提升行业的竞争力。

上述笔记全面梳理了C++相关知识。你对内容的详略、结构安排等方面有什么看法或进一步需求,欢迎随时告知。

相关推荐
Python私教1 分钟前
Java手写链表全攻略:从单链表到双向链表的底层实现艺术
java·python·链表
小麟有点小靈14 分钟前
VSCode写java时常用的快捷键
java·vscode·编辑器
程序猿chen25 分钟前
JVM考古现场(十九):量子封神·用鸿蒙编译器重铸天道法则
java·jvm·git·后端·程序人生·java-ee·restful
mahuifa27 分钟前
(2)VTK C++开发示例 --- 绘制多面锥体
c++·vtk·cmake·3d开发
&白帝&44 分钟前
java HttpServletRequest 和 HttpServletResponse
java·开发语言
阿杆1 小时前
🤯我写了一套无敌的参数校验组件④ | 现已支持 i18n
java·spring
小样vvv1 小时前
【微服务管理】注册中心:分布式系统的基石
java·数据库·微服务
23级二本计科1 小时前
C++ Json-Rpc框架-3项目实现(2)
服务器·开发语言·c++·rpc
amagi6001 小时前
Java中的正则表达式(Regular Expression)
java
喵手1 小时前
如何快速掌握 Java 反射之获取类的字段?
java·后端·java ee