[大师C语言(第二十四篇)]C语言指针探秘

2024-06-10 14:52

本文主要是介绍[大师C语言(第二十四篇)]C语言指针探秘,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

引言

在C语言的学习和应用中,指针无疑是最重要、最难以掌握的概念之一。它为C语言提供了强大的功能和灵活性,同时也带来了不少的复杂性。本文将深入探讨C语言指针背后的技术,帮助你更好地理解和应用指针。

第一部分:指针的基本概念和操作

1.1 内存地址

计算机中的内存是由一系列连续的存储单元组成的,每个存储单元都有一个唯一的地址,用于访问该单元。在C语言中,我们使用指针来表示和操作内存地址。

1.2 指针的定义和声明

指针是一个变量,用于存储内存地址。在C语言中,定义一个指针变量需要使用星号(*)来表示该变量是一个指针。指针变量的类型表示它所指向的数据类型。

int *p; // 定义一个指向整数的指针变量
double *d; // 定义一个指向双精度浮点数的指针变量

1.3 指针的初始化和赋值

指针变量可以通过初始化和赋值来存储内存地址。初始化指针时,我们可以使用地址运算符(&)来获取变量的地址。

int a = 10;
int *p = &a; // 初始化指针p,使其指向变量a的地址

指针变量也可以通过赋值来改变其所存储的地址。

int b = 20;
p = &b; // 将指针p的值改为变量b的地址

1.4 指针的解引用

指针的解引用是指通过指针变量访问其所指向的内存单元。在C语言中,我们使用星号(*)来解引用指针。

int a = 10;
int *p = &a;printf("%d\n", *p); // 输出变量a的值,即10

1.5 指针的运算

指针可以进行一些基本的运算操作,如自增(++), 自减(–)和指针算术运算。这些运算可以用于访问内存中的连续存储单元。

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 将指针p初始化为数组arr的首地址for (int i = 0; i < 5; i++) {printf("%d ", *(p + i)); // 输出数组arr的元素
}

1.6 指针与数组

在C语言中,数组名表示数组的首地址。因此,指针可以用于访问和操作数组元素。

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 将指针p初始化为数组arr的首地址for (int i = 0; i < 5; i++) {printf("%d ", p[i]); // 使用指针访问数组元素
}

1.7 指针与函数

指针可以作为函数的参数和返回值,用于传递和返回内存地址。这使得我们可以通过指针在函数外部修改变量的值。

void swap(int *x, int *y) {int temp = *x;*x = *y;*y = temp;
}int main() {int a = 10, b = 20;swap(&a, &b); // 交换变量a和b的值printf("a = %d, b = %d\n", a, b);return 0;
}

总结

本文介绍了C语言指针的基本概念和操作,包括内存地址、指针的定义和声明、初始化和赋值、解引用、指针的运算以及指针与数组和函数的关系。掌握这些基本知识是深入理解C语言指针的关键。在下一部分中,我们将继续探讨指针的高级应用和技巧。

第二部分:指针的高级应用和技巧

2.1 指针与多维数组

在C语言中,多维数组可以通过指针来访问和操作。多维数组的元素在内存中是连续存储的,指针可以通过适当的偏移量来访问这些元素。

int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*p)[3] = arr; // p是一个指向包含3个整数的数组的指针for (int i = 0; i < 2; i++) {for (int j = 0; j < 3; j++) {printf("%d ", p[i][j]); // 使用指针访问二维数组元素}printf("\n");
}

2.2 指针数组和数组指针

指针数组是一个数组,其元素是指针。数组指针是一个指针,它指向一个数组。

int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c}; // 指针数组,包含3个整型指针int (*p)[3]; // 数组指针,指向包含3个整数的数组

2.3 函数指针

函数指针是一个指针,它指向一个函数。在C语言中,函数名表示该函数的地址,因此可以用来初始化函数指针。

