# 类型转换、变量与生命周期

> [!info] 学习导航
> 返回：[[C++ 学习指南]]
> 前置知识：[[计算机系/C++/01 C++ 概览、基础类型与函数|C++ 概览、基础类型与函数]]

**类型转换是将一个数据类型的值转换为另一种数据类型的值。**

**C++ 中有四种类型转换：静态转换、动态转换、常量转换和重新解释转换。**

## 静态转换（Static Cast）

**静态转换是将一种数据类型的值强制转换为另一种数据类型的值。**

**静态转换通常用于比较类型相似的对象之间的转换，例如将 int 类型转换为 float 类型。**

**静态转换不进行任何运行时类型检查，因此可能会导致运行时错误。**

**实例**

```cpp
int i = 10; float f = static_cast<float>(i); // 静态将int类型转换为float类型
```

## 动态转换（Dynamic Cast）

**动态转换（dynamic_cast）是 C++ 中用于在继承层次结构中进行向下转换（downcasting）的一种机制。**

**动态转换通常用于将一个基类指针或引用转换为派生类指针或引用。**

**动态转换在运行时进行类型检查。如果转换失败，对于指针类型会返回 nullptr，对于引用类型则会抛出 std::bad_cast 异常。**

**语法：**

**dynamic_cast<目标类型>(表达式)**

- **目标类型：必须是指针或引用类型。**
- **表达式：需要转换的基类指针或引用。**

| **特性**           | **指针类型**                 | **引用类型**                 |
| ------------------ | ---------------------------- | ---------------------------- |
| **转换失败返回值** | **返回 nullptr**             | **抛出 std::bad_cast 异常**  |
| **适用场景**       | **向下转换、运行时类型检查** | **向下转换、运行时类型检查** |
| **性能开销**       | **较高**                     | **较高**                     |
| **基类要求**       | **必须具有虚函数**           | **必须具有虚函数**           |


1. dynamic_cast 的运行时类型检查（RTTI）

dynamic_cast 需要在运行时检查对象的实际类型，以确保转换的安全性。这种能力依赖于 RTTI（Run-Time Type Information），而 RTTI 的实现需要以下条件：

虚函数表（vtable）：
 当一个类有至少一个虚函数时，编译器会为该类生成一个虚函数表（vtable），其中不仅包含虚函数的地址，还会存储 类型信息（用于 RTTI）。

对象类型标识：
 对象的实际类型信息（如 Derived 或 Base）会存储在 vtable 中，供 dynamic_cast 在运行时查询。

虚析构函数的作用：

确保 delete ptr_base 能正确调用 Derived 的析构函数（多态销毁）。

为 Base 类生成 vtable，使 dynamic_cast 能通过 RTTI 检查 ptr_base 实际指向的对象是否为 Derived。

## 运行时类型识别（typeinfo）

C++ 标准库 \<typeinfo>

在 C++ 中，\<typeinfo> 是标准库的一部分，它提供了运行时类型识别（RTTI，Run-Time Type Identification）功能。RTTI 允许程序在运行时确定对象的类型。这是通过使用 typeid 运算符和 type_info 类实现的。


type_info 类是一个抽象基类，它提供了关于类型信息的接口。每个类型都有一个与之关联的 type_info 对象，可以通过 typeid 运算符访问。


语法

\<typeinfo> 相关的主要语法：


typeid 运算符：用于获取对象的类型信息。

type_info 类：包含类型信息的类。

类型信息类 type_info

typeinfo 头文件提供了对类型信息的运行时支持。它主要包含两个核心组件：std::type_info 类和 typeid 运算符。typeinfo 允许程序在运行时获取对象的类型信息，这在多态和类型安全的代码中非常有用。以下是对 typeinfo 的详细介绍：


std::type_info 类

std::type_info 类是 typeinfo 头文件的核心类，用于描述一个类型。它提供了多个成员函数用于查询类型的信息。常用成员函数如下：


const char\* name() const noexcept; 返回一个指向类型名称的 C 字符串指针。注意，这个名称不一定是人类可读的类型名，其格式由编译器实现决定。


bool before(const std::type_info& rhs) const noexcept; 按照某种顺序比较两个 type_info 对象，返回当前对象是否在 rhs 之前。


bool operator==(const std::type_info& rhs) const noexcept; 比较两个 type_info 对象是否表示相同的类型。


