第九章:内存模型和命名空间 | C++ Primer Plus 重点带看

程序的文件类型

1、头文件可以放:

  • 结构体/类声明
  • 函数原型声明
  • 模板定义
  • 内联函数定义
  • const 常量定义(内部链接)
  • 宏定义
  • 静态变量,不会重定义,但每个源文件获得的说独立副本
  • const 值,内部链接,每个源文件使用的独立副本

禁止防止:普通变量定义、非内联函数定义(会导致重定义)。

2、源文件:

  • 存放函数实现和变量定义。
  • 存放调用与结构相关的函数代码。

存储特性

1、自动存储:函数参数和局部变量,存储在栈上,作用域为局部,无链接性,

2、静态存储:全局变量和静态变量,存储在数据段/BSS段,程序启动时分配,结束时释放。(零初始化:未显式初始化的静态变量自动初始化为0(指针为nullptr))

3、动态存储: 常规 new 分配(失败抛出异常 std::bad_alloc 或指定 std::nothrow。区别于定位 new 分配)

char buffer[sizeof(string)];
string* p2 = new(buffer) string("hello");
p2->~string();  // 必须手动析构,不手动析构还是会造成类型内部的资源泄漏。因为编译器不知道 buffer上有个对象需要析构,不会自动调用对象的析构函数。仍旧需要程序员自己手动管理内存。

4、线程存储:thread_local 声明的变量,每个线程拥有独立副本,生命周期与线程相同。

#include <iostream>
#include <thread>
using namespace std;

thread_local int tCount = 0;  // 线程局部存储

void func(int id) {
    tCount = id * 10;
    cout << "线程" << id << ": tCount = " << tCount << endl;
}

int main() {
    thread t1(func, 1);
    thread t2(func, 2);
    thread t3(func, 3);
    
    t1.join();
    t2.join();
    t3.join();
    
    cout << "主线程: tCount = " << tCount << endl;
}
输出:
线程1: tCount = 10
线程2: tCount = 20
线程3: tCount = 30
主线程: tCount = 0

链接性

1、外部链接:可在多个文件间共享,其他文件通过 extern 声明访问。

// a.cpp
int global = 10;              // 外部链接
void func() {}                // 外部链接

// b.cpp
extern int global;            // 声明外部变量
void func();                  // 声明外部函数

int main() {
    cout << global << endl;   // 访问 a.cpp 的 global
    func();                   // 调用 a.cpp 的 func
}

2、内部链接:只能在定义的文件内部使用

// a.cpp
static int fileVar = 10;      // 内部链接(static 全局变量)
static void helper() {}       // 内部链接(static 函数)
const int MAX = 100;          // 内部链接(const 全局变量,默认)
constexpr int VAL = 42;       // 内部链接

namespace {
    int anonVar = 20;         // 内部链接(匿名命名空间)
}

// b.cpp
extern int fileVar;           // 链接错误!找不到

3、无链接:只在作用域内可见

void func() {
    int local = 10;           // 无链接(局部变量)
    static int count = 0;     // 无链接(静态局部变量,但有静态存储)
}

函数的作用域

函数只能定义在全局、命名空间或类中,不能在代码块内定义普通函数。Lambda 表达式和局部类成员函数是例外情况

void outer() {
    auto inner = []() { cout << "lambda" << endl; };  // Lambda
    inner();  // 调用
}

void outer() {
    struct Local {        // 局部类
        void method() {}  // 局部类的成员函数
    };
    Local obj;
    obj.method();
}

函数的默认存储和链接性

默认存储是静态存储,连接性是外部链接。但可以通过用 static 修饰改为内部链接,限制在本文件内使用。(常用于隐藏实现细节的辅助函数)

内联函数

可以在多个翻译单元有相同的定义,实际上改变了链接规则,即使内部变量是静态变量也不会产生多个实例。

// header.h
inline void func() {
    static int count = 0;  // 静态局部变量
    cout << ++count << endl;
}

// a.cpp
#include "header.h"
void testA() { func(); }

// b.cpp
#include "header.h"
void testB() { func(); }

// main.cpp
#include "header.h"
int main() {
    func();    // 输出 1(count: 0 → 1)
    testA();   // 输出 2(count: 1 → 2)
    testB();   // 输出 3(count: 2 → 3)
}

static 关键字作用

位置

存储特性

链接性

说明

全局变量前

静态(不变)

外部→内部

限制在本文件

函数前

静态(不变)

外部→内部

限制在本文件

局部变量前

自动→静态

延长生命周期

类成员变量前

静态

外部

属于类,所有实例共享

类成员函数前

属于类,无 this 指针

// 1. 全局 static 变量
static int fileVar = 10;  // 静态存储、内部链接

// 2. static 函数
static void helper() {}   // 静态存储、内部链接

