指针
一、指针是什么
想象你有一张藏宝图,这张藏宝图上有一个标记,告诉你宝藏在哪里。在C语言中,指针就像是这张藏宝图,它记录了一个变量(宝藏)在内存中的地址(位置)。
1. 定义指针
int *p;
这行代码定义了一个指针p
,它指向一个整数类型的变量。注意前面有一个*
,这是告诉编译器p
是一个指针。
2. 指针和变量
假设你有一个变量a
:
int a = 10;
你可以让指针p
指向a
:
p = &a; // &a 表示变量a的地址
现在,p
就记录了a
的地址。你可以通过p
来访问a
的值:
printf("%d", *p); // 输出10,*p 表示指针p指向的变量的值
二、指针的基本操作
1. 取地址(&
)
&
操作符用来获取一个变量的地址。比如:
int a = 10;
int *p = &a; // p现在保存了a的地址
2. 间接访问(*
)
*
操作符用来通过指针访问它指向的变量的值。比如:
printf("%d", *p); // 输出10,因为*p是通过指针p访问变量a的值
三、指针和数组
数组和指针关系非常紧密,数组名本身就是一个指针,指向数组的第一个元素。
1. 数组名作为指针
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 或者 int *p = &arr[0]; 这两种写法等价
现在p
指向了数组arr
的第一个元素。
int a = 10;
2. 通过指针访问数组元素
printf("%d", *(p + 2)); // 输出3,因为p+2指向数组的第三个元素
四、指针的指针
想象你有一张藏宝图,这张藏宝图上有一个标记,指向另一张藏宝图,而那张藏宝图上有一个标记,指向真正的宝藏。在C语言中,这就是指针的指针。
1. 定义指针的指针
int a = 10;
int *p = &a; // p指向a
int **pp = &p; // pp指向p
2. 通过指针的指针访问变量
printf("%d", **pp); // 输出10,因为**pp最终指向变量a
五、指针和函数
指针可以和函数一起用,比如把指针作为函数参数,或者让函数返回一个指针。
1. 指针作为函数参数
void increment(int *p) {
(*p)++; // 通过指针p修改它指向的变量的值
}
你可以这样调用这个函数:
int a = 10;
increment(&a); // 把a的地址传给函数
printf("%d", a); // 输出11
2. 函数返回指针
int* createArray(int size) {
int *arr = (int*)malloc(size * sizeof(int)); // 动态分配内存
for (int i = 0; i < size; i++) {
arr[i] = i;
}
return arr;
}
你可以这样使用这个函数:
int *arr = createArray(5);
printf("%d", arr[0]); // 输出0
六、指针和结构体
结构体是一种可以包含多个不同类型数据的集合。指针也可以指向结构体。
1. 定义结构体和结构体指针
typedef struct {
int age;
char name[50];
} Person;
Person *p;
2. 使用结构体指针
Person person = {25, "Alice"};
p = &person; // p指向person
printf("%s is %d years old.", p->name, p->age); // 使用->访问结构体成员
七、指针的常见问题
1. 野指针
野指针是指没有指向有效内存地址的指针。比如:
int *p; // p没有初始化,是一个野指针
使用野指针是非常危险的,可能会导致程序崩溃。
2. 悬挂指针
悬挂指针是指指向的内存已经被释放的指针。比如:
int *p; // p没有初始化,是一个野指针
八、数组与指针一些的区分
1.定义和内存分配
数组:
- 数组是一组相同类型数据的集合,定义时需要指定大小,内存分配是连续的,大小在编译时确定。
- 例如:
int arr[5];
定义了一个可以存储5个整数的数组,内存分配是连续的。
指针:
- 指针是一个变量,用于存储内存地址,内存分配是动态的,大小在运行时确定。
- 例如:
int *p;
定义了一个指针变量,用于存储整数的地址。
2.数组名和指针变量的区别
数组名:
- 数组名是一个常量指针,指向数组的第一个元素,不能修改。
- 例如:
int arr[5]; arr = &arr[1];
是错误的,因为数组名是常量指针。
指针变量:
- 指针变量可以动态地指向不同的内存地址。
- 例如:
int a = 10, b = 20; int *p; p = &a; p = &b;
这里指针变量p
可以动态地指向a
和b
的地址。
3.数组和指针在内存布局上的区别
数组:
- 数组在内存中占据连续的内存空间,数组名代表了数组的起始地址。
- 例如:
int arr[5] = {1, 2, 3, 4, 5};
数组在内存中连续存储这5个元素。
指针:
- 指针本身占据一个小的内存空间(存储地址),它指向的内存可以是动态分配的。
- 例如:
int *p = (int *)malloc(5 * sizeof(int));
这里动态分配了5个整数的内存空间。
4.总结
- 数组是一组相同类型数据的集合,内存连续,大小固定;指针是一个变量,存储内存地址,内存分配动态。
- 数组名是常量指针,指向数组首元素;指针变量可以指向不同的地址。
- 数组在内存中占据连续空间;指针本身占小空间,指向的内存可以动态分配。
- 数组作为函数参数传递首地址,影响原数组;指针作为参数传递地址值,可修改内容。
- 数组内存大小固定;指针可用于动态内存管理。
九、练习题
问题:程序的输出是什么?
练习题1:指针和数组的基本操作
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
// 通过指针访问数组元素
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i));
}
printf("\n");
// 修改数组元素的值
*(p + 2) = 10;
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
答案:
1 2 3 4 5
1 2 10 4 5
练习题2:指针的指针
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
int **pp = &p;
printf("%d\n", **pp); // 输出a的值
**pp = 20;
printf("%d\n", a);
return 0;
}
答案:
10
20
练习题3:指针和函数
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 5, y = 10;
swap(&x, &y);
printf("x = %d, y = %d\n", x, y);
return 0;
}
答案:
x = 10, y = 5
练习题4:指针和结构体
#include <stdio.h>
typedef struct {
int age;
char name[50];
} Person;
int main() {
Person person = {25, "Alice"};
Person *p = &person;
printf("%s is %d years old.\n", p->name, p->age);
p->age = 30;
printf("%s is now %d years old.\n", person.name, person.age);
return 0;
}
答案:
Alice is 25 years old.
Alice is now 30 years old.
练习题5:野指针和悬挂指针
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
printf("%d\n", *p);
free(p);
// p现在是一个悬挂指针
*p = 20; // 危险操作
printf("%d\n", *p);
int *q; // 野指针
printf("%d\n", *q); // 危险操作
return 0;
}
答案:
第一个printf输出10。
第二个printf输出未定义行为,因为p是一个悬挂指针。
第三个printf输出未定义行为,因为q是一个野指针。
十、总结口诀
-
指针定义要加
*
,取地址用&
,值间接访问还用*
。 -
数组名是指针,指向首元素,数组大小编译定,指针内存动态分。
-
指针可变地址,数组名是常量,指针数组紧相关,操作灵活效率高。
-
指针函数参数传,修改内容很方便,结构体指针用
->
,访问成员很简单。 -
野指针悬指针,使用需谨慎,内存管理要小心,释放后指针别乱用。
下一节详细解释函数和结构体,byb~