void func() {printf("Hello, World!\n");
}int main() {void (*fp)() = func; // 函数指针,指向函数funcfp(); // 通过函数指针调用函数return 0;
}

2.4 指针与动态内存分配

C语言提供了动态内存分配的功能,允许程序在运行时动态地分配和释放内存。动态内存分配通常与指针一起使用。

int *p = (int *)malloc(5 * sizeof(int)); // 分配5个整数的内存空间if (p != NULL) {for (int i = 0; i < 5; i++) {p[i] = i + 1; // 初始化动态分配的内存}for (int i = 0; i < 5; i++) {printf("%d ", p[i]); // 使用指针访问动态分配的内存}free(p); // 释放动态分配的内存
}

2.5 指针与字符串

在C语言中,字符串是通过字符数组实现的,指针可以用于访问和操作字符串。

char str[] = "Hello, World!";char *p = str; // 将指针p初始化为字符串的首地址while (*p) { // 循环遍历字符串,直到遇到空字符'\0'printf("%c", *p); // 输出字符串的字符p++; // 移动指针到下一个字符
}
printf("\n");

2.6 指针与结构体

结构体是C语言中一种用户自定义的数据类型,它允许将不同类型的数据组合在一起。指针可以用于访问和操作结构体变量。

struct Person {char name[50];int age;
};struct Person person = {"John", 30};
struct Person *p = &person; // 将指针p初始化为结构体变量person的地址printf("Name: %s, Age: %d\n", p->name, p->age); // 使用指针访问结构体成员

总结

在第二部分中,我们探讨了C语言指针的一些高级应用和技巧,包括指针与多维数组、指针数组和数组指针、函数指针、指针与动态内存分配、指针与字符串以及指针与结构体的关系。这些知识点进一步展示了C语言指针的强大功能和灵活性。在下一部分中,我们将继续探讨指针的深入话题和常见问题。

第三部分:指针的深入话题和常见问题

3.1 指针的类型转换

在C语言中,指针可以进行类型转换,但必须谨慎使用,因为不正确的类型转换可能导致数据损坏或程序崩溃。

int a = 10;
void *p = (void *)&a; // 将整型指针转换为void指针// 使用void指针时,需要转换为正确的类型
int *intPtr = (int *)p;
printf("%d\n", *intPtr);

3.2 指针的指针(多级指针)

C语言支持多级指针,即指向指针的指针。这可以在多层次的数据结构中使用,例如树、图等。

int a = 10;
int *p = &a; // 一级指针
int **pp = &p; // 二级指针printf("%d\n", **pp); // 输出10

3.3 指针与const关键字

const关键字可以与指针一起使用,用于定义指针本身、指针指向的数据或两者都是常量。

int a = 10;
int b = 20;// 指针指向的数据是常量,不能通过指针修改数据
const int *p1 = &a;
//*p1 = 30; // 错误,不能修改*p1// 指针本身是常量,不能修改指针的值
int *const p2 = &a;
//p2 = &b; // 错误,不能修改p2// 指针和指向的数据都是常量
const int *const p3 = &a;

3.4 指针与函数参数

指针作为函数参数时,可以实现函数内部对实参的修改,这是因为传递的是地址而不是数据的副本。

void increment(int *p) {(*p)++; // 修改指针指向的值
}int main() {int a = 10;increment(&a); // a的值现在为11printf("%d\n", a);return 0;
}

3.5 指针与数组的区别

虽然指针和数组名在某些情况下可以互换,但它们在底层还是有区别的。数组名是一个指向数组首元素的常量指针,而指针是一个变量,可以改变其指向。

int arr[3] = {1, 2, 3};
int *p = arr; // 数组名用作指向首元素的指针printf("%d\n", *arr); // 输出1,与*arr[0]相同
printf("%d\n", *p); // 输出1p++; // 合法,可以改变指针的值
// arr++; // 非法,不能改变数组名的值

3.6 指针与内存管理

