指针进阶
文章目录
继上文, 指针,我们已经大致了解了以下四点:
-
指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
-
指针的大小是固定的4/8个字节(32位平台/64位平台)。
-
指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
-
指针的运算。
那么这篇文章我们就指针来做深一步的讨论~
字符指针
字符指针是指向字符数据的指针 。它可以用于操作字符串,即以字符数组的形式存储的一组字符。在指针的类型中我一种指针类型为字符指针 char*
;
c
#include <stdio.h>
int main() {
char str[] = "Hello, World!"; // 字符数组,存储了字符串
char* ptr = str; // 字符指针,指向字符数组的首地址
printf("String: %s\n", ptr); // 通过字符指针输出字符串
while (*ptr != '\0') { // 循环遍历字符数组中的每个字符,直到遇到结束符 '\0'
printf("Character: %c\n", *ptr); // 输出当前字符
ptr++; // 指针移动到下一个字符
}
return 0;
}
//输出结果
String: Hello, World!
Character: H
Character: e
Character: l
Character: l
Character: o
Character: ,
Character:
Character: W
Character: o
Character: r
Character: l
Character: d
Character: !
字符指针的本质就是将字符串的首地址放到了指针中
需要注意的是,在操作字符指针和字符串时,需要确保字符串以结束符 '\0'
结尾,否则会导致意想不到的结果。在声明字符数组时,C语言会自动在字符串末尾添加结束符,因此我们可以使用上述方法遍历字符串。
另外,字符指针还可以用于字符串的处理,如拷贝、连接、比较等操作,同时还可以通过指针的偏移来修改字符串中的字符。
例子
所以再面试题中,有这么一道题:
c
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
这里的输出结果:
c
str1 and str2 are not same
str3 and str4 are same
解析
在这段代码中,我们声明了四个字符串变量 str1
、str2
、str3
、str4
来存储相同内容的字符串 "hello bit."。其中,str1
和 str2
是字符数组,而 str3
和 str4
是字符指针。
在比较 str1
和 str2
的地址时,由于它们分别是不同的字符数组,它们的地址也是不同的,所以 str1 == str2
将被认为是不相等的。因此,输出结果会是 "str1 and str2 are not same"
。
而在比较 str3
和 str4
的地址时,它们都是指向相同字符串 "hello bit."的常量字符指针。由于代码中的字符串字面值在编译时就被分配了固定的内存地址,所以 str3
和 str4
的值相等。因此,str3 == str4
将被认为是相等的。所以,输出结果会是 "str3 and str4 are same"
。
指针数组
概念
指针数组是一个存放指针的数组。
再次强调!指针数组是数组!数组!数组!!!
每个元素都是指针,指向不同的数据对象或变量。指针数组可以用于存储不同类型的指针 ,也可以存储指向相同类型的多个对象的指针。
要声明一个指针数组,需要指定指针的类型和数组的大小。
例如,int *arr[5]
声明了一个包含 5 个指向整型数据的指针的数组。
指针数组的元素可以通过索引访问。可以使用下标(从0开始)来指定要访问的元素。例如,对于指针数组 int *arr[5]
,可以使用 arr[0]
、arr[1]
等来访问其中的指针元素。
指针数组的使用可以带来一些好处。
例如,可以将多个指针作为参数传递给函数,可以方便地在代码中存储和处理多个指针对象。此外,指针数组还可以用于实现动态内存管理,例如创建和管理动态分配的对象的指针。
c
#include <stdio.h>
int main() {
int num1 = 1;
int num2 = 2;
int num3 = 3;
int* arr[3]; // 声明一个包含 3 个指向整型数据的指针的数组
arr[0] = &num1; // 将 num1 的地址赋值给数组的第一个元素
arr[1] = &num2; // 将 num2 的地址赋值给数组的第二个元素
arr[2] = &num3; // 将 num3 的地址赋值给数组的第三个元素
for (int i = 0; i < 3; i++) {
printf("Element %d: %d\n", i, *(arr[i])); // 使用指针解引用来输出指针指向的值
}
return 0;
}
输出结果会是:
Element 0: 1
Element 1: 2
Element 2: 3
在上述示例中,我们了一个包含 3 个指向整型数据的指针的数组 arr
。然后,我们分别将 num1
、num2
和 num3
的地址赋值给数组中的元素。最后,我们通过循环遍历指针数组,并使用指针解引用来输出指针指向的值。
指针数组使用非常灵活,可以根据实际的需求来存储和操作不同类型的指针。
数组指针
既然知道了什么是指针数组,那么数组指针又是什么呢?
是指针!指针!指针!(在地上打滚面红耳赤尖叫~)
写法
声明一个数组指针的格式为:
c
datatype (*pointer_name)[size];
/*
datatype 是指向的数组元素的数据类型,
pointer_name 是指针的名字,
size 是数组的大小。
*/
- 声明一个指向整型数组的指针:
c
int (*ptr)[5];
这个指针指向一个包含 5 个整型元素的数组。
- 声明一个指向字符数组的指针:
c
char (*ptr)[10];
这个指针指向一个包含 10 个字符元素的数组。
- 声明一个指向双精度浮点数数组的指针:
c
double (*ptr)[7];
这个指针指向一个包含 7 个双精度浮点数元素的数组。
- 声明一个指向二维整型数组的指针:
c
int (*ptr)[3][4];
这个指针指向一个包含 3 行 4 列的二维整型数组。
注意事项
括号是必需的,因为数组指针的优先级比*
高。括号的存在可以确保指针指向整个数组,而不是数组的某个元素。
实践
c
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
p1
是一个指针数组,包含 10 个指向整型变量的指针。每个指针可以指向一个整型变量。
p2
是一个指向整型数组的指针。该数组包含 10 个整型元素。
&数组名VS数组名
&数组名
和 数组名
在C语言中是有着不同的含义。区别如下:
-
&数组名
:使用&
运算符加上数组名,可以获取数组的地址 。这意味着它会返回指向数组的指针,指针的类型为数组类型的指针。例如,如果有一个名为
arr
的数组,那么&arr
将返回指向数组arr
的指针。 -
数组名
:在大多数情况下,数组名将被解释为指向数组第一个元素的指针。所以当使用数组名时,它实际上是一个指向数组第一个元素的指针。例如,如果有一个名为
arr
的数组,那么arr
将被解释为指向数组arr
第一个元素的指针。这个指针的类型会根据数组元素类型而定。
下面是一个示例来说明两者的区别:
c
int arr[5] = {1, 2, 3, 4, 5};
int *ptr1 = &arr; // 这种写法是错误的,&arr是一个指向整个数组的指针,与int*不匹配
int *ptr2 = arr; // 正确,arr被解释为指向数组第一个元素的指针
printf("ptr2[0] = %d\n", ptr2[0]); // 输出第一个元素的值
int (*ptr3)[5] = &arr; // 正确,&arr是一个指向数组的指针,ptr3为指向整个数组的指针
printf("(*ptr3)[0] = %d\n", (*ptr3)[0]); // 输出第一个元素的值
再举一个例子
c
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
输出结果为:
arr = 0x7fff99774d50
&arr= 0x7fff99774d50
arr+1 = 0x7fff99774d54
&arr+1= 0x7fff99774d74
解释:
-
arr
和&arr
的值是相等的,它们都是数组第一个元素的地址。因为数组名被解释为指向数组第一个元素的指针,所以arr
和&arr
的值是相同的。 -
arr+1
的值是数组中下一个元素的地址,即数组第二个元素的地址。因为arr
是指向数组第一个元素的指针,当加上1
后,指针向后移动了一个元素大小的距离。在这个例子中,整型元素的大小为4
字节,所以arr+1
的值比arr
的值增加了4
。 -
&arr+1
的值是整个数组之后的地址,也就是数组的尾部之后的地址。因为&arr
是指向整个数组的指针,所以当加上1
后,指针向后移动了整个数组的大小的距离。在这个例子中,整个数组的大小为40
字节(数组中有10
个整型元素,每个元素大小为4
字节),所以&arr+1
的值比&arr
的值增加了40
。
总结一下,&数组名
返回指向整个数组的指针,表示的是数组的地址。而 数组名
返回指向数组第一个元素的指针。它们在类型上有所不同,对于指针的声明和使用会有所区别。
使用
c
#include <stdio.h>
int main() {
int arr[3][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}
};
int (*ptr)[5]; // 声明一个指向包含 5 个整型元素的数组的指针
ptr = arr; // 将数组的地址赋值给指针
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
printf("Element[%d][%d]: %d\n", i, j, (*ptr)[j]); // 使用指针解引用操作访问数组元素
}
ptr++; // 指针进行算术运算,指向下一个元素的地址
}
return 0;
}
输出结果会是:
Element[0][0]: 1
Element[0][1]: 2
Element[0][2]: 3
Element[0][3]: 4
Element[0][4]: 5
Element[1][0]: 6
Element[1][1]: 7
Element[1][2]: 8
Element[1][3]: 9
Element[1][4]: 10
Element[2][0]: 11
Element[2][1]: 12
Element[2][2]: 13
Element[2][3]: 14
Element[2][4]: 15
在上述示例中,我们声明了一个包含 3 行 5 列的二维数组 arr
。然后,我们声明了一个数组指针 ptr
,并将 arr
的地址赋值给它。接下来,我们使用双重循环遍历数组,并通过指针解引用操作和数组指针的算术运算,输出了数组的各个元素。
数组指针的使用总结起来基本就是下面三种:
在C语言中,数组指针是一种特殊的指针类型,它可以用来指向数组。通过数组指针,我们可以方便地访问数组中的元素。
要声明一个数组指针,可以使用以下语法:
c
类型 (*指针名)[数组长度];
其中,类型
表示数组元素的类型,指针名
是指针的名称,数组长度
是数组的长度。
以下是几种常见的使用数组指针的方式:
- 数组指针作为函数参数:
c
void printArray(int (*ptr)[5], int length) {
for (int i = 0; i < length; i++) {
printf("%d ", (*ptr)[i]);
}
printf("\n");
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int (*ptr)[5] = &arr;
printArray(ptr, 5);
return 0;
}
在这个例子中,printArray
函数接受一个指向长度为5的整型数组的数组指针作为参数。在 main
函数中,我们创建了一个指向 arr
数组的指针 ptr
,然后将 ptr
作为参数传递给 printArray
函数。函数内部通过 (*ptr)[i]
的方式访问数组元素。
- 数组指针的数组:
c
int main() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[4] = {10, 20, 30, 40};
int (*arrPtr[2])[5] = {&arr1, &arr2};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 5; j++) {
printf("%d ", (*arrPtr[i])[j]);
}
printf("\n");
}
return 0;
}
在这个例子中,我们声明了一个数组 arrPtr
,它包含两个指针,这两个指针分别指向长度为5的整型数组。然后我们通过 (*arrPtr[i])[j]
的方式访问数组元素。
- 动态分配数组指针:
c
int main() {
int (*ptr)[5] = malloc(sizeof(int[5]));
for (int i = 0; i < 5; i++) {
(*ptr)[i] = i + 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", (*ptr)[i]);
}
printf("\n");
free(ptr);
return 0;
}
在这个例子中,我们使用 malloc
函数动态分配了一个长度为5的整型数组,并将返回的指针赋值给 ptr
。通过 (*ptr)[i]
的方式访问数组元素。最后记得使用 free
函数释放内存。
最后用我觉得数组指针的难点它与&数组名之间的区别,通俗来理解就是:数组指针是整个数组的地址,这本质上与&数组名是有区别的,&数组名是数组首元素的地址,不过这两者在遍历数组上基本是一样的,唯一区别就是数组指针它可以指定在任意一个元素开始遍历~
数组传参和指针传参
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
一维数组传参
当将一维数组传递给函数时,有三种常见的方式:
-
传递指针
将数组名作为指针参数传递给函数。由于数组名本身就是数组首元素的地址,因此在函数中可以通过指针操作符
*
来访问数组元素。
c
void printArray(int *arr, int size) {
// 使用指针操作符 * 来访问数组元素
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
// 传递数组名作为指针参数
printArray(arr, size);
return 0;
}
-
传递数组长度
将数组名作为指针参数传递给函数,并使用额外的参数将数组的长度传递给函数。这种方式适用于在函数中需要知道数组长度的情况。
c
void printArray(int *arr, int size) {
// 使用传入的数组长度进行循环
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
// 传递数组名作为指针参数,同时传递数组长度
printArray(arr, sizeof(arr) / sizeof(arr[0]));
return 0;
}
-
使用固定长度的数组
在函数定义中指定数组的长度,并将数组作为参数传递给函数。这种方式适用于固定长度的数组,并且可以在函数内部直接使用这个已知长度的数组。
c
void printArray(int arr[], int size) {
// 直接使用指定长度的数组
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
// 将数组作为参数传递给函数,同时传递数组长度
printArray(arr, sizeof(arr) / sizeof(arr[0]));
return 0;
}
二维数组传参
当将二维数组传递给函数时,同样有多种方式:
-
传递指针
可以将二维数组的首行地址作为指针参数传递给函数。在函数中,可以使用指针操作符
*
和数组下标来访问二维数组元素。
c
void printMatrix(int (*arr)[cols], int rows, int cols) {
// 使用指针操作符 * 和数组下标访问二维数组元素
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main() {
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int rows = sizeof(matrix) / sizeof(matrix[0]);
int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);
// 传递二维数组的首行地址作为指针参数
printMatrix(matrix, rows, cols);
return 0;
}
-
使用指针数组
可以将每一行看作是一个一维数组,并将指向二维数组的指针数组传递给函数。在函数中,使用指针操作符
*
来访问二维数组元素。
c
void printMatrix(int *arr[], int rows, int cols) {
// 使用指针操作符 * 访问二维数组元素
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main() {
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int *ptr[3];
int rows = sizeof(matrix) / sizeof(matrix[0]);
int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);
// 将指向二维数组的指针数组传递给函数
for (int i = 0; i < rows; i++) {
ptr[i] = matrix[i];
}
printMatrix(ptr, rows, cols);
return 0;
}
-
使用一维指针
可以将整个二维数组展开成一维数组,并传递该一维数组的指针给函数。在函数中,可以使用类似于一维数组的方式来访问元素。
c
void printMatrix(int *arr, int rows, int cols) {
// 使用类似于一维数组的方式访问元素
for (int i = 0; i < rows * cols; i++) {
printf("%d ", arr[i]);
if ((i + 1) % cols == 0) {
printf("\n");
}
}
}
int main() {
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int rows = sizeof(matrix) / sizeof(matrix[0]);
int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);
// 将二维数组展开成一维数组,并传递指针给函数
printMatrix((int *)matrix, rows, cols);
return 0;
}
一级指针传参
当将一级指针作为参数传递给函数时,有三种常见的方式:
-
传递指针的地址
可以将一级指针的地址作为参数传递给函数。在函数中,使用双重指针来接收该地址,并可以通过指针操作符
*
和->
来访问指针指向的值和成员。
c
void updatePointer(int **ptr) {
// 使用指针操作符 * 和 -> 访问指针指向的值和成员
*ptr = NULL;
}
int main() {
int *ptr;
int num = 10;
ptr = #
// 传递一级指针的地址
updatePointer(&ptr);
return 0;
}
-
传递指针的指针
可以直接将一级指针作为指针参数传递给函数。在函数中,使用指针操作符
*
和->
来访问指针指向的值和成员。
c
void updatePointer(int **ptr) {
// 使用指针操作符 * 和 -> 访问指针指向的值和成员
*ptr = NULL;
}
int main() {
int *ptr;
int num = 10;
ptr = #
// 直接传递一级指针参数
updatePointer(&ptr);
return 0;
}
-
传递指针的引用
在 C++ 中,可以使用引用来传递一级指针参数。在函数中,使用引用符号
&
来声明指针参数,并可以通过指针操作符*
和->
来访问指针指向的值和成员。
cpp
void updatePointer(int *&ptr) {
// 使用指针操作符 * 和 -> 访问指针指向的值和成员
ptr = NULL;
}
int main() {
int *ptr;
int num = 10;
ptr = #
// 传递指针的引用
updatePointer(ptr);
return 0;
}
二级指针传参
当将二级指针作为参数传递给函数时,有三种常见的方式:
-
传递指针的地址
可以将二级指针的地址作为参数传递给函数。在函数中,使用三重指针来接收该地址,并可以通过多次指针操作符
*
和->
来访问指针指向的值和成员。
c
void updatePointer(int ***ptr) {
// 使用指针操作符 * 和 -> 访问指针指向的值和成员
**ptr = NULL;
}
int main() {
int **ptr;
int num = 10;
ptr = #
// 传递二级指针的地址
updatePointer(&ptr);
return 0;
}
-
传递指针的指针
可以直接将二级指针作为指针参数传递给函数。在函数中,使用指针操作符
*
和->
来访问指针指向的值和成员。
c
void updatePointer(int **ptr) {
// 使用指针操作符 * 和 -> 访问指针指向的值和成员
*ptr = NULL;
}
int main() {
int **ptr;
int num = 10;
ptr = #
// 直接传递二级指针参数
updatePointer(ptr);
return 0;
}
-
传递指针的引用
在 C++ 中,可以使用引用来传递二级指针参数。在函数中,使用引用符号
&
来声明指针参数,并可以通过多次指针操作符*
和->
来访问指针指向的值和成员。
cpp
void updatePointer(int **&ptr) {
// 使用指针操作符 * 和 -> 访问指针指向的值和成员
ptr = NULL;
}
int main() {
int **ptr;
int num = 10;
ptr = #
// 传递二级指针的引用
updatePointer(ptr);
return 0;
}
注意事项:
在C语言中不能直接使用传递指针的引用(例如int *&ptr
),因为C语言没有引用类型。
ok,到这里就暂时将指针进阶的上半部分介绍了,明天或者今晚会再更新下半部分,敬请期待~