bool operator!=(const std::type_info& rhs) const noexcept; 比较两个 type_info 对象是否表示不同的类型。


typeid 运算符

typeid 运算符用于在运行时获取类型信息。typeid 可以作用于对象（带有多态行为的指针或引用）或类型（无需实例化对象）。


typeid(object)：返回一个 std::type_info 对象，表示 object 的动态类型。如果 object 是一个多态类型（即包含虚函数），则 typeid 会返回该对象的实际类型。

typeid(T)：返回一个 std::type_info 对象，表示类型 T。

实例

下面是一个使用 \<typeinfo> 的简单示例：


实例

```cpp
#include <iostream>
#include <typeinfo>
class Base {
public:
  virtual void show() { std::cout << "Base show" << std::endl; }
};
class Derived : public Base {
public:
  void show() override { std::cout << "Derived show" << std::endl; }
};
int main() {
  Base* basePtr = new Derived();
  Base* basePtr2 = new Base();
  std::cout << "Type of basePtr: " << typeid(*basePtr).name() << std::endl;
  std::cout << "Type of basePtr2: " << typeid(*basePtr2).name() << std::endl;
  if (typeid(*basePtr) == typeid(Derived)) {
    std::cout << "basePtr is of type Derived" << std::endl;
  } else {
    std::cout << "basePtr is not of type Derived" << std::endl;
  }
  delete basePtr;
  delete basePtr2;
  return 0;
}
```

输出结果:


Type of basePtr: 9Derived // 注意：typeid的name()返回的类型名称可能因编译器而异

Type of basePtr2: 8Base   // 同上

basePtr is of type Derived

注意事项

RTTI 功能依赖于编译器的实现，因此 typeid 运算符返回的类型名称可能因编译器而异。

使用 RTTI 可能会对程序性能产生一定影响，因为它需要在运行时进行类型检查。

RTTI 只适用于多态类型，即具有虚函数的类。

\<typeinfo> 提供了一种在运行时识别对象类型的方法，这对于实现多态和类型安全非常有用。然而，开发者应该谨慎使用 RTTI，以避免不必要的性能开销和潜在的类型错误。

## 常量转换（Const Cast）

**常量转换用于将 const 类型的对象转换为非 const 类型的对象。**

**常量转换只能用于转换掉 const 属性，不能改变对象的类型。**

**实例**

```cpp
const int i = 10; int& r = const_cast<int&>(i); // 常量转换，将const int转换为int
```

## 重新解释转换（Reinterpret Cast）

**重新解释转换将一个数据类型的值重新解释为另一个数据类型的值，通常用于在不同的数据类型之间进行转换。**

**重新解释转换不进行任何类型检查，因此可能会导致未定义的行为。**

**实例**

```cpp
int i = 10; float f = reinterpret_cast<float&>(i); // 重新解释将int类型转换为float类型
```

## 不带初始化的定义

**带有静态存储持续时间的变量会被隐式初始化为 NULL（所有字节的值都是 0），其他所有变量的初始值是未定义的。**

**当局部变量被定义时，系统不会对其初始化，您必须自行对其初始化。定义全局变量时，系统会自动初始化为下列值：**

| **数据类型** | **初始化默认值** |
| ------------ | ---------------- |
| **int**      | **0**            |
| **char**     | **'\0'**         |
| **float**    | **0**            |
| **double**   | **0**            |
| **pointer**  | **NULL**         |


## 命名空间


-  命名空间可以嵌套
-  命名空间可以包含类和函数
-  **using namespace会引入命名空间中的所有符号**
-  **命名空间能解决名称冲突**


## C++ 中的变量声明

**变量声明向编译器保证变量以给定的类型和名称存在，这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义，在程序连接时编译器需要实际的变量声明。**

**当您使用多个文件且只在其中一个文件中定义变量时（定义变量的文件在程序连接时是可用的），变量声明就显得非常有用。您可以使用 extern 关键字在任何地方声明一个变量。虽然您可以在 C++ 程序中多次声明一个变量，但变量只能在某个文件、函数或代码块中被定义一次。**

**同样的，在函数声明时，提供一个函数名，而函数的实际定义则可以在任何地方进行。**

## C++ 中的左值（Lvalues）和右值（Rvalues）

**C++ 中有两种类型的表达式：**

