C语言学习2-指针的使用、const 指针

指针

一、指针基础概念

1.1 什么是指针?

指针是一种特殊变量,用于存储内存地址

1.2 为什么需要专门的指针类型?

虽然地址本质上是整数,但使用特定类型可以:

  • 明确指向的数据类型
  • 支持指针算术运算
  • 帮助编译器进行类型检查

1.3 基本指针类型

char*           // char 类型变量的地址
short*          // short 类型变量的地址  
int*            // int 类型变量的地址
float*          // float 类型变量的地址
double*         // double 类型变量的地址
unsigned char*  // unsigned char 类型变量的地址
XXX*            // 任意 XXX 类型变量的地址

二、指针的基本操作

2.1 指针的定义与使用

#include <stdio.h>

int main() {
    int *p;        // 定义指针变量 p
    int i = 3;     // 定义整型变量 i
    
    p = &i;        // p 保存 i 的地址(p 指向 i)
    
    int j;
    j = *p;        // 通过 *p 访问 i 的值
    
    printf("i = %d, j = %d, *p = %d\n", i, j, *p);
    return 0;
}

2.2 理解指针语法

  • int *p; - p 是指针变量名
  • int* - 数据类型,表示 p 存放 int 变量的地址
  • &i - 取地址运算符,获取 i 的内存地址
  • *p - 解引用运算符,访问 p 指向的内存

关键理解*pi 访问的是同一块内存,可以互相替换

2.3 指针声明风格

以下三种写法完全等价:

int* p;    // 风格1:强调 p 是 int* 类型
int * p;   // 风格2:空格分隔  
int *p;    // 风格3:强调 *p 是 int 类型

三、指针的重要规则

3.1 类型安全规则

int a = 10;
int* pa = &a; 

// ❌ 错误:不同类型指针不能互相赋值
double* pd = pa;     // 错误!int* → double*
char c = 'A';
float* pf = &c;      // 错误!char* → float*

原因:不同类型的数据在内存中的表示方式和大小不同。

3.2 星号操作:通过地址访问内存

int a = 0x123;
int *p = &a;        // p 指向 a 的内存

*p += 2;            // 修改 p 指向的内存:a = 0x125

int b = *p;         // 读取 p 指向的值:b = 0x125
int c = *p + 2;     // 使用指针值运算:c = 0x127

四、指针作为函数参数

4.1 基本用法

#include <stdio.h>

void modify_value(int *p) {
    *p = 10;  // 修改指针指向的值
}

int main() {
    int a = 0;
    modify_value(&a);
    printf("a = %d\n", a);  // 输出:a = 10
    return 0;
}

4.2 实际应用:交换两个变量

#include <stdio.h>

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;
    printf("交换前: x=%d, y=%d\n", x, y);
    
    swap(&x, &y);
    printf("交换后: x=%d, y=%d\n", x, y);
    
    return 0;
}

4.3 指针参数的优点

  • 读取上一层函数的变量值
  • 修改上一层函数的变量值
  • ✅ 实现函数返回多个值
  • 高效传递大型数据结构

五、指针与数组

5.1 数组名即指针

#include <stdio.h>

// 两种完全等价的函数声明
int sum_array(int *arr, int length) {
// int sum_array(int arr[], int length) {  // 等价写法
    int sum = 0;
    for(int i = 0; i < length; i++) {
        sum += arr[i];  // arr[i] 等价于 *(arr + i)
    }
    return sum;
}

int main() {
    int numbers[] = {1, 2, 3, 4, 5};
    
    // 不同的调用方式
    int total1 = sum_array(numbers, 5);       // 整个数组
    int total2 = sum_array(numbers, 3);       // 前3个元素
    int total3 = sum_array(numbers + 1, 3);   // 从第2个元素开始
    
    printf("结果: %d, %d, %d\n", total1, total2, total3);
    return 0;
}

重要原则:传递数组时,必须同时传递长度信息!

六、const 指针

6.1 const 指针的基本概念

// 1. 普通指针:可读可写
int a = 10;
int* p = &a;
*p = 20;        // ✅ 可以修改
int b = *p;     // ✅ 可以读取

// 2. const 指针:只读
int a = 10;
const int* p = &a;
// *p = 20;     // ❌ 错误:不能修改
int b = *p;     // ✅ 可以读取

6.2 const 指针的实际应用

#include <stdio.h>

