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;
}
练习
- 上述的链表操作,实现一个函数,将链表中的每个节点值都加100。
码字不易,欢迎大家点赞,关注,评论,谢谢!
C++训练营
专为校招、社招3年工作经验的同学打造的1V1 C++训练营,量身定制学习计划、每日代码review,简历优化,面试辅导,已帮助多名学员获得大厂offer!