《斯坦福大学:编程范式》第三节:* 与&的技巧,每种数据结构的内存布局和内存地址

---------------例1---------

double d = 3.1416;

char c = *(char*) &d;   

 

&d 拿到指向d的内存地址,根据内存的起始点不同,值不同。

(char*)  把它当做  (char*)  类型,也就是指向char的指针。

*  解引用:根据指向的地址的起始点,向后 拿  8bits(也就是 char的内存大小,1字节 Byte)  

结果是 会取 d的前八位。翻译为 char类型的值。

---------------例2-----------------------------

short s = 45;

double d = *(double*)& s;

 

跟上文一样,因为double 是 8字节,

所以 从 s第一个字节往后取 8字节。

但s本身是short,只有2字节,

所以,取完s的内存,还会往后取6字节。

如果后面有内存,则会拿到完整的8字节,翻译为double;

如果没有,则会导致程序崩溃。

 

 

--------struct-------------------------

struct fraction{

   int  num;

       int denum;

}

占用4+4=8字节。 两个int的内存分配紧密挨着。 num的地址在下面,denum的地址在上面(因为num先存储)

结构体的地址 = 第一个域的地址,也就是 num的地址。

-----------------例子1----------------------------

pi.num =22;

pi.denum=7;

fraction * & (pi.denum) ->num = 12;

cout<<pi.num<<endl;

cout<<pi.denum<<endl;

1. 运算符从右往左,写作 ( fraction *) &  (pi.denum)  更清晰。

   & ( pi.denum) 拿到指向结构体pi的第二个域denum的开头的指针

  ( fraction *)  当作 一个struct来解析,所以 内存会继续向上寻找4位来作为一个新的结构体,我们可以命名它叫 pi2

然后,上面的程序实际上是  pi2 ->num = 12;

pi2的num实际上就是 pi的denum,他们的位地址完全一样。

所以此刻,pi.num 值不变=22, pi.denum  从7变为 = 12 

 

--------指针运算---------------------------------数组-------------

int array[10];

array[0] =44;

array[9] = 100;

在内存中,它是从左往右紧密存储:

44,XXXXXXXX,100  (这里的X表示没赋值)

array的地址  =  第一个元素的地址,也就i是array[0] 。   我们直接写 array 后面没有索引,就表示array数组的地址。

array  = & array[0]; 

 array + k  = & array[k];   

这就是指针运算。跟位运算不同。

这里加K,是表示加 K个数组元素类型的元素。

array 是数组名,实际上它是 int* 类型,

所以  array + k   相当于 位运算里,从数组起点 移动 K * typeof(int) 个字节

---------------例子1-----------------------------------

array [10]= 1;  

在java中数组有边界检查,但是C/C++中没有。 

 

---------------------------对数组名 解引用---------------------------

因为数组名 实际上是指针,所以可以解引用:

* array  ==  array[0];

* (array+K)  ==  array[k];

 

------------例子2:------------

array[-4] = 77

对于int数组。 前进和回退 typeof(int) * (index) 这么多个字节

真正的底层运算是

 

*(array-4) = 77

指针向左移动4个int单位,然后解引用。

 

所以我们明确一点: 

指针不直接操作bit,也不直接操作bytes,而是以申明的类型 为最小单位!

 

比如 long* 指针,以 一个long 8bytes 为最小移动和指向单位。

 

-----------------------例子3:----------------------

int arr = [5]

arr[3] = 128;

(short*)arr[6] =2;

cout<<arr[3]<<endl;

结果是 640;

分析:

(short*)arr[6]  == ((short*)arr)[6]

 (short*)  把 a 洗脑 ,在内存的二进制上,当做short类型来看待,然后 arr[6]赋值为2,arr[6] 此刻实际上是之前的 int*类型的arr[3]的前面两个字节。

因为 arr[3]  =128 ,字节为  0000,0000,0100,0000

所以 前面两个字节被解析为short覆盖了,为   0000,0001,0100,0000  = 640

 

---------总结:-------我们可以强制转换类型来操作原本操作不到的内存--------------------------------

                 (short* )( (char*)&arr[1] +8) = 100;

额。。各种转换,只要你乐意。

 

----------------------------------struct 与数组混合----------------------------------- 

struct student{

  char*  name ;

  char  snid[8];

  int numUnits;

}

student peoples [4];

peoples[0].numUnits = 21;

peoples[2].name = strdup("Adam");   //strdup是内存复制的函数,用于动态分配字符串。

 

在之前我们说过,在内存布局上,我们把stuct里面的数据布局,看做栈一样的布局。 name是第一个,所以在最底层, snid在中间,numUnits在上面。

对于数组和指针的布局,是从左往右。

 

整个数组,是存在于栈上

不过上文 动态分配出来的 “Adam” 字符串,是存在于堆上   A,d,a,m,\0   占用了五个字节。name 只是指向堆上的它。 name所在位置,并不写入字符串

 

 

peoples[3].name = peoples[0].snid +6;

 

peoples[0].snid 是一个 char* 类型, 所以 +6 是一个指针操作。

 

所以 peoples[3].name  指向了 peoples[0].snid  后面+6个char的位置

 

 

strcpy (peoples[1].suid,  "40415xx" );                // strcpy跟strdup 一样拷贝字符串,但不申请内存。 基于bit 挨个拷贝,直到遇见了0. 所以 suid所在的栈上的位,真的写入了字符串。

--------------打印字符串------- 打印char --------打印地址----------------

 

  char* str = "colleen";   //在内存中表示为  colleen\0   \0是字符串的结尾

cout<< *str <<endl;     //输出 colleen

cout<< str <<endl;    //输出的还是 colleen

 cout<< str+1 <<endl;    //输出 olleen

cout<< str+6 <<endl;  //遇到0 结束, 所有什么都不打印。

 

 在C++中,字符串是以空终止符('\0')结尾的字符数组,通过字符串中第一个字符的指针访问字符串。也就是说,字符串的值是字符串中第一个字符的(常量)地址。                                                          可以看到,我们打印的 按理说是 str,是一个char*,也就是指针,但是我们并没有打印出它的地址。

 

因为,如果要输出char string类型的指针的地址,需要把它强转为void* , 也就是无类型,告诉编译器,不要按字符串类型去解释后面的二进制了。

即强制char *转换成void *,那么,char型变量和字符串的地址就可以以十六机制的格式输出了,如下所示:

cout<< static_cast<void *>(str ) <<endl;

cout<< (int *)str<<endl;   //也可以输出内存地址,如上所说,只有char*  和string 才需要强转为void*   因为 字符串很特殊,相对数值类型,有"\0“结尾。然而内存地址没结尾

对于 指向 int ,foalt ,long等基本数值类型的指针,则不需要强转为void*

 

---------对于char是同样的道理--

char ch = 'a';

cout<< &ch  <<endl;     //这里输出不是‘a’ ,而是乱码。

因为对 char类型取地址 然后输出, 并不能拿到地址,char 跟string 和char*不同, 没有"\0"在内存中多占用最后一字节作为结尾。

所以输出乱码。

 

 

posted on 2018-06-20 23:43  百无禁忌  阅读(354)  评论(0编辑  收藏  举报

导航