当前位置: 首页 > news >正文

C基础笔记_指针专题

一:C 和 C++ 偏爱使用指针的原因

1. 通过指针可以直接操作内存

C 和 C++ 是系统级编程语言,它们的设计目标之一是允许开发者直接与硬件交互并高效地管理资源。

  • 指针的本质:指针本质上是一个存储内存地址的变量,它让程序员可以直接访问和操作内存中的数据。
  • 底层控制:通过指针,程序员可以精确地控制内存分配、释放以及数据布局,这对于编写操作系统、驱动程序、嵌入式系统等底层软件至关重要。

int x = 10;
int *p = &x;  //指针变量 p 存储了 x 的地址
*p = 20;      // 修改原始数据 x 的值为 20

2. 高效性和灵活性

指针提供了极高的效率和灵活性,特别是在性能敏感的应用中。

  • 减少复制开销:通过传递指针而不是整个数据结构,可以避免大量数据的复制,从而提高性能。
  • 动态内存分配:指针结合 malloc/free(C)或 new/delete(C++)可以动态分配和释放内存,这在需要灵活管理内存时非常有用。

struct Node {
    int data;
    struct Node *next;
};

struct Node *head = (struct Node *)malloc(sizeof(struct Node));
head->data = 42;
head->next = NULL;

 Java 和 Python 中也有类似的功能(如对象引用和垃圾回收),但这些功能是由运行时环境自动管理的,程序员无法直接干预。

3. 支持复杂的数据结构

指针是实现复杂数据结构(如链表、树、图等)的核心工具。

  • 在 C/C++ 中,通过指针可以轻松构建动态数据结构。例如,链表节点通常包含一个指向下一个节点的指针。
  • 在没有指针的语言中,这些数据结构通常由运行时环境或标准库提供,程序员无法直接控制底层实现。
struct LinkedList {
    int value;
    struct LinkedList *next;
};

 在 Java 中,类似的结构可以通过对象引用来实现,但底层的内存管理和指针操作是由 JVM 自动完成的。

4. 兼容底层硬件

C 和 C++ 被广泛用于开发操作系统、编译器、嵌入式系统等底层软件,这些场景需要直接操作硬件寄存器和内存。

  • 硬件交互:许多硬件设备通过特定的内存地址进行通信。指针使得程序员可以直接读写这些地址,从而与硬件交互。
  • 中断处理:在实时系统中,指针常用于快速响应中断信号并访问共享资源。

#define PORT_A (*(volatile unsigned int *)0x1000)  // 映射到硬件寄存器
PORT_A = 0xFF;  // 写入数据到硬件寄存器

 Java 和 Python 这样的高级语言完全屏蔽了这种底层操作。

 5. 提供更高的自由度

C 和 C++ 的设计哲学是“信任程序员”,给予程序员极大的自由来决定如何使用资源。

  • 手动内存管理:C 和 C++ 允许程序员完全控制内存的分配和释放,这虽然增加了出错的风险,但也提供了更高的性能和灵活性。
  • 零成本抽象:C++ 的核心理念之一是“零成本抽象”(zero-cost abstraction),即高级特性(如类和模板)不会引入额外的运行时开销。指针是实现这一理念的重要工具。

6. 指针的危险性 

尽管指针非常强大,但它也带来了潜在的危险性,这也是为什么许多现代语言选择隐藏指针的原因。

  • 野指针:未初始化或已释放的指针可能导致未定义行为。
  • 内存泄漏:忘记释放动态分配的内存会导致内存泄漏。
  • 缓冲区溢出:错误的指针操作可能导致数组越界或其他安全问题。
int *p = malloc(sizeof(int));  // 分配内存 字节大小 sizeof(int)=8byte
*p = 42;
free(p);
*p = 10;  // 错误:对已释放的内存进行访问

Java 和 Python 通过垃圾回收机制和边界检查避免了这些问题,但这也牺牲了一部分性能和灵活性。 

7. 现代语言的选择

现代语言(如 Java、Kotlin 和 Python)选择隐藏指针的原因在于:

  • 安全性:通过封装指针操作,减少了程序员犯错的可能性。
  • 易用性:隐藏底层细节后,程序员可以专注于业务逻辑,而无需担心内存管理。
  • 自动化管理:垃圾回收器自动管理内存,减少了手动管理的复杂性。

int[] arr = new int[10];  // 创建数组
arr[0] = 42;              // 直接操作数组元素

 在底层,arr 实际上是一个指向数组首地址的指针,但 Java 隐藏了这些细节,并提供了边界检查以防止越界访问。

二:XX类型的指针

在C语言中,指针是一种非常强大的工具,它可以指向任何类型的数据。除了 int 类型的指针外,还有多种其他类型的指针,包括但不限于基本数据类型、数组、结构体、联合体和函数指针等。

1. 基本数据类型的指针

int 外,还可以有指向其他基本数据类型的指针,如 char, float, double 等。

char *cp;      // 指向 char 类型的指针变量cp
float *fp;     // 指向 float 类型的指针变量fp
double *dp;    // 指向 double 类型的指针变量dp

2. 数组指针类型

可以声明一个指针来指向数组。需要注意的是,数组名本身就是一个指向数组第一个元素的指针。 

int arr[5];
int (*ap)[5] = &arr;  // ap 是一个指向包含5个 int 元素的数组的指针

3. 指向指针的指针(多级指针)

你可以有一个指向指针的指针,甚至更多级别。

int x = 10;
int *p = &x;       // p 是指向 int 的指针
int **pp = &p;     // pp 是指向 int类型指针p   的指针

4. 结构体指针类型

指针也可以用来指向结构体类型的数据。

struct Point {
    int x, y;
};

