注:此文章主要介绍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/free
与new/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(标准模板库,如 vector 、map ) |
3. C++ 兼容 C,但不是 100%
尽管 C++ 兼容大部分 C 代码,但仍存在一些 不兼容的地方,例如:
-
C 允许隐式转换
void*
,C++ 需要强制转换c复制编辑void *ptr = malloc(10); // C 允许 int *p = ptr; // C 允许,但 C++ 需要 (int*)
-
C++ 关键字冲突(如
new
、class
在 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. 总结
- C 是 C++ 的子集,但不是严格的子集,C++ 向后兼容 C 但有些细微差别。
- C++ 提供面向对象、泛型、异常处理等高级特性,使代码更易扩展和维护。
- 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. 结论
- Java 是从 C++ 发展而来的,但去除了 C++ 的复杂性,使其更易用、更安全、更跨平台。
- C++ 更快,更适用于底层开发和高性能应用,而 Java 适合企业应用和移动开发。
- 如果追求性能,选 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 |
不能 | 可以 |
适用场景 | 更安全,更直观 | 更灵活,可用于数组、动态分配等 |
使用引用的场景
- 需要给变量取别名(而不是指针)
- 需要函数参数的传递(避免了低效率的拷贝过程)
- 常量引用(不需要修改)
- 右值引用(用于移动语义)
auto关键字
能根据右边的表达式自动推导左边变量的类型,常用于很长的类型的简写。
for的妙用
for(auto e : arr)
{
cout << e << " ";
}
cout << endl;
//自动迭代数组arr中的数据
内联函数
用于解决宏函数的缺点->内联函数的本质:编译时展开(Expand),避免函数调用开销
它告诉编译器将函数调用替换为函数体本身,从而减少函数调用的开销。提高小型函数(如果函数过大,可能会导致代码膨胀(Code Bloat),影响性能。)的执行效率。
编译器决定是否内联:即使加了 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
C++ 中的空指针 nullptr
在 C++11 之前,空指针通常用 NULL
或 0
表示。但 NULL
本质上是 #define NULL 0
,属于整数 0
,容易导致歧义。歧义如下:
C++11 引入 nullptr
,专门用于表示空指针,避免了 NULL
的问题。
1. nullptr
的特点
nullptr
是指针类型,不是整数,不会与int
发生混淆。nullptr
可以隐式转换为任何指针类型(int*
、double*
、void*
等)。nullptr
不能隐式转换为int
,可以避免NULL
引发的二义性问题。、nullptr
使用时无需包含头文件- 在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
代替 NULL
和 0
,避免歧义,使代码更安全、可读性更强。
5.引申
语法更新的宗旨是向前兼容,旧的东西依然可用,但新版本有更好的方法替代。例如上方的NULL和nullptr。
特例是python2和python3。