- **左值（lvalue）：指向内存位置的表达式被称为左值（lvalue）表达式。左值可以出现在赋值号的左边或右边。**
- **右值（rvalue）：术语右值（rvalue）指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式，也就是说，右值可以出现在赋值号的右边，但不能出现在赋值号的左边。**

**变量是左值，因此可以出现在赋值号的左边。数值型的字面值是右值，因此不能被赋值，不能出现在赋值号的左边。**

## C++ 变量作用域

**一般来说有三个地方可以定义变量：**

- **在函数或一个代码块内部声明的变量，称为局部变量。**
- **在函数参数的定义中声明的变量，称为形式参数。**
- **在所有函数外部声明的变量，称为全局变量。**

**作用域是程序的一个区域，变量的作用域可以分为以下几种：**

- **局部作用域：在函数内部声明的变量具有局部作用域，它们只能在函数内部访问。局部变量在函数每次被调用时被创建，在函数执行完后被销毁。**
- **全局作用域：在所有函数和代码块之外声明的变量具有全局作用域，它们可以被程序中的任何函数访问。全局变量在程序开始时被创建，在程序结束时被销毁。**
- **块作用域：在代码块内部声明的变量具有块作用域，它们只能在代码块内部访问。块作用域变量在代码块每次被执行时被创建，在代码块执行完后被销毁。**
- **类作用域：在类内部声明的变量具有类作用域，它们可以被类的所有成员函数访问。类作用域变量的生命周期与类的生命周期相同。**

**注意：如果在内部作用域中声明的变量与外部作用域中的变量同名，则内部作用域中的变量将覆盖外部作用域中的变量。**

**块作用域指的是在代码块内部声明的变量：**

```cpp
#include <iostream>
 int main() {
   int a = 10;
   {
     int a = 20; // 块作用域变量
     std::cout << "块变量: " << a << std::endl;
   }
   std::cout << "外部变量: " << a << std::endl;
   return 0;
 }
```

**以上实例中，内部的代码块中声明了一个名为 a 的变量，它与外部作用域中的变量 a 同名。内部作用域中的变量 a 将覆盖外部作用域中的变量 a，在内部作用域中访问 a 时输出的是20，而在外部作用域中访问 a 时输出的是 10。**

**类作用域**

**类作用域指的是在类内部声明的变量**

**注意一定要是static。**

**Python中不同。**

```cpp
#include <iostream>
 class MyClass {
 public:
   static int class_var; // 类作用域变量
 };
 int MyClass::class_var = 30;
 int main() {
   std::cout << "类变量: " << MyClass::class_var << std::endl;
   return 0;
 }
```

**局部变量**

**在函数或一个代码块内部声明的变量，称为局部变量。它们只能被函数内部或者代码块内部的语句使用。下面的实例使用了局部变量：**


**实例**

```cpp
#include <iostream>
using namespace std;
int main ()
{
// 局部变量声明
 int a, b;
 int c;
// 实际初始化
 a = 10;
 b = 20;
 c = a + b;
 cout << c;
 return 0;
}
```

**全局变量**

**在所有函数外部定义的变量（通常是在程序的头部），称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。**


**全局变量可以被任何函数访问。也就是说，全局变量一旦声明，在整个程序中都是可用的。下面的实例使用了全局变量和局部变量：**


**实例**

```cpp
#include <iostream>
using namespace std;
// 全局变量声明
int g;
int main ()
{
// 局部变量声明
 int a, b;
// 实际初始化
 a = 10;
 b = 20;
 g = a + b;
 cout << g;
 return 0;
}
```

**在程序中，局部变量和全局变量的名称可以相同，但是在函数内，局部变量的值会覆盖全局变量的值。下面是一个实例：**


**实例**

```cpp
#include <iostream>
using namespace std;
// 全局变量声明
int g = 20;
int main ()
{
// 局部变量声明
 int g = 10;
 cout << g;
 return 0;
}
```

**当上面的代码被编译和执行时，它会产生下列结果：**


**10**

**当局部变量被定义时，系统不会对其初始化，您必须自行对其初始化。定义全局变量时，系统会自动初始化为下列值：**


**数据类型    初始化默认值**

**int 0**

**char '\0'**

**float   0**

**double 0**

**pointer NULL**


**块作用域指的是在代码块内部声明的变量：**


**实例**

