【C++基础】Day 4:关键字之 new、malloc、constexpr、const、extern及static

目录

[一、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. 一句话总结)

三、extern

[1. 简要回答](#1. 简要回答)

[2. 详细解释](#2. 详细解释)

[3. 图表总结](#3. 图表总结)

[4. 代码示例](#4. 代码示例)

[5. 面试常问](#5. 面试常问)

[6. 一句话总结](#6. 一句话总结)

四、static

[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:constexprconst 的关系?

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的八股内容,见下文:

【C++基础】Day 2:关键字之 const 与 static-CSDN博客https://blog.csdn.net/m0_58954356/article/details/155004996?spm=1001.2014.3001.5502

1. 简要回答

static 在 C++ 中有三大作用:

  1. 改变变量的存储期(静态存储期);

  2. 改变链接属性(内部链接,只在当前文件可见);

  3. 在类中表示"类级别"的静态成员(所有对象共享)。


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 = 延长生命周期 + 限制可见范围 + 提供类级共享。

相关推荐
无敌最俊朗@1 小时前
如何把一个压缩的视频文件,解压成一张张原始图片-decode_video.c
c++
fpcc1 小时前
C++编程实践——手动实现std::visit
c++
重启的码农1 小时前
enet源码解析(4)多通道机制 (Channels)
c++·网络协议
重启的码农1 小时前
enet源码解析(3)数据包 (ENetPacket)
c++·网络协议
wefg12 小时前
【C++】智能指针
开发语言·c++·算法
MSTcheng.2 小时前
【C++模板进阶】C++ 模板进阶的拦路虎:模板特化和分离编译,该如何逐个突破?
开发语言·c++·模板
Demon--hx3 小时前
[c++]string的三种遍历方式
开发语言·c++·算法
valan liya3 小时前
C++list
开发语言·数据结构·c++·list
小毅&Nora4 小时前
【后端】【C++】智能指针详解:从裸指针到 RAII 的优雅演进(附 5 个可运行示例)
c++·指针