指针是C语言中最强大也最复杂的特性之一。简单来说,指针是一个变量,其值为另一个变量的内存地址。通过指针,我们可以间接访问和操作存储在特定内存位置的数据。
为了理解指针,必须先了解计算机内存的基本原理。
计算机内存可以想象为一系列连续编号的字节(每个字节8位)。每个字节都有一个唯一的地址,从0开始递增。
地址: 0x1000 0x1001 0x1002 0x1003 0x1004 ...
内容: [0x42] [0x65] [0x6C] [0x6C] [0x6F] ...
假设有一个整型变量:
int num = ; // 假设在内存地址0x1000处
在内存中的表示(假设int为4字节):
地址: 0x1000 0x1001 0x1002 0x1003
内容: [0x39] [0x30] [0x00] [0x00] // 12345的二进制表示,考虑小端序
指针变量本身也占用内存空间,存储的是地址值:
int num = ; // 地址: 0x1000
int *p = # // 假设p的地址是0x2000
内存中的结构:
地址: 0x1000 0x1001 0x1002 0x1003
内容: [0x39] [0x30] [0x00] [0x00] // num的值
地址: 0x2000 0x2001 0x2002 0x2003 0x2004 0x2005 0x2006 0x2007
内容: [0x00] [0x10] [0x00] [0x00] [0x00] [0x00] [0x00] [0x00] // p的值 (0x1000)
指针的类型决定了:
类型 *指针名;
例如:
int *p; // 指向整型的指针
char *str; // 指向字符的指针
double *dp; // 指向双精度浮点数的指针
void *vp; // 无类型指针,可以指向任何类型
int *p1, *p2, *p3; // 三个指向整型的指针
int* p1, p2, p3; // 一个指向整型的指针p1,两个整型变量p2和p3
int * p1; // 等同于 int *p1;
void指针是一种"通用"指针,可以指向任何类型的数据,但在解引用之前必须进行类型转换:
void *vp = #
int *ip = (int*)vp; // 在C中需要显式转换
printf("%d", *ip); // 正确
printf("%d", *vp); // 错误,不能直接解引用void指针
int num = ;
int *p = # // p现在保存了num的地址
int num = ;
int *p = #
*p = ; // 通过p修改num的值
printf("%d", num); // 输出20
int x = , y = ;
int *p1 = &x;
int *p2 = &y;
p1 = p2; // p1现在指向y
if (p1 == p2) { // 比较两个指针是否指向同一个内存位置
printf("指向同一个地址");
}
if (*p1 == *p2) { // 比较两个指针指向的值是否相等
printf("指向的值相等");
}
int *p = NULL; // NULL通常定义为(void*)0
if (p == NULL) {
printf("这是一个空指针");
}
在C语言中,数组名称实际上是指向数组第一个元素的常量指针。
C语言中文社区
学习路线 | AI编程 | 代码实例 | 实战源码 | 开发工具 | 视频教程 | 面试题 | 电子书 | 专注于C语言编程技术分享
180篇原创内容
公众号
int arr[] = {, , , , };
int *p = arr; // 等价于 p = &arr[0]
printf("%d", *p); // 输出10
printf("%d", *(p+)); // 输出20
int arr[] = {, , , , };
int *p = arr;
// 以下表达式等价
printf("%d", arr[]);
printf("%d", *(arr+));
printf("%d", *(p+));
printf("%d", p[]);
虽然数组名称可以当作指针使用,但它们有关键区别:
arr++; // 错误,数组名不能被修改
p++; // 正确,p是变量
sizeof(arr); // 返回整个数组的大小:5 * sizeof(int)
sizeof(p); // 只返回指针本身的大小,通常是4或8字节
int matrix[][]; // 3行4列的二维数组
// 访问元素
matrix[][] = ;
// 使用指针访问
*(*(matrix+)+) = ; // 等价于上面的语句
// 指向行的指针
int (*row)[] = matrix; // 指向有4个int元素的数组的指针
在C中,字符串是以空字符('\0')结尾的字符数组。字符串与指针有密切关系。
char str1[] = "Hello"; // 字符数组
char *str2 = "World"; // 字符指针,指向常量字符串
char *str3 = (char*)malloc(); // 动态分配的字符串
strcpy(str3, "Dynamic");
// 字符串拷贝
char dest[];
char *src = "Source";
strcpy(dest, src);
// 字符串连接
strcat(dest, " String");
// 字符串长度
int len = strlen(dest);
// 字符串比较
if (strcmp(str1, str2) == ) {
printf("相等");
}
char str[] = "Hello";
char *p = str;
// 遍历字符串
while (*p != '\0') {
printf("%c", *p);
p++;
}
// 字符串复制
char *src = "Source";
char *dest = (char*)malloc(strlen(src) + );
char *p_dest = dest;
while (*src) {
*p_dest++ = *src++;
}
*p_dest = '\0';
指针与函数的结合是C语言中最强大的特性之一。
// 通过指针修改变量值
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = , y = ;
swap(&x, &y);
// 现在x=10, y=5
return ;
}
int* findMax(int arr[], int size) {
int *max_ptr = &arr[];
for (int i = ; i < size; i++) {
if (arr[i] > *max_ptr) {
max_ptr = &arr[i];
}
}
return max_ptr;
}
int main() {
int numbers[] = {, , , , };
int *max = findMax(numbers, );
printf("最大值: %d\n", *max);
return;
}
返回指向局部变量的指针是危险的:
int* createInt() {
int x = ;
return &x; // 危险!x在函数返回后将不再有效
}
正确的做法是使用动态内存分配:
int* createInt() {
int *p = (int*)malloc(sizeof(int));
*p = ;
return p; // 返回指向堆内存的指针,需要调用者释放
}
指针可以进行加减运算,但增减的单位是指针类型的大小。
int arr[] = {, , , , };
int *p = arr; // p指向arr[0]
p = p + ; // p指向arr[1]
p += ; // p指向arr[3]
p--; // p指向arr[2]
两个指针相减的结果是它们之间的元素数量:
int arr[] = {, , , , };
int *p1 = &arr[];
int *p2 = &arr[];
int diff = p2 - p1; // diff = 3,表示从p1到p2有3个元素的距离
char *cp = (char*)0x1000;
int *ip = (int*)0x1000;
cp++; // 现在cp = 0x1001 (增加1字节)
ip++; // 现在ip = 0x1004 (增加4字节,假设int为4字节)
指针可以指向另一个指针,形成多级指针结构。
int num = ;
int *p = # // 一级指针
int **pp = &p; // 二级指针
printf("%d", **pp); // 输出10
内存表示:
num: [10] 地址: 0x1000
p: [0x1000] 地址: 0x2000
pp: [0x2000] 地址: 0x3000
int **matrix = (int**)malloc(rows * sizeof(int*));
for (int i = ; i < rows; i++) {
matrix[i] = (int*)malloc(cols * sizeof(int));
}
void allocateBuffer(char **buffer, int size) {
*buffer = (char*)malloc(size);
}
int main() {
char *myBuffer = NULL;
allocateBuffer(&myBuffer, );
strcpy(myBuffer, "Hello");
free(myBuffer);
return ;
}
int ***ppp; // 三级指针
int ****pppp; // 四级指针
虽然理论上可以使用任意级别的指针,但实际应用中很少使用三级以上的指针,因为结构会变得难以理解和维护。
指针数组是一个数组,每个元素都是指针:
int *arr[]; // 包含5个int指针的数组
int a=, b=, c=, d=, e=;
arr[] = &a;
arr[] = &b;
// ...
printf("%d", *arr[]); // 输出2
指针数组常用于管理字符串:
char *names[] = {"John", "Alice", "Bob", "Carol"};
printf("%s", names[]); // 输出"Alice"
数组指针是指向数组的指针:
int (*p)[]; // 指向包含5个int元素的数组的指针
int arr[] = {, , , , };
p = &arr; // p指向整个数组
printf("%d", (*p)[]); // 输出3
int *arr[5]
- 指针数组:一个有5个元素的数组,每个元素是int指针int (*arr)[5]
- 数组指针:一个指针,指向有5个int元素的数组int matrix[][];
// 使用数组指针访问
int (*p)[] = matrix; // p是指向4个int元素的数组的指针
printf("%d", p[][]); // 访问matrix[1][2]
函数指针是指向函数的指针变量,可以用来调用函数或者作为参数传递。
// 声明一个函数指针类型
typedef int (*Operation)(int, int);
// 定义函数
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
// 使用函数指针
Operation op = add;
int result = op(, ); // 调用add函数,结果为8
op = subtract;
result = op(, ); // 调用subtract函数,结果为2
int processNumbers(int a, int b, int (*func)(int, int)) {
return func(a, b);
}
int main() {
int result1 = processNumbers(, , add); // 结果为8
int result2 = processNumbers(, , subtract); // 结果为2
return ;
}
int (*operations[])(int, int) = {add, subtract, multiply, divide};
int result = operations[](, ); // 调用add函数
函数指针常用于实现回调机制:
// 排序函数
void bubbleSort(int arr[], int n, int (*compare)(int, int)) {
for (int i = ; i < n-1; i++) {
for (int j = ; j < n-i-1; j++) {
if (compare(arr[j], arr[j+]) > ) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j+];
arr[j+] = temp;
}
}
}
}
// 比较函数
int ascending(int a, int b) { return a - b; }
int descending(int a, int b) { return b - a; }
// 使用
int arr[] = {, , , , };
bubbleSort(arr, , ascending); // 升序排序
bubbleSort(arr, , descending); // 降序排序
C语言提供了一组函数用于动态内存管理,这些函数与指针密切相关。
分配指定字节数的内存:
int *p = (int*)malloc( * sizeof(int)); // 分配5个int的空间
if (p != NULL) {
for (int i = ; i < ; i++) {
p[i] = i + ;
}
}
分配指定数量的元素,并初始化为0:
int *p = (int*)calloc(, sizeof(int)); // 分配5个int的空间,并初始化为0
调整已分配内存的大小:
int *p = (int*)malloc( * sizeof(int));
// ...使用p...
// 扩展内存到10个int
p = (int*)realloc(p, * sizeof(int));
释放动态分配的内存:
free(p); // 释放内存
p = NULL; // 避免悬挂指针
如果分配了内存但没有释放,就会发生内存泄漏:
void leakMemory() {
int *p = (int*)malloc(sizeof(int));
*p = ;
// 函数结束时没有调用free(p),导致内存泄漏
}
// 分配3行4列的二维数组
int **matrix = (int**)malloc( * sizeof(int*));
for (int i = ; i < ; i++) {
matrix[i] = (int*)malloc( * sizeof(int));
}
// 使用
matrix[][] = ;
// 释放
for (int i = ; i < ; i++) {
free(matrix[i]);
}
free(matrix);
typedef struct Node {
int data;
struct Node *next;
} Node;
// 创建节点
Node* createNode(int data) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 在链表尾部添加节点
void appendNode(Node **head, int data) {
Node *newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
Node *current = *head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
// 打印链表
void printList(Node *head) {
Node *current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// 释放链表
void freeList(Node *head) {
Node *current = head;
Node *next;
while (current != NULL) {
next = current->next;
free(current);
current = next;
}
}
typedef struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
// 创建节点
TreeNode* createNode(int data) {
TreeNode *newNode = (TreeNode*)malloc(sizeof(TreeNode));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
// 插入节点
TreeNode* insertNode(TreeNode *root, int data) {
if (root == NULL) {
return createNode(data);
}
if (data < root->data) {
root->left = insertNode(root->left, data);
} else {
root->right = insertNode(root->right, data);
}
return root;
}
// 中序遍历
void inorderTraversal(TreeNode *root) {
if (root != NULL) {
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}
// 释放树
void freeTree(TreeNode *root) {
if (root != NULL) {
freeTree(root->left);
freeTree(root->right);
free(root);
}
}
使用void指针实现通用数据结构:
typedef struct GenericNode {
void *data;
struct GenericNode *next;
} GenericNode;
// 创建节点
GenericNode* createNode(void *data, size_t dataSize) {
GenericNode *newNode = (GenericNode*)malloc(sizeof(GenericNode));
newNode->data = malloc(dataSize);
memcpy(newNode->data, data, dataSize);
newNode->next = NULL;
return newNode;
}
int *p; // 未初始化,包含随机值
*p = ; // 危险!可能导致段错误
正确做法:
int *p = NULL;
if (p != NULL) {
*p = ; // 安全检查
}
// 或者
int num;
int *p = #
*p = ; // 安全
void memoryLeak() {
int *p = (int*)malloc(sizeof(int));
// 没有调用free(p)
}
正确做法:
void noLeak() {
int *p = (int*)malloc(sizeof(int));
// 使用p
free(p);
p = NULL; // 避免悬挂指针
}
int *p = (int*)malloc(sizeof(int));
free(p);
*p = ; // 危险!p已经成为野指针
int arr[] = {, , , , };
int *p = arr;
*(p+) = ; // 危险!超出数组边界
int* badFunction() {
int x = ;
return &x; // 危险!x在函数结束后不再有效
}
正确做法:
int* goodFunction() {
int *p = (int*)malloc(sizeof(int));
*p = ;
return p; // 返回堆内存的指针,调用者负责释放
}
int num = ;
char *cp = (char*)#
*cp = 'A'; // 只修改了num的第一个字节
char *str = "Hello";
str[] = 'h'; // 危险!尝试修改字符串常量
正确做法:
char str[] = "Hello";
str[] = 'h'; // 安全,str是数组
int *p = NULL; // 初始化为NULL
int num = ;
int *q = # // 初始化为有效地址
int *p = (int*)malloc(size);
if (p == NULL) {
// 处理内存分配失败
fprintf(stderr, "Memory allocation failed\n");
return -1;
}
p = (int*)malloc(size);
// 使用p
free(p);
p = NULL; // 避免悬挂指针
for (int i = ; i < size; i++) {
// 而不是直接 p++
process(p + i);
}
void printString(const char *str) {
// str指向的内容不能被修改
printf("%s", str);
}
如果简单变量就能解决问题,就不要使用指针:
// 不好的做法
void addOne(int *num) {
(*num)++;
}
// 对于简单计算,可以这样写
int addOne(int num) {
return num + ;
}
typedef int* IntPtr;
IntPtr p1, p2; // 两个指向int的指针
实现一个函数来删除字符串中的所有空格:
char* removeSpaces(const char *str) {
if (str == NULL) returnNULL;
int len = strlen(str);
char *result = (char*)malloc(len + );
if (result == NULL) returnNULL;
int j = ;
for (int i = ; i < len; i++) {
if (str[i] != ' ') {
result[j++] = str[i];
}
}
result[j] = '\0';
return result;
}
// 使用
char *original = "Hello World!";
char *processed = removeSpaces(original);
printf("%s\n", processed); // 输出"HelloWorld!"
free(processed);
#include <stdio.h>
#include <stdlib.h>
// 定义内存块结构体
typedefstruct MemoryBlock {
struct MemoryBlock *next;
} MemoryBlock;
// 定义内存池结构体
typedefstruct MemoryPool {
MemoryBlock *freeList;
size_t blockSize;
size_t blockCount;
void *pool;
} MemoryPool;
// 初始化内存池
void initMemoryPool(MemoryPool *pool, size_t blockSize, size_t blockCount) {
pool->blockSize = blockSize;
pool->blockCount = blockCount;
// 分配大块内存
pool->pool = malloc(blockSize * blockCount);
if (pool->pool == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return;
}
// 初始化空闲列表
pool->freeList = (MemoryBlock *)pool->pool;
MemoryBlock *current = pool->freeList;
for (size_t i = ; i < blockCount - ; i++) {
current->next = (MemoryBlock *)((char *)current + blockSize);
current = current->next;
}
current->next = NULL;
}
// 从内存池分配内存
void *allocateFromPool(MemoryPool *pool) {
if (pool->freeList == NULL) {
fprintf(stderr, "Memory pool is full\n");
returnNULL;
}
MemoryBlock *allocated = pool->freeList;
pool->freeList = pool->freeList->next;
return (void *)allocated;
}
// 将内存块释放回内存池
void freeToPool(MemoryPool *pool, void *block) {
MemoryBlock *released = (MemoryBlock *)block;
released->next = pool->freeList;
pool->freeList = released;
}
// 销毁内存池
void destroyMemoryPool(MemoryPool *pool) {
free(pool->pool);
pool->freeList = NULL;
pool->blockSize = ;
pool->blockCount = ;
pool->pool = NULL;
}
int main() {
MemoryPool pool;
size_t blockSize = ;
size_t blockCount = ;
// 初始化内存池
initMemoryPool(&pool, blockSize, blockCount);
// 分配内存
void *block1 = allocateFromPool(&pool);
void *block2 = allocateFromPool(&pool);
if (block1 != NULL && block2 != NULL) {
printf("Allocated two blocks from memory pool\n");
}
// 释放内存
freeToPool(&pool, block1);
freeToPool(&pool, block2);
printf("Freed two blocks back to memory pool\n");
// 销毁内存池
destroyMemoryPool(&pool);
return;
}
此内存池用于管理固定大小的内存块,基本思路是预先分配一大块内存,然后把它分割成多个固定大小的内存块,每次分配时从空闲列表里取出一个可用的内存块,释放时将该内存块放回空闲列表。
MemoryBlock
结构体:用于表示内存块,其中 next
指针用于构建空闲列表。MemoryPool
结构体:表示内存池,包含空闲列表指针 freeList
、每个内存块的大小 blockSize
、内存块数量 blockCount
以及指向大块内存的指针 pool
。initMemoryPool
函数:初始化内存池,分配大块内存并初始化空闲列表。allocateFromPool
函数:从内存池分配一个内存块,若空闲列表为空则输出错误信息。freeToPool
函数:将一个内存块释放回内存池,插入到空闲列表头部。destroyMemoryPool
函数:销毁内存池,释放大块内存并重置相关指针和计数器。main
函数:演示了如何使用内存池进行内存分配和释放。C语言中文社区
学习路线 | AI编程 | 代码实例 | 实战源码 | 开发工具 | 视频教程 | 面试题 | 电子书 | 专注于C语言编程技术分享
180篇原创内容