```cpp
#include <iostream>
int main() {
  int a = 10;
  {
    int a = 20; // 块作用域变量
    std::cout << "块变量: " << a << std::endl;
  }
  std::cout << "外部变量: " << a << std::endl;
  return 0;
}
```

**以上实例中，内部的代码块中声明了一个名为 a 的变量，它与外部作用域中的变量 a 同名。内部作用域中的变量 a 将覆盖外部作用域中的变量 a，在内部作用域中访问 a 时输出的是20，而在外部作用域中访问 a 时输出的是 10。**


**当上面的代码被编译和执行时，它会产生下列结果：**


**块变量: 20**

**外部变量: 10**

**类作用域**

**类作用域指的是在类内部声明的变量：**


**实例**

```cpp
#include <iostream>
class MyClass {
public:
  static int class_var; // 类作用域变量
};
int MyClass::class_var = 30;
int main() {
  std::cout << "类变量: " << MyClass::class_var << std::endl;
  return 0;
}
```

**以上实例中，MyClass 类中声明了一个名为 class_var 的类作用域变量。可以使用类名和作用域解析运算符 :: 来访问这个变量。在 main() 函数中访问 class_var 时输出的是 30。**


**类变量: 30**


**但是在函数内的局部变量与全局变量是两个独立的变量，互不影响。**


**下述代码中，全局变量定义了一个int g=99，局部变量定义了一个int g=10，由于这两个g所在的作用域不同，所以各自独立。**


```cpp
#include <iostream>
using namespace std;
// 全局变量声明
int g = 99;
// 函数声明
int func();
int main()
{
// 局部变量声明
  int g = 10;
// cout << g;
  int kk = func();
  cout << kk;
  return 0;
}
// 函数定义
int func()
{
  return g;
}
```

**当变量间出现重名的情况下，作用域小的屏蔽作用域大的**

**全局变量从定义处开始至程序结束起作用，即全局变量存在有效作用域**

**若要想让 main 函数也使用全局变量 a，可以用 extern 对全局变量进行声明，就可以合法使用了。**

```cpp
#include<iostream>
using namespace std;
int main()
{
   extern int a;
   cout<<"a= "<<a<<endl; // 合法，输出10
   return 0;
}
int a=10; // 全局变量从此处定义
```

## 生命周期（生存期）和作用域

1. 核心区别

特性    作用域（Scope） 生存期（Lifetime）

定义    标识符（变量名、函数名等）在代码中的可见范围   对象占用的内存资源从创建到释放的时间周期

决定因素    代码的静态结构（如 {} 块、类定义、名字空间等）  对象的存储位置（栈、堆、静态区等）和创建方式

关注点  名称的可访问性  资源的有效性

示例    变量名只能在函数内部访问   对象的内存是否已被释放

2. 联系与互动

作用域是生存期的必要条件


一个对象的名称必须在其作用域内才能被访问，但对象可能在其名称失效后仍然存在（如动态分配的对象）。


反例：指针指向的动态对象（无名称作用域），但仍有生存期。


生存期可能超出作用域


静态局部变量：作用域是函数内，但生存期到程序结束。


```cpp
void foo() {
  static int x = 0; // 作用域：foo()内；生存期：程序结束才销毁
}
```

动态分配对象：通过指针访问，无名称作用域，但生存期由 new/delete 控制。


```cpp
int* p = new int(42); // 无名称作用域，但生存期持续到 delete
```

作用域结束时可能触发生存期结束


对于栈对象（局部变量、形参等），离开作用域时会自动销毁。


```cpp
{
  int x = 10; // 作用域开始
} // 作用域结束，x 的生存期终止
```

3. 典型场景对比

场景1：局部变量

```cpp
void func() {
  int x = 10; // 作用域：func()内；生存期：func()执行期间
} // x 的作用域和生存期同时结束
```

作用域：仅在 func() 内可访问 x。


生存期：func() 执行时创建，返回时销毁。


场景2：静态局部变量

```cpp
void counter() {
  static int count = 0; // 作用域：counter()内；生存期：程序结束
  count++;
}
```

作用域：count 仅在 counter() 内可见。


生存期：程序启动时初始化，结束时销毁（超出作用域仍存在）。


场景3：动态分配对象

```cpp
int* createInt() {
  int* p = new int(42); // 作用域：p 在函数内；生存期：持续到 delete
  return p;
}
```

作用域：指针 p 的名称在 createInt() 外不可见。


