C语言指针

对于C语言指针这一部分内容仅供参考可能会有出错的地方,也可能会有不足之处,希望大家可以积极地指出,会认真的汲取经验。

指针是 C 语言的核心特性之一,也是 C 语言的难点所在。掌握指针能够帮助我们更高效地操作内存,实现复杂的数据结构和算法。

一、指针的基本概念

指针是一个变量,其值为另一个变量的内存地址。通过指针,我们可以间接访问和修改内存中的数据。

1.1 指针变量的定义与初始化

指针变量的定义格式:数据类型 *指针名;

int a = 10;      // 定义普通变量a
int *p;          // 定义int类型的指针p
p = &a;          // 将a的地址赋值给指针p,&是取地址运算符
int *q = &a;     // 定义的同时初始化

1.2 指针的解引用

通过*运算符可以访问指针所指向的变量的值,这个过程称为解引用:

#include <stdio.h>

int main() {
    int num = 25;
    int *p = &num;  // p指向num的地址
    
    printf("num的值: %d\n", num);
    printf("num的地址: %p\n", &num);
    printf("指针p的值: %p\n", p);  // 指针p存储的是num的地址
    printf("p指向的值: %d\n", *p);  // 通过*解引用获取值
    
    *p = 30;  // 通过指针修改所指向变量的值
    printf("修改后num的值: %d\n", num);
    
    return 0;
}

这段代码的输出会显示:

  • num 的地址和指针 p 的值相同
  • 通过*p可以获取和修改 num 的值

二、指针与数组

数组名本质上是数组首元素的地址,这使得指针和数组有着紧密的联系。

2.1 指针访问数组元素

#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;  // 数组名就是首元素地址,等价于&arr[0]
    
    // 两种访问方式等价
    printf("arr[0] = %d, *p = %d\n", arr[0], *p);
    printf("arr[1] = %d, *(p+1) = %d\n", arr[1], *(p+1));
    
    // 使用指针遍历数组
    printf("数组元素: ");
    for(int i = 0; i < 5; i++) {
        printf("%d ", *(p + i));  // p+i指向第i个元素
    }
    printf("\n");
    
    return 0;
}

2.2 指针的算术运算

指针可以进行特殊的算术运算,其运算结果与指针指向的数据类型有关:

#include <stdio.h>

int main() {
    int  a = 10;
    char b = 'A';
    int  *p_int = &a;
    char *p_char = &b;
    
    printf("int指针初始地址: %p\n", p_int);
    printf("int指针+1后地址: %p (差值为4字节,int类型大小)\n", p_int + 1);
    
    printf("\nchar指针初始地址: %p\n", p_char);
    printf("char指针+1后地址: %p (差值为1字节,char类型大小)\n", p_char + 1);
    
    return 0;
}

这段代码展示了指针算术运算的特点:指针加 1 并不是简单的地址加 1,而是加上所指向数据类型的大小(字节数)。

三、指针与函数

指针在函数中的应用主要体现在两个方面:传递地址参数和返回指针。

3.1 指针作为函数参数

通过指针传递参数,可以在函数内部修改外部变量的值:

#include <stdio.h>

// 通过指针交换两个变量的值
void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    int a = 10, b = 20;
    
    printf("交换前: a = %d, b = %d\n", a, b);
    swap(&a, &b);  // 传递变量地址
    printf("交换后: a = %d, b = %d\n", a, b);
    
    return 0;
}

3.2 函数返回指针

函数可以返回指针,但要注意不能返回局部变量的指针,因为局部变量在函数结束后会被释放:

#include <stdio.h>
#include <stdlib.h>  // 包含malloc函数

// 正确:返回动态分配内存的指针
int *createArray(int size) {
    int *arr = (int *)malloc(size * sizeof(int));
    if(arr == NULL) {
        printf("内存分配失败\n");
        exit(1);
    }
    return arr;
}

int main() {
    int *nums = createArray(5);
    
    // 使用数组
    for(int i = 0; i < 5; i++) {
        nums[i] = i * 10;
    }
    
    printf("数组元素: ");
    for(int i = 0; i < 5; i++) {
        printf("%d ", nums[i]);
    }
    
    // 释放动态分配的内存
    free(nums);
    return 0;
}

