指针与运用(Pointers):

指针与运用(Pointers):

引用运算符(&):

  1. 基本引用

     #include <iostream>
    
     int main() {
         int a = 10;
         int& ref = a; // ref 是 a 的引用,ref 和 a 绑定在一起
         ref = 20;     // 修改 ref 也会修改 a
    
         std::cout << "a: " << a << std::endl; // 输出 20
         return 0;
     }
    
  2. 函数参数中的作用

    传递变量的引用:

     #include <iostream>
    
     void modify(int& x) {
         x *= 2;  // 直接修改原变量
     }
    
     int main() {
         int num = 5;
         modify(num);
         std::cout << "num: " << num << std::endl; // 输出 10
         return 0;
     }
    

    传递const引用(避免复制且保护数据):

     #include <iostream>
    
     void print(const std::string& str) {
         std::cout << str << std::endl;
     }
    
     int main() {
         std::string text = "Hello, C++!";
         print(text); // 通过引用传递,但 print() 不能修改 text
         return 0;
     }
    
  3. 返回值引用:

    返回局部变量的引用(⚠️危险,不要这样做)

     int& dangerous() {
         int x = 10; // 局部变量
         return x;   // ⚠️ 返回局部变量的引用,x 在函数结束后会被销毁
     }
    

    可能出现的问题:

    1. 溃(Segmentation Fault): 访问已销毁的内存,程序直接崩溃

    2. 垃圾值:ref指向的内存可能已经被其他代码覆盖,导致不可预测的输出

    3. 偶尔看似“正常”:某些情况下,内存未被立即覆盖,代码可能“看起来正常”,但它依然是不安全的。

  4. 解决方法:

    1. 返回静态变量的引用(安全)

       int& dangerous() {
           static int x = 10; // 局部变量
           return x;   // ⚠️ 返回局部变量的引用,x 在函数结束后会被销毁
       }
      
      • static变量存储在全局数据区,不会在函数返回后销毁。
    2. 返回值而不是引用

       int dangerous() {
           int x = 10;
           return x;   
       }
      
      • 直接返回int,触发值拷贝,不会返回悬空引用。
  5. 右值引用(C++)

取地址运算符:

在C++中的&有两种不同的用途,取决于它出现的上下文:

  1. 取地址运算符 (当用在表达式中):返回变量的内存地址

     int x = 10;
     int* ptr = &x; // &x 获取变量x的内存地址
    

    所以在int* ptr = &x 这个语句中:

    • int*声明ptr是一个指向整数的指针

    • &x使用取地址运算符获取x的内存地址

    • 整个语句的含义是:创建一个指向整数的指针ptr, 并将它初始化为指向变量x的地址

  2. 引用声明符号(当用在类型声明中): 声明一个引用类型

     int x = 10;
     int& ref = x; // 声明ref为x的引用
    

指针和地址:

在C++中,指针(Pointer)是存储地址(Address)的变量。它用于间接访问和操作内存,是C++语言的重要特性之一。

  1. 什么是指针?

    指针是一个变量,它存储另一个变量的地址。每个变量都有一个地址,而指针就是用于存储这个地址的变量。

    式例:定义指针

     #include <iostream>
    
     int main() {
         int a = 10;     // 普通变量
         int* ptr = &a;  // 指针变量,存储 a 的地址
    
         std::cout << "变量 a 的值: " << a << std::endl;
         std::cout << "变量 a 的地址: " << &a << std::endl;
         std::cout << "指针 ptr 的值: " << ptr << std::endl;
         std::cout << "指针 ptr 指向的值: " << *ptr << std::endl; // 解引用
    
         return 0;
     }
    
     /**
         变量 a 的值: 10
         变量 a 的地址: 0x7ffeefbff5c4
         指针 ptr 的值: 0x7ffeefbff5c4
         指针 ptr 指向的值: 10
     /
    
  • &a获取变量a的地址

  • ptr存储a的地址

  • *ptr(解引用)访问 ptr 指向的值。

指针的基本操作

  1. 修改指针指向的值:

     int a = 10;
     int* ptr = &a;
     *ptr = 20; // 通过指针修改 a 的值
     std::cout << a; // 输出 20
    
    • *ptr = 20; 通过指针修改a的值
  2. 指针的赋值:

     int a = 5, b = 10;
     int* ptr = &a;
     ptr = &b; // 指针现在指向 b
    
  3. 指针的初始化:

    | 方式 | 示例 | 说明 | | --- | --- | --- | | 指向变量 | int* p = &a; | 指向p存储a的地址 | | 指向nullptr | int* p = nullptr | nullptr代表空指针 | | 指向数组 | int arr[5] = {1, 2, 3, 4, 5}; int* p = arr; | p 指向 |

指针与数组

数组变量本质上是指针常量,它的值是数组的首地址。

示例:数组和指针

#include <iostream>

int main() {
    int arr[] = {10, 20, 30};
    int* ptr = arr; // 等价于 &arr[0]

    std::cout << *ptr << std::endl;   // 10
    std::cout << *(ptr + 1) << std::endl; // 20
    std::cout << *(ptr + 2) << std::endl; // 30

    return 0;
}
  • ptr + 1 向后移动一个int大小,指向arr[1]

指针和动态内存分配

(1)使用new申请动态内存:

int* p = new int(42); // 在堆上创建一个 int 变量,值为 42
std::cout << *p; // 输出 42
delete p; // 释放内存
  • new申请堆内存,必须用delete释放,否则会内存泄漏

(2)动态数组

int* arr = new int[5]; // 申请 5 个整数
arr[0] = 10;
arr[1] = 20;
delete[] arr; // 释放数组
  • delete[] arr; 释放动态数组。

指针的类型

指针类型示例说明
普通指针int* p = &a;存储变量地址
常量指针const int* p = &a;不能修改指向的值
指针常量int* const p = &a;不能修改指针的地址
空指针int* p = nullptr;指向nullptr,避免野指针
野指针(⚠️危险)int* p;未初始化,指向未知地址,可能崩溃

栈内存 vs 堆内存

栈内存(Stack)

栈(Stack)是一种自动管理内存区域,用于存储局部变量和函数调用信息

栈的特点:

  1. 自动分配和释放

    • 当函数被调用时,局部变量会被自动分配到栈上

    • 当函数返回时,变量会自动销毁,无需手动释放内存

  2. 内存大小受限

    • 栈的大小是有限的,通常由操作系统设置

    • 如果在栈上分配了过大的数组或者递归过深,可能会导致栈溢出(Stack Overflow)

  3. 访问速度快:

    • 由于栈采用LIFO(后进先出)结构,内存访问速度非常快,比堆快得多
  4. 变量作用域受限:

    • 栈上的变量只能在函数内部存活,一旦函数执行完毕,变量就会被销毁。

示例:

void example() {
    int x = 10;  // x 存储在栈上
    // 当 example() 结束时,x 会被自动释放
}

堆内存(Heap):

堆(Heap)是一种用于动态分配内存的区域,程序可以在运行时手动管理它

堆的特点:

  1. 手动分配和释放:

    • 堆上的内存必须手动分配(new)和手动释放(delete)

    • 如果没有正确释放,可能会导致内存泄漏(Memory Leak)

  2. 内存大小较大:

    • 堆比栈大得多,但需要谨慎管理,以避免占用过多资源。
  3. 访问速度较慢:

    • 由于堆内存是动态分配的,系统需要额外的操作来管理它,因此访问速度比栈慢。
  4. 变量作用域可控:

    • 在队上创建的对象不会自动销毁,它们可以在多个函数间共享,直到显式释放。
对比项栈(Stack)堆(Heap)
内存管理方式自动(函数调用时分配,函数返回时释放)手动(new 分配,delete 释放)
内存大小较小(受操作系统限制)较大(取决于系统可用内存)
访问速度(LIFO 结构,缓存友好)(动态分配,额外管理开销)
变量作用域仅在函数内部有效,函数结束后自动释放只要不 delete,变量可以持续存在
典型问题栈溢出(过深递归、大数组)内存泄漏(忘记 delete
适用场景局部变量、小型对象需要长期存活或较大对象