// 使用 const 保护输入数据不被修改
int calculate_sum(const int* arr, int length) {
    int sum = 0;
    for(int i = 0; i < length; i++) {
        sum += arr[i];     // ✅ 可以读取
        // arr[i] = 0;    // ❌ 错误:不能修改
    }
    return sum;
}

void process_data(const int* input, int* output, int length) {
    for(int i = 0; i < length; i++) {
        output[i] = input[i] * 2;  // ✅ 读取input,写入output
    }
}

int main() {
    int data[] = {1, 2, 3, 4, 5};
    int result[5];
    
    int sum = calculate_sum(data, 5);
    process_data(data, result, 5);
    
    printf("总和: %d\n", sum);
    return 0;
}

6.3 const 使用场景总结

  • 函数参数:表明该参数是输入参数,函数内不会修改
  • 保护数据:防止意外修改重要数据
  • 提高代码可读性:明确标识数据的只读属性

注意:const 只限制通过指针修改内存,不影响指针本身的算术运算。

七、指针的安全使用

7.1 空指针(Null Pointer)

int *p = NULL;        // 或 int *p = 0;

// ❌ 危险:解引用空指针
// printf("%d\n", *p); 

// ✅ 安全做法:使用前检查
if (p != NULL) {
    printf("%d\n", *p);
}

7.2 空指针的实际应用

#include <stdio.h>

void find_values(const int* arr, int length, 
                 int* pmin, int* pmax) {
    if (length == 0) return;
    
    int min = arr[0], max = arr[0];
    for (int i = 1; i < length; i++) {
        if (arr[i] < min) min = arr[i];
        if (arr[i] > max) max = arr[i];
    }
    
    // 安全赋值:只在指针非空时写入
    if (pmin) *pmin = min;
    if (pmax) *pmax = max;
}

int main() {
    int data[] = {5, 2, 8, 1, 9};
    int min_val, max_val;
    
    // 获取最小值和最大值
    find_values(data, 5, &min_val, &max_val);
    printf("最小值: %d, 最大值: %d\n", min_val, max_val);
    
    // 只获取最小值
    find_values(data, 5, &min_val, NULL);
    printf("最小值: %d\n", min_val);
    
    return 0;
}

7.3 野指针(Wild Pointer)

int *p;            // ❌ 危险:未初始化的指针(野指针)
// printf("%d", *p); // 指向随机内存,可能导致崩溃

// ✅ 好习惯:始终初始化指针
int *safe_ptr = NULL;

7.4 数组越界

int arr[4] = {1, 2, 3, 4};
int *p = arr;

// p += 4;        // ❌ 越界:指向数组后的位置
// *p = 12;       // 修改未知内存,极其危险!

// ✅ 安全做法:检查边界
if (p >= arr && p < arr + 4) {
    *p = 12;
}

7.5 悬挂指针(Dangling Pointer)

#include <stdio.h>

int main() {
    int *p = NULL;
    
    {   // 代码块开始
        int a = 10;    // a 的生命期开始
        p = &a;        // p 指向 a
    }   // 代码块结束,a 的生命期结束
    
    // *p = 11;       // ❌ 危险:p 指向已失效的内存
    return 0;
}

八、安全使用指针的黄金法则

8.1 初始化规则

int *p = NULL;           // ✅ 初始化为空
int *q = &valid_var;     // ✅ 初始化为有效地址

8.2 使用前检查

void safe_function(int *ptr) {
    if (ptr == NULL) {
        printf("错误:空指针\n");
        return;
    }
    // 安全使用指针
    *ptr = 100;
}

8.3 生命周期管理

// ✅ 安全:指向全局变量
int global_var;
int *safe_ptr = &global_var;

// ✅ 安全:指向动态分配内存
int *dynamic_ptr = malloc(sizeof(int));

// ❌ 危险:指向即将失效的局部变量
int* dangerous_function() {
    int local = 10;
    return &local;  // 错误!
}

九、总结

9.1 指针的核心价值

  • 多返回值:通过指针参数返回多个值
  • 高效传参:传递地址比传递大对象更高效
  • 动态内存:实现动态数据结构和内存管理
  • 硬件访问:直接操作内存映射的硬件寄存器

9.2 安全指针使用检查清单

  1. ✅ 指针是否已初始化?
  2. ✅ 指针是否指向有效内存?
  3. ✅ 是否检查了空指针?
  4. ✅ 数组访问是否越界?
  5. ✅ 指向的内存生命周期是否有效?
  6. ✅ 是否需要使用 const 保护数据?

掌握这些原则,你就能安全有效地使用C语言指针这个强大工具!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容