3.3 函数指针

#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 choice, x = 10, y = 5;
    
    printf("选择操作: 1-加法, 2-减法, 3-乘法: ");
    scanf("%d", &choice);
    
    switch(choice) {
        case 1:
            operation = add;
            break;
        case 2:
            operation = subtract;
            break;
        case 3:
            operation = multiply;
            break;
        default:
            printf("无效选择\n");
            return 1;
    }
    
    int result = operation(x, y);
    printf("结果: %d\n", result);
    
    return 0;
}

三(2). 动态内存分配

3.1 malloc, calloc, realloc, free

#include <stdio.h>
#include <stdlib.h>

int main() {
    int n;
    printf("请输入数组大小: ");
    scanf("%d", &n);
    
    // 使用malloc分配内存
    int *arr = (int*)malloc(n * sizeof(int));
    if(arr == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    
    // 初始化数组
    for(int i = 0; i < n; i++) {
        arr[i] = i * 10;
    }
    
    printf("分配的数组:\n");
    for(int i = 0; i < n; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    
    // 重新分配内存
    int new_size = n + 3;
    int *new_arr = (int*)realloc(arr, new_size * sizeof(int));
    if(new_arr == NULL) {
        printf("内存重新分配失败!\n");
        free(arr);
        return 1;
    }
    
    arr = new_arr;
    // 初始化新分配的元素
    for(int i = n; i < new_size; i++) {
        arr[i] = i * 10;
    }
    
    printf("\n重新分配后的数组:\n");
    for(int i = 0; i < new_size; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    
    // 释放内存
    free(arr);
    
    return 0;
}

四、多级指针

多级指针是指向指针的指针,常用于处理二维数组或需要修改指针本身的场景:

#include <stdio.h>

int main() {
    int a = 100;
    int *p = &a;       // 一级指针,指向int变量
    int **pp = &p;     // 二级指针,指向一级指针
    int ***ppp = &pp;  // 三级指针,指向二级指针
    
    printf("a的值: %d\n", a);
    printf("通过p访问: %d\n", *p);
    printf("通过pp访问: %d\n", **pp);
    printf("通过ppp访问: %d\n", ***ppp);
    
    printf("\n地址关系:\n");
    printf("&a = %p, p = %p\n", &a, p);
    printf("&p = %p, pp = %p\n", &p, pp);
    printf("&pp = %p, ppp = %p\n", &pp, ppp);
    
    return 0;
}

五、指针与字符串

在 C 语言中,字符串是以\0结尾的字符数组,通常使用字符指针来操作字符串:

#include <stdio.h>

int main() {
    char str[] = "Hello, World!";  // 字符数组
    char *ptr = "Hello, Pointer!";  // 字符串常量,指针指向首字符
    
    // 使用指针遍历字符串
    printf("字符串内容: ");
    while(*ptr != '\0') {
        printf("%c", *ptr);
        ptr++;  // 指针移动到下一个字符
    }
    printf("\n");
    
    return 0;
}

六、指针的常见错误与注意事项

1.** 野指针 **:未初始化的指针,指向未知内存区域

int *p;  // 野指针,危险!
*p = 10; // 未定义行为,可能导致程序崩溃

2.** 空悬指针 **:指向已释放内存的指针

int *p = (int*)malloc(sizeof(int));
free(p);  // 释放内存
*p = 10;  // 错误!p现在是空悬指针

3.** 指针越界 **:访问超出数组范围的内存

int arr[5] = {1,2,3,4,5};
int *p = arr;
*(p + 10) = 100;  // 越界访问,可能导致数据损坏

4.** 正确使用方法 **:

  • 指针定义时初始化,不确定指向时设为NULL
  • 释放内存后将指针设为NULL
  • 访问指针前检查是否为NULL
  • 避免指针越界访问

七. 指针与结构体

7.1 结构体指针

#include <stdio.h>
#include <string.h>

// 定义结构体
typedef struct {
    char name[50];
    int age;
    float salary;
} Person;

// 函数接受结构体指针作为参数
void printPerson(const Person *p) {
    printf("姓名: %s\n", p->name);
    printf("年龄: %d\n", p->age);
    printf("薪资: %.2f\n", p->salary);
}

void increaseSalary(Person *p, float percentage) {
    p->salary += p->salary * percentage / 100;
}

int main() {
    Person person1;
    Person *ptr = &person1;
    
    // 通过指针访问结构体成员
    strcpy(ptr->name, "张三");
    ptr->age = 30;
    ptr->salary = 5000.0;
    
    printf("原始信息:\n");
    printPerson(ptr);
    
    increaseSalary(ptr, 10);
    printf("\n加薪后信息:\n");
    printPerson(ptr);
    
    return 0;
}

八. 常见指针错误及避免方法

8.1 常见错误示例

#include <stdio.h>
#include <stdlib.h>

void demonstrateErrors() {
    // 错误1: 未初始化的指针
    int *ptr1;
    // *ptr1 = 10;  // 错误:未初始化的指针
    
    // 错误2: 空指针解引用
    int *ptr2 = NULL;
    // *ptr2 = 20;  // 错误:空指针解引用
    
    // 错误3: 野指针
    int *ptr3;
    {
        int temp = 30;
        ptr3 = &temp;
    }
    // *ptr3 = 40;  // 错误:temp已超出作用域
    
    // 错误4: 内存泄漏
    int *leak = (int*)malloc(sizeof(int));
    *leak = 50;
    // 忘记free(leak);
    
    // 正确做法
    int *correct = (int*)malloc(sizeof(int));
    if(correct != NULL) {
        *correct = 60;
        // 使用完毕后释放内存
        free(correct);
        correct = NULL;  // 避免野指针
    }
}

int main() {
    demonstrateErrors();
    return 0;
}

十. 综合示例

10.1 指针综合应用

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义学生结构体
typedef struct {
    char name[50];
    int score;
} Student;

// 比较函数用于排序
int compareStudents(const void *a, const void *b) {
    const Student *studentA = (const Student*)a;
    const Student *studentB = (const Student*)b;
    return studentB->score - studentA->score;  // 降序排列
}

// 动态创建学生数组
Student* createStudents(int count) {
    return (Student*)malloc(count * sizeof(Student));
}

// 输入学生信息
void inputStudents(Student *students, int count) {
    for(int i = 0; i < count; i++) {
        printf("输入第%d个学生的姓名: ", i + 1);
        scanf("%s", students[i].name);
        printf("输入第%d个学生的分数: ", i + 1);
        scanf("%d", &students[i].score);
    }
}

// 打印学生信息
void printStudents(const Student *students, int count) {
    printf("\n学生信息:\n");
    printf("%-20s %-10s\n", "姓名", "分数");
    printf("-------------------- ----------\n");
    for(int i = 0; i < count; i++) {
        printf("%-20s %-10d\n", students[i].name, students[i].score);
    }
}

int main() {
    int studentCount;
    
    printf("请输入学生人数: ");
    scanf("%d", &studentCount);
    
    // 动态分配内存
    Student *students = createStudents(studentCount);
    if(students == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    
    // 输入数据
    inputStudents(students, studentCount);
    
    // 排序
    qsort(students, studentCount, sizeof(Student), compareStudents);
    
    // 输出结果
    printStudents(students, studentCount);
    
    // 计算平均分
    int total = 0;
    for(int i = 0; i < studentCount; i++) {
        total += students[i].score;
    }
    printf("\n平均分: %.2f\n", (float)total / studentCount);
    
    // 释放内存
    free(students);
    
    return 0;
}

指针是C语言中最强大但也最容易出错的功能之一。通过本文的学习,你应该掌握:

  1. 指针的基本概念和操作

  2. 指针与数组、函数的关系

  3. 动态内存管理

  4. 多级指针的使用

  5. 指针与结构体的结合

  6. 常见指针错误及避免方法

记住以下最佳实践:

  • 总是初始化指针

  • 检查malloc/calloc/realloc的返回值

  • 使用完毕后及时释放内存

  • 避免野指针和空指针解引用

  • 使用const修饰符保护数据

通过不断练习和实践,你将能够熟练运用指针,编写出高效、安全的C语言程序。对于编程语言的学习是没有止境的,对于编程语言的理解也是随着时间的推移发生变化的,上部分关于C语言的理解可能会有疏漏。希望大家积极的指出。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值