通俗易懂:指针(二)传值调用与传址调用说明,指针和数组的关系,特殊指针类型的介绍:指针数组和数组指针,函数指针和函数指针数组以及字符指针

传值调用与传址调用

在C语言编程中,理解指针以及其在函数调用中的作用是至关重要的。本文将深入探讨C语言中的两种函数参数传递方式:传值调用(Call by Value)和传址调用(Call by Reference),以及它们对指针的影响。

传值调用(Call by Value)

在传值调用中,函数的实参(actual parameter)将其值复制给形参(formal parameter)。这意味着函数内部对形参的修改不会影响到实参。(即形参只是实参的一份临时拷贝,在进入函数increment时单独开辟一份空间给形参

示例代码:

#include <stdio.h>

void increment(int value) {
    value = value + 1;
    printf("Inside increment: %d\n", value);
}

int main() {
    int a = 10;
    increment(a);
    printf("In main: %d\n", a);
    return 0;
}

输出:

Inside increment: 11
In main: 10

在这个例子中,尽管我们在increment函数中增加了value的值,但这个改变并没有反映到主函数main中的变量a上。这是因为increment函数仅仅是用a的值来初始化它自己的局部变量value

传址调用(Call by Reference)

与传值调用不同,传址调用允许函数直接修改实参的值。在C语言中,我们通过传递参数的地址来实现这一点,通常使用指针

示例代码:

#include <stdio.h>

void increment(int *valuePtr)
{
    *valuePtr = *valuePtr + 1;
    printf("Inside increment: %d\n", *valuePtr);
}

int main() 
{
    int a = 10;
    increment(&a);
    printf("In main: %d\n", a);
    return 0;
}

在这个例子中,我们传递了变量a的地址到increment函数

 increment(&a);

并且需要increment函数用指针来接收(即int* 类型的指针)(传址/值调用时,两边(main和需要传入的函数中变量)的名字可以不同

void increment(int *valuePtr)

输出:

Inside increment: 11
In main: 11

这使得increment函数可以直接在内存中找到并修改a的值。因此,当我们在main函数中打印a的值时,我们看到它已经被修改。

指针的重要性

在C语言中,指针是实现传址调用的关键。指针存储变量的内存地址,允许函数和其他部分的代码直接访问和修改存储在特定地址的数据。

传值与传址的选择

选择传值还是传址通常取决于以下因素:

  • 性能:如果函数需要处理大型数据结构,如大数组或结构体,传址会更高效,因为它避免了复制大量数据。
  • 安全性:传值可以保护原始数据不被无意修改,因为它仅仅传递了数据的副本。
  • 功能需求:如果需要函数来修改调用者中的数据,传址是必须的。

结论

理解传值调用和传址调用对于编写可靠和高效的C语言代码至关重要。它们各有优势和用途,合理选择可以让你的程序更加健壮。记住,传值是安全的,但可能低效;传址则可以高效,但需要更谨慎地处理数据。

数组的本质

在C语言中,数组是一系列相同类型数据的集合。当我们声明一个数组时,比如 int arr[10];,我们在内存中预留了一段连续的空间,可以存放10个整数。数组的名字(在这个例子中是 arr)可以被看作是数组首元素的地址。

指针与数组的关系

指针可以指向数组的一个元素,也可以指向整个数组的开始。这意味着指针和数组在很多情况下可以互换。例如:

int arr[] = {1, 2, 3, 4, 5};
int *p = arr; // p指向数组的第一个元素

在这里,p 是一个整型指针,它指向 arr 的第一个元素。由于数组的元素在内存中是连续存储的,因此我们可以通过指针来遍历整个数组:

for (int i = 0; i < 5; i++) 
{
    printf("%d ", *(p + i));
}

这段代码将会输出数组 arr 的所有元素。

指针运算和数组索引

指针运算是数组理解的另一个关键点。因为数组的连续性质,指针加上一个整数会移动到数组中相应的位置。这就是为什么 p + i 可以用来访问 arr 中的第 i 个元素。实际上,arr[i] 在C语言中完全等同于 *(arr + i)

这里就可以解释在上一章中aar和&arr[0]是等价的问题了(*与&互相抵消

因为&arr[0]=&*(arr+0)=arr

指针和多维数组

当我们使用多维数组时,指针的概念也可以扩展。例如,一个二维数组 int arr[3][2] 可以通过一个指向指针的指针(也就是二级指针)来访问:

int arr[3][2] = {{1, 2}, {3, 4}, {5, 6}};
int (*ptr)[2] = arr;
int arr[3][2] = {{1, 2}, 
                 {3, 4}, 
                 {5, 6}};

arr[3][2]可以理解为一个一维数组中含有3个元素(即三行),而每一个元素中又含有2个元素(即两列)

在一维数组和指针中,在ptr指针后面不需要加上[ ],那为什么二维数组的时候要呢?

int arr[6] = {1, 2, 3, 4, 5, 6};
int *ptr = arr;

原因就是二维数组多了一个列的概念,本来{1,2}是一个元素,但是一个元素里面又有两个(小)元素,所以为了访问的清楚,我们在使用指针时也必须指明这个指针指向的数组的全貌(即需要告知一个元素中还有几个(小)元素)。

所以在这里,ptr 是一个指向含有两个整数(一个元素中含有两个整数的意思)的数组的指针。通过适当的指针运算,我们同样可以遍历整个二维数组。

那为什么是int (*ptr)[2] = arr;括号又是什么意思呢?

因为是指针所以用()保证先和*结合表明是指针类型,在接上[ 2],表示是一个元素中含有两个整数的意思。

要使用ptr来遍历arr数组中的所有元素,你可以使用两层循环。外层循环遍历行,内层循环遍历列。这里是这样做的:

#include <stdio.h>

int main() {
    int arr[3][2] = {{1, 2}, {3, 4}, {5, 6}};
    int (*ptr)[2] = arr;

    for (int i = 0; i < 3; ++i) {        // 外层循环遍历行
        for (int j = 0; j < 2; ++j) {    // 内层循环遍历列
            printf("%d ", ptr[i][j]);     // 使用ptr访问元素
        }
        printf("\n");
    }

    return 0;
}

在这个代码中:

  • ptr[i]是指向第i行的指针。
  • ptr[i][j]是访问第i行第j列的元素。

当你运行这段代码时,它会输出二维数组arr中的所有元素:

1 2
3 4
5 6

结论

通过指针,我们不仅可以访问和操作数组中的单个元素,还可以处理整个数组结构。在C语言中,理解指针和数组的关系对于编写高效和优雅的代码至关重要。

字符指针

在C语言中,字符串通常是通过字符数组来处理的,但是字符指针在这里发挥着至关重要的作用。字符指针提供了一种灵活的方式来操作和传递字符串。现在,让我们深入了解字符指针。

字符指针的定义和初始化

字符指针就是指向字符类型数据的指针。它可以指向单个字符,也可以指向字符数组(字符串)的第一个字符。例如:

char *str = "Hello, World!";

在这里,str 是一个字符指针,指向字符串字面量 "Hello, World!" 的第一个字符 'H'。字符串字面量在内存中是一个常量字符数组,我们可以通过字符指针来遍历或者读取这个数组。

注意常量字符数组无法被修改

char *str = "Hello, World!"; // str 指向字符串字面量,它是不可修改的
str[0] = 'h'; // 未定义行为:可能会导致运行时错误

字符指针与字符数组的区别

虽然字符数组和字符指针在很多情况下可以互换,但它们之间存在重要的区别。当你使用字符数组时,数组的大小是固定的,并且数组名代表的是整个数组的内存位置。当使用字符指针时,你可以更改指针的指向,使其指向不同的字符串。

字符指针的操作

通过字符指针,我们可以轻松地遍历字符串中的每个字符,只需简单地递增指针即可:

char *str = "Hello, World!";
while (*str) 
{
    putchar(*str++);
}

这段代码将打印出 "Hello, World!",因为 str 会逐个字符地移动,直到遇到字符串结束的空字符 \0

字符指针的常见用途

字符指针在很多标准函数中都有应用,例如 strcpy(), strcat(), strcmp(), 和 strlen() 等。这些函数通过字符指针来接收和返回字符串。

例如,strlen() 函数的原型如下:

size_t strlen(const char *str);

这里,str 是一个指向常量字符的指针,表示 strlen() 函数不会修改字符串本身

字符指针和内存管理

使用字符指针时,我们需要注意内存管理。如果我们有一个指向动态分配内存的字符指针,当不再需要这块内存时,我们必须使用 free() 函数来释放它,以避免内存泄漏

结论

字符指针是处理C语言中字符串的强大工具。它们提供了一种高效访问和操作字符串的方法,是许多字符串操作和函数实现的基础。

指针数组、数组指针、函数指针、函数指针数组

指针数组

定义: 指针数组是一个数组,其每个元素都是一个指针。想象一下,你有一排储物柜(数组),每个储物柜的抽屉(元素)可以放一把钥匙(指针),这把钥匙可以打开某个房间(指向的地址)。

代码示例

int *ptrArray[5]={arr1,arr2,arr3,arr4,arr5}; // 这是一个有5个整型指针的数组

通俗解释ptrArray 是一个可以存放5个不同整数地址的集合(即数组)。你可以使用 ptrArray[0] 来访问第一个指针(arr1,表示arr1第一个数组的首元素地址(即指针)),ptrArray[1] 来访问第二个指针,以此类推。

例如,如果 arr1 指向一个整数数组,那么 *ptrArray[0] 将给出该数组的第一个元素的值。如果你想访问 arr1 所指向的数组的第二个元素,则可以使用 *(ptrArray[0] + 1)

数组指针

定义: 数组指针是一个指向数组的指针。就像你有一张地图(指针),这张地图指向一个宝藏地点(数组的起始地址)。

例如:

int (*ptrToArray)[5] = &arr1;
 |     |          |
 |     |          |
 |     |      ptrToArray指向数组的元素个数
 | ptrToArray是数组指针变量名
 ptrToArray指向的数组的元素类型

在这个例子中,ptrToArray 是一个指向包含5个整数的数组的指针。这意味着 ptrToArray 指向的是一个整体的数组,而不是数组中的单个元素。并且类型为

int (*)[] 

通俗解释:ptrToArray 是一个指针,它指向一个有5个整数的数组。如果我们把数组比作一排金条,那么 ptrToArray 就是指向这整排金条的起点的地图。通过这个指针,你可以知道金条的位置以及这排金条的长度(即数组的大小)。

使用这个指针,你可以像这样访问数组的元素:

(*ptrToArray)[0] // 访问第一个元素
(*ptrToArray)[1] // 访问第二个元素
...
(*ptrToArray)[4] // 访问第五个元素

或者,由于数组名在大多数表达式中会被解释为指向其第一个元素的指针,你也可以使用指针算术来遍历数组:

int *p = (int *)ptrToArray;
for(int i = 0; i < 5; i++) {
    printf("%d\n", p[i]); // 输出数组的每个元素
}

在上面的循环中,p 是一个整型指针,它首先指向 ptrToArray 所指向的数组的第一个元素,然后通过增加 i 来遍历数组的每个元素。

函数指针

定义: 函数指针是指向函数的指针。想象你有一张餐厅的优惠券(指针),这张优惠券让你可以免费享用一个特定的菜品(函数)。

代码示例

int (*funcPtr)(int, int); // 这是一个指向函数的指针,该函数接受两个int参数并返回一个int

通俗解释funcPtr 可以被设置为指向任何接受两个整数参数并返回一个整数的函数。你可以通过 funcPtr(5, 3) 来调用它指向的函数,就像你使用优惠券来兑换菜品一样。

并且可以看出函数指针的类型

int(*)()

函数指针数组

定义: 函数指针数组是一个数组,其每个元素都是指向函数的指针。这就像你有一本菜谱(数组),里面有不同的食谱(指针),每个食谱教你如何准备一道菜(函数)。

代码示例

int (*funcPtrArray[3])(int, int); // 这是一个有3个元素的数组,每个元素都是指向函数的指针

通俗解释funcPtrArray 是一个集合,里面有3个可以调用不同函数的“优惠券”。你可以选择 funcPtrArray[0] 来调用第一个函数,funcPtrArray[1] 来调用第二个函数,等等。

区分和混淆点

区分

  • 指针数组:是数组,存放指针。
  • 数组指针:是指针,指向一个完整的数组。
  • 函数指针:是指针,指向函数。
  • 函数指针数组:是数组,存放函数指针。

混淆点

  • 指针数组 和 数组指针 容易混淆,因为它们的名字很相似。记住,指针数组强调的是“数组”(一系列指针),而数组指针强调的是“指针”(指向一个数组)。
  • 函数指针 和 函数指针数组 之间的混淆通常来自于它们都与函数有关。区分它们的关键在于,一个是单一的指针,可以调用一个函数;另一个是一个集合,可以存储多个这样的指针,每个都可以调用不同的函数。

记忆技巧

  • 对于 指针数组 和 数组指针,关注“数组”和“指针”哪个词在前。在前的词是实质所在(“指针数组”是一个数组,而“数组指针”是一个指针)。
  • 对于 函数指针 和 函数指针数组,同样的规则适用。如果“函数指针”在前,那么它是指向单个函数的指针;如果“数组”在前,那么它是多个函数指针的集合。

希望这篇文章能帮助你更好地理解C语言中的指针和数组关系的机制。如果觉得写的好的可以给作者一键三连,你有任何疑问或想要深入讨论,欢迎在评论区留言!

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值