当前位置: 首页 > news >正文

十、名字控制(Name Control)

九、名字控制(Name Control)

引言

​ 你可以控制名称的创建可见性 ,控制这些名称的 存储 位置,以及名称的连接性

  • 创建(Creation):指名称(变量、函数等)什么时候创建,以及如何被创建
  • 可见性(Visibility):指在程序的哪些地方可以使用这个名字
  • 存储(Storage):指这个名字的数据存放在哪里,以及存活多久
  • 连接性(Linkage):指同一个名字是否能在多个文件之间共享,即文字的跨文件访问能力。例如:
    • extern 声明的变量可以在不同文件之间链接。
    • static 声明的变量在文件内部有内部链接(internal linkage),外部文件访问不到

10.1 静态元素(Static elements)

概述

所谓的静态对象(静态元素)是指: 在程序运行期间,存储在静态存储区(static storage area)
生命周期贯穿整个程序的对象。

常见的静态对象有:

类型示例说明
全局变量int x = 5;存在于整个程序
静态局部变量static int y = 3;函数内部static,记住上次值
类的静态成员变量static int count;属于整个类
namespace作用域的静态对象同样是静态存储
静态类对象static MyClass obj;构造一次,用到最后
  • 关键字 static 有两个基本含义:

    • 对象被创建在一个特殊的静态数据区(static data area) ,而不是每次函数调用时创建的**栈(stack)**上,这就是静态存储(static storage) 的概念。
    • static 控制名字的可见性(visibility),使得这个名字无法被外界的翻译器单元或者类看到。这也涉及了连接性(linkage) 的概念,即决定链接器(linker)能看到哪些名字。
    #include <iostream>
    using namespace std;
    void f()
    {static int n = 1;// static int n;int x = 1;//int x;cout << "n = " << n++;cout << ",";cout << "x = " << x++ << endl;
    }
    void main(){f();f();
    }
    

    输出:

    n = 1,x = 1
    n = 2,x = 1
    

    一个翻译单元就是
    一个源文件 .cpp 加上它包含的所有头文件 .h 展开后组成的整体
    编译器是以“翻译单元”为单位来编译程序的。

    如果一个内建类型static 变量没有显示初始化器,编译器会保证这个变量被初始化为0。而普通内建类型则不会。

    比如这里如果采取后面注释的写法

    #include <iostream>
    using namespace std;
    void f()
    {static int n;int x;cout << "n = " << n++;cout << ",";cout << "x = " << x++ << endl;
    }
    void main(){f();f();
    }
    

    编译器可能报错也可能继续运行,我的编译器则报错

    image-20250428103834263

    这是因为编译器不会给int x;自动初始化,而是会给static int n;自动初始化,所以报错是使用了未初始化的局部变量x

    如果正常运行,那么n都会初始化为0,而x会是一个垃圾值。

函数内部的静态类对象

  • 如果没有提供初始化器,静态的**内建类型(built-in-types)**会被自动化为零。
  • 但是静态的用户自定义类型(user-defined types),必须通过构造函数调用来初始化。
内建类型(built-int types)

就是C++语言本身就定义好的基础数据类型,不需要用户自己额外设计。

