C语言基础之【指针】(中)
C语言基础之【指针】(中)
- 指针和数组
- 数组名
- 指针操作数组元素
- 数组名加减运算
- 指针加减运算
- 加法运算
- 减法运算
- 指针数组
- 多级指针
- 指针和函数
- 函数形参改变实参的值
- 数组名做函数参数
- 指针作为函数的参数
- 指针作为函数的返回值
往期《C语言基础系列》回顾:
链接:
C语言基础之【C语言概述】
C语言基础之【数据类型】(上)
C语言基础之【数据类型】(下)
C语言基础之【运算符与表达式】
C语言基础之【程序流程结构】
C语言基础之【数组和字符串】(上)
C语言基础之【数组和字符串】(下)
C语言基础之【函数】
C语言基础之【指针】(上)
指针和数组
数组名
数组名是一个指向数组首元素的指针常量 ---> arr == &arr[0]
数组名的值不能被修改(即:不能指向其他地址)
int arr[3] = {10, 20, 30}; int *p = arr; // arr是一个指向arr[0]的指针,所以我们不必&arr这样来获取数组的地址 arr = 10; //err, 数组名只是常量,不能修改
数组和指针的关系:
数组名与指针变量
数组名可以赋值给指针变量
int arr[3] = {10, 20, 30}; int *p = arr; // p指向arr[0]
数组下标与指针算术
数组下标操作
arr[i]
等价于指针算术*(arr + i)
int arr[3] = {10, 20, 30}; printf("%d\n", arr[1]); // 输出20 printf("%d\n", *(arr + 1)); // 输出20
数组和指针的区别:
特性 | 数组 | 指针 |
---|---|---|
定义 | 存储一组相同类型的元素 | 存储一个内存地址 |
内存分配 | 静态分配,大小固定 | 动态分配,大小可变 |
大小 | sizeof(arr) 返回整个数组的大小 | sizeof(p) 返回指针的大小(通常为4或8字节) |
赋值 | 数组名是常量,不能赋值 | 指针是变量,可以赋值 |
参数传递 | 数组名退化为指针 | 直接传递指针 |
指针操作数组元素
指针操作数组元素
:是利用指针的算术运算
和解引用操作
来访问和修改数组中的数据。
通过指针访问数组元素:
1.
使用指针算术
- 指针算术允许通过加减操作移动指针的位置。
- 例如:
p + i
表示指向arr[i]
的指针int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; printf("%d\n", *(p + 0)); // 输出arr[0]:10 printf("%d\n", *(p + 1)); // 输出arr[1]:20 printf("%d\n", *(p + 2)); // 输出arr[2]:30
2.
使用数组下标
- 指针变量可以像数组一样使用下标访问元素。
- 例如:
p[i]
等价于*(p + i)
int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; printf("%d\n", p[0]); // 输出arr[0]:10 printf("%d\n", p[1]); // 输出arr[1]:20 printf("%d\n", p[2]); // 输出arr[2]:30
通过指针遍历数组元素:
1.
通过指针变量指向数组
可以定义一个指针变量,使其指向数组的首地址,然后通过指针来访问和操作数组元素。
- 指针
p
指向数组arr
的首元素- 通过
*(p + i)
的方式可以访问和修改数组中的第i
个元素#include <stdio.h> int main() { int arr[] = { 1, 2, 3, 4, 5 }; int* p = arr; // 指针p指向数组arr的首元素 // 通过指针访问数组元素 for (int i = 0; i < 5; i++) { printf("%d ", *(p + i)); } // 通过指针修改数组元素 *(p + 2) = 10; printf("\n"); // 再次输出数组元素,验证修改是否成功 for (int i = 0; i < 5; i++) { printf("%d ", *(p + i)); } return 0; }
输出:
1 2 3 4 5
1 2 10 4 5
2.
利用数组名作为指针
数组名本身就可以看作是一个指向数组首元素的常量指针,因此可以直接利用数组名来操作数组元素。
arr
就相当于一个指向arr[0]
的指针*(arr + i)
等价于arr[i]
,可以方便地进行数组元素的访问和修改#include <stdio.h> int main() { int arr[] = { 1, 2, 3, 4, 5 }; // 通过数组名访问数组元素 for (int i = 0; i < 5; i++) { printf("%d ", *(arr + i)); } // 通过数组名修改数组元素 *(arr + 3) = 20; printf("\n"); // 再次输出数组元素,验证修改是否成功 for (int i = 0; i < 5; i++) { printf("%d ", *(arr + i)); } return 0; }
输出:
1 2 3 4 5
1 2 3 20 5
3.
使用指针的自增操作
可以通过对指针进行自增操作,使其依次指向数组中的每个元素,从而实现对数组元素的操作。
- 通过
p++
使指针p
依次指向数组的每个元素,从而实现了对数组元素的访问和修改。#include <stdio.h> int main() { int arr[] = { 1, 2, 3, 4, 5 }; int* p = arr; // 通过指针自增访问数组元素 while (p < arr + 5) { printf("%d ", *p); p++; }//打印结束后,p指向一块无效地址空间 (p变成野指针) // 将指针重新指向数组首元素 p = arr; // 通过指针自增修改数组元素 while (p < arr + 5) { *p *= 2; p++; } printf("\n"); // 再次输出数组元素,验证修改是否成功 p = arr; while (p < arr + 5) { printf("%d ", *p); p++; } return 0; }
输出:
1 2 3 4 5
2 4 6 8 10
综上所述:
arr[0] == *(arr + 0) == p[0] == *(p + 0)
数组名加减运算
int main(void)
{
short a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
printf("a = %p\n", a);
printf("&a[0] = %p\n", &a[0]);
printf("a+1 = %p\n", a+1);
printf("&a = %p\n", &a);
printf("&a+1 = %p\n", &a + 1);
system("pause");
return EXIT_SUCCESS;
}
分析:
printf("a = %p\n", a);
:输出的是数组a
第一个元素的地址。
- 在 C 语言中,数组名代表数组首元素的地址,所以这里输出的是数组
a
第一个元素的地址。printf("&a[0] = %p\n", &a[0]);
:输出的是数组a
第一个元素的地址。printf("a+1 = %p\n", a+1);
:输出的是数组a
第二个元素的地址。
- 由于
a
是short
类型数组,a + 1
表示跳过一个short
类型元素的地址,即指向数组第二个元素的地址。printf("&a = %p\n", &a);
:输出的是整个数组的地址。
- 输出数组
a
本身的地址,虽然值上可能和a
相同,但意义不同,&a
代表整个数组的地址。printf("&a+1 = %p\n", &a + 1);
:输出的是跳过整个数组的地址。
- 因为
&a
代表整个数组的地址,&a + 1
则是跳过整个数组的地址。输出:
a = 00000010D7EFF6D8 &a[0] = 00000010D7EFF6D8 a+1 = 00000010D7EFF6DA &a = 00000010D7EFF6D8 &a+1 = 00000010D7EFF6EC
指针加减运算
指针的加减运算
:通过移动指针的位置来访问和操作内存中的数据。
- 指针加减运算的
结果
:是一个新的指针。
- 指向原指针向前或向后移动若干单位后的内存地址
- 指针加减运算的
单位
:是指针所指向数据类型的大小。
int *p
的加减单位是sizeof(int)
(通常为4字节)char *p
的加减单位是sizeof(char)
(通常为1字节)注意:指针与整数的区别
- 指针存储的是内存地址,而整数存储的是数值。
- 指针运算考虑了数据类型的大小,而整数运算直接操作数值。
加法运算
指向基本数据类型的指针的加法:
底层原理:
新地址 = 原地址 + (n * sizeof(数据类型))
例如:
int *p + 2
的实际计算:新地址 = p + (2 * sizeof(int))
指向数组的指针的加法:
指针加法的语法:
指针 + 整数
结果是:指针向后移动
n
个单位(n
是整数)int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; // p指向arr[0] p = p + 2; // p指向arr[2] printf("%d\n", *p); // 输出30
指针与指针的加法:
注意:
在 C 语言中,直接将两个指针相加是没有实际意义的
- 因为这样的操作不符合逻辑,也无法得到有价值的结果,所以通常不允许进行指针与指针的加法运算。
减法运算
指向基本数据类型的指针的减法:
底层原理:
新地址 = 原地址 - (n * sizeof(数据类型))
例如:
char *p - 2
的实际计算:新地址 = p - (2 * sizeof(char))
指向数组的指针的减法:
指针减法的语法:
指针 - 整数
结果是:指针向前移动
n
个单位(n
是整数)int arr[5] = {10, 20, 30, 40, 50}; int *p = arr + 4; // p指向arr[4] p = p - 2; // p指向arr[2] printf("%d\n", *p); // 输出30
指针与指针的减法:
指针与指针的减法语法:
指针1 - 指针2
两个指针指向同一数组时:
- 结果是:两个指针之间的
元素个数
(而不是字节数)两个指针指向非同一数组时:
结果是:行为未定义
int arr[5] = {10, 20, 30, 40, 50}; int *p1 = arr + 2; // p1指向arr[2] int *p2 = arr; // p2指向arr[0] printf("%ld\n", p1 - p2); // 输出2(p1和p2之间相差2个元素)
示例代码:指针加减运算的使用
#include <stdio.h> int main() { int arr[5] = { 10, 20, 30, 40, 50 }; int* p = arr; // p指向arr[0] // 指针加法 p = p + 2; // p指向arr[2] printf("p + 2: %d\n", *p); // 输出30 // 指针减法 p = p - 1; // p指向arr[1] printf("p - 1: %d\n", *p); // 输出20 // 指针与指针的减法 int* p1 = arr + 3; // p1指向arr[3] int* p2 = arr; // p2指向arr[0] printf("p1 - p2: %ld\n", p1 - p2); // 输出3 // 遍历数组 for (int* ptr = arr; ptr < arr + 5; ptr++) { printf("%d ", *ptr); // 输出:10 20 30 40 50 } return 0; }
输出:
p + 2: 30 p - 1: 20 p1 - p2: 3 10 20 30 40 50
示例代码:探究指针与指针的减法
#include <stdio.h> int main() { int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int* p1 = &a[1]; // p1指向a[1]的地址 int* p2 = &a[2]; // p2指向a[2]的地址 printf("p1 = %p, p2 = %p\n", p1, p2); int n1 = p2 - p1; // 指针减法,计算p2和p1之间的元素个数 int n2 = (int)p2 - (int)p1; // 将指针转换为整数,计算地址的字节差 printf("n1 = %d, n2 = %d\n", n1, n2); return 0; }
输出:
p1 = 0x1004, p2 = 0x1008 n1 = 1, n2 = 4
分析 p1,p2:
定义数组
a
a
是一个包含 9 个整数的数组,初始化为{1, 2, 3, 4, 5, 6, 7, 8, 9}
- 假设数组
a
的起始地址为0x1000
,则:
a[0]
的地址为0x1000
a[1]
的地址为0x1004
(假设int
占 4 字节)a[2]
的地址为0x1008
定义指针
p1
和p2
p1
指向a[1]
的地址,即0x1004
p2
指向a[2]
的地址,即0x1008
输出
p1
和p2
的值
printf("p1 = %p, p2 = %p\n", p1, p2);
p1 = 0x1004, p2 = 0x1008
分析 n1,n2:
计算
n1
和n2
n1 = p2 - p1; 这是指针减法,计算的是两个指针之间的元素个数
p2
和p1
分别指向a[2]
和a[1]
,它们之间相差 1 个元素。n2 = (int)p2 - (int)p1; 这是将指针转换为整数后计算地址的字节差
p2
的地址是0x1008
,p1
的地址是0x1004
。0x1008 - 0x1004 = 4
(假设int
占 4 字节)输出
n1
和n2
的值
printf("n1 = %d, n2 = %d\n", n1, n2);
n1 = 1, n2 = 4
关键点总结:
指针减法
p2 - p1
- 计算的是两个指针之间的元素个数,而不是字节数。
- 结果是一个整数,表示两个指针之间相差多少个元素。
指针转换为整数
(int)p2 - (int)p1
- 将指针转换为整数后,计算的是两个地址之间的字节差。
- 结果是一个整数,表示两个地址之间相差多少字节。
根据上面的知识学习,我们可以总结出这样一个小技巧。
代码示例:利用指针计算数组中的元素个数
#include <stdio.h> #include <stdlib.h> int main(void) { int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; int* p = a; for (size_t i = 0; i < 10; i++) { p++; } printf("p - a = %d\n", p - a); system("pause"); return EXIT_SUCCESS; }
代码示例:重更新实现strlen() 函数
// 借助数组实现 int mystrlen(char str[]) { int i = 0; while (str[i] != '\0') { i++; } return i; } // 借助指针++实现 int mystrlen2(char str[]) { char* p = str; while (*p != '\0') { p++; } return p - str; // 返回数组元素的个数 }
指针数组
指针数组
:是一个数组,其元素都是指针。
- 指针数组中的每个指针可以
指向不同类型的数据
(如:int
、char
、float
等)- 指针数组中的每个指针也可以
指向其他数组
或动态分配的内存
指针数组定义的语法:
数据类型 *数组名[数组大小];
数据类型
:指针所指向的数据类型。数组名
:指针数组的名称。数组大小
:数组中指针的数量。int *arr[5]; //定义一个包含5个int指针的数组 char *strArr[] = {"Apple", "Banana", "Cherry"}; //定义一个包含3个字符串的数组
char *strArr[] = {"Apple", "Banana", "Cherry"};
该指针数组也被称为字符串数组指针数组的初始化:
指向普通变量
指针数组的元素可以指向普通变量。
int a = 10, b = 20, c = 30; int *arr[3] = {&a, &b, &c}; // 初始化指针数组
指向字符串
指针数组常用于存储字符串(字符串是字符数组)
const char *strArr[] = {"Hello", "World", "C Language"}; // 初始化字符串指针数组
指向动态分配的内存
指针数组的元素可以指向动态分配的内存。
int *arr[3]; for (int i = 0; i < 3; i++) { arr[i] = (int *)malloc(sizeof(int)); // 动态分配内存 *arr[i] = i + 1; // 初始化值 }
代码示例:指针数组的本质
#include <stdio.h> #include <stdlib.h> // 指针数组1 int main(void) { int a = 10; int b = 20; int c = 30; int* p1 = &a; int* p2 = &b; int* p3 = &c; int* arr[] = { p1, p2, p3 }; // 整型指针数组arr, 存的都是整型地址 printf("*(arr[0]) = %d\n", *(*(arr + 0))); //arr[0] == *(arr+0) printf("*(arr[0]) = %d\n", **arr); system("pause"); return EXIT_SUCCESS; }
输出:
*(arr[0]) = 10 *(arr[0]) = 10
#include <stdio.h> #include <stdlib.h> // 指针数组2 int main(void) { int a[] = { 10 }; int b[] = { 20 }; int c[] = { 30 }; int* arr[] = { a, b, c }; // 整型指针数组arr, 存的都是整型地址 printf("*(arr[0]) = %d\n", *(*(arr + 0))); //arr[0] == *(arr+0) printf("*(arr[0]) = %d\n", **arr); system("pause"); return EXIT_SUCCESS; }
输出:
*(arr[0]) = 10 *(arr[0]) = 10
总结:
arr[0][0] == *(*(arr + 0) + 0) == **arr
指针数组本质,是一个二级指针;二维教组本质,也是一个二级指针
指针数组的使用:
1.
访问指针数组的元素
通过下标访问指针数组的元素,然后解引用指针以访问数据。
int a = 10, b = 20, c = 30; int *arr[3] = {&a, &b, &c}; for (int i = 0; i < 3; i++) { printf("%d ", *arr[i]); // 输出:10 20 30 }
2.
遍历字符串指针数组
字符串指针数组常用于存储多个字符串。
char *strArr[] = {"Hello", "World", "C Language"}; for (int i = 0; i < 3; i++) { printf("%s\n", strArr[i]); // 输出每个字符串 }
3.
动态分配的多维数组
指针数组可以用于实现动态分配的多维数组。
#include <stdio.h> #include <stdlib.h> int main() { int* arr[3]; // 定义一个指针数组 for (int i = 0; i < 3; i++) { arr[i] = (int*)malloc(4 * sizeof(int)); // 每个指针指向一个动态数组 for (int j = 0; j < 4; j++) { arr[i][j] = i * 4 + j; // 初始化值 } } // 输出动态分配的二维数组 for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d ", arr[i][j]); } printf("\n"); } // 释放内存 for (int i = 0; i < 3; i++) { free(arr[i]); } return 0; }
输出:
0 1 2 3
4 5 6 7
8 9 10 11
示例代码:指针数组的定义、初始化和使用
#include <stdio.h> #include <stdlib.h> int main() { // 示例1:指向普通变量的指针数组 int a = 10, b = 20, c = 30; int* arr1[3] = { &a, &b, &c }; for (int i = 0; i < 3; i++) { printf("%d ", *arr1[i]); // 输出:10 20 30 } printf("\n"); // 示例2:字符串指针数组 char* arr2[] = { "Hello", "World", "C Language" }; //error:"const char *" 类型的值不能用于初始化 "char *" 类型的实体 for (int i = 0; i < 3; i++) { printf("%s\n", arr2[i]); // 输出每个字符串 } // 示例3:动态分配的多维数组 int* arr3[3]; for (int i = 0; i < 3; i++) { arr3[i] = (int*)malloc(4 * sizeof(int)); for (int j = 0; j < 4; j++) { arr3[i][j] = i * 4 + j; // 初始化值 } } // 输出动态分配的二维数组 for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d ", arr3[i][j]); } printf("\n"); } // 释放内存 for (int i = 0; i < 3; i++) { free(arr3[i]); } return 0; }
代码错误片段:
char *arr2[] = {"Hello", "World", "C Language"}; //error:"const char *" 类型的值不能用于初始化 "char *" 类型的实体 //***真实的情况是C程序并不会报错,而是C++程序会报错。原因是C++对const的语法进行了加强,已不允许上面的行为了***//
错误原因:
"Hello"
、"World"
和"C Language"
这些字符串字面量是只读的(常量),因此它们的类型是const char*
char* arr2[]
的类型是char*
const char*
:表示指向常量字符
的指针。char*
:表示指向可修改字符
的指针。所以:将
const char*
赋值给char*
会导致潜在的风险(可能修改只读数据
),因此编译器会报错。
解决方法:
1.使用
const char*
定义指针数组(如果不需要修改字符串内容
)
- 将 arr2 的类型改为
const char*
,以匹配字符串字面量的类型。const char *arr2[] = {"Hello", "World", "C Language"};
这样,
arr2
中的每个元素都是const char*
类型,与字符串字面量的类型一致。
2.使用
char数组
存储字符串(如果需要修改字符串内容
)
- 将字符串字面量复制到可修改的
char数组
中。char arr2[3][20] = {"Hello", "World", "C Language"};
这样,
arr2
是一个二维字符数组,每个字符串都存储在一个独立的char数组
中,可以修改。
3.动态分配内存并复制字符串(
如果需要在运行时动态分配内存并存储字符串
)#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char* arr2[3]; arr2[0] = (char*)malloc(20 * sizeof(char)); arr2[1] = (char*)malloc(20 * sizeof(char)); arr2[2] = (char*)malloc(20 * sizeof(char)); strcpy(arr2[0], "Hello"); strcpy(arr2[1], "World"); strcpy(arr2[2], "C Language"); for (int i = 0; i < 3; i++) { printf("%s\n", arr2[i]); } // 释放内存 for (int i = 0; i < 3; i++) { free(arr2[i]); } return 0; }
总结:
- 如果
不需要
修改字符串内容,使用const char*
定义指针数组。- 如果
需要
修改字符串内容,使用char数组
或动态分配内存
定义指针数组。
多级指针
多级指针
:是指向指针的指针。多级指针通常用于
动态内存管理
、多维数组
、函数参数传递
等场景。
多级指针定义的语法:
数据类型 *指针变量名; // 一级指针:指向普通变量的指针 数据类型 **指针变量名; // 二级指针:指向一级指针的指针 数据类型 ***指针变量名; // 三级指针:指向二级指针的指针
int a = 10; int *p = &a; // p是一级指针,指向a int **pp = &p; // pp是二级指针,指向p int ***ppp = &pp; // ppp是三级指针,指向pp
注意事项:
多级指针不能跳跃定义
***ppp == **pp == *p == a //整型变量 **ppp == *pp == p == &a //一级指针 *ppp == pp == &p; //二级指针 ppp == &pp; //三级指针
多级指针的使用:
1.
访问变量的值
通过多级指针可以间接访问变量的值。
int a = 10; int *p = &a; // p指向a int **pp = &p; // pp指向p int ***ppp = &pp; // ppp指向pp printf("%d\n", ***ppp); // 输出10
2.
动态内存分配
多级指针常用于动态分配多维数组。
#include <stdio.h> #include <stdlib.h> int main() { // 分配行指针 int** arr = (int**)malloc(3 * sizeof(int*)); // 检查内存分配是否成功 if (arr == NULL) { printf("内存分配失败,无法分配行指针!\n"); return 1; } // 分配每行的列 for (int i = 0; i < 3; i++) { arr[i] = (int*)malloc(4 * sizeof(int)); // 检查内存分配是否成功 if (arr[i] == NULL) { printf("内存分配失败,无法分配第 %d 行的列!\n", i); // 释放之前已经分配的内存 for (int j = 0; j < i; j++) { free(arr[j]); } free(arr); return 1; } } // 初始化数组 for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { arr[i][j] = i * 4 + j; } } // 打印数组元素 printf("初始化后的二维数组元素如下:\n"); for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d ", arr[i][j]); } printf("\n"); } // 释放内存 for (int i = 0; i < 3; i++) { free(arr[i]); } free(arr); return 0; }
输出:
初始化后的二维数组元素如下:
0 1 2 3
4 5 6 7
8 9 10 113.
函数参数传递
多级指针可以用于在函数中修改指针的值。
void allocateMemory(int **p) { *p = (int *)malloc(sizeof(int)); // 修改一级指针的值 **p = 100; // 修改指针指向的值 } int main() { int *p = NULL; allocateMemory(&p); // 传递一级指针的地址 printf("%d\n", *p); // 输出100 free(p); return 0; }
指针和函数
函数形参改变实参的值
在 C 语言中,函数参数传递有两种方式:
值传递
和地址传递
- 函数参数传递默认是值传递,即函数内部对形参的修改不会影响实参的值。
- 然而,通过使用指针,可以实现地址传递的效果,从而在函数内部修改实参的值。
值传递
:是指将实参的值复制一份传递给函数的形参。
在函数内部对形参的修改不会影响到实参的值。
#include <stdio.h> void swap(int a, int b) { int temp = a; a = b; b = temp; } int main() { int x = 10, y = 20; swap(x, y); printf("x = %d, y = %d\n", x, y); // 输出:x = 10, y = 20 return 0; }
地址传递
:是指将实参的地址传递给函数的形参。
在函数内部可以通过指针来修改实参的值。
#include <stdio.h> void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int x = 10, y = 20; swap(&x, &y); // 传递x和y的地址 printf("x = %d, y = %d\n", x, y); // 输出:x = 20, y = 10 return 0; }
示例代码:函数参数的两种传递方式
#include <stdio.h> void swap1(int x, int y) { int tmp; tmp = x; x = y; y = tmp; printf("x = %d, y = %d\n", x, y); } void swap2(int* x, int* y) { int tmp; tmp = *x; *x = *y; *y = tmp; } int main() { int a = 3; int b = 5; swap1(a, b); //值传递 printf("a = %d, b = %d\n", a, b); a = 3; b = 5; swap2(&a, &b); //地址传递 printf("a2 = %d, b2 = %d\n", a, b); return 0; }
输出:
x = 5, y = 3
a = 3, b = 5
a2 = 5, b2 = 3
数组名做函数参数
数组名作为函数参数时退化为指针,传递的实际是数组首元素的地址(即:指针)而不是整个数组的副本。
因此:函数内部可以通过指针访问和修改数组的内容。
这意味着:
函数内对数组的修改会影响原数组
数组名作为函数参数的语法:
//以下两种写法完全等价: 返回值类型 函数名(数据类型 数组名[], int 数组大小); 返回值类型 函数名(数据类型 *数组名, int 数组大小);
//以下两种写法完全等价: void printArray(int arr[], int n); // 数组名作为参数 void printArray(int *arr, int n); // 指针作为参数
数组名作为函数参数的使用:
1.访问数组元素
在函数内部,可以通过指针或下标访问数组元素。
void printArray(int arr[], int n) { for (int i = 0; i < n; i++) { printf("%d ", arr[i]); // 通过下标访问数组元素 } printf("\n"); } int main() { int arr[] = {1, 2, 3, 4, 5}; int n = sizeof(arr) / sizeof(arr[0]); printArray(arr, n); // 传递数组名 return 0; }
2.修改数组元素
在函数内部,可以通过指针或下标修改数组元素。
void modifyArray(int *arr, int n) { for (int i = 0; i < n; i++) { arr[i] *= 2; // 修改数组元素的值 } } int main() { int arr[] = {1, 2, 3, 4, 5}; int n = sizeof(arr) / sizeof(arr[0]); modifyArray(arr, n); // 传递数组名 for (int i = 0; i < n; i++) { printf("%d ", arr[i]); // 输出:2 4 6 8 10 } return 0; }
数组名作为函数参数的注意事项:
数组长度需额外传递
数组名作为函数参数时,函数内部无法通过指针获取数组长度,因此通常需要额外显式传递数组的大小。
void printArray(int arr[], int n)
所以:
- 当
整数数组
做函数参数时,我们通常在函数定义中,封装2个参数。
一个表示整数数组首地址,一个表示数组元素个数
- 当
字符串(字符数组)
做函数参数时,不需要提供2个参数。 因为每个字符串都有'\0'
- 当
字符串数组
做函数参数时,我们通常在函数定义中,封装2个参数。
一个表示字符串数组首地址,一个表示数组元素个数
代码示例:在函数参数中数组名会退化为指针
#include <stdio.h> #include <stdlib.h> void BubbleSort(int arr[]) // void BubbleSort(int *arr) { printf("BubbleSort: sizeof(arr) = %d\n", sizeof(arr)); printf("BubbleSort: sizeof(arr[0]) = %d\n", sizeof(arr[0])); int n = sizeof(arr) / sizeof(arr[0]); printf("BubbleSort: n = %d\n", n); for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } int main() { int a = 10; int* p = &a; printf("该平台指针所占的字节数为:%d\n", sizeof(p)); int arr[] = { 5, 89, 3, 22, 40, 31, 9, 22, 67, 28, 45, 78 }; printf("main: sizeof(arr) = %d\n", sizeof(arr)); printf("main: sizeof(arr[0]) = %d\n", sizeof(arr[0])); int n = sizeof(arr) / sizeof(arr[0]); printf("main: n = %d\n", n); BubbleSort(arr); printf("冒泡排序后的结果为:\n"); for (size_t i = 0; i < n; i++) { printf("%d ", arr[i]); } return 0; }
输出:
该平台指针所占的字节数为:8 main: sizeof(arr) = 48 main: sizeof(arr[0]) = 4 main: n = 12 BubbleSort: sizeof(arr) = 8 BubbleSort: sizeof(arr[0]) = 4 BubbleSort: n = 2 冒泡排序后的结果为: 5 89 3 22 40 31 9 22 67 28 45 78
分析:
1.main函数
指针大小:
printf("该平台指针所占的字节数为:%d\n", sizeof(p));
- 这里
sizeof(p)
返回的是指针p
的大小,通常是 8(64 位系统)或 4(32 位系统)数组大小计算:
int n = sizeof(arr) / sizeof(arr[0]);
sizeof(arr)
返回的是数组arr
的总大小。sizeof(arr[0])
返回的是单个元素的大小。n
的值将是数组arr
的元素个数,即 12调用
BubbleSort
函数:BubbleSort(arr);
由于
BubbleSort
函数内部的n
计算错误,排序不会正确执行。由于排序未正确执行,打印的数组将是原始数组的顺序。
2.BubbleSort函数
- 数组大小计算:
int n = sizeof(arr) / sizeof(arr[0]);
sizeof(arr)
返回的是指针的大小(8 或 4),而不是数组的大小。- 因为
arr
作为函数参数时,数组名会退化为指针,所以实际上是一个指针(int*
),而不是数组。sizeof(arr[0])
返回的是单个元素的大小。(int
类型的大小)n
的值将是 2(64 位系统)或 1(32 位系统),这显然不是数组的实际大小。
代码示例:数组名作为函数参数时额外传递数组长度
#include <stdio.h> #include <stdlib.h> void BubbleSort(int arr[], int n) { for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } int main() { int a = 10; int* p = &a; printf("该平台指针所占的字节数为:%d\n", sizeof(p)); int arr[] = { 5, 89, 3, 22, 40, 31, 9, 22, 67, 28, 45, 78 }; printf("main: sizeof(arr) = %d\n", sizeof(arr)); printf("main: sizeof(arr[0]) = %d\n", sizeof(arr[0])); int n = sizeof(arr) / sizeof(arr[0]); printf("main: n = %d\n", n); BubbleSort(arr, n); printf("冒泡排序后的结果为:\n"); for (size_t i = 0; i < n; i++) { printf("%d ", arr[i]); } return 0; }
输出:
该平台指针所占的字节数为:8 main: sizeof(arr) = 48 main: sizeof(arr[0]) = 4 main: n = 12 冒泡排序后的结果为: 3 5 9 22 22 28 31 40 45 67 78 89 //由于数组名作为函数参数时额外传递数组长度n,n值为正确的值 //所以冒泡排序函数排序正确
指针作为函数的参数
指针作为函数参数的应用:
1.修改普通变量的值
通过指针可以在函数中修改普通变量的值。
void increment(int *p) { (*p)++; // 修改指针指向的值 } int main() { int a = 10; increment(&a); // 传递a的地址 printf("a = %d\n", a); // 输出:a = 11 return 0; }
2.动态内存分配
通过指针可以在函数中动态分配内存,并将分配的内存地址返回给调用者。
void allocateMemory(int **p) { *p = (int *)malloc(sizeof(int)); // 分配内存 **p = 100; // 修改指针指向的值 } int main() { int *p = NULL; allocateMemory(&p); // 传递指针的地址 printf("*p = %d\n", *p); // 输出:*p = 100 free(p); // 释放内存 return 0; }
3.修改数组元素
通过指针可以在函数中修改数组元素的值。
void modifyArray(int *arr, int n) { for (int i = 0; i < n; i++) { arr[i] *= 2; // 修改数组元素的值 } } int main() { int arr[] = {1, 2, 3, 4, 5}; int n = sizeof(arr) / sizeof(arr[0]); modifyArray(arr, n); // 传递数组名(即数组首元素的地址) for (int i = 0; i < n; i++) { printf("%d ", arr[i]); // 输出:2 4 6 8 10 } return 0; }
指针作为函数的返回值
指针作为函数的返回值:
指针作为函数的返回值时,返回的指针不能指向局部变量
- 因为:局部变量在函数返回后会被销毁
int *createArray(int n) { int *arr = (int *)malloc(n * sizeof(int)); // 动态分配内存 for (int i = 0; i < n; i++) { arr[i] = i + 1; } return arr; // 返回动态分配的数组 } int main() { int *arr = createArray(5); for (int i = 0; i < 5; i++) { printf("%d ", arr[i]); // 输出:1 2 3 4 5 } free(arr); // 释放内存 return 0; }
代码示例:指针作为函数的返回值的注意事项
#include <stdio.h> #include <stdlib.h> int m = 100; // 全局变量 int* test_func1(int a, int b) { return &m; } int* test_func2(int a, int b) { int n = 1234; // 局部变量 return &n; } int main(void) { int* ret1 = NULL; // NULL == 0 int* ret2 = NULL; // NULL == 0 ret1 = test_func1(10, 20); ret2 = test_func2(10, 20); printf("ret1 = %d\n", *ret1); printf("ret1 = %d\n", *ret1); printf("ret1 = %d\n", *ret1); printf("ret2 = %d\n", *ret2); printf("ret2 = %d\n", *ret2); printf("ret2 = %d\n", *ret2); system("pause"); return EXIT_SUCCESS; }
输出:
ret1 = 100 ret1 = 100 ret1 = 100 ret2 = -858993460 // 未定义行为,可能打印 1234(残留值) ret2 = -858993460 // 未定义行为,可能打印 -858993460 或其他值 ret2 = -858993460 // 未定义行为,可能打印 -858993460 或其他值
分析:
int m = 100; // 全局变量 int* test_func1(int a, int b) { return &m; }
全局变量
m
存储在全局数据区
,生命周期与程序相同。函数
test_func1
返回m
的地址时,这个地址在程序运行期间始终有效。因此
ret1
指向的是一个有效的内存地址。int* test_func2(int a, int b) { int n = 1234; // 局部变量 return &n; }
- 局部变量
n
存储在栈上,生命周期仅限于test_func2
的执行期间。- 函数
test_func2
返回n
的地址时,n
的内存空间会被释放掉,返回的指针指向的内存区域不再有效。- 因此
ret2
指向的是一个无效的内存地址。
👨💻 博主正在持续更新C语言基础系列中。
❤️ 如果你觉得内容还不错,请多多点赞。⭐️ 如果你觉得对你有帮助,请多多收藏。(防止以后找不到了)
👨👩👧👦
C语言基础系列
持续更新中~,后续分享内容主要涉及C++全栈开发
的知识,如果你感兴趣请多多关注博主。