生存期：\*p 的内存生存期持续到手动 delete。


场景4：全局变量

```cpp
int global = 100; // 作用域：文件内；生存期：程序启动到结束
```

作用域：从定义处到文件末尾可见。


生存期：程序运行时始终存在。


4. 关键结论

作用域是编译期概念，决定名称的可见性；生存期是运行时概念，决定资源的有效性。


作用域不直接控制生存期，但：


对于栈对象，作用域结束通常意味着生存期结束。


对于静态/全局对象，生存期可能远超作用域。


动态对象的生存期完全由程序员控制，与作用域无关。


5. 常见误区澄清

误区    正解

"作用域结束等于对象销毁" 仅适用于栈对象（局部变量、形参），不适用于静态/动态对象。

"名称不可见等于对象不存在"  对象可能仍存在（如通过指针访问动态对象）。

"生存期由作用域完全决定" 生存期由存储位置（栈、堆、静态区）决定，作用域仅影响名称访问。

程序运行中，变量或对象从开始建立到最后消失为止的周期称为变量或对象的生存期，

C++用标识符命名对象、函数和类型、类成员、类模板等，标识符作用域指通过该名字可以直接访问使用的范围。

C++名字作用域从小到大依次有：复合语句作用域、函数原型作用域、函数作用域、类作用域、名字空间作用域和文件作用域

对象的生存周期代表程序运行时，对象从建立到消失的时间周期。作用域和生存期是两个不同概念。

声明在函数原型里的形式参数名字，只在该函数声明内有效，作用域是该函数声明，函数声明内的形参名字也因此可以省略

声明或定义在复合语句内的名字，作用域只是改复合语句。作为对象名字时，相应对象称为局部对象；程序在开始执行该局部对象所在符合语句时，在运行栈上建立局部对象，程序执行完局部对象所在的复合语句离开时撤销局部对象。

声明或定义在函数体内的名字，作用域是该函数体，作为对象名字时，相应对象也是局部对象，程序执行完函数体离开时撤销局部对象。

函数定义时，形参作用域就是该函数定义，形参对象也是局部对象，程序在开始执行该局部对象所在函数时，在运行栈上建立局部对象，程序执行完函数离开时撤销局部对象

定义在类外和函数外，但是在名字空间里的标识符，具有该名字空间作用域，定义在名字空间里的对象在程序开始执行时，在全局对象和静态对象上专用存储区建立外部对象，程序执行结束时撤销外部对象。

定义在类外和函数外，不在名字空间内的对象时外部对象，作用域限定在定义开始到文件结束，在程序开始执行时在全局对象和静态对象专用存储区上建立，程序执行结束时撤销

带extern修饰的外部对象，代表本文件或其它文件定义的同名外部对象，作用域限定在该文件内

带static修饰的外部静态对象的生存期域普通外部对象的生存期相同，作用域限定在该文件内，其它文件不可引用该外部对象

带static 修饰的局部静态对象的作用域域普通局部对象的作用域相同，都限定在局部范围内，兵器为在程序第一次执行到该静态对象定义语句时没在全局对象和静态对象专用存储区上建立，在程序执行结束时撤销

定义不同作用域中的标识符名字相同时，根据最小作用域原则确定标识符代表的对象、函数、类型等，动态分配生成的uix是匿名对象，通过指针间接访问，new执行动态分配在堆空间生成，在delete删除操作时撤销

（删除只是指针所指向的内存空间，此时指针变为野指针）


1. 局部对象（复合语句/函数作用域）

```cpp
void foo() {
  int x = 10; // 局部对象，作用域：foo函数内
  {
    int y = 20; // 局部对象，作用域：当前复合语句内
    cout << y; // 输出 20
  }
// cout << y;  // 错误！y 已失效
  cout << x; // 输出 10
}
```

生存期：x 在 foo() 调用时创建，返回时销毁；y 在复合语句结束时销毁。


作用域：x 在整个函数内可见，y 仅在 {} 内可见。


2. 函数形参（函数原型/函数作用域）

```cpp
void bar(int param); // 函数声明，param 作用域仅在此行
void bar(int param) { // param 作用域：整个函数体
  cout << param; // 输出传入的值
}
```

生存期：param 在函数调用时创建，返回时销毁。


作用域：仅在函数体内有效。


3. 类作用域

