代码随想录八股训练营第二十三天| 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 <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. 内存管理:静态变量占用的内存在程序的整个运行期间都存在,因此需要谨慎管理以避免内存泄露。

1.2.全局变量

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

1.2.1.特点

  1. 全局作用域:全局变量在整个程序中都是可见的,直到程序结束。
  2. 持久性:全局变量在程序运行期间一直存在。
  3. 初始化:全局变量需要在定义时或在程序开始执行前(在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. 命名冲突:全局变量可能在不同的文件中被重复定义,导致命名冲突。
  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++程序员来说非常重要,因为它们影响着程序的性能、内存使用和数据安全。正确理解和使用这些概念可以提高代码质量,避免常见的编程错误。

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二的算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值