【C++】Chapter 0:当你学习C++之前首先需要了解的

注:此文章主要介绍C++中的特征及基本语法问题。

C与C++的关系

C 与 C++ 的关系可以概括为:C++ 是 C 语言的超集(C with Classes),即 C++ 兼容大部分 C 语法,但提供了更多高级特性。它们之间的关系主要体现在以下几个方面:


1. C 是 C++ 的基础

  • C++ 最初是由 Bjarne Stroustrup 在 1979 年基于 C 语言开发的,最早的名字是 “C with Classes”,后来才改名为 C++(“C 增强版”)。
  • C++ 继承了 C 语言的基本语法、数据类型、控制流、指针等特性,因此 几乎所有的 C 代码都能在 C++ 中编译运行(但某些情况例外,如 malloc/freenew/delete 混用可能会出错)。

2. C++ 扩展了 C 语言

C++ 在 C 的基础上添加了许多新的特性,使其支持更高级的编程范式,例如:

C 语言 C++ 语言(扩展特性)
结构化(Procedural)编程 支持面向对象(OOP)
仅支持 struct 定义数据结构 class(支持封装、继承、多态)
不能重载函数 支持函数重载、运算符重载
没有异常处理 支持 try-catch
没有 namespace 支持 namespace 避免命名冲突
malloc/free 分配内存 new/delete 进行内存管理
printf/scanf 进行输入输出 cout/cin 提供更安全的输入输出
只能使用 C 标准库 额外提供 STL(标准模板库,如 vectormap

3. C++ 兼容 C,但不是 100%

尽管 C++ 兼容大部分 C 代码,但仍存在一些 不兼容的地方,例如:

  • C 允许隐式转换 void*,C++ 需要强制转换

    c复制编辑void *ptr = malloc(10); // C 允许
    int *p = ptr;           // C 允许,但 C++ 需要 (int*)
    
  • C++ 关键字冲突(如 newclass 在 C 可能是变量名,但在 C++ 是关键字)。

  • C++ 更严格的类型检查,C 允许隐式 int,但 C++ 需要显式声明。


4. C++ 可以调用 C 代码

如果在 C++ 代码中想调用 C 语言编写的函数,需要使用 extern "C" 避免 C++ 名字修饰(name mangling)

cpp复制编辑// C 语言头文件 mymath.h
#ifdef __cplusplus
extern "C" {
#endif
    int add(int a, int b);
#ifdef __cplusplus
}
#endif

这样 C++ 代码就能正确调用 C 的 add() 函数。


5. 应用场景的区别

  • C 语言
    • 操作系统(Linux 内核、Windows 内核)
    • 嵌入式开发(单片机、驱动程序)
    • 高性能计算(如 FFT、数学库)
  • C++ 语言
    • 游戏开发(Unreal Engine、Unity)
    • GUI 开发(Qt、MFC)
    • 服务器端(Redis 源码部分使用 C++)

6. 总结

  1. C 是 C++ 的子集,但不是严格的子集,C++ 向后兼容 C 但有些细微差别。
  2. C++ 提供面向对象、泛型、异常处理等高级特性,使代码更易扩展和维护
  3. C 适用于底层开发,C++ 适用于大型软件工程

如果是系统级、嵌入式开发,C 是更好的选择;如果是应用软件、游戏开发,C++ 更适合。

C++与Java的关系

C++ 与 Java 之间的关系可以总结为:Java 借鉴了 C++ 的语法和面向对象特性,但去除了指针、手动内存管理等复杂性,主打跨平台和安全性


1. C++ 与 Java 的相似之处

Java 受到 C++ 影响很大,因此它们在语法上有许多相似之处:

特性 C++ Java
语法 继承自 C 语言,语法类似 语法风格类似 C++
面向对象 支持 OOP(类、封装、继承、多态) 支持 OOP(类、封装、继承、多态,所有代码必须在类中)
继承 支持 多继承 不支持 多继承(改用接口 interface
构造函数 支持 支持
运算符重载 支持 不支持
函数重载 支持 支持
异常处理 try-catch try-catch,但必须捕获异常
代码块 {} 相同 相同

示例代码(Java 语法类似 C++):

cpp复制编辑// C++ 类
class Animal {
public:
    void speak() { cout << "Animal speaks" << endl; }
};
java复制编辑// Java 类
class Animal {
    void speak() { System.out.println("Animal speaks"); }
}

2. Java 对 C++ 进行了改进

Java 在 C++ 的基础上进行了简化,去除了复杂和危险的特性,例如:

C++(更复杂) Java(更简单)
指针(容易导致内存泄漏) 无指针,改用引用
手动内存管理new/delete 自动垃圾回收(GC)
多继承(容易引起二义性) 只支持单继承,改用 interface 代替
结构体 struct struct,所有代码必须在类中
运算符重载 不支持运算符重载
头文件(#include) 无头文件,改用 import

示例:

cpp复制编辑// C++ 需要手动管理内存
Person* p = new Person();
delete p;
java复制编辑// Java 由 GC 自动回收对象,无需 delete
Person p = new Person();

3. 运行机制的不同

特性 C++(编译型) Java(解释型+编译型)
编译方式 直接编译成机器码 先编译成字节码(Bytecode),再由 JVM 解释执行
执行速度 运行速度快(直接执行机器码) 运行速度较慢(JVM 解释执行,但 JIT 可加速)
可移植性 依赖平台(Windows 版与 Linux 版不通用) 跨平台(JVM 使 Java 程序可以运行在不同系统上)
底层开发 可直接操作内存、硬件(如操作系统、驱动) 运行在 JVM 之上,不可直接操作底层硬件

示例:

sh复制编辑# C++ 编译
g++ program.cpp -o program  # 生成可执行文件

# Java 编译
javac Program.java  # 生成 Program.class(字节码)
java Program  # 运行

Java 依赖 JVM(Java Virtual Machine) 进行跨平台支持,C++ 直接编译成本地机器码,所以 C++ 运行效率更高,但 Java 具有更好的跨平台性


4. C++ 与 Java 的应用场景

语言 应用领域
C++ 操作系统(Linux、Windows 内核)、嵌入式系统、游戏开发(Unreal Engine)、高性能应用(数据库、浏览器内核)
Java 企业级应用(银行、ERP)、Web 开发(Spring)、Android 开发(Android Studio)、大数据(Hadoop、Spark)
  • C++ 适用于 高性能计算、游戏引擎、系统级编程。
  • Java 适用于 企业应用、Web 开发、移动应用(Android)。

5. 主要区别总结

对比点 C++ Java
语言类型 编译型语言 解释+编译型语言
内存管理 手动 new/delete 自动垃圾回收(GC)
指针 支持 指针 不支持 指针
继承 支持 多继承 只支持单继承,改用 interface
运行效率 略低(JIT 可优化)
平台依赖 依赖平台(Windows/Linux) 跨平台(JVM 运行)
主要应用 操作系统、游戏引擎、嵌入式 企业级应用、Web 开发、Android

6. 结论

  1. Java 是从 C++ 发展而来的,但去除了 C++ 的复杂性,使其更易用、更安全、更跨平台
  2. C++ 更快,更适用于底层开发和高性能应用,而 Java 适合企业应用和移动开发
  3. 如果追求性能,选 C++;如果追求跨平台和开发效率,选 Java

namespace

using namespcae std

局部域-》全局域-》展开了命名空间域or指定访问命名空间域

所以不要轻易using namespace

命名空间可以嵌套

同名的命名空间会合并成一个

如果命名重复了就报错

#include <iostream>->c++标准库将.h都去掉了

也就是说一般只展开常用的命名空间即可。**using std::out、endl...

命名空间是为了解决C语言的缺陷:命名冲突

cout可以自动识别类型

endl换行

std命名空间的使用惯例: std是C++标准库的命名空间,如何展开std使用更合理呢? 1. 在日常练习中,建议直接using namespace std即可,这样就很方便。 2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对 象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模 大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 + using std::cout展开常用的库对象/类型等方式。

c中的输入输出比c++中的输入输出快:由于c++兼容c语言,所以在使用c++的输入输出时会先考虑是否有c语言的输入输出

c++支持重载而c不支持

C++支持函数重载是因为它引入了**函数签名(修饰规则)**的概念。函数签名由函数的名称和参数类型组成。当在C++中定义多个函数具有相同的名称但不同的参数类型或参数个数时,编译器可以根据函数签名来区分它们,从而保证不会冲突,并选择正确的函数进行调用。
(g++中)c++的函数签名公式如下:_Z + 函数名长度+ 函数名+ 参数类型首字母

又如下:

C语言不支持函数重载的主要原因是它没有引入函数签名的概念。在C中,函数的名称是唯一的,并且C是通过函数名字去其他符号表中寻找地址的,C语言函数名的存储是直接转化使用函数名,所以如果C语言存在函数重载,那么在调用函数时不知道调用哪个函数,因此不支持函数重载

理论上来说,是因为C语言的存储规则,才导致C不支持重载,所以编译时才不用记录参数

函数重载的好处

C++引入函数重载的好处是可以提高代码的可读性和灵活性。通过使用相同的函数名称来表示具有不同功能的函数,可以使代码更加清晰和易于理解。此外,函数重载还可以减少代码的冗余,避免为类似的功能编写多个不同名称的函数。

引用

语法:

  • 左值引用
类型 & 引用变量名(对象名) = 引用实体
ex:
int x = 10;
int& ref = x;  // ref 是 x 的别名
ref = 20;      // x 变为 20
  • 右值引用
int&& rref = 5; // 5 是右值,因此可以绑定
std::cout << rref;  // 输出 5
  • 常量引用
const int& cref = 10;  // ✅ 可以绑定到右值
int a = 100;
const int& refA = a;  // ✅ 允许绑定左值
refA = 200;  // ❌ 编译错误,const 不能修改
  • 引用必须初始化

  • 一个变量可以有多个引用

  • 引用一旦引用一个实体,则不可以引用其他实体(java与c++的区别)

作用:

  • 做参数
  • 做返回值

任何场景都可以引用传参

传值和传引用的效率比较:

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直 接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效 率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

谨慎使用引用做返回值,出了函数作用域就不要使用引用返回。

引用过程中,权限不能放大(使用范围),但可以平移或者缩小

引用 VS 指针

特性 引用(Reference) 指针(Pointer)
是否可更改绑定 不可以,必须初始化 可以,可以更改指向的对象
是否必须初始化 ,必须在定义时初始化 ,可以为空
是否支持运算 不支持指针运算 支持指针算术运算
可否指向 nullptr 不能 可以
适用场景 更安全,更直观 更灵活,可用于数组、动态分配等

image-20250203225710528

使用引用的场景

  • 需要给变量取别名(而不是指针)
  • 需要函数参数的传递(避免了低效率的拷贝过程)
  • 常量引用(不需要修改)
  • 右值引用(用于移动语义)

auto关键字

能根据右边的表达式自动推导左边变量的类型,常用于很长的类型的简写。

for的妙用

for(auto e : arr)
{
 cout << e << " ";
}
cout << endl;
//自动迭代数组arr中的数据

内联函数

用于解决宏函数的缺点->内联函数的本质:编译时展开(Expand),避免函数调用开销

image-20250204130140705

它告诉编译器将函数调用替换为函数体本身,从而减少函数调用的开销。提高小型函数(如果函数过大,可能会导致代码膨胀(Code Bloat),影响性能。)的执行效率。

image-20250204131216817

编译器决定是否内联:即使加了 inline,编译器可能不会内联。(反编译的时候看见call 函数名,就说明没有展开,即没有内联)

语法

inline 返回类型 函数名(参数列表) {
    // 函数体
}

inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。->所以内联函数一般定义在**头文件(.h)**中。

内联函数与 #define 宏的对比

对比项 内联函数(inline) 宏函数(#define)
是否有类型检查 ✅ 有类型检查 ❌ 无类型检查
是否支持调试 ✅ 支持,能单步调试 ❌ 不能调试
是否支持作用域 ✅ 受作用域影响 ❌ 无作用域
是否能使用 return ✅ 可以 ❌ 不能

5.2 不适合内联的情况

递归函数

  • 内联展开会导致无限递归,编译失败。
cpp复制编辑inline int factorial(int n) {  // ❌ 递归不适合内联
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

包含循环或复杂逻辑

  • 代码太长会增加指令缓存压力,降低性能。
cpp复制编辑inline void complexFunction() {  // ❌ 逻辑复杂,不适合内联
    for (int i = 0; i < 100; ++i) {
        std::cout << i << " ";
    }
}

虚函数(Virtual Function)

  • 虚函数调用依赖 vtable,不能内联(除非编译器做特殊优化)。
cpp复制编辑class Base {
public:
    virtual inline void show() {  // ❌ 虚函数不能内联
        std::cout << "Base class";
    }
};

空指针nullptr

image-20250208102155451

C++ 中的空指针 nullptr

在 C++11 之前,空指针通常用 NULL0 表示。但 NULL 本质上是 #define NULL 0,属于整数 0,容易导致歧义。歧义如下:

image-20250208102432168

C++11 引入 nullptr,专门用于表示空指针,避免了 NULL 的问题


1. nullptr 的特点

  1. nullptr 是指针类型,不是整数,不会与 int 发生混淆。
  2. nullptr 可以隐式转换为任何指针类型int*double*void* 等)。
  3. nullptr 不能隐式转换为 int,可以避免 NULL 引发的二义性问题。、
  4. nullptr使用时无需包含头文件
  5. 在C++11中,sizeof(nullptr) sizeof((void*)0)所占的字节数相同。

2. nullptr vs NULL vs 0

表示方式 C++11 及以上 C++11 之前
nullptr 推荐,专门表示空指针 不支持
NULL 仍可用,但不推荐(是 0 的宏定义) 可用,常见用法
0 可能产生二义性 习惯用法

示例:

#include <iostream>
using namespace std;

void func(int x) {
    
     cout << "int overload: " << x << endl; }
void func(void* p) {
    
     cout << "Pointer overload" << endl; }

int main() {
    
    
    func(0);     // 调用 func(int) → "int overload: 0"
    func(NULL);  // C++98 可能也调用 func(int),易混淆
    func(nullptr); // C++11 调用 func(void*),正确

    return 0;
}

在 C++98 中,NULL 可能会被解析为 int,导致 func(int) 被调用,而不是 func(void*)
使用 nullptr 解决了这个问题


3. nullptr 适用场景

  • 初始化空指针

    int* ptr = nullptr;  // 推荐
    
  • 函数重载时避免歧义

    void foo(int);
    void foo(char*);
    
    foo(0);      // 可能调用 foo(int)
    foo(nullptr); // 确保调用 foo(char*)
    
  • 作为返回值表示失败

    int* findValue(int value) {
          
          
        return nullptr;  // 代替 NULL,更清晰
    }
    
  • nullptr_t 结合使用

    std::nullptr_t nptr = nullptr; // `nullptr_t` 只能存 nullptr
    

4. 结论

C++11 及以上版本推荐使用 nullptr 代替 NULL0,避免歧义,使代码更安全、可读性更强。

5.引申

语法更新的宗旨是向前兼容,旧的东西依然可用,但新版本有更好的方法替代。例如上方的NULL和nullptr。

特例是python2和python3。