C进阶
1. 指针常量和常量指针
我总结的简单理解方式 >
const挨着谁,就是修饰谁。
指针常量
指针常量是一个常量,其值(即指针所指向的地址)在初始化后不能更改,但可以修改它所指向的数据。
int value = 42;
int data = 10;
int *const ptr = &value;
// ptr = &data; // 非法
*ptr = 50; // 合法
常量指针
常量指针是一个指针。因此无法通过这个指针来修改它所指向的数据,但可以改变指针本身所指向的地址。
int value = 42;
int data = 10;
const int temp = 20;
const int *ptr = &value;
// int const *ptr = &value; // 也可以这样写(但我不喜欢)
// *ptr = 60; // 非法
ptr = &data; // 合法
ptr = &temp; // 合法
指向常量的指针常量
既不能修改指针所指向的数据,也不能改变指针本身所指向的地址。
const int *const ptr = &value; // ptr 是一个指向常量的指针常量
2. 指针数组和数组指针
我总结的简单理解方式 >
数组指针,会用括号强调指针类型
指针数组
指针数组是一个包含多个指针的数组,每个指针可以指向不同类型的对象或相同类型的多个对象。
指针数组的大小在声明时确定,并且每个元素都是一个独立的指针变量。
int *array[3]; // 一个包含3个整型指针的数组
// 初始化指针数组
int x = 10;
int y = 20;
int z = 30;
array[0] = &x;
array[1] = &y;
array[2] = &z;
// 访问指针数组中的值
int value = *array[1]; // value 现在为 20
数组指针
数组指针是一个指向数组的指针,它本身不是一个数组,而是指向一个数组首元素的指针。
数组指针的大小是固定的,它指向的数组大小也是固定的。
int (*pointer)[3]; // 一个指向包含3个整型的数组的指针
// 初始化数组指针
int data[3] = {10, 20, 30};
pointer = &data; // 数组指针指向arr数组的首地址
// 访问数组指针所指向的数组中的值
int value = (*pointer)[1]; // value 现在为 20
// 或者使用数组表示法
value = pointer[0][1]; // value 同样为 20
区别
- 指针数组 是一个包含多个指针的数组,每个指针可以独立地指向不同的对象。
- 数组指针 是一个指向数组的指针,它总是指向一个固定大小的数组的首元素。
用途
- 指针数组 通常用于存储多个指针,这些指针可能指向不同类型的对象或相同类型的多个对象。它们常用于函数参数中,以允许函数接受可变数量的指针。
- 数组指针 在某些情况下用于处理多维数组,尤其是当需要传递多维数组到函数中时,并保持数组的尺寸信息。它们也用于创建动态大小的数组(尽管这通常通过指针和
malloc
等函数来完成)。
3. 指针运算
在C语言中,指针运算可以改变指针所指向的内存地址,主要用于数组和内存管理。
不同类型的指针进行算术运算时,移动的内存大小是不同的。
例如,一个char *
指针加1将只移动1个字节,而一个int *
指针加1将移动4个字节(或更多,取决于系统和编译器)。因此,将不同类型的指针相互转换并进行算术运算通常是不安全的。
注意
在进行指针运算时,必须确保指针所指向的内存区域是有效的,并且你有权限访问它。否则,你可能会遇到未定义的行为,包括程序崩溃和数据损坏。
指针加法
指针加法运算时,指针会按照其指向类型的大小来移动。
例如,如果你有一个指向整型的指针,那么加1将会使指针向前移动一个整型变量的大小(通常是4个字节)。
int array[10];
int *p = &array[0];
p = p + 1; // p现在指向array[1]的位置
指针减法
指针减法会按照指针指向类型的大小来向后移动指针。
#include <stdio.h>
int main()
{
int array[10] = {0,1,2,3,4,5,};
int *p = &array[2]; // p现在指向array[2]的位置
p = p - 1;
printf("result = %d\n", *p);
p = &array[4]; // p现在指向array[4]的位置
printf("result = %d\n", p[-1]); // 非常规用法,但是C语言支持
return 0;
}
指针递增和递减
递增和递减操作分别使指针向前或向后移动一个类型的大小。
int *p = &value;
p++; //指向下一个整型的位置
int *p2 = &value;
p2--; //指向前一个整型的位置
指针之间的减法
当你对两个指针进行减法运算时,结果将是两个指针之间元素数量的差值,而不是内存地址的差值。这两个指针必须指向同一块连续的内存区域(例如数组的元素)。
int array[10];
int *p = &array[0];
int *q = &array[5];
int diff = q - p; // diff的值是5,因为q指向p后面5个整型的位置
4. 函数指针
函数指针本质上是一个指针变量,它存储的是函数的地址,而不是函数调用的结果。
- 可以将函数作为参数传递给其他函数
- 可以将函数作为数据结构(如结构体或联合体)的一部分,
- 可以将函数作为返回值从函数中返回。
定义
定义函数指针,你需要指定函数指针的类型。这通常包括函数的返回类型和参数类型。示例如下:
// 表示一个指向函数的指针,该函数接受一个int类型的参数并返回一个int类型的值。
typedef int (*function_t)(int);
// 或者直接定义函数指针变量
int (*pfunction)(int);
应用
函数指针通常用于实现回调函数(callback functions)和函数表(function tables)。
回调函数
回调函数是一种通过函数指针调用的函数,它允许一个函数在运行时确定将要调用哪个函数。这在事件驱动编程和异步编程中非常常见。
#include <stdio.h>
// 定义一个回调函数类型
typedef void (*callback_t)(int);
// 一个回调函数的示例
void print_number(int number) {
printf("Number: %d\n", number);
}
// 一个接受回调函数作为参数的函数
void process_data(int data, callback_t callback) {
callback(data);
}
int main() {
process_data(42, print_number);
return 0;
}
函数表
函数表是一个包含多个函数指针的数组,它允许程序在运行时选择并调用不同的函数。
#include <stdio.h>
// 函数表类型定义
typedef void (*func_table_t)(int);
// 两个示例函数
void functionA(int x) {
printf("Function A called with %d\n", x);
}
void functionB(int x) {
printf("Function B called with %d\n", x);
}
int main() {
// 创建一个函数表
func_table_t functionTable[] = {functionA, functionB};
// 使用函数表调用函数
functionTable[0](10); // 输出 "Function A called with 10"
functionTable[1](20); // 输出 "Function B called with 20"
return 0;
}
5. 数组和指针注意点
用数组做函数参数
传递数组给函数时,实际上是将数组的首地址作为参数传递给函数。
void print_array(int a[])
{
printf("sizeof(a) = %d\n", sizeof(a));
}
上述示例中,无论实参数组的大小是多少,sizeof(a)都等于4(32位系统)。
对数组取地址
int a[4] = {1,2,3,4};
虽然,在数值上有 a == &a[0] == &a
,但它们有不同的含义。
- 数组名或对数组元素取地址,相当于是元素类型的指针。比如
a 和 &a[0]
- 对数组名取地址,相当于是数组指针。比如
&a
#include <stdint.h>
#include <stdio.h>
#define COUNT 16
int main(void) {
// int型数组
int a[COUNT] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
printf("%d\n", *a);
printf("%d\n", *(&a[0] + 4)); // 4
printf("%d\n", *((&a)[0] + 4)); // 4
printf("%d\n", *(&a[1] + 6)); // 6
printf("%d\n", *((&a)[1] + 6)); // 未知数据
// 从数据上不好分清,但是从地址上可以区分。
// ------------------------------
// 首地址,三种表达方式在数值上相等。
// 但是含义不同
printf("%p\n", a);
printf("%p\n", &a);
printf("%p\n", &a[0]);
// &a[x] 本质上是int型指针
printf("%p\n", &a[1]); // &a[0] + 1 * sizeof(int)
printf("%p\n", a + 1);
// &a 本质上是数组指针
printf("%p\n", (&a)[1]); // &a[0] + 1 * (COUNT * sizeof(int))
return 0;
}
/*
// 输出结果
0
4
4
7
-1572082288
0x7ffd36ba8100
0x7ffd36ba8100
0x7ffd36ba8100
0x7ffd36ba8104
0x7ffd36ba8104
0x7ffd36ba8140
*/