目录
[一、new vs malloc](#一、new vs malloc)
[1. 简要回答](#1. 简要回答)
[2. 详细解释](#2. 详细解释)
[3. 图表总结](#3. 图表总结)
[4. 代码示例](#4. 代码示例)
[5. 面试常问](#5. 面试常问)
[6. 一句话总结](#6. 一句话总结)
[二、constexpr vs const](#二、constexpr vs const)
[1. 简要回答](#1. 简要回答)
[2. 详细解释](#2. 详细解释)
[3. 图表总结](#3. 图表总结)
[4. 代码示例](#4. 代码示例)
[5. 面试常问](#5. 面试常问)
[6. 一句话总结](#6. 一句话总结)
[1. 简要回答](#1. 简要回答)
[2. 详细解释](#2. 详细解释)
[3. 图表总结](#3. 图表总结)
[4. 代码示例](#4. 代码示例)
[5. 面试常问](#5. 面试常问)
[6. 一句话总结](#6. 一句话总结)
[1. 简要回答](#1. 简要回答)
[2. 详细解释](#2. 详细解释)
[3. 图表总结](#3. 图表总结)
[4. 代码示例](#4. 代码示例)
[5. 面试常问](#5. 面试常问)
[6. 一句话总结](#6. 一句话总结)
一、new vs malloc
1. 简要回答
-
new/delete:C++ 运算符,类型安全 ,会调用构造/析构函数,可以重载,用于对象级内存管理。 -
malloc/free:C 语言库函数,只管"字节块",不调用构造/析构 ,返回void*,需要强转。
2. 详细解释
malloc(size):
-
只知道要分配
size字节的原始内存; -
返回类型为
void*,需要手动强制类型转换; -
不做任何初始化,不调用构造函数,也不调用析构;
-
分配失败时返回
NULL; -
无法被重载,只能按 C 语义使用。
new Type(args...):
-
知道要创建的是
Type对象,无需手写sizeof(Type); -
做两件事:
1)分配足够的内存;
2)调用对应构造函数完成初始化;
-
释放时
delete做两件事:1)调用析构函数;
2)释放内存;
-
分配失败时默认抛出
std::bad_alloc异常(可通过nothrow控制); -
可通过重载
operator new/operator delete实现自定义内存池,是 C++ 高级用法。
3. 图表总结
| 对比项 | new/delete |
malloc/free |
|---|---|---|
| 所属 | C++ 运算符 | C/C++ 标准库函数 |
| 返回类型 | 目标类型指针(如 A*) |
void*,需强转 |
| 申请分配内存 | 无需指定内存块大小 | 显示指出所需内存尺寸 |
| 分配内存空间 | 从自由存储区上 | 从堆上动态分配内存 |
| 构造/析构 | ✅ 自动调用 | ❌ 不调用 |
| 失败行为 | 默认抛 std::bad_alloc |
返回 NULL |
| 大小指定 | 不必写字节数 | 必须传 sizeof(T) |
| 是否可重载 | ✅ 可重载 operator new/delete |
❌ 不可 |
| 库 / 运算符 | C++的运算符 | C++/C语言的标准库函数 |
4. 代码示例
cpp
#include <iostream>
#include <cstdlib> // malloc/free
using namespace std;
class A {
public:
A() { cout << "A constructor\n"; }
~A() { cout << "A destructor\n"; }
void hello() { cout << "hello from A\n"; }
};
int main() {
// 1) 使用 new/delete
A* p1 = new A; // 分配内存 + 调用构造函数
p1->hello();
delete p1; // 调用析构函数 + 释放内存
cout << "------------\n";
// 2) 使用 malloc/free
A* p2 = (A*)malloc(sizeof(A)); // 仅分配字节,不调用构造函数
// p2->hello(); // ❌ 未构造就调用成员函数,可能 UB
// 手动调用构造:placement new
new (p2) A; // 在已分配内存上"构造"对象
p2->hello();
// 手动调用析构
p2->~A();
free(p2);
return 0;
}
典型输出:
cpp
A constructor
hello from A
A destructor
------------
A constructor
hello from A
A destructor
5. 面试常问
Q:为什么 C++ 中不建议混用 new/delete 与 malloc/free?
A:new/delete 会调用构造/析构,malloc/free 不会。混用会导致对象没构造就使用,或构造过的对象未正常析构,带来资源泄露或未定义行为。
Q:谁更"类型安全"?
A:new 更安全,它返回具体类型指针,且通过构造函数保证对象初始化正确。
Q:如何自定义内存池?
A:重载类或全局的 operator new / operator delete,在内部使用自定义的内存管理策略(如内存池)。
6. 一句话总结
new 是 C++ 的对象级内存分配(带构造/析构、类型安全),malloc 只是 C 风格的字节分配,两者语义完全不同,不能混用更不能互相替代。
二、constexpr vs const
1. 简要回答
-
const:表示"只读",不保证初始化发生在编译期,也可以定义运行期常量。 -
constexpr:表示"编译期常量",只能定义编译期常量。
2. 详细解释
const 变量:
-
表示变量在语义上"不允许修改";
-
初始值可以是编译期常量,也可以是运行期值(例如函数返回);
-
因此:
const不一定是常量表达式。
constexpr 变量:
-
声明时要求初始化表达式必须是常量表达式;
-
如果初始化不是常量表达式 → 编译期报错;
-
常用于:
-
数组长度
-
模板参数
-
switch的 case 标签 -
需要在编译期就确定的场景
-
constexpr 函数:
-
传入编译期常量实参时,可在编译期求值;
-
传入运行期值时,就当普通函数执行;
-
constexpr函数是指能用于常量表达式的函数。
函数的返回类型和所有形参类型都是字面值类型,函数体有且只有一条return语句。
cpp
constexpr int new() {return 42;}
3. 图表总结
| 对比项 | const |
constexpr |
|---|---|---|
| 语义 | 只读 | 编译期常量 |
| 初始化要求 | 不强制是常量表达式 | 必须是常量表达式 |
| 使用位置 | 非常广泛 | 限制在要求编译期常量的地方 |
| 隐含关系 | const 未必是 constexpr |
constexpr 一定是 const |
| 主要用途 | 防止修改、表达只读意图 | 编译期优化、模板参数、数组长度等 |
4. 代码示例
必须使用常量初始化:
cpp
constexpr int n = 20;
constexpr int m = n + 1;
static constexpr int MOD = 1000000007;
如果constexpr声明中定义了一个指针,constexpr仅对指针有效,和所指对象无关。
cpp
constexpr int* p1 = nullptr; // 编译期常量指针,指针值在编译期已知,语义类似 int* const
const int* p2 = nullptr; // 底层 const,指向常量的指针:指针可改,指向的值不可改
int* const p3 = nullptr; // 顶层 const,常量指针:指针本身不可改,指向的值可改
cpp
#include <iostream>
using namespace std;
// constexpr 函数(C++14 之后可多条语句)
constexpr int factorial(int n) {
return (n <= 1) ? 1 : (n * factorial(n - 1));
}
int get_runtime_value() {
int x;
cin >> x;
return x;
}
int main() {
const int a = 10; // 可能是常量表达式,也可能不是(取决于初始化方式)
constexpr int b = 20; // 一定是常量表达式
// 1)用作数组长度、模板参数
int arr1[a]; // 一般也当常量表达式使用
int arr2[b]; // 必然是常量表达式
// 2)constexpr 函数在编译期计算
constexpr int f5 = factorial(5); // 编译期算出 120
// 3)const 但非 constexpr 示例
int input = get_runtime_value(); // 运行期输入
const int c = input; // 运行期常量
// constexpr int d = input; // ❌ 编译错误:input 不是常量表达式
cout << "f5 = " << f5 << endl;
cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
return 0;
}
输出示例(假设输入 7):
cpp
f5 = 120
a = 10, b = 20, c = 7
5. 面试常问
Q:constexpr 和 const 的关系?
A:所有 constexpr 都是 const,但所有 const 并不都是 constexpr。
Q:为什么需要 constexpr?
A:复杂表达式是否是"编译期常量"人肉很难判断,constexpr 交给编译器做验证;同时还能帮助编译器进行更强的编译期优化。
Q:constexpr 函数一定在编译期执行吗?
A:不一定,取决于实参是否是编译期常量。编译期常量实参与上下文允许 → 在编译期算,否则按普通函数运行期算。
6. 一句话总结
const 是"只读",constexpr 是"编译期已知";两者都能定义常量,但 constexpr 是现代 C++ 中进行编译期计算与优化的核心工具。
三、extern
1. 简要回答
extern 表示"这个变量/函数在别的翻译单元里定义,这里只是声明一下 ",用于多文件间共享全局变量或函数。
声明外部变量【在函数或者文件外部定义的全局变量】
2. 详细解释
典型用法:
cpp
// a.cpp
int g_value = 42; // 定义(分配存储)
cpp
// b.cpp
extern int g_value; // 声明:该变量在别处定义
extern:
-
告诉编译器:"不要在这里分配存储,链接时去别处找定义";
-
不分配内存;
-
避免多次定义同一个全局变量;
-
常出现在头文件里,配合一个 .cpp 中的真正定义;
-
extern "C"用于关闭 C++ 名字修饰,以 C 的方式导出符号。
3. 图表总结
| 特性 | 描述 |
|---|---|
| 本质 | 声明符,声明外部符号 |
| 常用场景 | 多文件共享全局变量 / 函数 |
与 static |
static 限制在当前文件可见,含义相反 |
| 是否分配存储 | ❌ 不分配存储(仅声明) |
4. 代码示例
cpp
// file1.cpp
#include <iostream>
using namespace std;
int g_count = 0; // 真正的定义(分配存储)
void inc() {
++g_count;
}
cpp
// file2.cpp
#include <iostream>
using namespace std;
extern int g_count; // 声明:这个变量在别处定义
void print() {
cout << "g_count = " << g_count << endl;
}
cpp
// main.cpp
void inc();
void print();
int main() {
inc();
inc();
print(); // 输出 g_count = 2
return 0;
}
运行输出:
cpp
g_count = 2
5. 面试常问
Q:extern int a; 和 int a; 的区别?
A:前者是声明,不分配存储;后者是定义,分配存储。
Q:头文件里放定义还是声明?
A:通常放 extern 声明,真正的定义放在某一个 .cpp 中。
6. 一句话总结
extern 是"外部符号声明",负责跨文件引用,不负责定义与分配存储。
四、static
static 在之前的文章已经详细的复习的static的八股内容,见下文:
1. 简要回答
static 在 C++ 中有三大作用:
-
改变变量的存储期(静态存储期);
-
改变链接属性(内部链接,只在当前文件可见);
-
在类中表示"类级别"的静态成员(所有对象共享)。
2. 详细解释
函数内 static 局部变量:
cpp
void foo() {
static int count = 0;
++count;
}
-
只初始化一次;
-
生命周期是整个程序运行期;
-
作用域仍然只在函数内部。
文件作用域 static 变量 / 函数:
cpp
static int g_x = 0; // 只在当前文件可见
static void helper(); // 函数只在当前文件可见
-
拥有 内部链接;
-
不会和其他文件的同名符号冲突;
-
用于隐藏实现细节。
类的静态成员:
cpp
class A {
public:
static int count;
};
int A::count = 0;
-
所有对象共享一份
count; -
独立于任何具体对象存在,可通过
A::count访问; -
静态成员函数没有
this指针,只能访问静态成员。
3. 图表总结
| 用法位置 | 含义 |
|---|---|
| 函数内变量 | 静态存储期 + 函数作用域 |
| 全局/命名空间变量 | 内部链接,仅当前编译单元可见 |
| 函数(C风格) | 函数只在当前文件可见 |
| 类内成员变量 | 所有对象共享一份数据 |
| 类内静态成员函数 | 无 this 指针,只能访问静态成员 |
4. 代码示例
示例1:静态局部变量
cpp
#include <iostream>
using namespace std;
void foo() {
static int count = 0; // 只初始化一次,生命周期整个程序
++count;
cout << "foo called " << count << " times\n";
}
int main() {
foo(); // 1
foo(); // 2
foo(); // 3
return 0;
}
输出:
cpp
foo called 1 times
foo called 2 times
foo called 3 times
示例2:类静态成员共享
cpp
#include <iostream>
using namespace std;
class A {
public:
A() { ++count; }
~A() { --count; }
static int getCount() { return count; }
private:
static int count; // 所有对象共享一份
};
int A::count = 0;
int main() {
A a1;
cout << A::getCount() << endl; // 1
{
A a2, a3;
cout << A::getCount() << endl; // 3
}
cout << A::getCount() << endl; // 1
return 0;
}
输出:
cpp
1
3
1
5. 面试常问
Q:static 局部变量和普通局部变量的区别?
A:静态局部变量只初始化一次,生命周期是整个程序;普通局部变量随栈帧创建和销毁。
Q:类静态成员一定要在类外定义吗?
A:一般需要(除非是 inline / constexpr 等特殊情况),否则会出现链接错误。
Q:为什么要用 static 限制全局变量可见范围?
A:控制链接范围,避免污染全局命名空间,实现"模块内封装"。
6. 一句话总结
static = 延长生命周期 + 限制可见范围 + 提供类级共享。