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

目录

一、new vs malloc

1. 简要回答

2. 详细解释

3. 图表总结

4. 代码示例

5. 面试常问

6. 一句话总结

二、constexpr vs const

1. 简要回答

2. 详细解释

3. 图表总结

4. 代码示例

5. 面试常问

6. 一句话总结

三、extern

1. 简要回答

2. 详细解释

3. 图表总结

4. 代码示例

5. 面试常问

6. 一句话总结

四、static

1. 简要回答

2. 详细解释

3. 图表总结

4. 代码示例

5. 面试常问

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/deletemalloc/free
所属C++ 运算符C/C++ 标准库函数
返回类型目标类型指针(如 A*void*,需强转
申请分配内存无需指定内存块大小显示指出所需内存尺寸
分配内存空间从自由存储区上从堆上动态分配内存
构造/析构✅ 自动调用❌ 不调用
失败行为默认抛 std::bad_alloc返回 NULL
大小指定不必写字节数必须传 sizeof(T)
是否可重载✅ 可重载 operator new/delete❌ 不可
库 / 运算符C++的运算符C++/C语言的标准库函数

4. 代码示例

#include 
#include   // 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;
}

典型输出:

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语句。

constexpr int new() {return 42;}

3. 图表总结

对比项constconstexpr
语义只读编译期常量
初始化要求不强制是常量表达式必须是常量表达式
使用位置非常广泛限制在要求编译期常量的地方
隐含关系const 未必是 constexprconstexpr 一定是 const
主要用途防止修改、表达只读意图编译期优化、模板参数、数组长度等

4. 代码示例

必须使用常量初始化:

constexpr int n = 20;
constexpr int m = n + 1;
static constexpr int MOD = 1000000007;

如果constexpr声明中定义了一个指针,constexpr仅对指针有效,和所指对象无关。

constexpr int* p1 = nullptr; // 编译期常量指针,指针值在编译期已知,语义类似 int* const
const int* p2 = nullptr;     // 底层 const,指向常量的指针:指针可改,指向的值不可改
int* const p3 = nullptr;     // 顶层 const,常量指针:指针本身不可改,指向的值可改
#include 
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):

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. 详细解释

典型用法:

// a.cpp
int g_value = 42;   // 定义(分配存储)
// b.cpp
extern int g_value;  // 声明:该变量在别处定义

extern

  • 告诉编译器:“不要在这里分配存储,链接时去别处找定义”;

  • 不分配内存;

  • 避免多次定义同一个全局变量

  • 出现在头文件里,配合一个 .cpp 中的真正定义;

  • extern "C" 用于关闭 C++ 名字修饰,以 C 的方式导出符号。


3. 图表总结

特性描述
本质声明符,声明外部符号
常用场景多文件共享全局变量 / 函数
staticstatic 限制在当前文件可见,含义相反
是否分配存储❌ 不分配存储(仅声明)

4. 代码示例

// file1.cpp
#include 
using namespace std;
int g_count = 0;  // 真正的定义(分配存储)
void inc() {
    ++g_count;
}
// file2.cpp
#include 
using namespace std;
extern int g_count;  // 声明:这个变量在别处定义
void print() {
    cout << "g_count = " << g_count << endl;
}
// main.cpp
void inc();
void print();
int main() {
    inc();
    inc();
    print();   // 输出 g_count = 2
    return 0;
}

运行输出:

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 局部变量:

void foo() {
    static int count = 0;
    ++count;
}
  • 只初始化一次;

  • 生命周期是整个程序运行期;

  • 作用域仍然只在函数内部。

文件作用域 static 变量 / 函数:

static int g_x = 0;   // 只在当前文件可见
static void helper(); // 函数只在当前文件可见
  • 拥有 内部链接

  • 不会和其他文件的同名符号冲突;

  • 用于隐藏实现细节。

类的静态成员:

class A {
public:
    static int count;
};
int A::count = 0;
  • 所有对象共享一份 count

  • 独立于任何具体对象存在,可通过 A::count 访问;

  • 静态成员函数没有 this 指针,只能访问静态成员。


3. 图表总结

用法位置含义
函数内变量静态存储期 + 函数作用域
全局/命名空间变量内部链接,仅当前编译单元可见
函数(C风格)函数只在当前文件可见
类内成员变量所有对象共享一份数据
类内静态成员函数无 this 指针,只能访问静态成员

4. 代码示例

示例1:静态局部变量

#include 
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;
}

输出:

foo called 1 times
foo called 2 times
foo called 3 times

示例2:类静态成员共享

#include 
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;
}

输出:

1
3
1

5. 面试常问

Q:static 局部变量和普通局部变量的区别?
A:静态局部变量只初始化一次,生命周期是整个程序;普通局部变量随栈帧创建和销毁。

Q:类静态成员一定要在类外定义吗?
A:一般需要(除非是 inline / constexpr 等特殊情况),否则会出现链接错误。

Q:为什么要用 static 限制全局变量可见范围?
A:控制链接范围,避免污染全局命名空间,实现“模块内封装”。


6. 一句话总结

static = 延长生命周期 + 限制可见范围 + 提供类级共享。

posted @ 2025-12-18 18:08  clnchanpin  阅读(10)  评论(0)    收藏  举报