一、指针简介
指针(Pointer)是C语言的一个重要知识点,其使用灵活、功能强大,是C语言的灵魂。指针与底层硬件联系紧密,使用指针可以操作数据的地址,实现数据的间接访问。指针是C语言区别于其他高级语言的关键特性之一。
二、计算机存储机制
在计算机系统中,小端(Little Endian)和大端(Big Endian)是两种不同的字节序(Byte Order)方式,它们定义了多字节数据在内存中的存储顺序。了解这些概念对于操作系统、网络协议和低级编程(如嵌入式系统)非常重要,数组的存储不受大小端的影响,例如数组是char型数组,其数据就按照顺序依次存储,但如果数组是short型的,那么其存储顺序虽然不受影响,但是每个元素的存储方式还是要按照大小端的定义来存储。
小端(Little Endian)
小端模式是将数据的低字节存储在内存的低地址处,而高字节存储在内存的高地址处。换句话说,数据的最低有效字节在最前面(最低地址),最高有效字节在最后面(最高地址)。
理解指针的基础是理解计算机的存储机制。计算机通过内存地址来访问数据,变量在内存中占据特定的地址空间。例如:
int a = 0x12345678;
short b = 0x5A6B;
char c[] = {0x33, 0x34, 0x35};
小端存储模式
大端(Big Endian)
大端模式是将数据的高字节存储在内存的低地址处,而低字节存储在内存的高地址处。换句话说,数据的最高有效字节在最前面(最低地址),最低有效字节在最后面(最高地址)。
大端存储模式
三、定义指针
指针即指针变量,用于存放其他数据单元(变量/数组/结构体/函数等)的首地址。若指针存放了某个数据单元的首地址,则这个指针指向了这个数据单元,若指针存放的值是0,则这个指针为空指针。
定义一个指针变量:
数据类型 | 指向该数据类型的指针 | ||
(unsigned) char | 1字节 | (unsigned) char * | x字节 |
(unsigned) short | 2字节 | (unsigned) short * | x字节 |
(unsigned) int | 4字节 | (unsigned) int * | x字节 |
(unsigned) long | 4字节 | (unsigned) long * | x字节 |
float | 4字节 | float * | x字节 |
double | 8字节 | double * | x字节 |
注:16位系统:x=2,32位系统:x=4,64位系统:x=8
四、指针的操作
定义一个指针变量后,可以对指针进行多种操作。假设有以下定义:
int a; // 定义一个int型的数据
int *p; // 定义一个指向int型数据的指针
则对指针p的操作方式如下:
操作方式 | 举例 | 解释 |
取地址 | p=&a; | 将数据a的首地址赋值给p |
取内容 | *p; | 取出指针指向的数据单元 |
加 | p++; | 使指针向下移动1个数据宽度 |
p=p+5; | 使指针向下移动5个数据宽度 | |
减 | p--; | 使指针向上移动1个数据宽度 |
p=p-5; | 使指针向上移动5个数据宽度 |
如下例子所示:
#include <stdio.h>
int main(void)
{
int a = 0x66;
int *p=&a;
printf("P++操作前地址:0x%x\n",p);
p++;
printf("P++操作后地址:0x%x\n",p);
return 0 ;
}
运行结果如下:
P指针向下移动了一个数据宽度,由于是int型数据,所以为4个字节,由0x62fe14变为0x62fe18。
五、数组与指针
数组是一些相同数据类型的变量组成的集合,其数组名即为指向该数据类型的指针。数组的定义等效于申请内存、定义指针和初始化。例如:
char c[] = {0x33, 0x34, 0x35};
等效于:
int *c;
c = malloc(3*4);
*c = 0x33;
*(c+1) = 0x34;
*(c+2) = 0x35;
利用下标引用数组数据也等效于指针取内容。
例如:
c[0]; | 等效于 | *c; |
c[1]; | 等效于 | *(c+1); |
c[2]; | 等效于 | *(c+2); |
六、注意事项
在使用指针时需要注意以下几点:
- 指针的合法性:在对指针取内容之前,一定要确保指针指向合法的位置,否则将会导致程序出现不可预知的错误。
- 指针赋值:同级指针之间才能相互赋值,跨级赋值将会导致编译器报错或警告。例如,int类型指针不能赋值给char类型指针。
- 指针运算:在进行指针运算时,必须注意指针的类型和步长。例如,int类型指针加1实际是增加4个字节(在32位系统中)。
七、指针的应用
指针在C语言中的应用广泛,主要包括以下几个方面:
1. 传递参数
a. 使用指针传递大容量的参数
使用指针传递大容量的参数,可以避免参数传递过程中的数据复制,提高运行效率,减少内存占用。例如,我们有一个大数组需要传递给函数进行处理。
#include <stdio.h>
void processArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // 将数组中的每个元素乘以2
}
}
int main() {
int largeArray[1000];
for (int i = 0; i < 1000; i++) {
largeArray[i] = i;
}
processArray(largeArray, 1000); // 传递数组指针
printf("largeArray[0] = %d\n", largeArray[0]); // 输出:largeArray[0] = 0
printf("largeArray[1] = %d\n", largeArray[1]); // 输出:largeArray[1] = 2
return 0;
}
b. 使用指针传递输出参数
使用指针传递输出参数,可以实现函数返回多个值。例如,我们可以通过指针返回两个整数的和与差。
#include <stdio.h>
void sumAndDifference(int a, int b, int *sum, int *difference) {
*sum = a + b;
*difference = a - b;
}
int main() {
int a = 10, b = 5;
int sum, difference;
sumAndDifference(a, b, &sum, &difference); // 传递输出参数的指针
printf("Sum: %d\n", sum); // 输出:Sum: 15
printf("Difference: %d\n", difference); // 输出:Difference: 5
return 0;
}
2. 传递返回值
将模块内的公有部分返回,让主函数持有模块的“句柄”,便于程序对指定对象的操作。例如,使用指针返回一个动态分配的数组,并在主函数中操作它。
#include <stdio.h>
#include <stdlib.h>
int* createArray(int size) {
int *arr = (int *)malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
arr[i] = i * 10; // 初始化数组
}
return arr; // 返回指针
}
int main() {
int size = 10;
int *array = createArray(size); // 获得数组指针
for (int i = 0; i < size; i++) {
printf("array[%d] = %d\n", i, array[i]);
}
free(array); // 释放内存
return 0;
}
3. 直接访问物理地址下的数据
在嵌入式系统中,经常需要直接访问硬件设备的特定内存地址。例如,访问某个设备的ID号。
#include <stdio.h>
// 假设设备ID存储在内存地址0x4000处
#define DEVICE_ID_ADDRESS 0x4000
int main() {
unsigned int *deviceID = (unsigned int *)DEVICE_ID_ADDRESS;
printf("Device ID: 0x%x\n", *deviceID); // 直接访问设备ID
return 0;
}
4. 数据格式转换
示例:将结构体转换为字节数组
我们将定义一个包含整数和浮点数的简单结构体,并展示如何将其序列化为字节数组,以及如何从字节数组中反序列化为结构体。
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
float value;
} SimpleData;
// 序列化函数:将结构体复制到字节数组中
void serializeSimpleData(SimpleData *data, unsigned char *buffer) {
memcpy(buffer, data, sizeof(SimpleData));
}
// 反序列化函数:从字节数组中恢复结构体
void deserializeSimpleData(unsigned char *buffer, SimpleData *data) {
memcpy(data, buffer, sizeof(SimpleData));
}
int main() {
SimpleData data = {42, 3.14};
unsigned char buffer[sizeof(SimpleData)];
// 序列化数据
serializeSimpleData(&data, buffer);
// 清空原数据结构
memset(&data, 0, sizeof(SimpleData));
// 反序列化数据
deserializeSimpleData(buffer, &data);
// 输出恢复后的数据
printf("ID: %d\n", data.id); // 输出:ID: 42
printf("Value: %.2f\n", data.value); // 输出:Value: 3.14
return 0;
}
八、指针函数
指针函数是指返回指针的函数。它的返回类型是一个指针,意味着函数返回一个地址,而不是一个具体的值。
示例:指针函数
假设我们有一个函数,它返回一个指向整数的指针:
#include <stdio.h>
// 定义指针函数,返回一个指向整数的指针
int* getPointerToInteger(int *num) {
return num;
}
int main() {
int a = 10;
int *p;
// 调用指针函数并获取返回的指针
p = getPointerToInteger(&a);
printf("Value of a: %d\n", *p); // 输出:Value of a: 10
return 0;
}
函数指针
函数指针是指向函数的指针。它可以用来存储函数的地址,并通过它调用函数。这在实现回调函数和函数表(如操作系统中的系统调用表)时非常有用。
示例:函数指针
假设我们有两个函数,它们实现简单的数学运算,我们可以定义一个函数指针来调用它们。
#include <stdio.h>
// 定义两个简单的函数
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
// 定义一个函数指针类型
typedef int (*operationFunc)(int, int);
int main() {
// 声明函数指针
operationFunc op;
// 将函数指针指向add函数
op = add;
printf("Addition: %d\n", op(3, 4)); // 输出:Addition: 7
// 将函数指针指向multiply函数
op = multiply;
printf("Multiplication: %d\n", op(3, 4)); // 输出:Multiplication: 12
return 0;
}