指针
-
0. 语法
- =右边变量类型 * 变量 = &变量;
-
1.概念
- 记录地址数据的变量
-
2.声明
- int *p_val;
- int 表示指针记录整数类型存储区的地址(这个指针指向了一个整数类型的存储区)
- *表示这个变量是一个指针
- 如果一条语句里声明了多个指针变量就需要在每个指针变量名称前加*
- 可以用typedef关键字给指针类型起别名,别名可以用来声明指针变量
#include <stdio.h>
//给指针类型起来一个类型名称
//p表示这是一个指针,int表示这是一个指向整数类型存储区的指针,_t表示这是一个类型名称
typedef int *pint_t; //这条语句给整数类型指针int * 起了一个名字叫 pint_t
int main() {
pint_t p_val; //用类型名称声明指针变量的时候,不用加*
return 0;
}
-
3.分类
- 有效指针:记录了一个有效地址的
- 无效指针:没有记录有效地址
- 空指针:固定记录空地址(程序中用NULL表示),空地址的数值固定就是0 ,可以和有效指针区分开
- 野指针: 除了空指针以外的所有无效指针,无法和有效指针区分开,所以程序中绝对不可以出现野指针,所以c语言中所有的指针必须进行初始化
-
4.初始化
- 指针变量必须初始化
- 指针变量初始化的时候*没有参与赋值过程
#include <stdio.h>
int main() {
int val = 0;
//如果不对这两个指针进行初始化,这两个变量里面的内容是随机的,它很可能不是0,也没有记录有效地址,所以它是野指针
//p_val指针被初始化成val变量存储区的地址,(p_val指针指向的是val变量的存储区)
int *p_val = &val, *p_val1 = NULL; //不论把指针初始化成什么样,只要它不是野指针就行。
//*没有参与赋值过程,被赋值的只是指针变量p_val本身
//因为这个时候指针变量p_val还没有记录有效地址,前面的*是不能使用的
return 0;
- 5.用法
- 只有记录有效地址的指针才能使用
- 主要用途就是用来找另外一个普通类型的存储区(可以把指针想象成手机里电话号码本里的一个条目,这个条目里记录的电话号码就可以看做是指针里记录的那个地址,通过这个号码可以找到某个人)
- 在有效指针前使用*可以找到它指向的存储区
#include <stdio.h>
int main() {
int val = 0;
//p_val指针被初始化成val变量存储区的地址,(p_val指针指向的是val变量的存储区)
int *p_val = &val, *p_val1 = NULL;
//指针变量名称前加*,表示p_val指针指向的存储区,也就是val变量的存储区,然后对存储区进行赋值
*p_val = 10; //这条语句其实是在对val变量的存储区进行赋值
printf("%d \n", val);
return 0;
}
//output: 10
- 在有些特殊的情况下不能用变量表示存储区,这样就可以用指针记录这个存储区的地址(int p_val = &val;),然后通过指针前面加的方式来表示这个存储区(*p_val = 10;)
- 指针除了可以用来表示某个特定的存储区,还可以用来代表存储区的身份(一种高级的使用指针的方法)
#include <stdio.h>
int main() {
int val = 0, val1 = 0, val2 = 0;
//p_min指针代表了最小数字这个身份,使用它的时候并不关心这个指针指向的存储区是哪一个,只关心那个存储区是否满足这个指针所代表的那个身份
//指针指向的存储区可能会一直变,但最小身份是不会变的
int *p_min = &val;
printf("请输入3个数字: ");
scanf("%d%d%d", &val, &val1, &val2);
if (*p_min > val1) {
p_min = &val1;
}
if (*p_min > val2) {
p_min = &val2;
}
printf("最小数字是: %d\n", *p_min);
}
/*
input: 1 2 3
output: 1
*/
- 6.地址计算规则
- 指针的使用方法和指针里面记录的地址数据的使用方法是一样的,之所以可以在指针前加*来表示它所指向的存储区是因为可以在地址数据前加 * 来表示这个地址来源的存储区。地址除了可以在前面加 *,还可以进行加减计算。
- 地址可以加减整数
- 实际上是加减多个存储区的大小
- 数组里第一个存储区的地址加下标可以得到下标对应存储区的地址,可以用这种方法来表示数组里的存储区
- 地址之间还可以做减法
- 结果是两个地址之间包含的存储区之间的个数
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *p_val = arr; //指针指向数组里的第一个存储区 arr 就是 &arr[0]
printf("arr是%p\n", arr);
printf("arr + 1 是%p\n", arr + 1);
printf("arr + 3 是%p\n", arr + 3);
printf("&arr[3]是%p\n", &arr[3]);
*(p_val + 2) = 10; //用指针表示数组里存储区的写法
printf("arr[2]是%d\n", arr[2]);
printf("&arr[3] - arr 是%d\n", &arr[3] - arr);
}
/*
output:
arr是0x7fff0ade0510
arr + 1 是0x7fff0ade0514 //因为地址来源是一个整数类型的存储区,一个整数类型存储区占4个字节
arr + 3 是0x7fff0ade051c
&arr[3]是0x7fff0ade051c
arr[2]是10
&arr[3] - arr 是3
*/
- 指针和数组之间的关系
- 如果指针记录数组里第一个存储区的地址 就可以用指针代表数组
- 可以用指针做循环变量依次处理数组里的存储区
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *p_val = NULL;
for (p_val = arr; p_val <= arr + 4; p_val++) {
printf("%d ", *p_val);
}
printf("\n");
return 0;
}
//output: 1 2 3 4 5
-
7.const关键字
- const关键字写在类型名称前(const int *p_val = arr;),表示不可以通过指针对它指向的存储区做赋值,但可以对指针本身做赋值。 (常见)
- const关键字写在指针变量名称前( int const *p_val = arr;),表示可以通过指针对它指向的存储区做赋值,但不可以对指针本身进行赋值。(很少见)
-
8.无类型指针
- 使用void做类型声明的指针
- void *p_v = NULL;
- 不限定它指向的存储区类型
- 不应该直接进行加减计算也不行该直接使用*操作符
- 使用前需要先强制类型转换成有类型指针
- 使用void做类型声明的指针
#include <stdio.h>
int main() {
char ch = 'r';
int val = 56;
float fval = 0.3f;
void *p_v = NULL; //声明一个无类型指针并初始化成空指针
//因为没有限定指针指向存储区的类型,所以p_v可以执行任何类型存储区
p_v = &ch; //用来源是字符类型存储区的地址给p_v赋值
//不能在p_v前直接加*来表示它指向的字符类型存储区
//因为这样表示不知道p_v指向的存储区的类型
//所有必须先用强制类型转换把p_v转换成字符型指针,然后才可以在前面加*
printf("%c\n",*(char *)p_v);
p_v = &val;
printf("%d\n", *(int *)p_v);
p_v = &fval;
printf("%g\n", *(float *)p_v);
return 0;
}
/*
output:
r
56
0.3
*/
//任何一直明确的指针都不可以这样指向这3种不同类型的指针的
- 9.使用指针类型的形式参数可以让被调函数使用其它函数的存储区
- 声明指针类型参数的时候尽量使用const关键字
- 无类型指针通常作为形式参数使用,从调用函数向被调用函数传递任意类型存储区
//经典例子 swap函数交换主函数里两个存储区的数值
#include <stdio.h>
void swap(int *p_val, int *p_val1) { //用指针形参指向主函数里那两个变量的存储区
int tmp = 0;
tmp = *p_val;
*p_val = *p_val1;
*p_val1 = tmp;
}
int main() {
int val = 3, val1 = 7;
swap(&val, &val1);
printf("val是%d,val1是%d\n", val, val1);
return 0;
}
/*
output: val是7,val1是3
*/
//数组做形参的本质,真正的形参就是一个指针,利用调用函数的存储区做数值传递
#include <stdio.h>
//如果能加上const就尽量加上(const表示函数不会去修改这个指针指向存储区的内容)
void print(const int *p_val, int size) {
int num = 0;
for (num = 0; num <= size -1; num++) {
printf("%d ", *(p_val + num));
}
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
print(arr, 5);
return 0;
}
//output: 1 2 3 4 5
- 10.使用指针类型的存储区(存储区地址)存放返回值可以让调用函数使用被调用函数的存储区
#include <stdio.h>
//read函数负责从键盘得到一个整数,然后把整数放到存储区里,再把这个存储区传递给调用函数
//read函数需要提供一个指针类型的存储区,来存放这个作为返回值的地址
int *read(void) {
//如果val是非静态局部变量,read函数结束后就不能再使用了,val消失&val跟着消失,是不能把这种变量传递给调用函数的
static int val = 0;
printf("请输入一个数字: ");
scanf("%d", &val);
return &val;
}
int main() {
int *p_val = NULL;
p_val = read();
printf("%d\n", *p_val);
return 0;
}