```cpp
class MyClass {
public:
  static int s_var; // 类静态成员，作用域：类内
  int m_var; // 成员变量，作用域：类内
  void print() {
    cout << m_var; // 访问成员变量
  }
};
int MyClass::s_var = 0; // 静态成员定义，作用域：全局
```

生存期：


m_var：随对象创建/销毁。


s_var：程序启动时初始化，结束时销毁。


作用域：


成员变量通过对象访问（如 obj.m_var）。


静态成员通过类名访问（如 MyClass::s_var）。


4. 名字空间作用域

```cpp
namespace NS {
  int ns_var = 30; // 名字空间变量，作用域：NS 内
}
cout << NS::ns_var; // 输出 30
```

生存期：程序启动时创建，结束时销毁。


作用域：通过 NS:: 访问。


5. 全局对象（文件作用域）

```cpp
int global_var = 40; // 全局对象，作用域：当前文件
void func() {
  cout << global_var; // 输出 40
}
```

生存期：程序启动时创建，结束时销毁。


作用域：从定义处到文件末尾。


6. extern 和 static 修饰符

```cpp
// file1.cpp
int shared_var = 50; // 全局对象，可被其他文件访问
// file2.cpp
extern int shared_var; // 声明共享变量
void use_shared() {
  cout << shared_var; // 输出 50
}
static int file_local = 60; // 文件静态对象，仅当前文件可见
```

extern：跨文件共享全局变量。


static：


修饰全局变量时，限制作用域为当前文件。


修饰局部变量时，延长生存期至程序结束（但作用域不变）。


7. 局部静态对象

```cpp
void counter() {
  static int count = 0; // 局部静态对象
  count++;
  cout << count; // 每次调用递增（1, 2, 3...）
}
```

生存期：首次调用时初始化，程序结束时销毁。


作用域：仍仅在函数内可见。


8. 动态分配对象（堆内存）

```cpp
int* p = new int(70); // 动态分配，生存期由程序员控制
cout << *p; // 输出 70
delete p; // 手动释放
// cout << *p;     // 危险！p 现在是野指针
```

生存期：new 创建，delete 销毁。


作用域：通过指针访问，无名字作用域。


关键总结

类型    作用域规则  生存期规则

局部对象    复合语句/函数内 进入作用域时创建，离开时销毁

函数形参    函数体内    函数调用时创建，返回时销毁

类成员  类内（通过对象或类名访问）  随对象创建/销毁（静态成员全局生存）

名字空间变量   名字空间内  程序启动到结束

全局对象    定义处到文件末尾   程序启动到结束

static 局部变量  函数/复合语句内 首次调用时创建，程序结束时销毁

动态分配对象   通过指针访问（无名字作用域）   new 创建，delete 销毁

通过合理选择作用域和生存期，可以优化内存管理并避免悬空指针等问题！

在 C++ 中，函数原型（Function Prototype）中声明的形参名字的作用域是一个特殊规则。这句话的意思是：


函数原型中形参的作用域

仅在该函数声明内有效

函数原型中写的形参名字（如果有的话），只在原型这一行代码中可见，其他地方无法使用。


它只是声明参数类型和顺序，名字本身无实际意义（甚至可以省略）。


示例解析


```cpp
// 函数原型（声明）
void foo(int x, double y); // x 和 y 的作用域仅限这一行
// 其他地方尝试使用 x 或 y 会报错
// cout << x; // 错误！x 未定义
// 函数定义（实现）
void foo(int a, double b) { // 形参名可以和原型不同（a代替x，b代替y）
  cout << a << ", " << b; // 这里只能用 a 和 b
}
```

原型中的 x 和 y 仅用于说明参数类型，实际定义时可以用其他名字（如 a 和 b）。


为什么可以省略名字？

因为函数原型只需要告诉编译器参数类型和顺序，名字是可选的：


```cpp
void bar(int, char); // 合法！省略形参名
```

与函数定义中形参的区别

场景    形参名字作用域  是否必须写名字  示例

函数原型    仅限原型所在行  可省略  void func(int x);
函数定义    整个函数体  必须写  void func(int x) { ... }
核心概念

函数原型的作用：提前声明函数签名（返回类型、参数类型和顺序），便于编译器检查调用是否合法。


形参名字的局限性：原型中的名字无实际绑定，仅作为文档提示（类似注释）。

