一、内存和地址
想要了解指针是什么,我们首先要明白内存和地址之间的关系。通过名字其实就能想象得到它们之间的关系,那让我们举一个例子来进行说明:就比如有一个大学里的寝室楼,把我们这些学生存放到里面,每一个楼层都有很多房间,而此时我的朋友要来找我探讨C语言里的指针~,可是这些房间没有门牌号,我的朋友只能一个个的找才找到我。这样看来,如果朋友想找到我是一件非常麻烦的事情,那现在我们给每个房间加上门牌号,下次朋友再来找我就能够通过门牌号精准的直接找到我。
而寝室楼里存放的我们和寝室的门牌号,就像内存和地址之间的关系一样。cpu在工作时,需要用到的数据是从内存中取出来的(就像导员要来寝室楼里找人!!!)。经过处理后会将数据再次放到内存里。而内存是有大有小的,所以有很多计算机存储单位:
1 bit - ⽐特位 1Byte = 8bit
2 Byte - 字节 1KB = 1024Byte
3 KB 1MB = 1024KB
4 MB 1GB = 1024MB
5 GB 1TB = 1024GB
6 TB 1PB = 1024TB
7 PB
而想要读取内存就像我的朋友在楼里找我,因为需要标明寝室的地址(门牌号),而这个地址在C语言中就被称作“指针”,没错,C语言中地址就是指针,指针就是地址。给内存单元的编号就相当于门牌号,也就相当于地址,相当于指针。如图,此时我们创建了一个arr数组,我们查看它的地址就能找到它里面的元素,这就像通过寝室门牌号找到了同学。我们再创建一个整型变量,(整型变量占四个字节,一个字节有八个比特位)让我们看看这四个寝室吧。
这就是内存中用于存放整形变量a的四个字节。
二、指针变量的使用
说了那么多,虽然明白了指针变量是什么,但指针变量该如何去使用呢?让我们来认真的,仔细地,一步一步的看清指针变量的样子吧!
1.取地址操作符
在我们编程的过程中创建变量其实就是向内存申请空间。而每次向内存申请空间都会得到相应的字节,每一个字节又都有自己的地址,那我们该如何来得到地址呢?这就需要一个非常重要的符号,取地址操作符(&),该如何使用呢?看这一段代码:
#include <stdio.h>
int main()
{
int a = 10;
&a;
//将变量a的地址取出
printf("%d 的地址是 %p\n", a, &a);
//%p是用来打印地址(指针地址)的
return 0;
}
这样就能够将一个变量的地址取出来了,只需要在数据名前加个取地址操作符(&)就可以了!
是不是觉得这个取地址操作符好像在哪里见过呀?嘿嘿,没错。取地址操作符的符号和上次学习过的&(与)操作符,占位与操作符是一样的,但是在代码中会根据它身边变量的个数来决定它到底是什么操作符,如果身边有两个变量(如a&b)那就是占位与操作符,如果身边只有一个变量(如&a)那就是取地址操作符!~
2.解引用操作符
在C语言中数据都可以被存储起来,那有的人可能就想问了,地址就不能被存起来吗?答案是当然可以存起来啦,而用来存储指针的变量也就是指针变量啦!
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
//(int*)代表p是一个用来存放指针的指针变量
// 将变量a的地址取出,存到指针p中
printf("%d 的地址是 %p\n", a, p);
return 0;
}
这就是指针变量的创建方法,怎么样?还挺简单的吧。
注意:(int*)是一个整体,p是一个整体,int*代表p的数据类型。
那此时我们已经学会了如何存储指针(创建指针变量),能够做到将数据的地址存储在指针变量中,但是既然能做到将数据对应的地址存储,那反过来思考,我们也应该能做到通过地址将对应的数据取出来。这就是接下来我们要了解的解引用操作符。接下来我们看一段代码。
#include <stdio.h>
int main()
{
int a = 32;
int* p = &a;
//创建了一个指针变量p用来储存a的地址
printf("%d的地址是 %p\n", a, p);
printf("指针p指向的地址是 %p 值为%d\n", p, *p);
*p = 100;
//此时使用了解引用操作符
//由于解引用操作符的作用是返回内存地址中对应的对象,所以直接改变了a的值
printf("指针p指向的地址是 %p 值为%d\n", p, a);
return 0;
}
由此我们可以看出,解引用操作符的作用是返回内存地址中对应的对象。并且我们可以通过解引用操作符来改变内存地址对应对象的值。
(创建的指针变量只会存放对应变量的首地址,确定对应变量的完整地址需要通过变量的类型进行判断。)
(指针变量p也有自己的地址,只不过里面存放的是别人的地址。)
三、指针变量的大小
在C语言中,在不同的编译环境(32位x86,64位x64)中,变量的大小也是会随之改变的,那让我们来一起看一看在不同编译环境下指针变量的大小吧!
感觉和我们想的不太一样呢,看来在相同的编译环境中不论是什么类型变量的指针所占的字节都是一样的。(32位机器 指针变量存储4个字节的地址,64位机器 指针变量存储8个字节的地址)。
四、指针类型
那可能有的人就想问了,既然编译环境相同的情况下,所有的指针变量存储字节大小都是一样的,我不管三七二十一的,所有指针变量我都只用相同的类型不就好了吗?不不不,这是大错特错的,至于为什么错,我们先引入一段代码运行一遍就能够一目了然了。
#include <stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
int b = 0x12345678;
char* pb = (char*)&b;
*pb = 0;
return 0;
}
我们先看一下使用相同类型的变量与指针变量进行数值定义时的情况。
接下来我们再看看用char类型强制改变int类型会发生什么。
由此可见,结果一目了然,只使用一种类型的指针变量是不行的,因为虽然不同类型的指针变量在不同的编译环境下大小相同,但是:使用不同类型的变量对指针解引用,会影响在解引用时一次能操作几个字节。
五、指针运算
1.指针+-整数
指针的运算与数组的运算有一些相似之处,比如数组arr中存入十个元素,想要输出第二个元素就要输出arr[0+1],而如果定义一个char类型变量ch,使用char*指针变量pch时,想输出char类型变量地址的第二个字节,就要输出pch+(0+1)。都像是在0的基础上加上一个数字,然后输出对应的位置。这里我们上代码运行一下更加一目了然。
#include <stdio.h>
int main()
{
int a = 10;
char* pa = (char*)&a;
int* pb = &a;
printf("%p\n", &a);
printf("%p\n", pa);
printf("%p\n", pa + 1);
printf("%p\n", pb);
printf("%p\n", pb + 1);
return 0;
}
我们这里能够注意到,在使用int*类型指针变量+1时,跳过了四个字节。
使用char*类型指针变量+1时,跳过了一个字节。
而不同类型的指针变量+1时,跳过的字节大小取决于相应类型变量的字节大小。(-1也同理)
char类型:1字节
short类型:2字节
int类型:4字节
long类型:4字节(32位处理器)或8字节(64位处理器)
float类型:4字节
double类型:8字节
2.指针+-指针
我们通过这道例题来帮助我们理解指针之间的加减法。
例题:手动模拟实现strlen函数
思路:通过末尾指针-初始指针的方式得到字符串儿的长度
#include <stdio.h>
char Strlen(char* str)
{
char* str1 = str; //保存初始指针
while (*str1 != '\0')
{
str1 += 1;
}
return str1 - str;//末尾指针-初始指针
}
int main()
{
char arr[] = "helloworld";
printf("%d\n", strlen(arr));
printf("%d\n", Strlen(arr));
}
六、const修饰指针
在我们之前的学习当中,我们知道const的作用是阻止一个变量被改变,之前我们对变量进行改变都是直接对它的本身进行计算,但今天学习了指针之后,我们就有了新的算法,那就是获取变量的地址,通过对地址的解引用改变变量。
#include <stdio.h>
int main()
{
int a = 15;
a = 0;
printf("%d", a);
const int b = 15;
b = 15;//当程序运行时就会报错,因为此时b被当作常量
printf("%d", b);
}
而我们换一种方法,使用指针变量试试能不能改变b的值。
#include<stdio.h>
int main()
{
int a = 15;
a = 0;
printf("%d\n", a);
const int b = 15;
int* pb = &b;
*pb = 0;
printf("%d\n", *pb);
}
这里我们发现居然通过指针变量改变了原本被const所保护住的值,但const的作用就是防止它所保护的变量不被改变,但如果通过取地址,解引用的方法就能改变const保护的值,那const的意义在哪里呢?其实是因为const修饰指针变量时与修饰其他变量时是不同的~
1:const放在*左边时
2:const放在*右边时
3:const放在*两边时
七、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针在我们打代码的时候会很容易出现,而野指针出现的方式有很多,具体有哪些方法,让我们一起来看看吧。
1.指针未初始化
2.指针越界访问
3.指针指向的空间释放
如何规避:
1.记得指针初始化。
2.小心指针越界。
3.指针变量不再使用时,及时使用NULL,在指针使用前检查有效性。
好啦~如果你已经看到了这里,那么肯定已经基本掌握指针的用法啦,那让我们最后用一道例题结束今天的分享吧。
练习题:
实现一个函数,可以左旋字符串中的k个字符。
例如:
ABCD左旋一个字符得到BCDA
ABCD左旋两个字符得到CDAB
解题思路:利用指针变量表示数组中的元素,使用strlen函数求出数组的长度,运用循环使其向下一个元素逐个替换,直到替换到最后一个元素,再输入一个整形变量用来控制需要左旋字符串中的字符数量。
#include <stdio.h>
void move(char* arr, int a)
{
int i = 0;
int j = 0;
int m = strlen(arr) - 1;
for (i = 0; i < a; i++)
{
for (j = 0; j < m; j++)
{
char tmp = *(arr + j);
*(arr + j) = *(arr + j + 1);
*(arr + j + 1) = tmp;
}
}
printf("%s", arr);
}
int main()
{
char arr[20] = "ABCDEFG";
int a = 0;
scanf("%d", &a);
move(arr, a);
return 0;
}
好啦,那关于指针的相关知识就分享到这里啦,我们下期再见哦,拜拜ヾ(•ω•`)o