C语言中的指针与内存管理紧密相关。正确管理内存可以避免内存泄漏和野指针等问题。

int *p = (int *)malloc(5 * sizeof(int)); // 分配内存if (p != NULL) {for (int i = 0; i < 5; i++) {p[i] = i + 1; // 使用内存}free(p); // 释放内存p = NULL; // 将指针设置为NULL,避免野指针
}

总结

在第三部分中,我们探讨了C语言指针的一些深入话题和常见问题,包括指针的类型转换、多级指针、指针与const关键字、指针与函数参数、指针与数组的区别以及指针与内存管理。这些知识点进一步加深了我们对C语言指针的理解,并强调了正确使用指针的重要性。在下一部分中,我们将通过一些实际的编程示例来巩固和运用这些知识。

第四部分:指针的编程示例和最佳实践

4.1 示例:动态创建和操作数组

在C语言中,指针可以用来动态地创建数组,这意味着数组的大小可以在运行时确定,而不是在编译时。

#include <stdio.h>
#include <stdlib.h>int main() {int size, i;printf("请输入数组的大小: ");scanf("%d", &size);// 动态分配数组int *array = (int *)malloc(size * sizeof(int));if (array == NULL) {fprintf(stderr, "内存分配失败\n");return 1;}// 初始化数组for (i = 0; i < size; i++) {array[i] = i;}// 打印数组for (i = 0; i < size; i++) {printf("%d ", array[i]);}printf("\n");// 释放内存free(array);array = NULL;return 0;
}

4.2 示例:字符串操作

指针在字符串操作中非常有用,因为字符串本质上是一系列字符的数组。以下是一个使用指针来复制字符串的示例。

#include <stdio.h>void myStrCopy(char *dest, const char *source) {while (*source) {*dest = *source;dest++;source++;}*dest = '\0'; // 添加字符串结束符
}int main() {char source[] = "Hello, World!";char dest[20];myStrCopy(dest, source);printf("原字符串: %s\n", source);printf("复制后的字符串: %s\n", dest);return 0;
}

4.3 示例:函数指针数组

函数指针数组可以用来存储多个函数的地址,这样就可以通过数组索引来调用不同的函数。

#include <stdio.h>void func1() {printf("Function 1 called.\n");
}void func2() {printf("Function 2 called.\n");
}int main() {// 函数指针数组void (*funcArray[])(void) = {func1, func2};// 通过函数指针调用函数funcArray[0]();funcArray[1]();return 0;
}

4.4 示例:结构体和指针

结构体和指针结合使用,可以创建复杂的数据结构,如链表、树等。

#include <stdio.h>
#include <stdlib.h>typedef struct Node {int value;struct Node *next;
} Node;Node *createNode(int value) {Node *newNode = (Node *)malloc(sizeof(Node));if (newNode != NULL) {newNode->value = value;newNode->next = NULL;}return newNode;
}int main() {Node *head = createNode(1);head->next = createNode(2);head->next->next = createNode(3);// 遍历链表并打印值Node *current = head;while (current != NULL) {printf("%d ", current->value);current = current->next;}printf("\n");// 释放链表内存current = head;while (current != NULL) {Node *temp = current;current = current->next;free(temp);}return 0;
}

4.5 最佳实践:避免野指针

野指针是指未初始化或未正确释放内存的指针。为了避免野指针,应该在声明指针时初始化为NULL,并在使用完毕后及时释放内存。

int *p = NULL; // 声明时初始化为NULL// ... 使用指针 ...free(p); // 释放内存
p = NULL; // 释放后重新初始化为NULL

总结

在第四部分中,我们通过一系列编程示例来展示了C语言指针的实际应用,包括动态创建和操作数组、字符串操作、函数指针数组以及结构体和指针的结合使用。这些示例不仅加深了我们对指针的理解,还提供了在实际编程中应用指针的最佳实践。在最后一部分中,我们将探讨指针在C语言中的高级数据结构和算法中的应用。

