C/C++与C#——指针的作用

****指针是编程语言中用于存储变量内存地址 的特殊变量,它的核心价值是直接操作内存

C/C++

作用1. 直接访问和修改内存(最核心作用)

指针存储的是变量的内存地址,通过指针可以跳过语言的封装层,直接读写该地址对应的内存数据。这是指针最根本的价值,也是它兼具强大性和危险性的原因。

复制代码
#include <stdio.h>

int main() {
    int num = 10;
    int* p = &num; // p是指针,存储num的内存地址(&是取地址符)

    printf("num的值:%d\n", num); // 输出:10
    printf("num的地址:%p\n", &num); // 输出:num的内存地址(如0x7ffeefbff5c4)
    printf("指针p存储的地址:%p\n", p); // 输出:和num的地址一致
    printf("通过指针访问num的值:%d\n", *p); // *是解引用符,访问指针指向的内存数据,输出:10

    // 通过指针修改内存数据(直接操作内存)
    *p = 20;
    printf("修改后num的值:%d\n", num); // 输出:20

    return 0;
}

*p = 20这行代码直接修改了num所在内存地址的数据,无需通过变量名num,让num = 20,这就是指针 "直接操作内存" 的体现。

作用2.实现动态内存分配(动态数组 / 对象)

在 C/C++ 中, 动态内存分配(堆内存)完全依赖指针实现。程序运行时可以根据需求申请 / 释放内存,避免静态内存的浪费或不足。

#include <iostream>

int main() {

int n;

std::cout << "请输入数组长度:";

std::cin >> n;

// 动态分配n个int的内存,返回指向该内存的指针

int* arr = new int[n];

// 通过指针操作动态数组

for (int i = 0; i < n; i++) {

arr[i] = i * 2; // 等价于*(arr + i) = i * 2

}

// 输出数组

for (int i = 0; i < n; i++) {

std::cout << arr[i] << " ";

}

// 释放动态内存(必须手动释放,否则内存泄漏)

delete[] arr;

arr = nullptr; // 避免野指针

return 0;

}

静态数组的长度必须是编译期常量(如int arr[5]),而指针配合new/malloc可以实现运行时动态指定长度,这是动态数据结构(如链表、树)的基础。

这里提一嘴arr[i] 的底层逻辑(下标访问)

C/C++ 编译器在处理数组下标访问 arr[i] 时,会自动将其翻译成 "指针偏移 + 解引用"的操作:

  1. 找到数组首元素的指针 arr
  2. 向后偏移 i 个元素,得到指向第 i 个元素的指针 arr + i
  3. 解引用这个指针,访问 / 修改第 i 个元素的值,即 *(arr + i)

也就是说,arr[i]*(arr + i)简写形式(语法糖),编译器会帮我们完成这个转换。

之后将0,2,4等赋值给arr[0],arr[1],arr[2]。

作用3. 传递大型数据时减少拷贝(提升性能)

当传递大型数据(如大数组、大结构体)时,直接传递变量会发生值拷贝(复制整个数据到栈内存),耗时且占用内存;而传递指针(仅拷贝 4/8 字节的内存地址,32 位系统 4 字节,64 位 8 字节),可以大幅提升性能。

复制代码
#include <iostream>
#include <cstring>

// 定义大结构体
struct BigData {
    char content[1024 * 1024]; // 1MB的数据
};

// 直接传递值:会拷贝整个1MB的结构体,性能差
void ProcessValue(BigData data) {
    std::cout << "值传递:" << data.content << std::endl;
}

// 传递指针:仅拷贝8字节的地址,性能好
void ProcessPointer(BigData* pData) {
    std::cout << "指针传递:" << pData->content << std::endl;
}

int main() {
    BigData data;
    strcpy(data.content, "这是大结构体的数据");

    // ProcessValue(data); // 值传递,性能差
    ProcessPointer(&data); // 指针传递,性能好

    return 0;
}

C# 中的 引用类型(如类、数组),本质上是 "安全的指针"(CLR 托管的引用),传递引用时也是拷贝内存地址,避免了值拷贝,原理和指针一致。

作用4. 实现复杂的数据结构(链表、树、图等)

链表、二叉树、图等动态数据结构的核心是节点之间的关联,而这种关联需要通过指针(存储下一个节点的内存地址)来实现。

