指针是 C 语言强大功能的重要支撑,掌握指针是深入学习和使用 C 语言的关键,也是后续学习其他编程语言以及从事系统编程、底层开发等工作的基础,接下来就让小编为大家简单介绍一些指针的相关知识。
一、指针的概述
在计算机系统里,内存被划分成了一个个小的存储单元,每个存储单元都有一个唯一的编号,这个编号就被称为内存地址。内存地址就如同现实生活中的门牌号一样,用于标识和定位内存中的数据。
指针变量是一种特殊的变量,它的作用是存储其他变量的内存地址。在 C 语言中,定义指针变量需要使用*
符号。例如:
int num = 42; // 整型变量
int* ptr_int = # // 指向整型的指针
double d = 3.14; // 双精度浮点型变量
double* ptr_double = &d; // 指向双精度浮点型的指针
char c = 'A'; // 字符型变量
char* ptr_char = &c; // 指向字符型的指针
通过*
运算符可以对指针进行解引用操作,解引用的作用是访问或修改指针所指向的内存地址中的值。
int num = 42;
int* ptr = #
printf("%d\n", *ptr); // 输出42,访问指针所指向的值
*ptr = 99; // 修改指针所指向的值
printf("%d\n", num); // 输出99,因为num的值已被修改
二、空指针与野指针
1.空指针
空指针是不指向任何有效内存地址的指针,在C语言中用NULL表示,我们在使用指针之前最好将其初始化为NULL,以避免产生野指针。下面是一个空指针的创建过程:
#include <stdio.h>
int main()
{
int* ptr = NULL; // 初始化为空指针
if (ptr == NULL)
{
printf("指针为空,不能解引用!\n");
}
return 0;
}
2.野指针
野指针是指向无效内存地址的指针,或者说是指向已释放内存或者未分配内存的指针,使用野指针是非常危险的行为,若不小心使用了野指针可能会导致程序崩溃、数据损坏或安全漏洞(如缓冲区溢出)。
那么野指针是如何构成的呢?常见的原因有:
1.指针在定义时未进行赋予初始化(上文所提到),指针会指向随机的内存地址。访问未分配的内存地址可能会导致程序崩溃或数据损坏。
int* ptr; // 未初始化的指针
*ptr = 20; // 危险!访问随机内存地址
2.使用free()或delete释放内存后,指针仍保留原地址,但该地址已无效。
int* ptr = (int*)malloc(sizeof(int));
*ptr = 41;
free(ptr); // 释放内存
// ptr成为野指针
printf("%d", *ptr); // 错误!访问已释放的内存
所以在释放内存后需要进行置空。
int* ptr = (int*)malloc(sizeof(int));
*ptr = 41;
free(ptr); // 释放内存
prt = NULL; //防止野指针
3.函数返回局部变量的地址,局部变量的生命周期在函数结束时结束,所以指向已经销毁的内存会造成野指针,所以函数内局部变量地址不应作为返回值。
int* getLocalPtr()
{
int x = 10;//局部变量,在函数结束时销毁
return &x; // 错误!返回局部变量的地址
}
int main()
{
int* ptr = getLocalPtr(); // ptr指向已销毁的内存
*ptr = 20; // 野指针访问
}
三、指针与数组
在 C 语言中,数组名在大多数表达式中会被隐式转换为指向数组首元素的指针。例如:
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr; // 等价于 int* ptr = &arr[0];
// 使用指针访问数组元素
printf("%d\n", *ptr); // 输出1
printf("%d\n", *(ptr+2)); // 输出3
printf("%d\n", ptr[2]); // 等价于*(ptr+2),输出3
// 使用数组名进行指针运算
printf("%d\n", *(arr+3)); // 输出4
四、指针运算
指针可以进行 加法 (+)、减法 (-)、自增 (++)、自减 (--) 运算,本质上是对内存地址的操作,但其结果取决于指针所指向的数据类型的大小来调整,下面详细说明。
1.加法与减法
当指针加上或减去一个整数 n时,实际的地址变化量为n*sizeof(数据类型)。
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr; // arr的地址是随机未被使用的,这里假设arr的起始地址是1000,int占4字节
printf("%d\n", &arr);//输出为1000
printf("%d\n", ptr); //因为指针指向数组arr,所以输出也为1000
ptr = ptr + 2;
printf("%d\n", ptr); // 此时ptr的值变为1000 + 2*4 = 1008
printf("%d\n",*ptr); //因为指针进行了运算,地址变到了1008,是arr[2]=30,所以输出结果为30
2.指针相减
两个指向同一数组的指针相减,得到的结果是它们之间的元素个数。
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr;
int* ptr2 = arr;
printf("%d\n",(ptr2+2)-ptr);//输出为2
指针在运算时需注意,指针运算必须在有效范围内进行,像数组边界外的指针运算是不被允许的。
指针运算的结果与数据类型的大小紧密相关,这体现了 C 语言 “类型安全” 的特性。
五、指针数组与数组指针
指针数组与数组指针在C语言中两个混淆的概念,它们的语法和用途有明显区别。
1.指针数组
指针数组:本质是数组,存储的内容是指针,以下是一个简单的指针数组的创建过程。
char* fruits[3] = { "Apple", "Banana", "Cherry" };
特点:每个元素都是一个指针,可指向不同的内存地址;常用于存储字符串数组(字符串以指针形式存储)或动态分配的内存块。
2.数组指针
数组指针:本质是指针,是一个指向一整个数组的指针,以下是一个简单的数组指针的创建过程。
int arr[5] = {10, 20, 30, 40, 50};
int (*ptr1)[5] = &arr; // 指向数组arr的指针
特点:指针偏移会跳过整个数组(步长为size *sizeof(type))。举个简单的例子
int arr[5] = {10, 20, 30, 40, 50};
int (*ptr1)[5] = &arr; // 指向数组arr的指针
printf("%d\n",*(*(&arr+1)-1)); // 输出为50
当对数组名进行偏移(&arr+1)时,偏移量会按整个数组的大小计算,因此偏移后得到的地址会指向数组末尾后的位置。此时对该地址减 1,指针会回退到数组的最后一个元素,再解引用就能获取该元素的值。
3.对比总结
综上所述,可以得出以下表格:
指针数组 | 数组指针 |
---|---|
type* array[size]; | type (*ptr)[size]; |
数组,元素为指针 | 指针,指向数组 |
常用于存储多个指针(如字符串数组) | 常用于操作多维数组或传递数组参数 |
指针运算步长为sizeof(type*) | 指针运算步长为size * sizeof(type) |
六、指针函数与函数指针
1.指针函数
指针函数:本质是一个函数,是指返回一个指针的函数,返回的是某个类型的指针,下面是一个指针函数的示例代码。
#include <stdio.h>
//
// 定义一个函数,返回指向静态数组的指针
// 静态数组在函数结束后不会被销毁,因此可以安全地返回其地址
// 指针函数原型:返回值类型* 函数名(参数列表);
int* get_array()
{
static int arr[5] = {1, 2, 3, 4, 5}; // 静态局部数组,初始化为{1, 2, 3, 4, 5}
return arr; // 返回数组的地址
}
int main()
{
int* ptr = get_array(); // 调用get_array函数,获取数组的指针并存储到ptr中
for (int i = 0; i < 5; i++)
{
printf("%d ", ptr[i]); // 打印当前数组元素的值
}
return 0;
}
2.函数指针
函数指针:本质是一个指针,是指向函数的指针变量,它可以像普通函数一样,用于调用函数以及传递函数参数,以下是一个函数指针的示例代码。
// 定义一个函数add,用于计算两个整数的和
int add(int a, int b) {
return a + b;
}
int main() {
// 声明一个函数指针func_ptr,指向返回值类型为int且接受两个int参数的函数
int (*func_ptr)(int, int);
func_ptr = add; // 将函数指针func_ptr指向add函数
printf("%d\n", func_ptr(3, 4)); // 使用函数指针调用add函数,并输出结果
return 0;
}
3.对比总结
指针函数 | 函数指针 |
---|---|
本质是函数 | 本质是指针 |
返回类型是指针 | 存储函数的地址 |
写法:int* func() | 写法:int (*prt)() |
用途:返回动态数据 | 用途:函数回调、函数表 |
内容还在持续更新中。。