1. 定义在名字空间内的对象

语法形式

```cpp
namespace MyNamespace {
  int x = 10; // 名字空间作用域的变量
  void foo() { /*...*/ }
}
```

核心特性

特性    说明

作用域  限定在名字空间 MyNamespace 内，需通过 MyNamespace::x 访问

存储期  静态存储期（程序启动时初始化，结束时销毁）

链接性  默认是外部链接（其他文件可通过 extern 声明访问）

访问控制    需显式指定名字空间（除非用 using 指令）

示例

```cpp
// File1.cpp
namespace MyNamespace {
  int x = 10; // 名字空间作用域变量
}
// File2.cpp
extern int MyNamespace::x; // 声明其他文件中定义的变量
```

2. 定义在全局作用域（无名名字空间）的对象

语法形式

```cpp
int y = 20; // 全局作用域变量（隐式属于无名名字空间）
```

核心特性

特性    说明

作用域  从定义处到文件末尾（文件作用域）

存储期  静态存储期（程序启动时初始化，结束时销毁）

链接性  默认是外部链接（其他文件可通过 extern 声明访问）

访问控制    直接通过变量名访问（或在其他文件中用 extern 声明）

示例

```cpp
// File1.cpp
int y = 20; // 全局作用域变量
// File2.cpp
extern int y; // 声明其他文件中定义的全局变量
```

关键区别总结

特性    名字空间内的对象   全局作用域的对象

作用域  限定在名字空间内（需 Namespace:: 访问） 限定在当前文件（从定义处到文件末尾）

链接性  默认外部链接（可跨文件）   默认外部链接（可跨文件）

访问控制    需显式指定名字空间  直接访问或 extern 声明

典型用途    避免命名冲突，模块化代码   简单的全局变量（需谨慎使用）


**存储在静态数据区的变量会在程序刚开始运行时就完成初始化，也是唯一的一次初始化。共有两种变量存储在静态存储区：全局变量和 static 变量，只不过和全局变量比起来，static 可以控制变量的可见范围，说到底 static 还是用来隐藏的。虽然这种用法不常见。**


**PS：如果作为 static 局部变量在函数内定义，它的生存期为整个源程序，但是其作用域仍与自动变量相同，只能在定义该变量的函数内使用该变量。退出该函数后， 尽管该变量还继续存在，但不能使用它。**


```cpp
#include <iostream>
using namespace std;
int count = 1; // 全局变量
int fun()
{
  static int count = 10; // 在第一次进入这个函数的时候，变量 count 被初始化为 10！并接着自减 1，以后每次进入该函数，count 的值是上一次函数运行之后的值
  return count--; // 就不会被再次初始化了，仅进行自减 1 的操作；在 static 发明前，要达到同样的功能，则只能使用全局变量
}
// int count = 1; //全局变量
int main()
{
   cout<<"global "<<"local staic"<<endl;
   for(; count <= 10; ++count)
    cout<<count<<"    "<<fun()<<endl;
   return 0;
}
```

**并且，由此可见全局变量和局部静态变量 count 的作用域是不同的**

**全局变量和和局部变量同名时，可通过域名在函数中引用到全局变量，不加域名解析则引用局部变量**


```cpp
#include<iostream>
using namespace std;
int a = 10;
int main()
{
  int a = 20;
  cout << ::a << endl; // 10
  cout << a << endl; // 20
  return 0;
}
```

**C++ 全局变量、局部变量、静态全局变量、静态局部变量的区别**


**C++ 变量根据定义的位置的不同的生命周期，具有不同的作用域，作用域可分为 6 种：全局作用域，局部作用域，语句作用域，类作用域，命名空间作用域和文件作用域。**


**从作用域看：**


**全局变量具有全局作用域。全局变量只需在一个源文件中定义，就可以作用于所有的源文件。当然，其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。**


**静态局部变量具有局部作用域，它只被初始化一次，自从第一次被初始化直到程序运行结束都一直存在，它和全局变量的区别在于全局变量对所有的函数都是可见的，而静态局部变量只对定义自己的函数体始终可见。**


**局部变量也只有局部作用域，它是自动对象（auto），它在程序运行期间不是一直存在，而是只在函数执行期间存在，函数的一次调用执行结束后，变量被撤销，其所占用的内存也被收回。**


