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

C语言基础系列【31】指针进阶4:指针与高级数据类型

博主介绍:程序喵大人

  • 35- 资深C/C++/Rust/Android/iOS客户端开发
  • 10年大厂工作经验
  • 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
  • 《C++20高级编程》《C++23高级编程》等多本书籍著译者
  • 更多原创精品文章,首发gzh,见文末
  • 👇👇记得订阅专栏,以防走丢👇👇
    😉C++基础系列专栏
    😃C语言基础系列专栏
    🤣C++大佬养成攻略专栏
    🤓C++训练营
    👉🏻个人网站

在C语言中,除了基本的整型、浮点型、字符型等数据类型外,还有一些高级数据类型。

位运算与位字段

位运算是对整数的二进制位进行操作的一系列运算,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移(<<)和右移(>>)等。

位运算在底层编程、性能优化和嵌入式系统等领域,使用很频繁。

示例代码(位运算)

#include <stdio.h>

int main() {
    unsigned int a = 5;  // 二进制: 0000 0101
    unsigned int b = 3;  // 二进制: 0000 0011

    // 按位与
    unsigned int and_result = a & b;  // 结果: 0000 0001 (即1)
    printf("a & b = %u\n", and_result);

    // 按位或
    unsigned int or_result = a | b;   // 结果: 0000 0111 (即7)
    printf("a | b = %u\n", or_result);

    // 按位异或
    unsigned int xor_result = a ^ b;  // 结果: 0000 0110 (即6)
    printf("a ^ b = %u\n", xor_result);

    // 按位取反
    unsigned int not_result = ~a;     // 结果: 1111 1010 (在32位系统中,结果为4294967290)
    printf("~a = %u\n", not_result);

    // 左移
    unsigned int left_shift_result = a << 1;  // 结果: 0000 1010 (即10)
    printf("a << 1 = %u\n", left_shift_result);

    // 右移
    unsigned int right_shift_result = a >> 1; // 结果: 0000 0010 (即2)
    printf("a >> 1 = %u\n", right_shift_result);

    return 0;
}

位字段是结构体中的一种特殊成员,它允许程序员指定每个成员占用的位数。位字段通常用于需要紧凑存储多个布尔值或小型整数值的场景,通过位字段可以优化结构体的占用空间,这在性能优化场景中很常见。

