指针的基本定义和使用
指针是C语言中一个核心且强大的概念,它允许程序直接访问和操作内存地址。理解指针,首先要理解内存和地址的基本概念。
内存与地址
计算机内存是由一系列连续的存储单元组成,每个单元都有一个唯一的地址。变量在内存中占用一定的空间,其地址就是该空间起始位置的编号。
指针是什么?
指针是一种变量,其存储的值是另一个变量的内存地址。通过指针,我们可以间接访问和修改该地址对应的数据。
指针的基本语法
c
int a = 100; // 定义一个整型变量a
int *p = &a; // 定义一个整型指针p,并将a的地址赋值给p
&a表示取变量a的地址。int *p表示p是一个指向int类型的指针。*p表示访问p指向的内存内容(解引用)。
指针的使用示例
c
#include <stdio.h>
int main() {
int a = 100;
int *p = &a;
printf("a = %d\n", a); // 输出: a = 100
printf("*p = %d\n", *p); // 输出: *p = 100
*p = 200; // 通过指针修改a的值
printf("a = %d\n", a); // 输出: a = 200
return 0;
}
指针与变量的关系
- 变量名是内存空间的标识符。
- 指针存储的是该内存空间的地址。
- 通过指针可以间接访问和修改变量的值,实现"远程操作"。
不同类型指针之间的使用
指针的类型必须匹配
指针的类型决定了它指向的数据类型。不同类型的指针不能混用,否则可能导致未定义行为。
c
char ch = 'A';
int num = 100;
int *p = &ch; // 错误:类型不匹配,char* 不能赋给 int*
类型转换与指针
可以使用强制类型转换,但必须谨慎,因为可能引发内存访问错误。
c
char ch = 'A';
int *p = (int*)&ch; // 强制转换,语法正确但危险
*p = 1000; // 可能导致内存越界
示例:类型不匹配的危险
c
#include <stdio.h>
int main() {
char ch = 'A';
int *p = (int*)&ch; // 强制转换
printf("ch = %c, %d\n", ch, ch); // 正常输出
*p = 1000; // 写入4字节,但ch只有1字节
// 可能导致程序崩溃或数据错误
return 0;
}
指针所占内存空间的大小
指针的大小与系统架构相关
- 在32位系统中,指针通常为4字节。
- 在64位系统中,指针通常为8字节。
所有指针的大小相同
无论指针指向什么类型(int、char、double 等),其大小都相同,因为它存储的只是地址。
c
#include <stdio.h>
int main() {
printf("sizeof(int*) = %zu\n", sizeof(int*));
printf("sizeof(char*) = %zu\n", sizeof(char*));
printf("sizeof(double*) = %zu\n", sizeof(double*));
printf("sizeof(void*) = %zu\n", sizeof(void*));
return 0;
}
常见的地址类型以及获取地址的方法
取地址运算符 &
& 用于获取变量的地址,其类型为"数据类型*"。
c
int a; // &a 的类型为 int*
double b; // &b 的类型为 double*
int c[5]; // &c 的类型为 int (*)[5]
int *d; // &d 的类型为 int**
char e[3][4]; // &e 的类型为 char (*)[3][4]
数组的地址
数组名在大多数情况下表示数组首元素的地址,类型为"元素类型*"。
c
int a[3]; // a 的类型为 int*(指向 a[0])
char b[3][4]; // b 的类型为 char (*)[4](指向 b[0])
int *c[5]; // c 的类型为 int**(指向 c[0])
例外情况:
&数组名:表示整个数组的地址,类型为"数组类型*"。sizeof(数组名):返回整个数组的大小。
数组元素类型的计算
在定义数组时,去掉数组名和第一组[],剩下的就是元素类型。
c
int a[3]; // 元素类型为 int
char b[3][4]; // 元素类型为 char [4]
int *c[5]; // 元素类型为 int*
int *d[4][5]; // 元素类型为 int (*)[5]
指针的定义与初始化
定义方法
指针的定义遵循"数据类型 *变量名"的形式。
c
int *p; // 定义一个指向int的指针
char *pc; // 定义一个指向char的指针
double *pd; // 定义一个指向double的指针
初始化
指针在使用前最好进行初始化,否则可能成为"野指针"。
c
int a = 10;
int *p = &a; // 正确初始化
int *q = NULL; // 初始化为空指针
易错形式
c
int *c, d; // 只有c是指针,d是普通int
int *e, *f; // e和f都是指针
指针的要素
指针的三个要素
- 指针本身的类型 :如
int*、char*。 - 指针的数值:存储的地址值。
- 指针的指向对象类型:决定了解引用时访问的空间大小和类型。
c
int a = 100;
int *p = &a;
p的类型:int*p的数值:&ap的指向对象类型:int
指向对象类型的意义
指向对象类型决定了:
*p访问的内存大小(如int为4字节)。*p的数据类型。
野指针
什么是野指针?
野指针是指未被初始化或指向无效内存的指针。使用野指针可能导致程序崩溃。
c
int *p; // 未初始化
*p = 10; // 错误:访问未知内存
避免野指针的方法
- 初始化指针为
NULL。 - 使用前检查指针是否有效。
c
int *p = NULL;
if (p != NULL) {
*p = 10;
}
空指针 NULL
NULL 是一个表示"空地址"的宏,通常定义为0。
c
int *p = NULL;
if (p == NULL) {
printf("指针为空\n");
}
指针的相关运算
算术运算
指针只能进行加法和减法运算,单位是指向对象类型的大小。
c
int a[5] = {1, 2, 3, 4, 5};
int *p = &a[0];
p = p + 1; // 指向 a[1],地址增加 sizeof(int) 字节
p = p - 1; // 指回 a[0]
示例:
c
int *p = NULL;
p = p + 1; // 地址增加 4 字节(假设int为4字节)
double *q = NULL;
q = q + 2; // 地址增加 16 字节(假设double为8字节)
指针相减
两个同类型指针相减,结果为它们之间的元素个数。
c
int a[5] = {1, 2, 3, 4, 5};
int *p1 = &a[0];
int *p2 = &a[3];
printf("%ld\n", p2 - p1); // 输出: 3
关系运算
指针可以进行比较(==、!=、<、> 等),通常用于数组遍历。
c
int a[5];
int *p = a;
while (p < a + 5) {
printf("%d ", *p);
p++;
}
赋值运算
指针之间可以直接赋值,但类型必须匹配。
c
int a = 10;
int *p = &a;
int *q = p; // q 也指向 a