指针类型和指针类型转换的理解

前几天在判断  值相同的两个指针所指向的变量的值可以不同 ”  这句话时,发现自己对指针类型一些概念仅仅是记住了结论。于是查阅了一些资料,记录一下一些与指针类型和指针类型转化相关的知识。

 一些用到的

开始之前,先来复习一些会用到的知识。

1.地址,字节,位

  位(bit)是电子计算机中最小的数据单位。每一位的状态只能是0或1。

  字节(Byte)是用于计量存储容量的一种单位,每一个字节由8位组成(1Byte = 8bit)。

  地址可以理解为在一片内存中,每个字节(Byte)的编号。

  他们在内存中的关系可以比作,内存是一栋大楼,字节(Byte)是大楼中的每一层,地址是楼层编号,位(bit)是每一层中的房间,每一层有8个房间。

   

指针类型和指针类型转换的理解_第1张图片

 

2.变量的内存

  编译器根据变量的类型,在内存中申请一块空间。例如32位与64位中 int 类型申请到4字节的空间,可理解为编译器申请了4层楼,作为”办公区域“。

3.指针变量

  指针是指程序数据在内存中的地址。在c语言当中,允许用一个变量来存放指针,这种变量称为指针变量。

 

指针变量类型的作用

1   int a;
2   int *p;
3   p = &a;
4   printf("%p %d\n",p,*p);

  以上程序中,”&“操作符取出了变量 a 在内存空间中的首地址,而后通过 “ * ”  操作符取出首地址所在内存空间的数据。

  前面我们提到,存储变量的内存,是由多个字节组成。而指针变量在只知道首地址(第一个字节的地址)的情况下,就能找到a的内存区域。它是怎么做到的?先来看看指针变量的声明。

  我们在声明一个指针变量的时候,会根据它将要指向的变量类型,声明对应的类型,例如:

复制代码

1     int a;
2     long b;
3     char c;
4 
5     int *pa = &a;
6     long *pb = &b;
7     char *pc = &c;

复制代码

  不管是什么类型的指针变量,所存的值都是地址(int类型的值)。那么声明不同类型的作用是什么?答案是规定指针在内存中每次移动的字节数。

  例如定义“int *pa = &a”,取值时,int类型占4个字节,指针就从首地址开始移动,读取4个字节。同理,short类型占2字节,指针就移动2字节。通过声明指针类型,告诉指针每次移动多少字节,来获取变量的值。

 

值相同的两个指针所指向的变量的值可以不同

  “值相同的两个指针变量”,意思是两个指针变量指向同一个首地址。但是如果指针变量的类型不同,因为指针移动的字节数量不同,就可能读取出不同的数据。

  要实现不同类型指针变量指向同一个地址,需要使用指针类型转换。

1     short a = 1;
2     short *p1 = &a;
3     int *p2 = (int *)p1;
4     printf("%d %d",*p1,*p2);

   以上例子将一个每次移动读取2字节的 short 类型指针变量,转化为一个每次读取4字节的int型指针变量。

   接下来,我们通过指针类型转换,用同一个首地址,取出不同的值。

复制代码

 1 #include 
 2 int main()
 3 {
 4     short c[2];               //等价于申请2个连续的内存空间,每个空间2字节 
 5     c[0] = 1;                //为第一个short空间赋值为1 
 6     c[1] = 1;                //为第二个short空间赋值为1 
 7     short *p1 = c;            //p1指向c[]首地址 
 8     int *p2 = (int *)p1;      //p2指向c[]首地址,并强制转换类型为 int 
 9 
10     printf("p1指向:%p\np2指向:%p\n",p1,p2);      
11     printf("p1取出:%d\np2取出:%d\n",*p1,*p2);
12     return 0;
13 }
14         

复制代码

  对应结果为: 

    p1指向:000000000062FE30
    p2指向:000000000062FE30
    p1取出:1
    p2取出:65537

  指针类型和指针类型转换的理解_第2张图片

    

    根据二进制转换得,10000000000000001 为 65537。由此可验证强制转换前指针读取2字节,转化后读取4字节。两个指针指向的首地址相同,但是读出了不同的结果。

 

 

//**************************

内存中的地址

  地址的本质就是一串0和1的机器代码,内存中的地址没有明确数据类型,但地址值有类型,以32位编译器为例,内存中的地址是一个32位的整数。无论什么类型的指针变量,在内存中本质上都是一样的,都是一个整数值的地址值,该地址值可以转换为其他类型,比如float或char,但一般不要强转,此时已不再是合法地址而是一个单纯的数据值,除了没有意义外,还会出现数据读取错误(后面会解释)。

1

int a;

  当我们用a时,由于前面把a定义为int型,则编译器知道从a的地址开始向后取4个字节再把它解释成int型。

指针变量及不同指针类型的含义

(1)指针变量

 1 int *a; 

  指针变量,本质上是一个变量,只是它是存放地址的变量,指针的类型代表的是它所指向的变量的类型。因此就有了指向整型、字符型、浮点型等其它类型的指针,但实际上所有类型的指针变量存放的都是int型。

  上述代码表示指向整型的指针变量a,其中a表示一个地址值,上面曾提到地址没有明确的数据类型,因为地址可以为指向整型的指针,可以为指向浮点型的指针。指针类型为整型,表示当我们对该地址进行访问(解引用)时,编译器会将它解释为整型。

  注意:指针地址只指向数据存储的内存的位置,具体变量的类型由编译器告知。

