C 中的指针 - 数组和字符串
0. 为什么是指针和数组?
在C语言中,指针和数组有着非常密切的关系。应该将它们放在一起讨论的原因是,使用数组表示法 ( arrayName[index]
) 可以实现的功能也可以使用指针实现,通常速度更快。
1. 一维数组
让我们看看当我们写的时候会发生什么int myArray[5];
。
从到 开始创建五个连续的内存块,其中包含垃圾值。每个块的大小为 4 字节。myArray[0]
myArray[4]
因此,如果 myArray[0] 的地址是100
(例如),则其余块的地址将为104
、108
、112
和116
。
现在,看看下面的代码 -
int prime[5] = {2,3,5,7,11};
printf("Result using &prime = %d\n",&prime);
printf("Result using prime = %d\n",prime);
printf("Result using &prime[0] = %d\n",&prime[0]);
/* Output */
Result using &prime = 6422016
Result using prime = 6422016
Result using &prime[0] = 6422016
那么 、&prime
、prime
和&prime[0]
都给出相同的地址,对吗?好吧,等待并阅读,因为你会感到惊讶(和困惑)。让我们尝试将&prime
、prime
和&prime[0]
分别递增 1。
printf("Result using &prime = %d\n",&prime + 1);
printf("Result using prime = %d\n",prime + 1);
printf("Result using &prime[0] = %d\n",&prime[0] + 1);
/* Output */
Result using &prime = 6422036
Result using prime = 6422020
Result using &prime[0] = 6422020
等等!为什么&prime + 1
结果与其他两个不同?为什么prime + 1
和&prime[0] + 1
仍然相等?让我们来回答这些问题。
1.prime
和&prime[0]
,都指向数组的第0个元素prime
。因此,数组的名称本身就是指向数组第 0 个元素的指针。
这里,两者都指向第一个大小为 4 字节的元素。当您向它们添加 1 时,它们现在指向数组中的第一个元素。因此地址增加了 4。
2.&prime
另一方面是指向大小为 5 的数组的指针int
。它存储数组的基地址prime[5]
,等于第一个元素的地址。然而,对其加 1 会导致地址增加 5 x 4 = 20 字节。
简而言之,arrayName
and&arrayName[0]
指向第 0 个元素,而&arrayName
指向整个数组。
我们可以使用下标变量访问数组元素,如下所示 -
int prime[5] = {2,3,5,7,11};
for( int i = 0; i < 5; i++)
{
printf("index = %d, address = %d, value = %d\n", i, &prime[i], prime[i]);
}
我们可以使用指针做同样的事情,它总是比使用下标更快。
int prime[5] = {2,3,5,7,11};
for( int i = 0; i < 5; i++)
{
printf("index = %d, address = %d, value = %d\n", i, prime + i, *(prime + i));
}
两种方法都会给出输出 -
index = 0, address = 6422016, value = 2
index = 1, address = 6422020, value = 3
index = 2, address = 6422024, value = 5
index = 3, address = 6422028, value = 7
index = 4, address = 6422032, value = 11
因此,
&arrayName[i]
和arrayName[i]
分别与arrayName + i
和*(arrayName + i)
相同。
2. 二维数组
二维数组是数组的数组。
int marks[5][3] = { { 98, 76, 89},
{ 81, 96, 79},
{ 88, 86, 89},
{ 97, 94, 99},
{ 92, 81, 59}
};
这里,marks
可以认为是一个包含 5 个元素的数组,每个元素都是包含 3 个整数的一维数组。让我们通过一系列程序来理解不同的下标表达式。
printf("Address of whole 2-D array = %d\n", &marks);
printf("Addition of 1 results in %d\n", &marks +1);
/* Output */
Address of whole 2-D array = 6421984
Addition of 1 results in 6422044
与一维数组一样,&marks
指向整个二维数组,marks[5][3]
。因此,增加 1(= 5 个数组 X 3 个整数,每个数组 X 4 个字节 = 60)会导致增加 60 个字节。
printf("Address of 0th array = %d\n", marks);
printf("Addition of 1 results in %d\n", marks +1);
printf("Address of 0th array =%d\n", &marks[0]);
printf("Addition of 1 results in %d\n", &marks[0] + 1);
/* Output */
Address of 0th array = 6421984
Addition of 1 results in 6421996
Address of 0th array = 6421984
Addition of 1 results in 6421996
如果marks
是一维数组,marks
则&marks[0]
指向第 0 个元素。对于二维数组,元素现在是一维数组。因此,marks
和&marks[0]
指向第 0 个数组(元素),加 1 指向第 1 个数组。
printf("Address of 0th element of 0th array = %d\n", marks[0]);
printf("Addition of 1 results in %d\n", marks[0] + 1);
printf("Address of 0th element of 1st array = %d\n", marks[1]);
printf("Addition of 1 results in %d\n", marks[1] + 1);
/* Output */
Address of 0th element of 0th array = 6421984
Addition of 1 results in 6421988
Address of 0th element of 1st array = 6421996
Addition of 1 results in 6422000
现在,差异来了。对于一维数组,marks[0]
将给出第 0 个元素的值。增量 1 将使值增加 1。
但是,在二维数组中,marks[0]
指向第 0 个数组的第 0 个元素。同样,marks[1]
指向第一个数组的第 0 个元素。增量 1 将指向第一个数组中的第一个元素。
printf("Value of 0th element of 0th array = %d\n", marks[0][0]);
printf("Addition of 1 results in %d", marks[0][0] + 1);
/* Output */
Value of 0th element of 0th array = 98
Addition of 1 results in 99
这是新的部分。marks[i][j]
给出第 i 个数组的第 j 个元素的值。对其进行增量会更改存储在 处的值marks[i][j]
。现在,让我们尝试marks[i][j]
用指针来写。
从我们之前的讨论中我们知道marks[i] + j
将指向第 j 个数组的第 i 个元素。取消引用它意味着该地址处的值。因此,marks[i][j]
等于*(marks[i] + j)
。
从我们对一维数组的讨论来看,marks[i]
与 相同*(marks + i)
。因此,marks[i][j]
可以写成*(*(marks + i) + j)
指针的形式。
以下是比较一维和二维数组的符号摘要。
表达 | 一维数组 | 二维阵列 |
---|---|---|
&ArrayName | 指向整个数组的地址,加1使地址增加1 x sizeof(ArrayName) | 指向整个数组的地址,加1使地址增加1 x sizeof(ArrayName) |
ArrayName | 指向 元素,加1增加元素0th 的地址1st | 指向元素(数组),加1增加元素(数组)0th 的地址1st |
&ArrayName[i] | 指向元素,加1增加元素ith 的地址(i+1)th | 指向元素(数组),加1增加元素(数组)ith 的地址(i+1)th |
ArrayName[i] | 给出元素的值,加 1 增加元素ith 的值ith | 指向数组0th 的元素,加1增加数组元素的ith 地址1st ith |
ArrayName[i][j] | 没有什么 | jth 给出数组元素的值,加 1 会增加数组元素ith 的值jth ith |
访问元素的指针表达式 | *(ArrayName + i) | *(*(ArrayName + i) + j) |
3. Strings
字符串是一个以 结尾的一维字符数组null(\0)
。当我们写入时char name[] = "Srijan";
,每个字符占用一个字节的内存,最后一个字符始终占据内存\0
。
与我们见过的数组类似,name
and&name[0]
指向0th
字符串中的字符,而 while 则&name
指向整个字符串。又可name[i]
写为*(name + i)
.
/* String */
char champions[] = "Liverpool";
printf("Pointer to whole string = %d\n", &champions);
printf("Addition of 1 results in %d\n", &champions + 1);
/* Output */
Address of whole string = 6421974
Addition of 1 results in 6421984
printf("Pointer to 0th character = %d\n", &champions[0]);
printf("Addition of 1 results in %d\n", &champions[0] + 1);
/* Output */
Address of 0th character = 6421974
Addition of 1 results in a pointer to 1st character 6421975
printf("Pointer to 0th character = %d\n", champions);
printf("Addition of 1 results in a pointer to 1st character %d\n", champions + 1);
/* Output */
Address of 0th character = 6421974
Addition of 1 results in 6421975
printf("Value of 4th character = %c\n", champions[4]);
printf("Value of 4th character using pointers = %c\n", *(champions + 4));
/* Output */
Value of 4th character = r
Value of 4th character using pointers = r
如前所述,还可以访问和操作二维字符数组或字符串数组。
/* Array of Strings */
char top[6][15] = {
"Liverpool",
"Man City",
"Man United",
"Chelsea",
"Leicester",
"Tottenham"
};
printf("Pointer to 2-D array = %d\n", &top);
printf("Addition of 1 results in %d\n", &top + 1);
/* Output */
Pointer to 2-D array = 6421952
Addition of 1 results in 6422042
printf("Pointer to 0th string = %d\n", &top[0]);
printf("Addition of 1 results in %d\n", &top[0] + 1);
/* Output */
Pointer to 0th string = 6421952
Addition of 1 results in 6421967
printf("Pointer to 0th string = %d\n", top);
printf("Addition of 1 results in %d\n", top + 1);
/* Output */
Pointer to 0th string = 6421952
Addition of 1 results in 6421967
printf("Pointer to 0th element of 4th string = %d\n", top[4]);
printf("Pointer to 1st element of 4th string = %c\n", top[4] + 1);
/* Output */
Pointer to 0th element of 4th string = 6422012
Pointer to 1st element of 4th string = 6422013
printf("Value of 1st character in 3rd string = %c\n", top[3][1]);
printf("Same using pointers = %c\n", *(*(top + 3) + 1));
/* Output */
Value of 1st character in 3rd string = h
Same using pointers = h
4. 指针数组
与 s 数组int
和 s 数组一样char
,也存在指针数组。这样的数组只是地址的集合。这些地址也可以指向单个变量或另一个数组。
声明指针数组的语法是 -
dataType *variableName[size];
/* Examples */
int *example1[5];
char *example2[8];
按照运算符优先级,第一个示例可以理解为 - example1
是一个包含 5 个指向 的指针的 array( []
)int
。同样,example2
是一个由 8 个指针组成的数组char
。
我们可以使用指针数组将二维数组存储为字符串top
,这样也可以节省内存。
char *top[] = {
"Liverpool",
"Man City",
"Man United",
"Chelsea",
"Leicester",
"Tottenham"
};
top
将包含所有相应名称的基地址。的基地址"Liverpool"
将存储在top[0]
、"Man City"
intop[1]
等中。
在之前的声明中,我们需要 90 个字节来存储名称。在这里,我们只需要 ( 58 (名称字节总和) + 12 (在数组中存储地址所需的字节) ) 70 个字节。
使用指针数组时,字符串或整数的操作变得更加容易。
如果我们尝试放在"Leicester"
之前,我们只需要切换和"Chelsea"
的值,如下所示 -top[3]
top[4]
char *temporary;
temporary = top[3];
top[3] = top[4];
top[4] = temporary;
如果没有指针,我们将需要交换字符串的每个字符,这将花费更多时间。这就是为什么字符串通常使用指针声明。
5. 指向数组的指针
就像“指向int
”或“指向char
”的指针一样,我们也有指向数组的指针。该指针指向整个数组而不是其元素。
还记得我们讨论过如何&arrayName
指向整个数组吗?嗯,它是一个指向数组的指针。
指向数组的指针可以这样声明 -
dataType (*variableName)[size];
/* Examples */
int (*ptr1)[5];
char (*ptr2)[15];
注意括号。如果没有它们,这些将是一个指针数组。第一个示例可以理解为 -是一个指向 5 (整数)ptr1
数组的指针int
。
int goals[] = { 85,102,66,69,67};
int (*pointerToGoals)[5] = &goals;
printf("Address stored in pointerToGoals %d\n", pointerToGoals);
printf("Dereferncing it, we get %d\n",*pointerToGoals);
/* Output */
Address stored in pointerToGoals 6422016
Dereferencing it, we get 6422016
当我们取消引用指针时,它会给出该地址处的值。类似地,通过取消引用指向数组的指针,我们得到该数组,并且该数组的名称指向基地址。如果我们找到数组的大小,我们就可以确认*pointerToGoals
给出了数组。goals
printf("Size of goals[5] = %d, *pointerToGoals);
/* Output */
Size of goals[5] = 20
如果我们再次取消引用它,我们将获得存储在该地址中的值。我们可以使用 打印所有元素pointerToGoals
。
for(int i = 0; i < 5; i++)
printf("%d ", *(*pointerToGoals + i));
/* Output */
85 102 66 69 67
指针和数组指针与函数配合使用时非常有用。我们将在下一篇中讨论它们。