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指向ppprintf("%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); // 输出100free(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 = 20return 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 = 10return 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 = 11return 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 = 100free(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 == 0int* ret2 = NULL; // NULL == 0ret1 = 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++全栈开发
的知识,如果你感兴趣请多多关注博主。