C语言指针:从入门到精通
指针是一种特殊的变量,它存储的不是数据本身,而是数据的内存地址。通过指针,我们可以间接访问和操作内存中的数据。int main()// 定义整型变量// 定义指针并赋值为number的地址printf("变量值: %d\n", number);printf("变量地址: %p\n", &number);printf("指针值(存储的地址): %p\n", pointer);printf("通过指针
·
指针是c/c++中必不可少的一种特殊的变量,基本任何场景都可以使用它,因此学好它是每一个程序员的必经之路,为了初学者可以方便的学习或者复习有关指针的基础知识,我将从零开始将它大多以代码的形式呈现出来,方便大家验证和使用。
一、指针基础入门
1.1 什么是指针?
指针是一种特殊的变量,它存储的不是数据本身,而是数据的内存地址。通过指针,我们可以间接访问和操作内存中的数据。
#include <stdio.h>
int main()
{
int number = 42; // 定义整型变量
int *pointer = &number; // 定义指针并赋值为number的地址
printf("变量值: %d\n", number);
printf("变量地址: %p\n", &number);
printf("指针值(存储的地址): %p\n", pointer);
printf("通过指针访问的值: %d\n", *pointer);
return 0;
}
1.2 指针的基本操作
#include <stdio.h>
int main()
{
int x = 10, y = 20;
int *p1 = &x, *p2 = &y;
printf("x的值: %d\n", *p1); // 解引用操作,读取指针指向的地址保存的数据
*p1 = 100; // 通过指针修改变量值
printf("修改后x的值: %d\n", x);
p1 = p2; // 指针赋值
printf("现在p1指向的值: %d\n", *p1);
return 0;
}
二、指针与数组的深度结合
2.1 数组名的秘密
在C语言中,数组名实际上是一个指向数组首元素的常量指针。
#include <stdio.h>
int main()
{
int numbers[5] = {1, 2, 3, 4, 5};
printf("数组访问方式对比:\n");
for(int i = 0; i < 5; i++) // 1. 传统下标法
{
printf("numbers[%d] = %d\n", i, numbers[i]);
}
int *ptr = numbers;
for(int i = 0; i < 5; i++)// 2. 指针算术法
{
printf("*(ptr + %d) = %d\n", i, *(ptr + i));
}
int *p = numbers;
for(int i = 0; i < 5; i++) // 3. 指针移动法
{
printf("*p = %d (地址: %p)\n", *p, p);
p++;
}
return 0;
}
2.2 指针数组 vs 数组指针
这是两个容易混淆但完全不同的概念:
#include <stdio.h>
int main()
{
int a = 1, b = 2, c = 3;
int *ptr_array[3] = {&a, &b, &c}; // 指针数组:存储指针的数组
printf("指针数组:\n");
for(int i = 0; i < 3; i++)
{
printf("元素%d: %d\n", i, *ptr_array[i]);
}
int arr[3] = {10, 20, 30};
int (*array_ptr)[3] = &arr; // 数组指针:指向数组的指针
printf("\n数组指针:\n");
for(int i = 0; i < 3; i++)
{
printf("元素%d: %d\n", i, (*array_ptr)[i]);
}
return 0;
}
三、指针与结构体的完美结合
3.1 指向结构体的指针
#include <stdio.h>
#include <string.h>
struct Student // 定义学生结构体
{
int id;
char name[20];
float score;
};
int main()
{
struct Student stu = {101, "张三", 95.5};
struct Student *stu_ptr = &stu;
printf("学生信息:\n");
printf("学号: %d\n", stu_ptr->id); // 使用->操作符
printf("姓名: %s\n", stu_ptr->name);
printf("成绩: %.2f\n", stu_ptr->score);
stu_ptr->score = 98.5; // 通过指针修改结构体成员
printf("修改后成绩: %.2f\n", stu.score);
return 0;
}
3.2 结构体中的指针成员
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Person
{
char *name; // 指针成员
int age;
};
int main()
{
struct Person p;
p.name = (char*)malloc(20 * sizeof(char)); // 为指针成员动态分配内存
if(p.name == NULL)
{
printf("内存分配失败!\n");
return 1;
}
strcpy(p.name, "李四");
p.age = 25;
printf("姓名: %s, 年龄: %d\n", p.name, p.age);
free(p.name); // 释放动态分配的内存
return 0;
}
四、多级指针的应用
#include <stdio.h>
int main()
{
int value = 100;
int *ptr = &value;
int **pptr = &ptr; // 指向指针的指针
int ***ppptr = &pptr; // 三级指针
printf("变量值: %d\n", value);
printf("一级指针访问: %d\n", *ptr);
printf("二级指针访问: %d\n", **pptr);
printf("三级指针访问: %d\n", ***ppptr);
***ppptr = 200; // 通过多级指针修改变量值
printf("修改后的值: %d\n", value);
return 0;
}
五、指针与动态内存管理
#include <stdio.h>
#include <stdlib.h>
int main()
{
int size;
printf("请输入数组大小: ");
scanf("%d", &size);
int *dynamic_array = (int*)malloc(size * sizeof(int)); // 动态分配内存
if(dynamic_array == NULL)
{
printf("内存分配失败!\n");
return 1;
}
for(int i = 0; i < size; i++) // 初始化数组
{
dynamic_array[i] = i * 10;
}
printf("动态数组内容:\n"); // 使用数组
for(int i = 0; i < size; i++)
{
printf("%d ", dynamic_array[i]);
}
printf("\n");
free(dynamic_array); // 释放内存
return 0;
}
六、指针函数详解
6.1 返回指针的函数
指针函数是返回指针类型的函数,这类函数在C语言中非常常见,特别是在动态内存分配和数据结构操作中。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* create_copy(const char* original) // 返回指针的函数:创建一个字符串副本
{
if(original == NULL) return NULL; // 检查输入有效性
size_t length = strlen(original);
char* copy = (char*)malloc(length + 1); // 分配内存
if(copy != NULL)
{
strcpy(copy, original); // 复制内容
}
return copy; // 返回指针
}
int main()
{
char* my_copy = create_copy("Hello, World!");
if(my_copy != NULL)
{
printf("副本内容: %s\n", my_copy);
free(my_copy); // 必须释放内存
}
return 0;
}
6.2 函数指针
函数指针是指向函数的指针变量,它允许我们将函数作为参数传递或存储在数据结构中。
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int subtract(int a, int b)
{
return a - b;
}
int multiply(int a, int b)
{
return a * b;
}
int main()
{
int (*operation)(int, int); // 声明函数指针
int x = 10, y = 5;
operation = add; // 使用函数指针执行加法
printf("%d + %d = %d\n", x, y, operation(x, y));
operation = subtract;// 使用函数指针执行减法
printf("%d - %d = %d\n", x, y, operation(x, y));
operation = multiply;// 使用函数指针执行乘法
printf("%d * %d = %d\n", x, y, operation(x, y));
return 0;
}
6.3 函数指针数组
函数指针数组允许我们通过索引调用不同的函数,这在实现状态机或命令模式时非常有用。
#include <stdio.h>
void say_hello()
{
printf("Hello!\n");
}
void say_goodbye()
{
printf("Goodbye!\n");
}
void say_thanks()
{
printf("Thank you!\n");
}
int main()
{
void (*messages[3])() = {say_hello, say_goodbye, say_thanks};// 函数指针数组
for(int i = 0; i < 3; i++) // 通过索引调用不同的函数
{
printf("调用函数%d: ", i);
messages[i]();
}
return 0;
}
6.4 回调函数
回调函数是通过函数指针实现的,允许我们将函数作为参数传递给其他函数。
#include <stdio.h>
typedef void (*Callback)(int);// 回调函数类型定义
void process_numbers(int arr[], int size, Callback callback) // 接受回调函数作为参数的函数
{
for(int i = 0; i < size; i++)
{
callback(arr[i]); // 对每个元素调用回调函数
}
}
void print_number(int num) // 两个不同的回调函数实现
{
printf("%d ", num);
}
void print_square(int num)
{
printf("%d ", num * num);
}
int main()
{
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("原始数组: ");
process_numbers(numbers, size, print_number);
printf("\n平方数组: ");
process_numbers(numbers, size, print_square);
printf("\n");
return 0;
}
七、常见错误与调试技巧
7.1 常见指针错误
#include <stdio.h>
int main()
{
int *dangerous_ptr; //未初始化的指针
// printf("%d", *dangerous_ptr); // 未定义指向
int *null_ptr = NULL; //空指针解引用,错误
// printf("%d", *null_ptr);
int *wild_ptr; // 使用野指针,错误
{
int temp = 50;
wild_ptr = &temp;
}
// printf("%d", *wild_ptr); // 危险访问
return 0;
}
7.2 调试技巧
-
打印指针值和地址:printf("指针地址: %p, 指向的值: %d\n", ptr, *ptr);
printf("指针地址: %p, 指向的值: %d\n", ptr, *ptr);
-
使用调试器:
Visual Studio Debugger (Windows) - 添加断言检查:
#include <assert.h> assert(ptr != NULL && "指针不能为NULL");
最后请大家记住每次使用指针时都要问自己三个问题:1、这个指针初始化了吗?2、这个指针可能为NULL吗?3、我们需要释放这个指针指向的内存吗?
更多推荐
所有评论(0)