代码随想录八股训练营第二十三天| C++

文章目录

前言

一、静态变量和全局变量、局部变量的区别、在内存上是怎么分布的?

1.1.静态变量

1.1.1.全局/命名空间作用域中的静态变量

1.1.2.类中的静态变量

1.1.3.注意事项

1.2.全局变量

1.2.1.特点

1.2.2.声明与定义

1.2.3.注意事项

1.3.局部变量

1.3.1特点

1.3.2.注意事项

1.4.三者的区别

1.4.1. 全局变量(Global Variables)

1.4.2. 局部变量(Local Variables)

1.4.3. 静态变量(Static Variables)

1.4.4.区别总结

二、指针和引用的区别

2.1. 定义和使用

2.2. 内存占用

2.3. 初始和重新绑定

2.4. 与 null 的关系

2.5. 数组和函数参数

三、C++内存分区

3.1. 栈(Stack)

3.2. 堆(Heap)

3.3. 全局/静态存储区(Static Storage)

3.4. 常量存储区(Constant Storage)

3.5. 代码区(Text Segment)

3.6. 数据段(Data Segment)

3.7. BSS段(Block Started by Symbol)

3.8. 内存分配和回收

总结


前言

本文首先介绍了静态变量、全局变量和局部变量的区别,包括它们在内存中的分布情况。接着,我们探讨了指针和引用的不同用法及其背后的原理。最后,我们详细解释了C++程序在运行时的内存布局,包括栈、堆和全局/静态存储区的角色和相互作用。


一、静态变量和全局变量、局部变量的区别、在内存上是怎么分布的?

1.1.静态变量

在C++中,静态变量(static变量)是一种特殊的变量存储类别,它在程序的整个运行期间只被初始化一次,并且其生命周期与程序的运行期相同。静态变量可以用于类中或全局/命名空间作用域中,它们的行为和用途各有不同。

1.1.1.全局/命名空间作用域中的静态变量

在全局或命名空间作用域中声明的静态变量具有以下特点:

  1. 持久性:静态变量的值在程序的整个运行期间保持,直到程序结束。
  2. 初始化时机:静态变量在第一次访问前初始化,且只初始化一次。
  3. 局部性:尽管声明在全局或命名空间作用域中,但静态变量的作用域限定在其声明的文件内,即它们具有文件内局部性(除非通过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.类中的静态变量

在类中声明的静态变量具有以下特点:

  1. 类共有:静态变量不是某个特定对象的成员,而是整个类的成员。这意味着所有对象共享同一个静态变量。
  2. 初始化时机:静态变量必须在类的外部进行初始化,且只初始化一次。
  3. 访问控制:静态变量可以通过类名直接访问,不依赖于任何对象。
//创建类对象访问
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 

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. 内存管理:静态变量占用的内存在程序的整个运行期间都存在,因此需要谨慎管理以避免内存泄露。

1.2.全局变量

在C++中,全局变量是在所有函数外部定义的变量,它们具有全局作用域,这意味着它们可以在整个程序的任何位置被访问(除非被局部作用域中的同名变量遮蔽)。全局变量的生命周期从定义它们的语句执行时开始,直到程序结束时结束。

1.2.1.特点

  1. 全局作用域:全局变量在整个程序中都是可见的,直到程序结束。
  2. 持久性:全局变量在程序运行期间一直存在。
  3. 初始化:全局变量需要在定义时或在程序开始执行前(在main函数之前)进行初始化。

1.2.2.声明与定义

  • 定义:全局变量的定义是指分配存储空间和初始化变量。
  • 声明:全局变量的声明可以使得在其他文件中可以访问这个全局变量,声明通常使用extern关键字。
// global_variable.cpp
#include 

// 定义并初始化全局变量
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 

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. 命名冲突:全局变量可能在不同的文件中被重复定义,导致命名冲突。
  2. 维护困难:全局变量使得程序的状态变得难以追踪,增加了程序的维护难度。
  3. 线程安全:在多线程环境中,全局变量可能会引起线程安全问题,需要特别注意同步和互斥。

尽管全局变量在某些情况下很方便,但它们通常不推荐在大型或复杂的项目中使用,因为它们可能导致代码难以理解和维护。在可能的情况下,建议使用局部变量、参数传递或封装在类中的成员变量。

1.3.局部变量

在C++中,局部变量是在函数、代码块或任何其他作用域内部声明的变量。局部变量只在其声明的作用域内可见,一旦退出该作用域,局部变量的生命周期就会结束,其占用的内存也会被释放。

1.3.1特点

  1. 局部作用域:局部变量只在声明它的函数或代码块内部可见。
  2. 自动存储期:局部变量存储在栈(stack)上,其生命周期仅限于声明它的函数调用或代码块的执行。
  3. 未初始化的初始值:如果局部变量在声明时没有初始化,它将拥有一个不确定的值。
  4. 生命周期:局部变量的生命周期从声明它的那一刻开始,到包含它的函数或代码块执行结束时终止。

1.3.2.注意事项

  1. 初始化:最好在声明局部变量时立即初始化,以避免使用不确定的值。
  2. 内存管理:局部变量的内存管理是自动的,不需要程序员手动释放。
  3. 递增递减运算符:在使用递增(++)或递减(--)运算符时,应注意它们是前缀形式还是后缀形式,这会影响返回值。
  4. 函数参数:函数参数也被视为局部变量,它们的作用域限制在函数内部。

局部变量是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++程序员来说非常重要,因为它们影响着程序的性能、内存使用和数据安全。正确理解和使用这些概念可以提高代码质量,避免常见的编程错误。

你可能感兴趣的:(c++)