(2)不同类型的指针

  声明不同类型的指针变量既是规定了该变量结合指针运算符时读取内存中的字节数,同样规定了在指针移动和指针的运算时(加、减)在内存中移动的最小字节数。

强制转换的原理

(1)普通变量强转

  (float)a,就是先按照int类型取出该数值,再将该数值按照int to float的规则转换成float型,如果反过来,则会发生数据截断。

(2)指针变量强转

  旧指针 to 新指针的强制类型转换是指将指针所指的内容的类型由原先的类型转换为后面的类型:即进行变量解释的时候,解释的类型变化。

  如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*和TYPE,那么语法格式是:(TYPE*)p;这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,(也就是说,新指针指向的数据将会用TYPE类型进行解释,如果之前是浮点型数据-3.75,先将其转换为二进制代码,然后转化为TYPE类型存储),它指向的地址就是原指针指向的地址。

  注意:(int &)y,告诉编译器将y当做int看待

复制代码

 1 void test02(){
 2 
 3     //-----------------------------
 4     //float指针相关的强制转换
 5     //不同类型的指针变量,在内存中本质上都是一样的,都是一个整数值的地址值,一般表现为8位十六进制数
 6     //不同的类型表示地址指向值的解释类型,及该变量在内存中占据字节的数目
 7     //地址值可以强转为其他类型,但将地址指向值解释为其他类型容易出错,如float解释为int
 8     //常量直接赋值给指针是不可以的,必须强转为合法地址,另外由于内存地址是整型值,因此浮点型数据不能成为合法地址
 9     //-----------------------------
10     float a = 10.1;
11     float *p_ = &a;
12 
13     cout << typeid(p_).name() << endl; //float*
14     cout << p_ << endl; //地址,8位十六进制数字(32位二进制数字)
15     cout << *p_ << endl; //地址指向的数据,同样为32位二进制数字,按照float进行解释,得到10.1
16 
17     //cout << (float)p_ << endl; //不允许将地址直接转化为浮点型,但可以间接修改
18     cout << (char)p_ << endl; //直接将地址转化为字符型
19     cout << (int)p_ << endl; //直接将地址转化为十进制整型
20 
21     cout << (int*)p_ << endl; //将地址float *类型转换为int*,后面的解释会发生变化,但地址的十六进制数字不会发生变化
22     cout << *(int*)p_ << endl; //地址指向的数据,会按照int型进行解释,得到1092721050
23     cout << (int&)(*p_) << endl; //同样的将数据解释为int型,得到1092721050, 则是告诉编译器将数据看成int对待
24 
25     cout << *(int*)&p_ << endl; //将地址(8位十六进制数字)转换为十进制,先取p_的地址,然后按照int进行解释
26     cout << *(float*)&p_ << endl; //将地址(8位十六进制数字)转换为float,先取p_的地址,然后按照float进行解释
27     cout << *(float*)p_ << endl; //输出10.1
28 
29     cout << (char*)p_ << endl; //将地址float*类型转换为char*类型,其实地址的十六进制数字不会发生变化,但<<会直接输出指向的字符串
30     cout << static_cast(p_) << endl; //可以这样输出转换为char*后的地址
31     cout << *(char*)&p_ << endl; //将地址(8位十六进制数字)转换为char,先取p_的地址,然后按照char进行解释
32     cout << *(char*)p_ << endl; //将数值型数据按照字符类型,这里无法将float解释为char型,但是int型默认可以转换为ASCII码,解释为char型
33 }

复制代码

(3)为什么内存中的地址值不能直接转换为float?

  当地址值转换为float时,编译器会有两种转换方式:(1)将整型地址值转换为浮点型;(2)将地址指向的值转换为浮点型,但不知道采用哪一种,需要我们显式地进行转换。

(4)地址值强转后访问的数据错误

  前面提到可以将地址值转为float或char,此时地址值变成了数据(有可能无法解释为char数据),而非合法地址。在对该地址进行访问时,需要将该地址值重新变为合法地址,通过(int*)强转,但浮点型数据不允许作为地址值。

(5)字符强转为合法地址

复制代码

 1 void test04(){
 2     char m = 'b';
 3     char *n = "b";
 4     //cout << typeid('b').name() << endl;
 5     float *p0;
 6     int *p1;
 7     float *p2;
 8     float *p3;
 9     p0 = (float*)(&m);
10     p1 = (int*)(m); //会先转换为m变量的ASCII值,98
11     p2 = (float*)(98); 
12     p3 = (float*)(n);
13     //cout << &(int(m)) << endl;
14     cout << static_cast(&m) << endl;
15     cout << p0 << endl;
16     cout << p1 << endl;
17     //cout << *p1 << endl;//相当于将98强转为合法地址,但并没有对该地址进行初始化,因此直接输出会显示访问冲突
18     cout << p2 << endl;
19     cout << p3 << endl;
20 }

复制代码

  特别注意:char*类型不能直接由cout输出对应地址,cout会直接输出指向的字符串,可以用static_cast(&m)来进行地址输出。

你可能感兴趣的:(指针)