第五部分:指针在高级数据结构和算法中的应用

5.1 链表

链表是一种常见的数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表的灵活性和动态性使其在C语言中广泛使用。

typedef struct Node {int data;struct Node *next;
} Node;Node *createNode(int data) {Node *newNode = (Node *)malloc(sizeof(Node));if (newNode != NULL) {newNode->data = data;newNode->next = NULL;}return newNode;
}void insertAtBeginning(Node **head, int data) {Node *newNode = createNode(data);newNode->next = *head;*head = newNode;
}void displayList(Node *head) {Node *current = head;while (current != NULL) {printf("%d ", current->data);current = current->next;}printf("\n");
}int main() {Node *head = NULL;insertAtBeginning(&head, 3);insertAtBeginning(&head, 2);insertAtBeginning(&head, 1);displayList(head);// 释放链表内存Node *current = head;Node *next;while (current != NULL) {next = current->next;free(current);current = next;}return 0;
}

5.2 树

树是一种层次化的数据结构,由节点组成,每个节点包含数据和一个或多个指向子节点的指针。树结构在C语言中经常使用指针来实现。

typedef struct TreeNode {int data;struct TreeNode *left;struct TreeNode *right;
} TreeNode;TreeNode *createTreeNode(int data) {TreeNode *newNode = (TreeNode *)malloc(sizeof(TreeNode));if (newNode != NULL) {newNode->data = data;newNode->left = newNode->right = NULL;}return newNode;
}void insert(TreeNode **root, int data) {if (*root == NULL) {*root = createTreeNode(data);} else if (data < (*root)->data) {insert(&(*root)->left, data);} else {insert(&(*root)->right, data);}
}int main() {TreeNode *root = NULL;insert(&root, 5);insert(&root, 3);insert(&root, 7);insert(&root, 2);insert(&root, 4);insert(&root, 6);insert(&root, 8);// 中序遍历树// ...(中序遍历的实现省略)// 释放树的内存// ...(释放树的实现省略)return 0;
}

5.3 图

图是由节点和边组成的数据结构,节点之间可以通过边相互连接。在C语言中,图通常使用邻接表或邻接矩阵表示,这两种表示方法都涉及到指针的使用。

typedef struct Edge {int dest;struct Edge *next;
} Edge;typedef struct Vertex {char label;Edge *edges;
} Vertex;Vertex *createVertex(char label) {Vertex *newVertex = (Vertex *)malloc(sizeof(Vertex));if (newVertex != NULL) {newVertex->label = label;newVertex->edges = NULL;}return newVertex;
}void addEdge(Vertex *vertex, int dest) {Edge *newEdge = (Edge *)malloc(sizeof(Edge));if (newEdge != NULL) {newEdge->dest = dest;newEdge->next = vertex->edges;vertex->edges = newEdge;}
}int main() {Vertex *vertices = (Vertex *)malloc(3 * sizeof(Vertex));vertices[0].label = 'A';vertices[1].label = 'B';vertices[2].label = 'C';addEdge(&vertices[0], 1);addEdge(&vertices[0], 2);addEdge(&vertices[1], 2);// ...(添加更多边和操作)// 打印图for (int i = 0; i < 3; i++) {printf("Vertex %c: ", vertices[i].label);Edge *current = vertices[i].edges;while (current != NULL) {printf("%d ", current->dest);current = current->next;}printf("\n");}// 释放图的内存// ...(释放图的实现省略)return 0;
}

5.4 排序算法

指针在排序算法中扮演着关键角色,尤其是在交换元素和处理数组时。以下是一个使用指针的冒泡排序算法的示例:

#include <stdio.h>void bubbleSort(int *arr, int size) {int i, j, temp;for (i = 0; i < size - 1; i++) {for (j = 0; j < size - i - 1; j++) {if (*(arr + j) > *(arr + j + 1)) {temp = *(arr + j);*(arr + j) = *(arr + j + 1);*(arr + j + 1) = temp;}}}
}int main() {int arr[] = {64, 34, 25, 12, 22, 11, 90};int size = sizeof(arr) / sizeof(arr[0]);bubbleSort(arr, size);printf("Sorted array: \n");for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");return 0;
}

5.5 搜索算法

指针在搜索算法中也很重要,尤其是在二分搜索和链表搜索中。以下是一个使用指针的二分搜索算法的示例:

#include <stdio.h>int binarySearch(int *arr, int l, int r, int x) {while (l <= r) {int m = l + (r - l) / 2;if (*(arr + m) == x) {return m;}if (*(arr + m) < x) {l = m + 1;} else {r = m - 1;}}return -1;
}int main() {int arr[] = {2, 3, 4, 10, 40};int n = sizeof(arr) / sizeof(arr[0]);int x = 10;int result = binarySearch(arr, 0, n - 1, x);if (result == -1) {printf("Element is not present in array\n");} else {printf("Element is present at index %d\n", result);}return 0;
}

5.6 指针与递归

递归函数通常使用指针来操作数据结构,如链表和树。以下是一个使用指针的递归函数,用于计算链表的长度。

#include <stdio.h>
#include <stdlib.h>typedef struct Node {int data;struct Node *next;
} Node;int length(Node *head) {if (head == NULL) {return 0;} else {return 1 + length(head->next);}
}int main() {Node *head = NULL;// ...(链表的创建和插入操作省略)int len = length(head);printf("Length of the linked list: %d\n", len);// 释放链表内存// ...(释放链表的实现省略)return 0;
}

总结

在第五部分中,我们探讨了指针在高级数据结构和算法中的应用,包括链表、树、图、排序算法、搜索算法以及递归。这些示例展示了指针在C语言中的强大功能和灵活性,以及它在数据结构和算法实现中的关键作用。通过这些示例,我们可以更好地理解指针在C语言编程中的重要性,并能够在实际应用中更加有效地使用它。

总结:

本文详细介绍了C语言指针的各个方面,从基本概念和操作到高级应用和技巧。首先,我们探讨了指针的基本概念,包括内存地址、指针的定义和声明、初始化和赋值、解引用以及指针的运算。接着,我们深入研究了指针与数组、函数、动态内存分配、字符串和结构体的关系,这些都是C语言中指针常见的使用场景。

在第三部分,我们讨论了指针的深入话题和常见问题,如指针的类型转换、多级指针、指针与const关键字、指针与函数参数、指针与数组的区别以及指针与内存管理。这些知识点帮助读者更好地理解指针的复杂性和灵活性。

第四部分通过一系列编程示例,展示了指针在实际编程中的应用,包括动态创建和操作数组、字符串操作、函数指针数组以及结构体和指针的结合使用。这些示例不仅加深了我们对指针的理解,还提供了在实际编程中应用指针的最佳实践。

最后,在第五部分,我们探讨了指针在高级数据结构和算法中的应用,包括链表、树、图、排序算法、搜索算法以及递归。这些示例展示了指针在C语言中的强大功能和灵活性,以及它在数据结构和算法实现中的关键作用。

通过本文的学习,读者应该能够全面理解C语言指针的原理和应用,从而在编程实践中更加熟练和有效地使用指针。

这篇关于[大师C语言(第二十四篇)]C语言指针探秘的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1048471

相关文章

C语言中联合体union的使用

本文编辑整理自: http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=179471 一、前言 “联合体”(union)与“结构体”(struct)有一些相似之处。但两者有本质上的不同。在结构体中,各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间, 一个联合变量

大语言模型(LLMs)能够进行推理和规划吗?

