前言
1、指针和数组
1.1、数组名与指针的区别
数组名是数组首元素的地址,是一个地址常量,在程序运行过程中不会改变。即arr==&arr[0]
。但可以把地址赋值给对应的指针变量,可以修改指针变量的值。
int* arr[10] = {0};
int* ptr;
ptr = arr; /* 把数组地址赋值给指针 */
*(arr + 2); /* arr第三个元素的值 */
*arr +2; /* arr第一个元素的值加2 */
*ptr ++; /* 先使用ptr 指向的值,再递增该指针 */
(*ptr)++; /* 先指向ptr指向的值,再递增该值 */
其中*ptr ++
等效于 *(ptr ++)
。一元运算符*与++的优先级相同,但结合律是从右往左。但由于++是后缀形式,所以是先指向值,再递增指针。
1.2、在函数中传递数组的多种方法
1、指定数组大小(静态数组)
#include <stdio.h>
// 函数定义:接收固定大小的数组
void print_fixed_array(int arr[5]) {
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
print_fixed_array(arr); // 必须传递长度为5的数组
return 0;
}
2、作为指针传递,并给出数组大小
#include <stdio.h>
// 函数定义:接收整型指针
void print_array(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]); // 等价于 *(arr + i)
}
printf("\n");
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
print_array(arr, 5); // 传递数组名(隐式转换为指针)
return 0;
}
3、传递首地址和末尾地址
#include <stdio.h>
// 函数接收数组的起始指针和结束后一位指针
void print_array(int *begin, int *end) {
for (int *p = begin; p < end; p++) {
printf("%d ", *p);
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
print_array(arr, arr + 5); // 传递首地址和结束后一位地址
return 0;
}
1.3、在函数中声明数组中形参
由于数组名是该数组首元素的地址,作为实际参数的数组名要求形式参数是一个与之匹配的指针,而且由于函数原型是可以省略参数名的,所以以下四种原型都是等价的。
int sum(int* arr,int size);
int sum(int arr[],int size);
int sum(int* ,int);
int sum(int [],int);
但是在函数定义中不能省略参数名。
3、指针与const关键字
3.1、使用const关键字约束函数形参中的指针
若使用函数的意图不是修改数组中的内容,那应在声明形式参数中使用关键字const。
#include <stdio.h>
// 函数声明:使用const修饰数组形参,表示不修改原数组
void print_array(const int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
// arr[i] = 0; // 错误:修改const数组会导致编译错误
}
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
print_array(arr, 5); // 传递数组,函数内部无法修改
return 0;
}
3.2、指向常量的指针和常量指针
它们的区别在于指针本身和指针指向的内容是否可变。
1、指针常量(即指向常量的指针)
即指针指向的内容不可变,但指针本身可以改变。const int* ptr
或 int const* ptr
(两种写法等价)。常用于防止函数修改原数组或变量。
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
const int* ptr = &a; // 指针ptr指向常量(逻辑上)
// *ptr = 100; // 错误:不能通过ptr修改所指向的内容
ptr = &b; // 允许:ptr本身可以指向其他地址
printf("%d\n", *ptr); // 输出20
return 0;
}
2、常量指针(指针本身是常量)
即指针本身不可变,但指针指向的内容可以改变。int* const ptr
,常用于指向硬件地址或寄存器地。
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
int* const ptr = &a; // 指针ptr本身是常量
*ptr = 100; // 允许:可以修改ptr指向的内容
// ptr = &b; // 错误:不能修改ptr本身的值
printf("%d\n", *ptr); // 输出100
return 0;
}
3、指向常量的指针常量
即指向的内容和指针本身都不可变化。const int* const ptr
。常用于全局配置参数。
#include <stdio.h>
const char* const CONFIG_KEY = "APP_ID";
int main() {
int a = 10;
const int* const ptr = &a; // 双重常量
// *ptr = 100; // 错误:不能修改内容
// ptr = &b; // 错误:不能修改指针
return 0;
}
4、指针和多维数组
4.1、指向二维数组的指针
数组名退化为指针原则:一维数组名退化为指向元素的指针int *
,而二维数组名退化为指向移位数组的指针 int(*)[N]
。
int arr[4][2];
1、arr
是指向一维数组的指针
arr
的类型是int(*)[2]
,即指向包含2个int元素的一维数组的指针。arr
指向数组的首地址,即第0行的起始地址。arr+1
的偏移量是1*2*sizeof(int)
字节,指向下一行。*arr
代表该数组首元素arr[0]
的值,但arr[0]
的值是一个地址。**arr
才表示*&arr[0][0]
。
2、arr[0]
是指向一维数组的指针
arr[0]
的类型是int*
,即指向int
的指针。arr[0]
是第0行第一个元素arr[0][0]
的地址,等价于&arr[0][0]
。arr[0]+1
的偏移量是1*sizeof(int)
字节,即指向第0行的第二个元素arr[0][1]
。-
*(arr[0])
代表存储在*arr[0][0]
位置的数值。
4.2、指向多维数组的指针
int (* ptr)[2]; /* 指向一个内涵两个int类型值的数组 */
int* ptr[2]; /* ptr是一个内含两个指针元素的数组,每个元素都指向int类型的指针 */
1、 int (*ptr)[2]
:指向数组的指针(数组指针)
- 优先级:括号
()
优先级高于[]
,因此ptr
先被声明为指针。 - 数据类型:
int (*)[2]
指向一个包含 2 个 int 元素的数组,类型为int (*)[2]
。 - 内存占用:通常为 4/8 字节(取决于系统架构)。
- 存储内容:存储一个数组的起始地址,数组长度固定为 2。
- 偏移量:
ptr + 1
跳过2×sizeof(int)
字节(即一个完整数组)。
#include <stdio.h>
int main() {
int arr[3][2] = {{1, 2}, {3, 4}, {5, 6}};
int (*ptr)[2] = &arr[0]; // 指向第0行(类型匹配)
printf("ptr指向的地址: %p\n", (void*)ptr);
printf("ptr + 1的地址: %p\n", (void*)(ptr + 1)); // 偏移8字节
// 访问元素:ptr[i][j] 等价于 *(*(ptr + i) + j)
printf("ptr[1][0] = %d\n", ptr[1][0]); // 输出3
return 0;
}
2、int* ptr[2]
:指针数组(元素为指针的数组)
- 优先级:
[]
优先级高于*
,因此ptr
先被声明为数组。 - 数据类型:
ptr
是一个包含 2 个int*
元素的数组,类型为int*[2]
。 - 内存占用:
int* ptr[2]
2×sizeof(int*) 字节(如 16 字节)。 - 存储内容:存储 2 个独立的指针,每个指针可指向不同的
int
变量或数组。 - 偏移量:
ptr[i]
是第 i 个指针,ptr[i] + 1
跳过sizeof(int)
字节。
#include <stdio.h>
int main() {
int a = 10, b = 20;
int arr[3] = {1, 2, 3};
int* ptr[2] = {&a, &arr[0]}; // 初始化2个指针
printf("ptr[0]指向的值: %d\n", *ptr[0]); // 输出a的值10
printf("ptr[1][1]的值: %d\n", ptr[1][1]); // 输出arr[1]的值2
// 修改指针指向
ptr[0] = &b;
printf("修改后ptr[0]的值: %d\n", *ptr[0]); // 输出b的值20
return 0;
}