前言
本文首先介绍了静态变量、全局变量和局部变量的区别,包括它们在内存中的分布情况。接着,我们探讨了指针和引用的不同用法及其背后的原理。最后,我们详细解释了C++程序在运行时的内存布局,包括栈、堆和全局/静态存储区的角色和相互作用。
一、静态变量和全局变量、局部变量的区别、在内存上是怎么分布的?
1.1.静态变量
在C++中,静态变量(static
变量)是一种特殊的变量存储类别,它在程序的整个运行期间只被初始化一次,并且其生命周期与程序的运行期相同。静态变量可以用于类中或全局/命名空间作用域中,它们的行为和用途各有不同。
1.1.1.全局/命名空间作用域中的静态变量
在全局或命名空间作用域中声明的静态变量具有以下特点:
- 持久性:静态变量的值在程序的整个运行期间保持,直到程序结束。
- 初始化时机:静态变量在第一次访问前初始化,且只初始化一次。
- 局部性:尽管声明在全局或命名空间作用域中,但静态变量的作用域限定在其声明的文件内,即它们具有文件内局部性(除非通过
extern
关键字在其他文件中声明)。
// file1.cpp
static int global_static_var = 10;
void function1() {
global_static_var++;
std::cout << "In function1: " << global_static_var << std::endl;
}
// file2.cpp
extern int global_static_var;
void function2() {
global_static_var++;
std::cout << "In function2: " << global_static_var << std::endl;
}
1.1.2.类中的静态变量
在类中声明的静态变量具有以下特点:
- 类共有:静态变量不是某个特定对象的成员,而是整个类的成员。这意味着所有对象共享同一个静态变量。
- 初始化时机:静态变量必须在类的外部进行初始化,且只初始化一次。
- 访问控制:静态变量可以通过类名直接访问,不依赖于任何对象。
//创建类对象访问
class MyClass {
public:
static int staticVar;
void display() {
std::cout << "Static var: " << staticVar << std::endl;
}
};
// 在类外初始化静态变量
int MyClass::staticVar = 0;
int main() {
MyClass obj1, obj2;
obj1.display(); // 显示静态变量的值
obj2.display(); // 显示静态变量的值
MyClass::staticVar = 5;
obj1.display(); // 显示更新后的静态变量的值
obj2.display(); // 显示更新后的静态变量的值
return 0;
}
//使用类直接访问
#include <iostream>
class MyClass {
public:
static int staticVar;
void display() {
std::cout << "Static var via instance: " << staticVar << std::endl;
}
static void displayStaticVar() {
std::cout << "Displaying static var: " << staticVar << std::endl;
}
};
int MyClass::staticVar = 0; // 静态成员初始化
int main() {
MyClass::displayStaticVar(); // 直接通过类名访问静态成员函数
MyClass obj;
obj.display(); // 通过对象访问静态成员
MyClass::staticVar = 100;
std::cout << "Static var after update: " << MyClass::staticVar << std::endl;
return 0;
}
1.1.3.注意事项
- 线程安全:静态变量的初始化在多线程环境中需要特别注意,以避免初始化时的竞态条件。
- 内存管理:静态变量占用的内存在程序的整个运行期间都存在,因此需要谨慎管理以避免内存泄露。
1.2.全局变量
在C++中,全局变量是在所有函数外部定义的变量,它们具有全局作用域,这意味着它们可以在整个程序的任何位置被访问(除非被局部作用域中的同名变量遮蔽)。全局变量的生命周期从定义它们的语句执行时开始,直到程序结束时结束。
1.2.1.特点
- 全局作用域:全局变量在整个程序中都是可见的,直到程序结束。
- 持久性:全局变量在程序运行期间一直存在。
- 初始化:全局变量需要在定义时或在程序开始执行前(在main函数之前)进行初始化。
1.2.2.声明与定义
- 定义:全局变量的定义是指分配存储空间和初始化变量。
- 声明:全局变量的声明可以使得在其他文件中可以访问这个全局变量,声明通常使用
extern
关键字。
// global_variable.cpp
#include <iostream>
// 定义并初始化全局变量
int globalVar = 10;
// 函数声明
void showGlobalVar();
int main() {
std::cout << "In main: " << globalVar << std::endl;
showGlobalVar();
return 0;
}
// 函数定义
void showGlobalVar() {
std::cout << "In showGlobalVar: " << globalVar << std::endl;
}
//使用 extern 声明全局变量
//如果你在一个文件中定义了全局变量,你可以在其他文件中使用 extern 关键字来声明它,
//以便在其他件中访问这个全局变量。
// global_variable.h
#ifndef GLOBAL_VARIABLE_H
#define GLOBAL_VARIABLE_H
extern int globalVar; // 声明全局变量
#endif
// file1.cpp
#include "global_variable.h"
#include <iostream>
void modifyGlobalVar() {
globalVar = 20; // 修改全局变量
std::cout << "In modifyGlobalVar: " << globalVar << std::endl;
}
// global_variable.cpp
#include "global_variable.h"
int globalVar = 10; // 定义并初始化全局变量
int main() {
std::cout << "In main: " << globalVar << std::endl;
modifyGlobalVar();
return 0;
}
1.2.3.注意事项
- 命名冲突:全局变量可能在不同的文件中被重复定义,导致命名冲突。
- 维护困难:全局变量使得程序的状态变得难以追踪,增加了程序的维护难度。
- 线程安全:在多线程环境中,全局变量可能会引起线程安全问题,需要特别注意同步和互斥。
尽管全局变量在某些情况下很方便,但它们通常不推荐在大型或复杂的项目中使用,因为它们可能导致代码难以理解和维护。在可能的情况下,建议使用局部变量、参数传递或封装在类中的成员变量。
1.3.局部变量
在C++中,局部变量是在函数、代码块或任何其他作用域内部声明的变量。局部变量只在其声明的作用域内可见,一旦退出该作用域,局部变量的生命周期就会结束,其占用的内存也会被释放。
1.3.1特点
- 局部作用域:局部变量只在声明它的函数或代码块内部可见。
- 自动存储期:局部变量存储在栈(stack)上,其生命周期仅限于声明它的函数调用或代码块的执行。
- 未初始化的初始值:如果局部变量在声明时没有初始化,它将拥有一个不确定的值。
- 生命周期:局部变量的生命周期从声明它的那一刻开始,到包含它的函数或代码块执行结束时终止。
1.3.2.注意事项
- 初始化:最好在声明局部变量时立即初始化,以避免使用不确定的值。
- 内存管理:局部变量的内存管理是自动的,不需要程序员手动释放。
- 递增递减运算符:在使用递增(
++
)或递减(--
)运算符时,应注意它们是前缀形式还是后缀形式,这会影响返回值。 - 函数参数:函数参数也被视为局部变量,它们的作用域限制在函数内部。
局部变量是C++编程中的基础概念,正确理解和使用局部变量对于编写清晰、有效的代码非常重要。
1.4.三者的区别
1.4.1. 全局变量(Global Variables)
- 作用域:全局变量在程序的任何位置都可以访问(除非被局部作用域中的同名变量遮蔽)。
- 生命周期:全局变量的生命周期贯穿整个程序,从定义开始直到程序结束。
- 存储位置:全局变量通常存储在程序的静态存储区(全局/数据段)。
- 初始化:全局变量在程序开始运行前就已经分配了空间和初始值(在没有显式初始化的情况下,可能是不确定的值)。
1.4.2. 局部变量(Local Variables)
- 作用域:局部变量仅在其定义的函数或代码块内部可见。
- 生命周期:局部变量的生命周期仅限于其定义的函数或代码块的执行期间。当函数调用结束或代码块执行完毕时,局部变量的生命周期结束,占用的内存被释放。
- 存储位置:局部变量通常存储在程序的栈(调用栈)上。
- 初始化:局部变量不会自动初始化,必须显式赋值,否则它们将包含垃圾值。
1.4.3. 静态变量(Static Variables)
- 作用域:静态变量的作用域通常与全局变量相似,但它们只能在定义它们的文件内访问(除非使用
extern
关键字在其他文件中声明)。 - 生命周期:静态变量的生命周期与全局变量相同,从定义开始直到程序结束。
- 存储位置:静态变量存储在静态存储区,与全局变量相同。
- 初始化:静态变量在程序开始运行前分配空间和初始值(在没有显式初始化的情况下,局部静态变量会被初始化为零)。
1.4.4.区别总结
- 作用域:全局变量作用域最广,局部变量作用域最窄,静态变量作用域介于两者之间。
- 生命周期:全局变量和静态变量的生命周期都是整个程序期间,而局部变量的生命周期仅限于其定义的函数或代码块的执行期间。
- 存储位置:全局变量和静态变量存储在静态存储区,局部变量存储在栈上。
- 初始化:全局变量和静态变量在程序开始运行前分配空间和初始值,局部变量需要显式初始化。
注意:文件作用域中的静态变量不能使用 extern
来改变其链接性,因为它们默认具有内部链接性,并且它们的作用域被限制在定义它们的文件内。类中的静态成员变量或函数可以使用 extern
关键词将其具有外部链接性。extern
关键字主要用于具有外部链接性的全局变量或函数,以便它们可以在声明它们的文件之外的其他文件中访问。
理解这些变量的区别对于编写有效和可维护的C++程序非常重要。正确选择变量的作用域和生命周期可以帮助管理内存使用,避免作用域冲突,并确保程序的正确性。
‘二、指针和引用的区别
在C++中,指针和引用是两种不同的机制,用于访问和操作变量。尽管它们在某些方面有相似之处,但它们之间存在一些关键的区别。
2.1. 定义和使用
-
指针:
- 指针是一个变量,它存储了另一个变量的内存地址。
- 定义指针时需指定指针指向的变量的类型。
- 可以通过
&
运算符获取变量的地址,并使用*
运算符来解引用指针,访问它所指向的变量的值。
int var = 10;
int* ptr = &var; // ptr 指向 var
int value = *ptr; // value 为 10,通过 ptr 访问 var 的值
-
引用:
- 引用是另一个变量的别名,它为变量提供了另一个名字。
- 引用在定义时必须被初始化,并且不能重新绑定到另一个变量。
- 引用的使用不需要特殊的运算符,直接使用变量名即可。
int var = 10;
int& ref = var; // ref 是 var 的引用
int value = ref; // value 为 10,直接通过 ref 访问 var 的值
2.2. 内存占用
-
指针:
- 指针变量本身在内存中占用空间(通常是一个地址的大小),它存储了它所指向的变量的地址。
-
引用:
- 引用不占用额外的内存,它们不存储地址,仅仅是原变量的另一个名字。
2.3. 初始和重新绑定
-
指针:
- 指针可以在定义时不初始化,可以在任何时候重新指向另一个变量。
-
引用:
- 引用必须在定义时被初始化,并且不能重新绑定到另一个变量。
2.4. 与 null 的关系
-
指针:
- 指针可以被设置为
nullptr
(C++11及以后版本),表示它不指向任何对象。
- 指针可以被设置为
-
引用:
- 引用不能被设置为“空”,它们必须始终引用一个有效的对象。
2.5. 数组和函数参数
-
指针:
- 指针可以用于数组和动态内存分配,它们在C语言风格字符串和数组参数传递中非常有用。
-
引用:
- 引用通常用于函数参数和返回值,以避免复制大型对象,提高效率。
三、C++内存分区
在C++中,内存分区主要分为以下几个部分:
3.1. 栈(Stack)
- 栈是一种后进先出(LIFO)的数据结构,用于存储局部变量和函数调用的上下文。
- 局部变量(包括函数参数和在函数内部定义的变量)通常存储在栈上。
- 栈内存分配和回收速度快,但容量相对较小。
3.2. 堆(Heap)
- 堆是用于动态内存分配的内存区域。
- 使用
new
和delete
操作符分配和释放堆内存。 - 堆内存的分配和回收速度比栈慢,但容量更大,且大小不固定。
- 程序员负责管理堆内存,不正确的管理可能导致内存泄漏或其他内存问题。
3.3. 全局/静态存储区(Static Storage)
- 全局变量和静态变量存储在此区域。
- 包括程序的常量、全局变量、静态变量和
const
修饰的变量。 - 这些变量在程序的整个运行期间都存在,直到程序结束时才被销毁。
3.4. 常量存储区(Constant Storage)
- 存储常量值,包括常量表达式和常量对象。
- 通常位于只读内存段中。
3.5. 代码区(Text Segment)
- 存储程序的执行代码,包括函数体和操作指令。
- 代码区通常是只读的,以防止程序运行时被意外修改。
3.6. 数据段(Data Segment)
- 存储程序中的初始化了的全局变量和静态变量。
- 数据段通常在程序启动时被初始化。
3.7. BSS段(Block Started by Symbol)
- 存储未初始化的全局变量和静态变量。
- BSS段在程序启动时被自动置零。
3.8. 内存分配和回收
- 栈内存:由编译器自动管理,进入函数时分配,离开函数时回收。
- 堆内存:由程序员手动管理,使用
new
进行分配,使用delete
进行回收。 - 全局/静态存储区:由操作系统在程序启动时分配,在程序结束时回收。
总结
本文介绍了C++中变量的存储类别、指针与引用的区别以及程序运行时的内存布局。这些基础知识对于C++程序员来说非常重要,因为它们影响着程序的性能、内存使用和数据安全。正确理解和使用这些概念可以提高代码质量,避免常见的编程错误。