指针是C语言中的重难点,学习指针的知识点很多,但是重在理解,才可以很灵活地运用。接下来就从指针的第一篇章来慢慢从头了解指针。
1.内存和指针
1.1内存
我们先想象一个生活中的例子,大学里每一间宿舍房间都有属于他自己的房间号,有了你所在房间的房间号这个地址,其他人才可以快速准确地知道你在哪里。而在计算机上,里面的CPU(中央处理器)在处理数据的时候,需要的数据都是在内存中读取的,处理后的数据也会放回内存中。那这些内存是如何高效地管理呢?
其实也就是把内存划分成一个一个的内存单元,每个内存单元的大小取一个字节。(就像是把一栋宿舍楼划分成一个一个的小宿舍房间,一个房间住8个人,即8个比特位)
在这里补充一下计算机中常见的单位进制转换:
1.常见单位有bit(比特位),Byte(字节),KB,MB,GB,TB,PB
2.一个比特位可以存储一个二进制位的0或者1
3.单位转换为:1Byte = 8bit ; 1KB = 1024Byte ; 1MB = 1024KB ; 1GB = 1024MB ; 1TB = 1024GB ; 1PB = 1024TB
生活中我们把房间号也叫做地址,在计算机中我们把内存单元的编号也称为地址。
C语言中给地址起了一个很高级的名字:指针
所以我们可以理解为
内存单元的编号==地址==指针
1.2怎么样理解编址
CPU若想要访问内存里的空间,就必须要知道这个内存的地址。而每个内存的地址记录,都是由计算机硬件来完成的。本质上是一种约定而成的共识,就像吉他钢琴上的do rei mi一样。
计算机中有很多硬件单元,但是互相都是独立的,需要用“线”将CPU和内存联系起来,才能够实现CPU访问内存中某个字节的空间,这里先来研究地址总线。
32位机器上有32根地址总线,每根线有两态,表示0和1。所以32根地址总线就能表示2^32种地址。
例:00000000000000000000000000000001就是一个地址,就是一个编号,这个可以理解成32个二进制组成的,为了方便就经常用16进制来编号,所以上面就可以写成0X00000001,
即二进制转换成16进制,每四个二进制换成一个16进制,每个2进制1比特,所以每个16进制4比特,两个16进制占据1字节。
2.指针变量和地址
2.1取地址操作符
在C语言中,创建变量实际上就是给内存申请空间。在上图中,在X86环境下,在调试环境下监视内存窗口,在地址中输入&a,出现的第一行就是取的地址最小的地址。所以这一个代码创建了整型变量0x11223344,内存便申请了4个字节,用于存放该整型变量。而这四个字节所在的地址就是0x008FF784, 0x008FF785, 0x008FF786, 0x008FF787。
得到这个地址,就用到了操作符(&)——取地址操作符。
&a取出的是a所占的4个字节中地址较小的字节的地址。
a所在位置如下:
0093FC6F |
0093FC6E |
0093FC6D |
0093FC6C |
所以知道第一个字节的地址,顺藤摸瓜就能推出4个字节的地址
2.2指针变量和解引用操作符(*)
2.2.1 指针变量
当我们得到一个字节的地址时,有时候我们需要把这个地址给存起来。那这个地址就要存储在指针变量中。
指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会被理解为地址。
2.2.2 如何拆解指针类型
在这里,pa左边写的是int*, 其中*是在说明pa是一个指针变量,就是说明pa的值是一个地址,而前面的int是在说明pa指向的是一个整型(int)类型的对象。
同样的道理,如果创建了一个char类型的变量ch,他的地址就应该放在char*中。
2.2.3 解引用操作符
在现实生活中,我们知道一个房间的地址后,需要有钥匙才可以进到房间里面去,而解引用操作符(*)就起着这个钥匙的作用。
取地址与解引用一来一回,都是单目操作符。*pa的意思就是通过pa中存放的地址,找到指向的空间,所以*pa其实就是a变量了。这个代码就把a改成了0。这样子的修改是通过取地址来修改的,多了一种这样的途径,能够使之后的代码更加地灵活。
2.3 指针变量的大小
通过前面的讲解我们已经知道,32位机器有32根地址总线。我们把32根地址总线产生的32位二进制序列当成一个地址的话,那么一个地址的大小就是32个比特位,即需要4个字节才可以储存。
(平常习惯用0x加上八个16进制的数字来进行表示,8个16进制也是4个字节)
同理,如果是64位机器,则需要8个字节来存储。
如上图,X86环境下为4个字节,X64环境下为8个字节。
注意指针变量的大小和类型是无关的,只要是指针类型的变量,在相同的平台下,大小都是相同的。即它存储的只是这个数据所在的地址数据,无论这个数据是整型也好,是浮点数也好,他所在的那个位置的地址大小都是不变的
3.指针变量的类型的意义
指针变量的大小与指针类型无关,但是指针变量类型有着特殊的含义。
3.1 指针的解引用
观察下面两个代码:
调试后我们可以看到,代码1会将n的四个字节都改为0,但是代码2只会将n的第一个较小地址的字节改为0。
结论:指针的类型决定了对指针解引用的时候有多大的权限(即一次能操作几个字节)。比如char*的指针解引用就只能访问一个字节,而int*的指针的解引用就能访问四个字节。
3.2 指针+-整数
观察上面代码的结果,char*类型的指针变量+1跳过1个字节,int*类型的指针变量+1跳过了4个字节。所以可以看出,指针+1或者-1,其实跳过1个指针指向元素的字节大小。
3.3 void*指针
在指针类型里面有一个特殊的指针叫做void*类型的指针,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型的指针,但是不能直接进行指针的+-整数和解引用的运算,因为肯本不知道void*类型的指针所指向的元素大小,所以不能直接进行运算。
如图,将一个int类型的变量的地址赋值给一个char*类型的指针变量。编译器会给出“从‘int*’到‘char*’的类型不兼容”的警告。是因为类型不兼容。而用void*类型就不会有这样子的问题。
因为void*类型的指针在进行运算的时候不知道要访问几个字节,所以不能进行直接的运算。
4.const修饰指针
4.1 const修饰变量
变量是可以修改的。如果将一个变量的地址赋值给一个指针,通过解引用这个指针就可以修改这个变量的值。
但是如果想要给这个变量加上一些限制,让他不被修改,就要使用到const。
int main()
{
int m = 0;
m = 20;//m是可以被修改的
const int n = 0;
n = 20;//n是不可以被修改的
}
上述代码中n是不可以被修改的,其实本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码里面对n进行了修改,就不符合语法规则,就会直接报错。
在C语言的环境中,n就是常变量,但是在c++的环境下,n就是一个常量。所以在下面的代码中,在C语言的环境下不行,但是在c++的环境下可以。
int main()
{
const int n = 10;
int arr[n] = { 0 };
return 0;
}
但是我们绕过n,使用n的地址,去修改n的值就可以做到了,但是这样子做有意打破语法规则。
虽然这样子做可行,但是刚开始已经用const修饰了变量n,就不想让n的值改变,这样子做有意打破这个想法。那如果想即使拿到了n的地址也不能修改n那该怎么做呢?
4.2 const修饰指针变量
int* p;//没有const修饰
int const * p;//放在*的左边做修饰
const int* p;//放在*的左边做修饰
int* const p;//放在*的右边做修饰
const int* const p;//放在*的左边和右边做修饰
现在看一下下面的代码分析一下:
//代码1-测试无const修饰的情况
void test1()
{
int n = 10;
int m = 20;
int* p = &n;
*p = 20;//可以
p = &m;//可以
}
//代码2-测试const在*左边的情况
void test2()
{
int n = 10;
int m = 20;
const int* p = &n;
*p = 20;//爆红,不可以
p = &m;//可以
}
//代码3-测试const在*右边的情况
void test3()
{
int n = 10;
int m = 20;
int* const p = &n;
*p = 20;//可以
p = &m;//爆红,不可以
}
//代码-测试const在*左边和右边的情况
void test4()
{
int n = 10;
int m = 20;
int const* const p = &n;
*p = 20;//爆红,不可以
p = &m;//爆红,不可以
}
int main()
{
test1();
test2();
test3();
test4();
return 0;
}
结论:const在修饰指针变量的时候,
const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。
const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。
即如果在*左边,则*p不可以变,p可以变,如果在*右边,则*p可以变,p不可以变。
抽象一点,把p抽象成男朋友,如果const在*的左边,则男朋友不会改正错误改变自己,但是你可以换男朋友。如果const在*的右边,则你不可以换男朋友,但是男朋友会改正错误改变自己。如果const在*的左边和右边,则你既不可以换男朋友,男朋友也不会改正错误改变自己。