传值调用与传址调用
在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语言中的指针和数组关系的机制。如果觉得写的好的可以给作者一键三连,你有任何疑问或想要深入讨论,欢迎在评论区留言!