常见的内建类型有:

  • int (整数)
  • float (浮点数)
  • double (双精度浮点数)
  • char (字符)
  • bool (布尔值)
  • shortlonglong long等各种整数扩展
  • unsigned int (无符号整数)
  • 指针类型(比如int*char*
用户自定义(users-defined types)

就是程序员自己定义的复杂数据类型,通常用classstructunionenum等关键字定义。

示例
//C10:StaticObjectsInFunctions.cpp
#include <iostream>
using namespace std;
class X {int i;
public:X(int ii = 0) :i(ii) {}//Default~X() { cout << "i = " << i << endl; }
};
void f() {static X x1(47);static X x2;
}
void main() {f();f();X x3(10);
}

输出:

i = 10 //x3
i = 0  // x2
i = 47 // x1

不加static


#include <iostream>
using namespace std;
class X {int i;
public:X(int ii = 0) :i(ii) {}//Default~X() { cout << "i = " << i << endl; }
};
void f() {static X x1(47);X x2;
}
void main() {f();f();X x3(10);
}

输出:

i = 0 //x2
i = 0 //x2
i = 10 //x3
i = 47 //x1

可见static 可以将变量的生命周期变为整个程序

静态对象的析构函数

  • 全局对象的构造函数总是进入main() 函数之前调用。
  • 函数内部静态对象,只有这些函数被调用时才执行。就是说,静态对象虽然定义在函数里,但它不会随着程序启动就创建,只有在函数第一次被调用的时候才创建,而且只创建一次
    • 第一次调用这个函数:对象才被真正构造(调用构造函数)
    • 后续再调用这个函数:不会重新构造,复用第一次构造好的静态对象
  • main() 退出时,所有已经构造的对象的析构函数会按照构造的相反顺序被依次调用。
示例

#include <iostream>
using namespace std;
class X {int i;
public:X(int ii = 0) :i(ii) {}//Default~X() { cout << "i = " << i << endl; }
};
X x0(5);
void f(){static X x1(47);static X x2;
}
void main(){f();f();X x3(10);
}

输出:

i = 10
i = 0
i = 47
i = 5

控制链接性(Controling linkage)

  • 可见性(Visibility):外部链接、内部链接
    • 外部链接(External linkage):文件作用域中的名字,对程序中所有翻译单元( .cpp 文件)都是可见的。全局变量和普通函数具有外部链接性。
    • 内部链接(Internal linkage,又叫文件静态):名字只在它所属的翻译单元 中可见。staticconstinline 声明的名字默认具有内部链接性。
  • 不同存储类型(Storage type)的内存分配:静态数据区(static data area)、栈(stack)、堆(heap)
    • 静态数据区:用于存储全局变量、静态变量
    • 栈:用于局部变量
    • 堆:通过 newdelete 动态分配的内存

10.2 命名空间(Namespaces)

概述

  • 尽管名字可以嵌套在类内部,但全局函数、全局变量和类的名字依然属于一个统一的全局命名空间

  • static 关键字允许你对这种情况进行一定程度的控制,它可以让变量函数具有内部链接性

    但是,在大型项目中,如果无法有效管理全局命名空间,仍然会引发问题。

  • 我们可以使用C++中的namespace 特性,将全局命名空间划为更易管理的小块区域

10.2.1 创建命名空间

//C10:MyLib.cpp
namespace MyLib{//members
}
void main(){}

class的区别:

  • namespace只能出现在全局作用域,或者嵌套在另一个命名空间内部。

  • 在闭合大括号}后,不需要加分号;

  • 命名空间的名字(比如MyLib)可以在多个头文件中使用

  • 命名空间的名字可以起别名,比如:

    namespace MyLib = Lib;
    
  • 不能创建命名空间的实例

10.2.2 使用命名空间

  • 作用域解析运算符 是 ::
  • using 指令:引入命名空间中的所有名字。
  • using 声明:一次性引入命名空间的一个名字

示例:作用域解析运算符 ::

//ScopeResolution.cpp
namespace X
{class Y{static int i;public:void f();};class Z;void func();
}int X::Y::i = 9;
void X::Y::f(){}
class X::Z{int u,v,w;
public:Z(int i);int g();
};
X::Z::Z(int i){u = v = w = i;}
int X::Z::g(){return u = v = w = 0;}
void X::func(){X::Z a(1);a.g();
}
void main(){}

示例:using 指令

#include <iostream>
using namespace std;namespace calculator{double Add(double x,double y){return x+y;}void Print(double x){cout << x << endl;}
}using namespace calculator;//Using 指令
void main(){double a,b;cin >> a >> b;Print(Add(a,b));
}

如果命名空间内函数和其他函数重名

