前言
🐱🐉为什么C语言到现在都还是这么NB,就是因为指针的存在,这很大原因使得C的效🐱🐉
🐱👤率在编程语言里都是数一数二的,那么指针作为C语言中的重难点,很多想学习C的🐱👤
🐱👓小伙伴都是倒在了这里,别担心,这篇文章会多方位的详细分析指针,以及指针在底层🐱👓
🐱🏍中是如何来运行的帮助你进一步理解指针,也附带了相关练习和代码进一步加深理 🐱🏍
🐱💻解,因为篇幅较长,将指针分为了三个部分来阐述。--------------------------------------🐱💻
一.C语言的顶梁柱:指针
我们原先可以通过一个变量直接进行赋值
int a;
a = 1024;
int b;
b = a;
只看这个a,a分为左右值,当a在等号的左边的时候代表的是一个可写的地址,表示我现在想往这个,地址里面写入内容,当a在等号的右边的时候代表的是一个可读的地址,表示我现在想在这个,地址里面读取内容,这个a是地址的一个名字,代号,通过这个代号我们可以访问这个内存空间,a不管在左边还是右边,它都是代表了一个地址,那么我们就可以想象,我知道这个a的地址了,那么我是不是就可以直接去访问这个地址,直接通过地址去访问内存空间 ---- 这种方式我们就叫指针
指针:我们的内存是分为很多个小格子,每一个格子被称为一个字节(8个bit),在做这个内存条的时候为了能找到这些存储的小格子,他们给这些格子都进行了,编号,也就是每一个格子都会有一个独立的编号,这些编号是连续但是不重复,当我知道这个编号之后,我就可以直接通过这个编号找到这个存储的格子,找到这个存储的格子之后,我就可以直接去操作这个存储空间,这个编号 — 我们就叫指针,通过这个指针去操作变量的这个过程我们叫通过地址间接的去操作这个变量,在c语言里面为了更好的去操作这个地址,我们弄出来了一个东西 — 指针变量。
1.1指针变量:
也是一个变量,代表的是一块存储空间,只是这块存储空间比较特殊,编译器在解释的时候会认为这个存储空间里面的,内容是一个内存的地址,不会当成是一个简单的值去处理
int a = 1024;
a里面的值为1024这个值,在地址编号里面也会有这么一个值,表示的是1024这个地址,a=1024,这个1024值是可以参与运算的一个值,但是1024这个地址是不能参与运算,通过1024这个值找到所对应的,哪个地址,这个地址是一个内存空间,通过这个地址找到这个,内存空间里面保存的内容,当我们将a里面的1024这个值强制让编译器认为是一个地址,那么这个,1024就和1024地址没有任何区别了,计算机底层是没有值与地址的区别的,对于计算机来说都是值,什么时候当成值什么时候当成地址完全看编译器的解释
指针变量:在我们的c语言里面,只是在普通变量前面加上一个 * 号
这个*号代表的就是指针变量
指针变量的定义:
数据类型 * 变量名;
所有的指针变量的大小都是一样的,也就是内存空间都是一样大
指向类型 * p;
sizeof§ -> 32位机等于4
64位机等于8
指向问题:当我们的一个指针变量保存了一个什么玩意儿地址,那么我们就说
这个指针变量指向了这个玩意儿
eg:
int a = 1024;
int * p = &a;//p就指向了a
指针操作里面有两个运算符:
& :取地址符,表示我要取得谁的地址 这个时候是一个单目运算符
* :在定义的时候使用表示后面的那个变量是指针
在单独使用的时候表示 指向这个地址
int a = 1024;//假设a的地址为0x100
int * p = &a;//p这个变量里面就会保存0x100这个值
*p = 2048;//指向p里面保存的那个地址
//等同于 *0x100 = 2048;
//将2048这个值写入到0x100这个地址里面去了
int b;
b = * p;//b = *0x100; 从p保存的那个地址里面读取内容
然后给到b这个变量
在*操作地址的之后一次操作几个字节?
不是看你指向的这个空间有多大,而是看你定义的这个指针是什么指向类型
由指向类型决定你一次指针操作操作几个字节
int a = 0x12345678;
int * q = &a;//q的指向类型为int 因此它一次操作就是4个字节
*q = 0x90abcdef;
a == 0x90abcdef
int b = 0x12345678;
char * p = (char *)&b;
*p = 0xef;
b == 0x123456ef
内存编号:假设你的内存条是4G,给这个存储条的格子去编号的时候
它有一个起始点和结束点
起始点为:0x00000000 -> 0
结束点为:0xffffffff ->
32位机的最大支持内存为:4G
练习:
int a = 3;
int b = 4;
利用指针间接的将a b变掉
c语言在赋值的时候一定要类型匹配,不同类型之间是不能直接赋值的
很多时候我们的类型搞不清楚
typeof -> 求类型
typeof(a) -> 求a的类型
int * q;
*q = &a;//typeof(*q) != typeof(&a) 所以这句是错的
typeof(q) -> int *
typeof(*q) -> int
typeof(a) -> int
typeof(&a) -> int *
q = &a;//这才可以赋值
1.2指针作为函数的参数:
函数的参数在传递的时候(只能是实参给形参做初始化)本身地址也是一个值,因此我也可以将某一个变量的地址给到形参.
练习:
void swap(int a,int b)
{
int t;
t = a;
a = b;
b = t;
}
int main()
{
int a = 3,b = 5;
swap(a,b);
printf(a b);//3 5 改不掉
}
请将上面的函数改写,让swap函数能将下面的a b改掉
swap(a,b);//我们必须让swap函数里面知道a b的地址
void swap(int *a,int *b)
{
//问题 1
/*
int *t;//这么写只会将swap里面的a b内存换了,对main里面的a b没有产生任何影响
t = a;
a = b;
b = t;*/
//问题2
/*int *t; //这个t没有赋值 因此指向不明确 我们叫这种指针为野指针
//野指针直接指向操作会大概率出问题
//警惕野指针
//编程建议:指针定义出来了之后如果不做初始化
请将它初始化为NULL
NULL指针不能读不能写,只能指向
表明这个指针是不能操作的
#define NULL (void *)(0)
if(t)//如果这个t不为NULL 那么它就是真的
*t = *a;
*a = *b;
*b = *t;*/
//正确的代码
int t;
t = *a;
*a = *b;
*b = t;
//或者
/*
int h;
int * t = &h;//int * t = malloc(4);
*t = *a;
*a = *b;
*b = *t;
*/
//free(t);
}
int main()
{
int a = 3,b = 5;
swap(&a,&b);
printf(a b);
}
1.3数组与指针
数组的名字代表了这个数组,int a[3];,typeof(a) ->int[3],a同样是代表了这个数组的首地址,因此a是一个指针,当我们将a看成是一个指针的时候,typeof(a) ->int *
int[3]和int * 有什么区别,底层没有区别,但是编译器会有解释,int[3]为连续的三个int,而int *没有指明边界
1.3.1指针的加减法:
指针 +/- 1:底层值不一定只动了1
int * p = a;//假设a的值为 0x100
p += 1;//p的值为 p += (sizeof(*p)) 底层值会+ 4
//因此p的值为 0x104
char * q = (char *)a;
q++;//q = 0x100 + sizeof(*q) 底层值会+ 1
//因此q的值为 0x101
指针加减法加减的是类型
int a[5] = {1,2,3,4,5};
int * p = a;//a看成是指针 因此类型为int * 可以直接赋值给p
p += 2;
*p = 250;//这里改的是 a[2] {1,2,250,4,5};
//底层 指针和数组是不分家的,数组下标操作底层实际上就是指针的 +/-
//指针可以像数组一样操作,数组也可以直接操作指针
p[1] = 1024; //现在改的是 a[3] {1,2,250,1024,5};
p[1] <-> *(p + 1) //他们可以直接进行互相转换
这么一来 a也可以像指针一样的操作
*(a + 1) = 360; // a[1] {1,360,250,1024,5};
a++;//这是错的 ************
//数组的名字就是一个指针 没有任何毛病
//但是这个指针本身是const的 只读
//a指向的空间可写没有毛病 但是这个a本身不可写
练习:
测试一个代码,想一想为什么
int a[10] = {1,2,3,4,5,6,7,8,9,10};
printf("a = %p,&a[0] = %p,a + 1 = %p,&a[0] + 1 = %p,&a + 1 = %p\n"
,a,&a[0],a + 1,&a[0] + 1,&a + 1);
int b[3][4];
printf("b = %p,&b = %p,&b[0] = %p,&b[0][0] = %p,b + 1 = %p, \
&b + 1 = %p,&b[0] + 1 = %p,&b[0][0] + 1 = %p\n",b,&b,&b[0],&b[0][0],
b + 1,&b + 1,&b[0] + 1,&b[0][0] + 1);
假设有一个数组 hehe b[3]:typeof(b) -> hehe *
int a[3][4] :将a看成一个一维数组 int[4] a[3];
表达式 表达式的含义 表达式的值
a 代表了整个数组 &a[0]
a看成是一个数组:typeof(a)->int[4][3]
a看成是一个指针:typeof(a)->int[4]*
a[0] 代表了第一行 &a[0][0]
a[0]看成一个数组:typeof(a[0]):int[4]
a[0]看成一个指针:typeof(a[0]):int *
&a[0][0] 第一个元素的地址 &a[0][0]
typeof(&a[0][0]):int *
----------------------------------------------------------------
a + 1 a要看成一个指针,跳到第二行 &a[1]
typeof(a) ->int[4] *
+1 ->跳过int[4]
&a 取整个数组的地址: &a
typeof(&a):int[4][3] *
&a + 1 typeof(&a):int[4][3] *
+1:要加上int[4][3]个字节
整体跳过了这个数组
&a[0] + 1 a[0]:看成一个数组 类型为 int[4] &a[1]
&a[0] + 1:int[4] * + 1
a[0] + 1 a[0]:看成一个指针 :类型为int * &a[0][1]
a[0] + 1就是往后面跳一个int
*(a + 1) + 2 a + 1:&a[1] *(a + 1):*&a[1]:a[1] &a[1][2]
a[1] + 2:int * + 2:往后面跳2个int元素
*(a[1] + 2) a[1] + 2:&a[1][2] *&a[1][2]:a[1][2] a[1][2]
*(*(a + 1) + 2) a + 1:&a[1] *&a[1]:a[1] a[1][2]
*(a[1] + 2):a[1][2]
a + 1 a看成一个指针:typeof(a) : int[4] *
int[4] * + 1:往后面跳过了一行:&a[1]
*(a + 1) *&a[1]:a[1]
*(a + 1) + 2:a[1] + 2
a[1]看成一个指针:typeof(a[1]):int *
int * + 2:往后面跳两个int:&a[1][2]
*(*(a + 1) + 2):*&a[1][2] : a[1][2]