struct Point pt = {1, 2};
struct Point *ppt = &pt;  // ppt 是一个指向 struct Point 的指针

//访问结构体成员时,如果使用指针,则需要使用箭头操作符 -> 而不是点操作符 .
printf("x: %d\n", ppt->x);  // 使用 -> 访问成员变量

5. 函数指针类型

函数指针是 指向函数的指针 ,允许你将函数作为参数传递给其他函数或从函数返回。

 

int add(int a, int b) {
    return a + b;
}

int (*fp)(int, int) = add;  // fp 是一个指向接受两个 int 参数并返回 int 类型的函数add的指针
int result = fp(5, 3);      // 调用通过指针指向的函数

6. Void 指针

void* 是一种特殊类型的指针,它可以指向任意类型的数据,但不能直接解引用,因为编译器不知道它指向的数据类型。

关键字

void

1. 表示“无类型”

void 的字面意思是“空”或“无”,用来表示“没有类型”。它可以用于以下场景:

(1) 函数返回值为 void

当一个函数不需要返回任何值时,可以将其返回类型声明为 void

void sayHello() {
    printf("Hello, World!\n");
}

(2) 函数参数列表为 void

当一个函数不接受任何参数时,可以将其参数列表声明为 void(虽然现代 C 中通常省略 void

这里的 void 表示函数不接受任何参数。

在 ANSI C 标准中,void 参数列表是显式表明函数没有参数的推荐方式。

int getRandomNumber(void) {
    return 42;  // 返回一个随机数
}

2. 指针类型:void*

void* 是一种特殊的指针类型,表示“指向未知类型的指针”。

void*特点:

  • void* 可以指向任何数据类型的变量。
  • 它是一种通用指针类型,常用于需要处理多种数据类型的情况。
  • 不能直接解引用 void*,因为它不知道所指向的数据的具体类型。
#include <stdio.h>

//printValue 函数接受一个 void* 类型的指针,并根据类型参数决定如何解引用。
//注意:void* 必须显式转换为目标类型后才能解引用。
void printValue(void *ptr, char type) {
    if (type == 'i') {
        printf("%d\n", *(int*)ptr);  // 将 void* 转换为 int*
    } else if (type == 'f') {
        printf("%f\n", *(float*)ptr);  // 将 void* 转换为 float*
    }
}

int main() {
    int x = 42;
    float y = 3.14;

    printValue(&x, 'i');  // 输出整数
    printValue(&y, 'f');  // 输出浮点数

    return 0;
}

 3. 表示“无操作”

在某些情况下,void 用于表示“无操作”或“忽略结果”。

int main() {
    printf("This is a test.\n");
    (void)printf("This result is ignored.\n");  // 明确忽略返回值
    return 0;
}

(void) 明确表示忽略表达式的返回值。这种写法在某些代码风格中被用来消除编译器警告。

4. 空参数列表

在旧版 C(K&R C)中,函数定义中的空参数列表默认表示“任意数量和类型的参数”。为了明确表示函数不接受任何参数,ANSI C 引入了 void

// K&R 风格:表示可以接受任意参数
int func() {
    return 42;
}

// ANSI C 风格:明确表示不接受参数
int func(void) {
    return 42;
}

现代 C 编程中,建议使用 void 来明确说明函数不接受参数。

5. 结合 typedef 使用

void 也可以与其他关键字结合使用,例如与 typedef 定义通用指针类型。

typedef void (*Callback)(int);

void executeCallback(Callback cb, int value) {
    cb(value);
}

void myCallback(int x) {
    printf("Callback called with %d\n", x);
}

int main() {
    executeCallback(myCallback, 42);
    return 0;
}

这里,Callback 是一个函数指针类型,指向一个接受 int 参数且返回 void 的函数。

void* 的疑惑

1. * 的作用

* 是指针声明符,表示“这是一个指针”。它的作用是修饰变量或类型,表明这个变量是一个指针,而不是普通的数据类型。

相关文章:

  • zk基础—5.Curator的使用与剖析一
  • 【FreeRTOS】二值信号量 是 消息队列 吗
  • FPGA_BD Block Design学习(一)
  • VBA高级应用30例应用4:打开工作薄时进行身份验证
  • 记录vscode连接不上wsl子系统下ubuntu18.04问题解决方法
  • LeetCode 3375 题解
  • LibreOffice 自动化操作目录
  • 常见算法模板总结
  • 高压安全新挑战:新能源汽车三电系统绝缘材料的漏电流与击穿特性研究
  • 如何判断家里的宽带是否有公网IPv4或公网IPv6
  • 14 GIS地类面积统计终极指南:3步速通「栅格VS矢量」双线操作
  • 洛谷 P11962:[GESP202503 六级] 树上漫步 ← dfs + 邻接表
  • 从静态绑定驱动模型到现代设备模型 —— 一次驱动架构的进化之旅
  • 第一讲、IsaacLab创建空场景
  • 【Rust开发】Rust快速入门,开发出Rust的第一个Hello World
  • 【Hadoop入门】Hadoop生态之Hive简介
  • Pycharm常用快捷键总结
  • pytorch小记(十七):PyTorch 中的 `expand` 与 `repeat`:详解广播机制与复制行为(附详细示例)
  • [定位器]晶艺LA1823,4.5V~100V, 3.5A,替换MP9487,MP9486A,启烨科技
  • 2025.4.9总结
  • 汉化主题做网站效果图/广州疫情最新情况
  • 网站与网站自动跳转代码/百度建站
  • 网站上广告/黄页网站推广app咋做广告
  • 领卷网站怎么做的/搜索引擎优化
  • 北京品牌网站建设公司/培训心得体会
  • ps网页设计作品欣赏/seo搜索引擎优化工资多少钱