1.内存和地址
2.指针变量和地址
3.指针变量类型的意义
4.指针运算
5.野指针
6.指针的使用和传址调用
7.const修饰指针
8.assert断言
1.内存和地址
1.1内存是什么?
酒店一般分为多个房间,内存就像一个大酒店,内存的“房间”叫内存单元,一个内存单元的大小是一个字节。
1.2什么是指针?
指针就是地址,是内存单元的地址。酒店的房间有房间号,内存单元也有“房间号”,内存单元的“房间号”叫指针,指针==地址,是一个意思。
1.3什么是编址?
每个内存单元都有“房间号”(地址),但是,这个房间号不会被记录下来。因为不需要记录,就像钢琴的琴键上没写“do re mi fa so la xi”,虽然没写,但是演奏者可以正常演奏!编址也是这个道理!
1.4地址总线。
cpu和内存之间有大量数据交互,地址作为一个数据,在传输时需要依靠地址总线,32位机器有32根地址总线,64位机器有64根,每根线都有两种状态(电脉冲的有无),分别表示0或1,那么32位机器就有2^32种地址。
2.指针变量和地址
2.1什么是指针变量?
指针变量就是一种变量,这个变量是专门用来储存指针的。
复习:什么是变量?
变量是可以变化的量,可以将其理解为一个容器,用来储存各种各样的数据。
2.2取地址操作符(&)
&是单目操作符,用于取出变量的地址。
%p是格式化打印地址
我们看第五行,我们创建了一个变量“a”,当我们创建这个变量时(即使不赋值),都会向内存申请一块连续的空间,那我们就可以打印a的地址。
取出a的地址,再用%p格式化打印到屏幕,可以看到00CFFBD0(16进制),而这个16进制数就是变量a的地址。
提问:变量a是int型的,应占4个字节,这4个字节对应4个指针,那么,我们打印出来的是哪一个地址?
答:最小的那个!
2.3什么是指针类型?
指针类型是一种类型,指针类型也有多种,比如int*(整数指针类型),float*(实型指针类型)。
我们看第六行,pa是一个指针,*表示pa是一个指针,int表示pa指针指向int型,int*表示pa是整数类型的指针。
2.4解引用操作符(*)
作用,通过地址找到地址里存放的东西。
我们从第六行开始看,pa是一个指向整型的指针变量,取出a的地址,将其放入pa这个指针变量中。
再看第7行,用解引用操作符(*)找到pa里存放的东西(变量a),并将其赋值为1,那么变量a里装的就是1了。
看第8行,没什么好说的。
主要看第九行,将pa接引用,找到里面装的变量a,将其打印,值为1.
2.5指针变量的大小是多少?
取决于环境,在32位环境下,地址要用32个二进制数表示,每8位占一个字节,共战4个字节。同理,在64位环境下占8字节。
3.指针变量类型的意义
还是这个图,我用它来卖个关子:第六行*前的int看起来没什么用,为什么要加个int。
3.1指针的解引用
科普:1.%x是用16进制格式化打印。
2.(char*)是类型强转,将a的地址取出,强转类型,并放入char*类型的pc中。
3.一个16进制位占4个2进制位,0X11223344正好占满4个字节。
4.一般情况下,一个数字在内存中是倒着存的,例如0x11223344,44被存在最小的地址 中,而11在最大的地址中。
看图,pa的地址和pc的地址是相同的,但是两个变量的类型不同,所以解引用后访问的字节数不同,int*型的指针变量解引用后能访问4个字节,所以能正常打印。
但是,char*类型的指针变量解引用后只能访问一个字节,也就是访问最小的字节,所以只能打印44,可见指针变量所指向的类型不同,访问的权限不同。所以,地址指向的类型要写明白!
3.2指针+-整数
科普:一般情况下,数组名是数组首元素的地址,所以arr就是一个指针,指向第一个元素。
显然打印的第一个值是1。
我们知道数组内的元素的地址在内存上是连续的,所以只要给第一个元素的地址加上4(int型占4个字节,所以每个元素的地址相差4,我在数组那一章讲了),但是把,我们看看第7行,我们访问第二个元素,应该给arr加上4才对啊,为什么是加1呢?
因为arr所指向的元素是int型,+1会跳过一个int型(也就是加上了4个字节)。
第八行,同理,访问第4个元素只需加三,就能跳过12个字节!
所以,地址指向的类型要写明白!地址指向不同的类型,加1所跳过的字节数是不同的。
3.3 void* 指针
void*指针是无具体类型的指针,void*型指针变量能容纳任何类型的指针。
a的地址不经过强转,是不能放入其他类型的指针变量的。
但是,void*型指针变量除外。
void*不能直接进行指针运算,例如,解引用是不可以的,具体怎么用,后续的进阶会讲。
4.指针运算
4.1指针-指针
BC-C8=C 转成10进制是12.
结果不应该是12吗?为什么哦?
我们从两个角度分析
角度1:地址相减计算的是两地址间元素的个数。
角度2:&arr【3】是第4个元素的地址,等价于arr+3(每+1跳过一个整型)。
5.野指针
5.1什么是野指针?
指向的位置不明确的指针叫野指针。
5.2为什么会出现野指针?
1.指针位初始化。
2.指针越界访问。
指针没有第三个元素,arr+2后就越界了。
3.指针指向的空间被释放。
test函数结束后,变量a的空间被回收,n的地址已经不能访问了,此时n的地址就成了野指针。
5.3如何规避野指针?
方法1:初始化指针。
方法2:小心指针越界。
方法3:指针变量不再使用是,给该变量赋值NULL。
给该变量赋值NULL,该变量包含的是空指针,不要和void*混淆。
方法4,不要在函数内返回局部变量的地址。
6.指针的使用和传址调用
题目:输入两个值,用函数交换两个值的位置。、
交换失败,该函数交换的是形参的位置,形参只是实参的一份临时拷贝,交换形参不会影响实参。
当我们将实参的地址传上去,在对地址解引用,找到实参,就可以实现实参的交换了。
题目:strlen的模拟实现。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int my_strlen(char* p)
{
int count = 0;
while (*p++ != '\0')
count++;
return count;
}
int main()
{
char arr[] = "qwertyuiop";
int a = my_strlen(arr);
printf("%d", a);
return 0;
}
至此,指针的基础已经讲完了!!!
朋友们,进阶开始!!!
7.const修饰指针
const的作用是为变量做一些限制,使变量不能被修改。
const也可以写在int的左边。
但是啊,我们可以绕一下,取出a的地址,解引用找到变量a,再修改。
显然,这是在胡闹,在打破语法规则,换成c++直接给你报错,所以,我们应该给变量的地址也进行const修饰,把路封死!
在*左边const是对变量a(int*p1)修饰,在右边是对a的指针修饰。
好了,现在就安全一些了。
8.assert断言
8.1:库函数assert被assert.h头文件所包含。
8.2用法:assert(语句);
语句不成立,就会报错,一般是用来验证一个指针是不是空指针,若为空,assert可以使程序报错!