// 3. 局部 static 变量
void func() {
    static int count = 0; // 静态存储、无链接
}

// 4. 类 static 成员
class A {
    static int sVar;      // 静态存储、外部链接
    static void method(); // 属于类
};

cv 限定符

const 全局变量默认内部链接,可用 extern 改为外部链接。

// a.cpp
extern const int MAX = 100;  // 强制外部链接

// b.cpp
extern const int MAX;  // 声明即可使用
cout << MAX << endl;   // 输出 100

volatile 防止编译器优化,确保每次都从内存读取,但不保证原子性,多线程应使用 std::atomic。

volatile int flag = 0;

// 等待中断或其他线程修改 flag
while (flag == 0) {
    // 没有 volatile,编译器可能优化为:
    // if (flag == 0) while (1);  // 死循环
    // 有 volatile,每次都从内存读取 flag
}

两种变量声明

定义声明(定义):分配存储空间,只能有一次。

引用声明(声明,extern):不分配内存,引用已有变量,可多次。

作用域解析运算符(::)

作用域解析运算符 :: 用于:全局作用域(::var 访问全局变量)、命名空间作用域(NS::member)、类作用域(Class::member)以及嵌套命名空间访问

命名空间

命名空间只能定义在全局或嵌套在其他命名空间中,不能位于代码块内。命名空间内成员默认外部链接,匿名命名空间成员为内部链接(等价于 static),常用于隐藏本文件内的实现细节。

命名空间可以放在头文件,但内部成员遵循与普通变量/函数相同的规则:声明放头文件,定义放源文件。变量定义和非 inline 函数定义在头文件会导致重定义错误。const 变量、inline 函数、static 变量、匿名命名空间成员可在头文件定义。(const、static、匿名命名空间成员产生独立副本;inline 函数被链接器合并为同一实体。

// ✓ 全局位置
namespace GlobalNS {
    int value = 10;
}

// ✓ 嵌套在另一个命名空间中
namespace Outer {
    namespace Inner {
        int value = 20;
    }
}

// ✗ 不能位于局部代码块
void func() {
    // namespace LocalNS { int x = 10; }  // 错误!
}
namespace MyNS {
    int a = 10;           // 外部链接
    static int b = 20;    // 内部链接
    const int c = 30;     // 内部链接
    void func() {}        // 外部链接
    static void helper() {} // 内部链接
}
namespace {
    int value = 10;     // 内部链接,等价于 static int value = 10
    void func() {}      // 内部链接,等价于 static void func()
}

// 其他文件无法访问这些成员
C++ Primer Plus 文章被收录于专栏

C++ Primer Plus 精读|从入门到面试,重点内容全程带看。 本专栏以《C++ Primer Plus》为蓝本,逐章提炼必考知识点、易错点、面试高频考点,跳过冗余示例,直击语法本质与工程实践,帮你高效吃透 C++ 基础,夯实底层开发必备能力。

全部评论
static作用全了
1 回复 分享
发布于 04-01 23:11 河北
头文件规则mark
点赞 回复 分享
发布于 04-02 20:50 河北
欢迎订阅专栏《C++/嵌入式开发-秋招面经》:https://www.nowcoder.com/creation/manager/columnDetail/MKaoll
点赞 回复 分享
发布于 04-01 21:25 河北

相关推荐

机智的豹子有点心碎:UU我还在找工作还没找到,一直在搜简历怎么改,总结了这些: 1.SEO:简历根据每一个岗位定制化:使用这个岗位中所描述的工作的词,它要求什么技能就把自己的技能描述成什么样子,把SEO用在自己身上(把我的简历和个人特质,当成一个热门产品来做 “搜索引擎优化”),让HR能用最低的门槛看到我 2."顺序:把岗位要求的技能跟经历放在简历的最开头、最显眼的位置" 3.包装:简历是一个最终交付说明书,只要最终学习成长做得到就可以,在合适的范围内自我吹捧(我这个人怎么能够在HR的角度被迅速的看懂和看到,减轻HR的工作压力) 4.每点加小标题​:用6~10字概括该段内容,便于面试官快速抓取信息。 5.避免空泛描述​:拒绝“培养了组织能力”等泛泛而谈,替换为具体行动和成果。 6."使用“三段式结构”​​:每段经历按“为什么做-做了什么-结果如何”展开: ​a) 为什么做​:痛点或目标(例如“品牌声量不足”) ​b) 做​了什么:方法论(例如“趋势洞察+竞品对标+人群细分”) ​c) 结果如何​:量化成果或影响(例如“推动客户投放20万预算”)" 7.量化成果​:用数字体现工作成效(如“整理500+份资料”“撰写2万字报告”)。 这些有的是我想去的岗的,如果对你有用的话按需修改就好~加油,早日上岸!
点赞 评论 收藏
分享
评论
5
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务