大语言模型(LLMs),基本上是经过强化训练的 n-gram 模型,它们在网络规模的语言语料库(实际上,可以说是我们文明的知识库)上进行了训练,展现出了一种超乎预期的语言行为,引发了我们的广泛关注。从训练和操作的角度来看,LLMs 可以被认为是一种巨大的、非真实的记忆库,相当于为我们所有人提供了一个外部的系统 1(见图 1)。然而,它们表面上的多功能性让许多研究者好奇,这些模型是否也能在通常需要系

人工和AI大语言模型成本对比 ai语音模型

这里既有AI,又有生活大道理,无数渺小的思考填满了一生。 上一专题搭建了一套GMM-HMM系统,来识别连续0123456789的英文语音。 但若不是仅针对数字,而是所有普通词汇,可能达到十几万个词,解码过程将非常复杂,识别结果组合太多,识别结果不会理想。因此只有声学模型是完全不够的,需要引入语言模型来约束识别结果。让“今天天气很好”的概率高于“今天天汽很好”的概率,得到声学模型概率高,又符合表达

C语言 将“China”译成密码

将“China”译成密码,密码规律是:用原来的字母后面的第4个字母代替原来的字母。例如,字母“A”后面的第4个字母是“E”,用“E”代替“A”。因此,“China”应译为“Glmre”。编译程序用付赋初值的方法使c1,c2,c3,c4,c5这五个变量的值分别为“C”,“h”,“i”,“n”,“a”,经过运算,使c1,c2,c3,c4,c5分别变成“G”,“l”,“m”,“r”,“e”。分别用put

C语言入门系列:探秘二级指针与多级指针的奇妙世界

文章目录 一,指针的回忆杀1,指针的概念2,指针的声明和赋值3,指针的使用3.1 直接给指针变量赋值3.2 通过*运算符读写指针指向的内存3.2.1 读3.2.2 写 二,二级指针详解1,定义2,示例说明3,二级指针与一级指针、普通变量的关系3.1,与一级指针的关系3.2,与普通变量的关系,示例说明 4,二级指针的常见用途5,二级指针扩展到多级指针 小结 C语言的学习之旅中,二级

利用结构体作为函数参数时结构体指针的定义

在利用结构体作为函数的参数进行传递时,容易犯的一个错误是将一个野指针传给函数导致错误。 #include <stdio.h>#include <math.h>#include <malloc.h>#define MAXSIZE 10typedef struct {int r[MAXSIZE]; //用于存储要排序的数组,r[0]作为哨兵或者临时变量int length;

【LinuxC语言】select轮询

文章目录 前言select函数详解selectfd_set类型一个小问题select函数使用步骤改进服务器代码select服务器示例代码 总结 前言 在Linux C语言编程中,我们经常需要处理多个I/O操作。然而,如果我们为每个I/O操作创建一个线程,那么当I/O操作数量增加时,线程管理将变得复杂且效率低下。这就是我们需要select轮询的地方。select是一种高效的I/

拓扑排序——C语言

拓扑排序(Topological Sorting)是一种用于有向无环图(DAG)的排序算法,其输出是图中所有顶点的线性排序,使得对于每条有向边 (u, v),顶点 u 在 v 之前出现。拓扑排序确定了项目网络图中的起始事件和终止事件,也就是顶点的执行顺序。         因为是有向无环图,所以拓扑排序的作用其实就是把先发生的排序在前面,后发生的排序到后面。 例如现在我们有一个

OC和 C语言中的const

const与宏对比 1.都是在其他的地方不可以改变 2.一个地方改了其他的地方都会改变。 而且宏定义的缺陷是, 是它会不断的开辟临时变量的存储空间 使用const的话 是都去使用同一的一份空间,使用同一个对象。 加const 之后变量还是全局的,只不过变为全局常量。 如果此时改变量不想被被类外面访问的话,可以加上static关键字, 3.下次想要定义一些宏的时候分

isa指针的理解

D3实例isa指向D3类对象。D3类的话isa指向D3元类对象。D3元类保存类中的方法调度列表,包括类方法和对象方法