C语言指针深度解析:函数参数与数组关系详解 - 教程

一、指针作为函数参数

1. 值传递(Pass by Value)

核心机制

  • 实参将值复制给形参

  • 形参是实参的一个副本

  • 在被调函数内部修改形参,不会影响实参的值

适用场景:被调函数只需要读取实参内容,不需要修改实参

代码示例

#include 
// 值传递函数示例
void swap_by_value(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    printf("函数内: a=%d, b=%d\n", a, b); // 值已交换
}
int main() {
    int x = 10, y = 20;
    printf("交换前: x=%d, y=%d\n", x, y);
    swap_by_value(x, y);
    printf("交换后: x=%d, y=%d\n", x, y); // 值未改变
    return 0;
}

内存示意图

主调函数栈帧:
x = 10, y = 20
被调函数栈帧(swap_by_value):
a = 10 (x的副本), b = 20 (y的副本)
交换后:a = 20, b = 10
返回主函数后:
x = 10, y = 20 (原值不变)

2. 地址传递(Pass by Address)

核心机制

  • 实参将自己的地址传递给形参

  • 形参是一个指向实参的指针

  • 通过间接访问方式读写实参内容

适用场景:被调函数需要读取和修改实参内容

代码示例

#include 
// 地址传递函数示例
void swap_by_address(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
void modify_array(int *arr, int size) {
    for(int i = 0; i < size; i++) {
        arr[i] *= 2; // 通过指针修改数组元素
    }
}
int main() {
    int x = 10, y = 20;
    int numbers[5] = {1, 2, 3, 4, 5};
    printf("交换前: x=%d, y=%d\n", x, y);
    swap_by_address(&x, &y);
    printf("交换后: x=%d, y=%d\n", x, y); // 值已交换
    modify_array(numbers, 5);
    printf("数组修改后: ");
    for(int i = 0; i < 5; i++) {
        printf("%d ", numbers[i]); // 所有元素乘以2
    }
    return 0;
}

内存示意图

主调函数栈帧:
x=10(地址0x1000), y=20(地址0x1004)
被调函数栈帧(swap_by_address):
a=0x1000(指向x), b=0x1004(指向y)
通过*a和*b直接操作x和y的内存空间

二、指针和数组的关系

1. 数组名的本质

核心概念:数组名是指向第一个元素的指针常量

int a[5] = {1, 2, 3, 4, 5};
int *p = a; // 等价于 int *p = &a[0]

2. 数组与指针的三个关键区别

特性

数组 int a[5]

指针 int *p

sizeof运算

sizeof(a) = 20(5×4字节)

sizeof(p) = 8(指针大小)

&运算符

&a类型为 int(*)[5](数组指针)

&p类型为 int**(二级指针)

可变性

指针常量,地址不可变

指针变量,地址可变

详细解释

int a[5] = {1, 2, 3, 4, 5};
int *p = a;
// 1. sizeof区别
printf("sizeof(a)=%lu\n", sizeof(a)); // 输出20
printf("sizeof(p)=%lu\n", sizeof(p)); // 输出8(64位系统)
// 2. &运算符区别
printf("&a类型: %p\n", (void*)&a);   // 数组指针
printf("&p类型: %p\n", (void*)&p);   // 指针的指针
// 3. 可变性区别
p++;        // 合法,指针可以移动
// a++;     // 错误!数组名是常量

3. 字符数组和字符串的传递

字符串遍历的三种方式

#include 
// 方式1:数组下标法
void output_str1(char a[]) {
    int i = 0;
    while(a[i] != '\0') {
        putchar(a[i]);
        i++;
    }
    putchar('\n');
}
// 方式2:指针法(推荐)
void output_str2(char *a) {
    while(*a) {  // 等价于 while(*a != '\0')
        putchar(*a);
        a++;
    }
    putchar('\n');
}
// 方式3:指针偏移法
void output_str3(char *a) {
    int i = 0;
    while(*(a + i)) {
        putchar(*(a + i));
        i++;
    }
    putchar('\n');
}
int main() {
    char str[] = "Hello, World!";
    output_str1(str);
    output_str2(str);
    output_str3(str);
    return 0;
}

三、需要掌握的字符串函数实现

1. 字符串长度计算(mystrlen)

#include 
int mystrlen(const char *str) {
    int len = 0;
    while(*str++) {  // 遇到'\0'时循环结束
        len++;
    }
    return len;
}
// 测试
int main() {
    char str[] = "Hello";
    printf("长度: %d\n", mystrlen(str)); // 输出5
    return 0;
}

2. 字符串复制(mystrcpy)

#include 
char *mystrcpy(char *dest, const char *src) {
    char *ret = dest;  // 保存目标字符串起始地址
    while((*dest++ = *src++)) {
        ;  // 循环复制,包括'\0'
    }
    return ret;
}
// 测试
int main() {
    char src[] = "Hello";
    char dest[20];
    mystrcpy(dest, src);
    printf("复制结果: %s\n", dest);
    return 0;
}

3. 字符串连接(mystrcat)

#include 
char *mystrcat(char *dest, const char *src) {
    char *ret = dest;
    // 移动到dest的末尾
    while(*dest) {
        dest++;
    }
    // 追加src内容
    while((*dest++ = *src++)) {
        ;
    }
    return ret;
}
// 测试
int main() {
    char str1[20] = "Hello";
    char str2[] = " World!";
    mystrcat(str1, str2);
    printf("连接结果: %s\n", str1); // 输出"Hello World!"
    return 0;
}

四、关键总结

参数传递选择指南

场景

传递方式

理由

只读不写

值传递

安全,不影响实参

需要修改实参

地址传递

可以直接操作原始数据

大结构体/数组

地址传递

避免复制开销

数组名与指针关系总结

  • 相同点:都可以用下标[]*运算符访问元素

  • 不同点:数组名是常量,包含长度信息;指针是变量,只存储地址

  • 字符串处理:利用'\0'作为结束标志,可以避免传递长度参数

posted @ 2026-01-31 13:07  yangykaifa  阅读(2)  评论(0)    收藏  举报