**静态全局变量也具有全局作用域，它与全局变量的区别在于如果程序包含多个文件的话，它作用于定义它的文件里，不能作用到其它文件里，即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量，它们也是不同的变量。**


**从分配内存空间看：**


**全局变量，静态局部变量，静态全局变量都在静态存储区分配空间，而局部变量在栈里分配空间。**


**全局变量本身就是静态存储方式， 静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序，当一个源程序由多个源文件组成时，非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域， 即只在定义该变量的源文件内有效，在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内，只能为该源文件内的函数公用，因此可以避免在其它源文件中引起错误。**


 **1)、静态变量会被放在程序的静态数据存储区（数据段）(全局可见)中，这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。**

 **2)、变量用static告知编译器，自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。**

**从以上分析可以看出， 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域，限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。**


**Tips:**


**A、若全局变量仅在单个C文件中访问，则可以将这个变量修改为静态全局变量，以降低模块间的耦合度；**

**B、若全局变量仅由单个函数访问，则可以将这个变量改为该函数的静态局部变量，以降低模块间的耦合度；**

**C、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时，需要考虑重入问题，因为他们都放在静态数据存储区，全局可见；**

**D、如果我们需要一个可重入的函数，那么，我们一定要避免函数中使用static变量(这样的函数被称为：带“内部存储器”功能的的函数)**

**E、函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时，则必须是static的局部变量的地址作为返回值，若为auto类型，则返回为错指针。**

**-----------------------------------------------------------------------------------------------------------**


**static 全局变量:改变作用范围，不改变存储位置**


**static 局部变量：改变存储位置，不改变作用范围**


**静态函数 ：在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同，它只能在声明它的文件当中可见，不能被其它文件使用。**


**如果在一个源文件中定义的函数，只能被本文件中的函数调用，而不能被同一程序其它文件中的函数调用，这种函数也称为内部函数。定义一个内部函数，只需在函数类型前再加一个“static”关键字即可。**

**全局变量和局部变量作用域不同，static 关键字可限定引用范围：**

```cpp
#include <stdio.h>
int s32Test; // 定义全局变量，系统默认初始化0
static void Fun(); // 声明只限定在被当前文件调用的函数
static void Fun()
{
  int s32Test = 1; // 局部变量,如果不初始化，会是一个随机数值
// 与全局变量名相同，会屏蔽全局变量调用,s32Test = 1
  printf("This is Fun()，s32Test = %d!", s32Test);
}
int main()
{
  Fun();
  return 0;
}
```

**MyClass::class_var 中的 :: 是作用域解析运算符，用于访问类的静态成员。而 std::cout 中的 :: 用于指定 cout 是 std 命名空间中的对象。这两者虽然在使用语法上看起来相似，但它们代表的含义和用途完全不同。**

```cpp
#include <iostream>
using namespace std;
int* leakyFunc() {
  int x = 10; // 作用域：函数内；生存期：函数结束
  int* p = new int(20); // 作用域：p在函数内；生存期：持续到delete
  return p; // 返回指针，x 已销毁，*p 仍有效
}
int main() {
  int* ptr = leakyFunc();
  cout << *ptr; // 输出 20（生存期未结束，但 ptr 无名称作用域）
  delete ptr; // 必须手动释放
  return 0;
}
```

## 变量定义的一般形式

**类型 变量；**

**类型 变量=初始表达式**

**类型 变量 （初始化表达式）**

**类型 变量 {初始化表达式} //C++11**

**类型 变量列表**

**定义多个具有相同类型的变量，变量列表里不同变量用逗号分隔**


**Extern int globalint，外部变量声明语句，不可初始化extern存储类别声明的语句，无定义性作用。**

**为避免二义性，定义变量如果没有初始化，不可带小括号**

**栈和函数调用的实现**

**每次遇到函数调用，就在运行栈上发呢配空间**

**1.**   **函数执行完毕后返回的地址**

**2.**   **形参和函数返回值类型**

**3.**   **函数体内的局部对象**

**4.**

## 相关内容
- [[计算机系/C++/11 多态与虚函数|多态与虚函数]]

---

## 学习导航
- 上一篇：[[计算机系/C++/01 C++ 概览、基础类型与函数|C++ 概览、基础类型与函数]]
- 返回目录：[[C++ 学习指南]]
- 下一篇：[[计算机系/C++/03 引用、指针与动态内存|引用、指针与动态内存]]