#include <iostream>

// 链表节点结构

struct ListNode {

int val;

ListNode* next; // 指针,指向后一个节点

ListNode(int x) : val(x), next(nullptr) {}

};

int main() {

// 创建链表:1 -> 2 -> 3

ListNode* head = new ListNode(1);

head->next = new ListNode(2);

head->next->next = new ListNode(3);

// 遍历链表

ListNode* p = head;

while (p != nullptr) {

std::cout << p->val << " ";

p = p->next; // 移动指针到下一个节点

}

// 释放链表内存(略)

return 0;

}

链表的每个节点通过next指针指向后续节点,没有指针就无法实现这种动态的节点关联。

作用5. 与硬件 / 系统底层交互(驱动、嵌入式开发)

在底层开发(如硬件驱动、嵌入式系统、操作系统内核)中,硬件设备的寄存器、内存映射 I/O 等都对应固定的内存地址,必须通过指针直接操作这些地址来控制硬件。

// 假设LED寄存器的地址是0x40020000

#define LED_REG_ADDR 0x40020000

// 定义指针指向该地址

volatile unsigned int* ledReg = (volatile unsigned int*)LED_REG_ADDR;

// 点亮LED:直接向该内存地址写入数据

*ledReg = 0x01;

// 熄灭LED

*ledReg = 0x00;

这种场景下,指针是唯一能直接操作硬件地址的方式,高级语言的封装无法满足需求。

C#

在C#中使用指针,需用到unsafe关键字。

C# 是托管语言 ,CLR(公共语言运行时)负责内存管理(垃圾回收),默认屏蔽了指针以保证内存安全。但在需要极致性能或底层交互时,可以通过unsafe关键字启用指针。

场景1. 极致性能优化(如高频数据处理)

当处理大量数据(如图像像素、音频数据)时,使用指针跳过 CLR 的边界检查、垃圾回收等机制,提升处理速度。

using System;

class Program

{

static void Main()

{

int[] arr = { 1, 2, 3, 4, 5 };

// 启用unsafe上下文

unsafe

{

// 获取数组的首地址(fixed固定数组,防止GC移动内存)

fixed (int* p = arr)

{

// 遍历数组(指针偏移)

for (int i = 0; i < arr.Length; i++)

{

Console.WriteLine(*(p + i)); // 输出数组元素

*(p + i) *= 2; // 修改数组元素

}

}

}

// 输出修改后的数组

foreach (var num in arr)

{

Console.Write(num + " "); // 输出:2 4 6 8 10

}

}

}

fixed关键字用于固定数组的内存地址,防止垃圾回收器移动数组(CLR 的 GC 会自动整理内存,移动对象位置,指针会失效,所以需要fixed)。

场景2. 调用非托管代码(如 C/C++ 写的 DLL)

C# 通过 P/Invoke 调用非托管 DLL 时,指针(IntPtr或直接指针)用于传递数据,实现与非托管代码的交互。

场景3. 底层系统交互(如操作系统 API 调用)

调用 Windows API 等底层函数时,需要使用指针传递内存地址(如窗口句柄、内存缓冲区)。

相关推荐
Yan-英杰3 小时前
从Free Tier到Serverless:用亚马逊云科技打造零门槛AI应用
服务器·开发语言·科技·ai·大模型
hugh_oo3 小时前
100 天学会爬虫 · Day 11:如何合理控制爬虫请求频率?让访问行为更像真人
开发语言·爬虫·python
天赐学c语言3 小时前
12.18 - 有效的括号 && C语言中static的作用
数据结构·c++·算法·leecode
JIngJaneIL3 小时前
基于java+ vue建筑材料管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
半梅芒果干4 小时前
vue3 新建文件store自动导入
开发语言·前端·javascript
Tony Bai4 小时前
Go 1.26 新特性前瞻:从 Green Tea GC 到语法糖 new(expr),性能与体验的双重进化
开发语言·后端·golang
悟能不能悟4 小时前
Java 中将 List 中对象的某一列转换为 Set
java·开发语言·list
vortex54 小时前
Bash Shell 的展开与补全机制
开发语言·bash
Dream it possible!4 小时前
LeetCode 面试经典 150_回溯_组合(99_77_C++_中等)
c++·leetcode·面试·回溯