(https://godbolt.org/z/6dYaTbnKb)

#include <stdio.h>

struct BitField {
unsigned int bit0 : 1;
unsigned int bit1 : 1;
unsigned int bit2 : 1;
unsigned int bit3 : 1;
unsigned int value : 4;  // 4位用于存储一个0到15之间的值
};

int main() {
    struct BitField bf;

    bf.bit0 = 1;
    bf.bit1 = 0;
    bf.bit2 = 1;
    bf.bit3 = 0;
    bf.value = 12;

    printf("bit0: %u\n", bf.bit0);
    printf("bit1: %u\n", bf.bit1);
    printf("bit2: %u\n", bf.bit2);
    printf("bit3: %u\n", bf.bit3);
    printf("value: %u\n", bf.value);
    printf("sizeof : %lu \n", sizeof(bf)); // 4

    return 0;
}

类型转换与强制类型转换

在C语言中,类型转换分为隐式类型转换和显式类型转换(即强制类型转换)。

  • 隐式类型转换是由编译器自动完成的,通常发生在不同数据类型之间的赋值或运算时。
  • 显式类型转换则需要程序员使用类型转换运算符((type))来明确指定转换的类型。
#include <stdio.h>

int main() {
    double d = 3.14;
    int i;

    // 隐式类型转换(double -> int),可能导致精度损失
    i = d;
    printf("Implicit conversion: %d\n", i);  // 输出: 3

    // 显式类型转换(int -> double)
    double id = (double)i;
    printf("Explicit conversion: %f\n", id); // 输出: 3.000000

    // 强制类型转换(通常用于将指针类型转换为其他类型)
    int *ptr = &i;
    unsigned int *uptr = (unsigned int *)ptr;
    printf("*uptr (after cast): %u\n", *uptr); // 输出: i的无符号整数值

    return 0;
}

注意:强制类型转换可能会破坏数据的完整性,特别是在将指针类型转换为不兼容的类型时。因此,在使用强制类型转换时应格外小心。

复杂数据类型:结构体指针与函数指针

结构体指针是指向结构体的指针,程序员可以通过指针来访问和修改结构体的成员。

(https://godbolt.org/z/ajfdYPnE6)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Person {
char name[50];
int age;
};

int main() {
    // 动态分配一个结构体
    struct Person *p = (struct Person *)malloc(sizeof(struct Person));
    if (p == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    // 初始化结构体成员
    strcpy(p->name, "Alice");
    p->age = 30;

    // 访问结构体成员
    printf("Name: %s\n", p->name);
    printf("Age: %d\n", p->age);

    // 释放内存
    free(p);

    return 0;
}

函数指针是指向函数的指针,它允许程序员将函数作为参数传递给其他函数,或者将函数赋值给指针变量并在需要时调用它。函数指针在回调函数、事件处理等场景中经常会用到。

#include <stdio.h>

// 定义一个函数类型
typedef int (*FuncPtr)(int, int);

// 实现一个加法函数
int add(int a, int b) {
    return a + b;
}

// 实现一个减法函数
int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 定义函数指针并指向加法函数
    FuncPtr fp = add;
    printf("Result of add(5, 3): %d\n", fp(5, 3));

    // 将函数指针指向减法函数
    fp = subtract;
    printf("Result of subtract(5, 3): %d\n", fp(5, 3));

    return 0;
}

复杂数据类型

结合结构体指针和函数指针,可以构建更复杂的数据结构和算法。

例如,我们可以使用结构体来存储链表节点的数据和指针,并使用函数指针来实现链表的遍历、插入和删除等操作。

示例代码(https://godbolt.org/z/x35YvGaKe)

#include <stdio.h>
#include <stdlib.h>

// 定义链表节点结构体
struct Node {
int data;
struct Node *next;
};

// 定义操作链表的函数类型
typedef void (*ListOp)(struct Node **head, int data);

// 实现插入节点函数
void insert(struct Node **head, int data) {
    struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = *head;
    *head = newNode;
}

// 实现删除节点函数(假设删除第一个匹配的节点)
void del(struct Node **head, int data) {
    struct Node *current = *head;
    struct Node *prev = NULL;

    while (current != NULL && current->data != data) {
        prev = current;
        current = current->next;
    }

    if (current != NULL) {
        if (prev == NULL) {
            *head = current->next;
        } else {
            prev->next = current->next;
        }
        free(current);
    }
}

// 实现遍历链表函数
void traverse(struct Node *head) {
    struct Node *current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

int main() {
    struct Node *head = NULL;
    ListOp operations[2] = {insert, del};

    // 插入一些节点
    operations[0](&head, 1);
    operations[0](&head, 2);
    operations[0](&head, 3);

    // 遍历链表
    printf("Linked list after insertions:\n");
    traverse(head);

    // 删除节点
    operations[1](&head, 2);

    // 遍历链表
    printf("Linked list after deletion:\n");
    traverse(head);

    // 释放链表内存(注意:这里没有实现释放所有节点的代码,
    // 在实际应用中需要确保在链表不再使用时释放所有动态分配的内存)

    return 0;
}

为了完整性和安全性,你可以添加一个释放链表所有节点的函数,如下所示:

// 实现释放链表所有节点的函数
void freeList(struct Node *head) {
    struct Node *current = head;
    struct Node *nextNode;

    while (current != NULL) {
        nextNode = current->next;
        free(current);
        current = nextNode;
    }
}

// 在main函数末尾调用freeList来释放链表内存
int main() {
    // ...(之前的代码保持不变)

    // 释放链表内存
    freeList(head);

    return 0;
}

练习

  1. 上述的链表操作,实现一个函数,将链表中的每个节点值都加100。

码字不易,欢迎大家点赞关注评论,谢谢!


C++训练营

专为校招、社招3年工作经验的同学打造的1V1 C++训练营,量身定制学习计划、每日代码review,简历优化,面试辅导,已帮助多名学员获得大厂offer!


http://www.kler.cn/a/613059.html

相关文章:

  • ctfshow-web入门-特定函数绕过(web396-web405)
  • 基于核函数的卷积操作 —— 理解卷积原理
  • 2025年危化品安全员考试题库及答案
  • 10. 七大排序(含四种版本快排及优化) ******
  • docker 部署 postgresql 切换用户
  • 短视频 NFC 碰一碰发视频靠谱吗?源码搭建,OEM贴牌
  • aws S3利用lambda edge实现图片缩放、质量转换等常规图片处理功能
  • 山洪预警秒级响应-AI本地化部署在极端降雨短临预测中的技术突破。AI智能体开发与大语言模型的本地化部署、优化技术
  • 计算机等级考试数据库三级(笔记2)
  • PhotoScissors快速抠图与背景填充
  • 美业数字化突围:小店通如何撬动下沉市场?
  • 常用的排序算法------练习3
  • 单片机GPIO模拟SPI SLAVE
  • Linux内核软中断分析
  • AWS AI学习笔记:机器学习的模式及选择
  • 【CVE-2025-30208】| Vite-漏洞分析与复现
  • 自动化构建攻略:Jenkins + Gitee 实现 Spring Boot 项目自动化构建
  • ICLR 2025|华科OVTR:首次实现端到端开放词汇多目标跟踪,刷新性能SOTA!
  • 寻找两个正序数组的中位数
  • 启山智软实现b2c单商户商城对比传统单商户的优势在哪里?