#include <iostream>
using namespace std;namespace calculator
{double Add(double x, double y) { return x + y; }void Print(double x) { cout << "Result is " << x << endl; }
}void Print(double x)
{cout << "This is an external function." << endl;
}using namespace calculator;void main()
{double a, b;cin >> a >> b;Print(Add(a, b));  // —— 这里调用 Print 时发生了歧义(ambiguous calling)
}
  • 正确的调用方式:

    • calculator::Print(Add(a,b));
    • ::Print(Add(a,b))

    分表指明是命名空间calculator的Print还是全局函数Print

  • 重要提醒:永远不要在头文件(header file)里使用using namespace 指令!因为这会引起严重的名字冲突问题。

    原因:头文件(.h 文件)是可以被很多不同源文件(.cpp 文件)包含 (#include )的

    一旦你在头文件里写了:

    using namespace std;
    

    或者

    using namespace calculator;
    

    那么所有包含了这个头文件的.cpp 文件,都会自动引入这个命名空间!这就可能导致严重的名字冲突问题!

示例:using 声明

  • 当一个名字在命名空间外经常使用时,如果每次都写上命名空间会很麻烦。这时可以使用"using"来简化
#include <iostream>
using namespace std;
namespace calculator
{double Add(double x, double y) { return x + y; }void Print(double x) { cout << "Result is " << x << endl; }
}void main()
{using calculator::Add;  // 使用声明,只引入 calculator 命名空间里的 Add 函数double a, b;cin >> a >> b;calculator::Print(Add(a, b));  // 使用 Add,不用再写 calculator::,但 Print 还是要写完整名字
}
  • 这里只引入了Add ,而没有把整个calculator 命名空间引入
  • 所以用Add(a,b) 可以直接写,而 Print 仍然需要些calculator::Print(……)

10.3 静态成员(Static members)

概述

  • 有时候我们需要一个单独的存储空间,供一个类的所有对象共同使用
  • 虽然全局变量也可以做到,但全局变量不安全,因为:
    • 任何人都能修改它。
    • 在大型项目中,名字容易和其它全局冲突。
  • 更好的做法是:
    • 使用类内部的静态数据成员
    • 它们像全局变量一样共享一份数据,但作用域限制在类内部,受类的保护。
  • 静态成员可以是public、private或protected,并且属于类本身,而不是某个特定对象。

10.3.1 静态数据成员

  • 静态成员 是在类中声明,但会被类的所有对象共享。
  • 静态数据成员必须进行初始化,并且需要遵循以下规则:
    • 在类体外部初始化
    • 初始化时不要再次写static 关键字
    • 初始化时需要类名限定
class Mycalss{static int obj;//静态数据成员
};//类外初始化
int Mycalss::obj = 8; //初始化

10.3.2 静态成员函数

  • 使用static 关键字声明。
  • 只能直接访问静态数据成员
  • 没有隐含的this 指针
  • 可以是inline 函数
#include <iostream>
using namespace std;
class Myclass {
public:static void show();
private:static int obj;int x;
};
int Myclass::obj = 8;
void Myclass::show() {cout << obj << endl;//cout << x;//error
}
void main() {Myclass m;m.show();Myclass::show();
}

10.4 静态初始化顺序

  • 在同一个翻译单元(specific translation unit)内,静态对象的初始化顺序保证按照对象定义出现的顺序进行。
  • 而且,销毁程序保证是初始化顺序的逆序。
  • 但是,跨翻译单元的静态对象初始化顺序没有任何保证。
  • 解决方案:把静态对象的定义放在同一个源文件中。

比如说

// A.cpp
#include <iostream>
extern int b;          // 声明B.cpp的b
int a = b + 1;         // a的初始化依赖b
// B.cpp
#include <iostream>
extern int a;          // 声明A.cpp的a
int b = a + 1;         // b的初始化依赖a

问题:

  • 编译器不能保证A.cpp和B.cpp谁先初始化!
  • 有可能b初始化时,a还没准备好,结果就成了未定义行为!

所以,正确示范:把ab 都写到一个.cpp文件里

// AB.cpp
#include <iostream>int b = 1;        // 先初始化b
int a = b + 1;    // 再初始化aint getA() { return a; }
int getB() { return b; }

相关文章:

  • 数据结构*栈
  • Spring MVC 基础 - 从零构建企业级Web应用
  • IIC 通信协议
  • 从传统制造到智能工厂:MES如何重塑电子制造业?
  • Airbnb更智能的搜索:嵌入式检索(Embedding-Based Retrieval,EBR)工作原理解析
  • 使用vue3 脚手架创建项目
  • springboot项目之websocket的坑:spring整合websocket后进行单元测试后报错的解决方案
  • 网易大神安卓版游戏社交互动体验及功能评测
  • C++23/26 静态反射机制深度解析:编译时元编程的新纪元
  • 开源 Agent 框架对比:LangChain vs AutoGen vs CrewAI
  • 这是一款好用的PDF工具!
  • 《Q2门式起重机司机》考试大纲的专项要求有哪些?
  • Hadoop伪分布式模式搭建全攻略:从环境配置到实战测试
  • 使用多线程快速向Excel中快速插入一万条数据案例
  • 使用POI和EasyExcel使用导入
  • 湖北理元理律师事务所:债务管理领域的平台化创新探索
  • 学习记录:DAY19
  • 【虚幻5蓝图Editor Utility Widget:创建高效模型材质自动匹配和资产管理工具,从3DMax到Unreal和Unity引擎_系列第二篇】
  • 【数据可视化-41】15年NVDA, AAPL, MSFT, GOOGL AMZ股票数据集可视化分析
  • 【数据可视化-42】杂货库存数据集可视化分析
  • 中美经贸高层会谈将在午餐后继续
  • 梅花奖在上海|朱洁静:穿越了人生暴风雨,舞台是最好良药
  • 马云再次现身阿里打卡创业公寓“湖畔小屋”,鼓励员工坚持创业精神
  • 上海第四批土拍成交额97亿元:杨浦宅地成交楼板单价半年涨近7000元
  • 外交部:习近平主席同普京总统达成许多新的重要共识
  • 习近平同瑞典国王卡尔十六世·古斯塔夫就中瑞建交75周年互致贺电