擎星逆行亦无惧,焚膏继晷恒穷年!
文章目录
前言
Hello everyone ! forever 今天开始跟大家分享一下自己之前学习指针的一些知识和一些经验,这里指针当时我是分为两个阶段来学的,一个就是今天要给大家介绍的指针初阶,还有一个就是后面要给大家介绍的指针进阶版。
说到指针,很多人其实挺头疼的,确实 forever 第一次接触到指针的时候其实和你们一样的,哈哈哈哈哈,直接脑仁儿疼,啊哈哈哈。但是当你静下心来时,先去学其简单的知识,再一步一步往深走,你就会发现慢慢的就会得心应手啦!因此,别怕,克服恐惧的最好方法就是面对他并且战胜它。
故而希望今天这篇指针初阶能带大家入门,给大家更多的启发和帮助,助大家寻找到学习指针的方向!
正文
一、指针类型的引出
之前的函数内容里面,forever 给大家讲了C语言在函数调用时,参数传递有两种方式,一种是传值调用,参数在传递时一般采用的都是值传递的方式。此时被调函数只对形参进行修改,不会返回到主调函数,不会修改主调程序中实参变量的值。但在实际应用中,有时候还需要将被调函数对形参的修改结果返回到主调函数,使得实参变量的值直接被修改,这里就出现了另一种传址调用,C语言中可以将变量的地址作为实参传递给被调函数,这时候被调函数修改该形参存储的地址时就相当于直接修改主调函数里面实参的地址值上的值,这就起到了修改实参的值。像这样的形参储存的是地址值,它的类型就是“地址类型”,C语言把这种类型称为指针类型。
二、指针和指针变量
1、指针的地位
指针是C语言中的一个重要概念,它是C语言的精华之一。用指针可以实现函数参数的引用传递,减少传递参数的开销,还能直接对内存地址进行操作,实现动态存储管理;使得程序简洁、紧凑、高效、灵活。
2、什么是指针
指针是内存中最小单元的一个编号,也就是地址。变量的指针也就是变量的地址。
我们口头上常说的指针指的是指针变量,指针变量——用来存放地址的变量。
代码示例如下:
#include <stdio.h>
int main()
{
int a = 10;
int* p;//定义指针变量p
p = &a;//给指针变量p赋地址值
printf("%p\n%p\n%d\n", p, &a, *p);
return 0;
}
运行结果:
总结:
1、这里int* 是变量p的类型 ,p 是指针变量,是用来存放地址的变量;
2、p 等于 &a,因此说明 p 中是 a 的地址,*p等于 a 的值,所以 *p 中就是 a 的值。
3、什么是指针变量
指针变量顾名思义,就是一个变量,因此必须先定义后使用。
指针变量:我们可以通过&(取地址操作符)取出变量的内存真实地址,把地址可以存放到一个变量中,这个变量就是指针变量。
总结:指针变量,是用来存放地址的变量。(存放在指针变量中的值都被当作地址处理)
三、指针类型
1、指针类型的介绍
指针变量是用来存放地址的,该地址所代表的变量可能是各种不同类型的,所以,定义一个指针变量时候,必须同时指定它所指向的变量的类型,称之为基类型。这里就有了指针类型之说。虽然指针变量存放的都是地址,但如果指向的基类型不同,指针变量的类型也就不同,故不能互相赋值。
#include <stdio.h>
int main()
{
int a = 10;
char b = 20;
double c = 30;
int* p1;// int* 类型指针变量
p1 = &a;
char* p2;//char* 类型指针变量
p2 = &b;
double* p3;//double* 类型指针变量
p3 = &c;
printf("sizeof(p1) = %d\nsizeof(p1) = %d\nsizeof(p1) = %d\n", sizeof(p1), sizeof(p2), sizeof(p3));
return 0;
}
在32位机上运行结果:
在64位机上运行结果:
总结:
1、int*、char* 等这是变量的类型;
2、指针的大小在32位机上是4个字节,在64位机上是8个字节。
2、指针类型的意义
1、指针的解引用
解引用:就是引用它所指向的变量的值
(1)int* 类型的解引用(这里主要看其内存发生的变化)
#include <stdio.h>
int main()
{
int a = 0x22334455;
int* p = &a;
*p = 0;//解引用,给 a 赋值为0
return 0;
}
int* 类型解引用内存变化如下两张图,解引用前:此时给a赋值为0x22334455,在内存中是倒着存放的如下的 55 44 33 22
解引用后:在 int* 类型这里解引用后直接四个字节都被改变了,其值都变为00 00 00 00
(2)char* 类型解引用
#include <stdio.h>
int main()
{
int a = 0x99887766;
char* p = (char*)&a;//类型转换
*p = 0;//解引用操作
return 0;
}
char* 类型解引用内存变化如下两张图,解引用前:a 中赋值为0x99887766,内存中存放的是66 77 88 99
解引用后:给 a 赋值为0,内存中变化为00 77 88 99
总结:
指针类型决定了在解引用时候一次能访问几个字节(指针的权限) int* 和float* 类型一次能访问4个字节 char* 一次能访问一个字节。
2、指针的前后移动
#include <stdio.h>
int main()
{
int a = 10;
int* p1 = &a;
printf("%p\n%p\n%p\n", p1 - 1, p1, p1 + 1);//加一代表指针向后走一步
char b = 20;
char* p2 = &b;
printf("%p\n%p\n%p\n",p2-1, p2, p2 + 1);//减一代表指针向前走一步
return 0;
}
运行结果:
总结:
1.指针类型决定了指针向前或向后走一步,走多大距离(单位是字节);
2.这里int 类型向前或向后走4个字节,char 向前向后走1个字节。
例题:利用指针打印数组
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
for (int i = 0; i < 10; i++)
{
scanf("%d", p + i);//p+i等价于&arr[i]
}
for (int i = 0; i < 10; i++)
{
p + i;//p+i实现地址后移动
printf("%d ", *(p+i));//*(p+i)即可以打印每一位地址上的值
}
return 0;
}
运行结果:
四、野指针
1、野指针概念
野指针就是指针指向位置是不可知的(随机的、不正确的、没有明确的)。
2、野指针的成因
1、指针未初始化
全局变量未初始化,系统会给其默认值为0;局部变量未初始化,系统会给其随机值。
错误示例: 指针未初始化示例
#include <stdio.h>
int main()
{
int* p; //野指针
*p = 20;//此时直接给其赋值是错误操作
return 0;
}
vs2019 报警告信息:
2、指针越界
这里一般就和数组越界有关,forever 之前在数组里面详细介绍过数组越界啦!因此,这里就不在过多浪费大家时间啦~
3、指针指向空间释放
int* test()
{
int a = 20;
return &a;//属于返回局部变量的地址
}
int main()
{
int* p=test();
printf("hehe\n");
printf("%d", *p);
return 0;
}
运行结果:
总结:
1.当a开辟一块空间之后,然后test函数结束之后空间就释放了,此时你主函数里的语句再去访问就无法访问到其地址,这里就属于野指针
2.如果按照正常思维这里输出的值是20,但是这里是野指针现象,因此出现错误,不过大家注意这里输出的 5 只是随机值。
4、无效指针
int main()
{
int* p=NULL;//NULL指的是空指针,NULL实际上和0一个意思
return 0;
}
3、如何规避野指针
1、指针初始化
2、小心指针越界
3、指针指向的空间释放及时的置为空指针
4、避免返回局部变量的地址
5、指针使用前检查有效性,在指针不为空的时候才使用它
五、指针的运算
1、指针±整数
(上面有讲解过)
这里就是前面指针类型里面讲的指针的前后移动,实际上就是指针加减整数运算。
利用指针±整数求字符串长度
//利用指针+-求字符串长度
#include <stdio.h>
int my_strlen(char* ch)
{
int count = 0;
while (*ch != '\0')
{
count++;//这里是计数器
ch++;//指针++,计数器统计一次,地址向下走一位
}
return count;
}
int main()
{
char ch[] = "abcdef";
int len = my_strlen(ch);
printf("%d\n", len);
return 0;
}
运行结果:
2、指针- 指针
前提:两个指针指向的是同一块空间
指针-指针=的到数组的长度
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%d\n", &arr[9] - &arr[0]);
printf("%d\n", &arr[0] - &arr[9]);
return 0;
}
运行结果:
利用指针-指针求字符串长度
//利用指针-指针求字符串长度
#include <stdio.h>
int my_strlen(char* ch)
{
char* first = ch;//定义指针变量first来存储数组中第一个字符串的位置
while (*ch != '\0')
{
ch++;//依次向后,从而获取最后一个字符串的位置
}
return ch-first;//最后一个字符串的位置减去初始位置,就是字符个数,即字符串长度
}
int main()
{
char ch[] = "abcdefgh!";
int len = my_strlen(ch);
printf("%d\n", len);
return 0;
}
运行结果:
3、指针的关系运算
来看看一段代码:
//指针的关系运算
#include <stdio.h>
#define N 5
int main()
{
int arr[N];
int* p = arr;
int* i;
for (i = &arr[N]; i > &arr[0];)
{
*--p=0;
}
return 0;
}
分析图如下:
错误版本,再来看看这段代码简化版:
//指针的关系运算错误展示
#include <stdio.h>
#define N 5
int main()
{
int arr[N];
int* p = arr;
int* i;
for (i = &arr[N-1]; i >= &arr[0];i--)
{
*i=0;
}
return 0;
}
分析图如下:
总结:
标准规定:
允许向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
六、指针和数组
指针——地址
数组——一组相同类型的数据
指针和数组本身是两种事物,其联系是:数组可以通过指针来访问
来看看一下代码:
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (int i = 0; i < sz; i++)
{
printf("%d\n", *(p + i));//通过指针打印数组的值
}
printf("\n");
for (int i = 0; i < sz; i++)
{
printf("%p==%p\n", p + i, &arr[i]);//通过指针直接访问数组的地址
}
return 0;
}
运行结果:
七、二级指针
定义一个a变量需要开辟一块空间,然后将a地址存到指针变量pa里面去,int pa里的pa是int*类型的变量,既然为变量,那么也会开辟一块空间,pa也有自己的地址。
二级指针变量就是存放一级指针地址的一个变量,上代码:
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa; //ppa是一个二级指针,,用来存放一级指针pa的地址
int*** pppa = &ppa; //pppa是一个三级指针,用来存放二级指针ppa的地址
printf("%p\n%p\n%p\n", pa, ppa,pppa);
printf("\n");
printf("%p\n%p\n%p\n", &a, &pa,&ppa);
return 0;
}
运行结果:
解释说明:
int** ppa = &pa;
这里最后一个是说明ppa是指针变量,第一个和int 连接在一起说明ppa的指向对象是pa,也指pa是int类型的
int** pppa = &pa;
同理最后一个 * 是指pppa是一个指针变量,前两个 ** 和int连接在一起说明pppa指向的对象是ppa,也指ppa是 int** 类型的
画图分析:
通过修改多级指针的地址从而修改初始地址的值:
//通过修改多级指针地址从而修改其值
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
int*** pppa = &ppa;
***pppa = 100;
printf("%d\n", a);
return 0;
}
运行结果:
总结:
二级指针是用来存放一级指针变量的地址的
三级指针是用来存放二级指针变量的地址的
八、指针数组
指针数组实际上是数组,是存放指针的数组,它的每一个元素都是指针。
看代码分析:
#include<stdio.h>
int main()
{
int arr[10];//整型数组—存放整型的数组
char brr[10];//字符数组—存放字符的数组
int a = 10;
int b = 20;
int c = 30;
//指针数组—存放指针的数组
int* crr[5] = { &a,&b,&c };//指针数组—存放整型指针的数组,这里只存了三个指针,数组其他空间都默认为空指针
return 0;
}
如图分析:
简单的使用:
通过指针数组找到数组里元素对应的值
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* crr[5] = { &a,&b,&c };//指针数组—存放整型指针的数组
for (int i = 0; i < 3; i++)
{
printf("%d\n", *(crr[i])); //通过指针数组找到数组里的地址所对应的值
}
return 0;
}
运行结果:
结语
今天,forever 带来了大家最头疼的指针,这里先介绍指针初阶到这里,后面再继续给大家带来指针进阶,以至于完全拿下指针不是问题。当然,forever 也是根据自己之前的学习知识和经验总结而来,若有什么不足或错误之处,还望大佬们积极指出来,对我进行批评指针哈!
谢谢观看!
再见啦!
以上代码均可运行,所用编译环境为 vs2019 ,运行时注意加上编译头文件#define _CRT_SECURE_NO_WARNINGS 1