lee-romantic 's Blog
Everything is OK!
Toggle navigation
lee-romantic 's Blog
主页
About Me
归档
标签
C/C++同一数组中保存多种数据类型的相关问题探讨
2019-05-23 14:11:47
323
0
0
lee-romantic
###一.引言 C/C++同一数组中还可以保存多种数据类型?比如int,char等?这有点颠覆我们的认知,通常在定义数组的时候,比如`int a[3]={0,1,2}`,我们都是把数组的类型确定了的,那么为何又说可以存放不同的类型的数据呢? ###二.引入问题 最开始思考这个问题的时候,是在学习《TCP/IP网络编程》的时候,在P88页的`op_client.c`代码的39行有这样的代码: ``` ... #define OPSZ 4 ... char opmsg[BUF_SIZE]; ... scanf("%d",(int*)&opmsg[i*OPSZ+1]); ... ``` opmsg保存了一些char类型和int类型的数据,用于TCP传输。 那么问题就在这儿,`opmsg`明明是一个`char`类型的数组,为什么还能强行向其中存储10进制整数?书上的解释很含糊: `4字节int型数据要保存到char数组,因而传换成int指针类型`。 ###三.C/C++数组实际上没有所谓的类型 在其他一些高级语言中,大都存在一种数组的类型,该类型包含了若干元素,还有元素个数等其它属性。这些数组类型的对象是被当作一个整体来对待的。 但是在C/C++中,可以说,不存在这种高级语言的数组类型。`C中的数组,仅仅是地址排列在一起的元素而已,不管是char、int还是复杂的struct数组,仅仅是排列在一起的该类型大小的内存而已。`数组的下标引用,仅仅是指针偏移的一种易读的形式!也可以说,数组在C中只是一个名字,简称而已。 如果你定义了一堆类型相同的变量,比如`int a, b, c, d, e, f, .....`只要它们编译后的内存地址是连续的,那么你也可以用数组的方式使用它们: ``` int *p = &a; p[0] = 1; //a p[1] = 2; //b p[3] = 3; //c ...... ``` 关于这个理解,参考: http://bbs.chinaunix.net/thread-1631299-1-1.html 所以,数组实际上只是定义了一块内存块的名字,`数组名代表首地址,数组类型实际上是确定每个元素所占的内存大小而已`。 ###四.数组与指针有相似的地方,但数组不是指针 指针就是指针,指针变量在32 位系统下,`永远占4 个byte,其值为某一个内存的地址`。指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。指针的变量类型决定了,指针每次`+1`时,地址的偏移量。比如`int* p`,int型需要4个字节,因此p 每次加1,p指向的地址将增加4。例如`0x00C8FC34`变为`0x00C8FC38` 数组就是数组,`其大小与元素的类型和个数有关`。定义数组时必须指定其元素的类型和个数。`数组可以存任何类型的数据,但不能存函数`。数组采用下标时,每次的地址偏移量与指针类似,也就是说,char类型的数组,my_char[0]和my_char[1]所代表的变量的地址之间,只相差1.如果将数组名当指针使用,比如`*(mychar+1)`,将会进行隐式转换,将数组转为常量指针(常量指针不能进行自增运算,因为是常量!) 关于这个,有一个经典的面试题: ``` main() { int a[5]={1,2,3,4,5}; int *ptr=(int *)(&a+1); //&a是整个数组的首地址,类型是数组,每次偏移整个数组的长度 printf("%d,%d",*(a+1),*(ptr-1)); //a是数组第一个元素的地址,每次偏移数组元素类型所占内存大小的长度 //答案是2,5,实际上就是考虑数组下标的地址偏移量,可以在下面的链接中查看解析 } ``` 对指针进行加1 操作,得到的是下一个元素的地址,而不是原有地址值直接加1。所以,一个类型为`T` 的指针的移动,以`sizeof(T)` 为移动单位。因此,对上题来说,`a` 是一个一维数组,数组中有5 个元素; `ptr` 是一个`int` 型的指针。 `&a + 1`: 取数组a 的首地址,该地址的值加上`sizeof(a)` 的值,即`a + 5*sizeof(int)`,也就是下一个数组的首地址,显然当前指针已经越过了数组的界限,但是因为不是采取的数组下标的方式,所以可以越界!采取数组下标的方式,是绝不允许越界的。 `(int *)(&a+1)`: 则是把上一步计算出来的地址,强制转换为int * 类型,赋值给ptr。 `*(a+1)`: `a,&a` 的值是一样的,但意思不一样,或者说类型不一样,所以`a+1`和`&a+1`,地址偏移量不一样。`a` 是数组首元素的首地址,也就是`a[0]`的首地址,`&a `是数组的首地址,`a+1` 是数组下一元素的首地址,即`a[1]`的首地址,`&a+1` 是下一个数组的首地址。所以输出`2*(ptr-1)`: 因为`ptr `是指向`a[5]`,并且`ptr `是`int * `类型,所以`*(ptr-1)` 是指向`a[4]` ,输出5。 个人认为,数组与指针最大的一个不同在于,`数组是有界的`,也就是说,数组采取类似`a[n]`这样的偏移量访问元素时,n不可以一直增加,总有个最大限制。但是指针,`int * p =&a`,`*(p++)`,p理论上可以一直增加,因为内存是连续的,没有限制。总之,`数组可能越界,指针不存在越界`。 更详细可以参考: http://c.biancheng.net/cpp/html/475.html ###五.char数组中保存int变量 前面明确了,数组实际上就是一个保存数据的内存块,存在边界,数组名是数组第一个元素的首地址,数组之所以必须定义类型,是为了确定,数组采取下标操作时,地址的偏移量。整个数组的类型是一定的,所以每次下标增加1,地址的偏移量也是一样的。 那么,当在数组中存储其他的类型的数据的时候,我们就不能使用数组的下标来写入数据或者读取数组了,因为其他数据的类型不一样,每个元素所占的内存也不一样。 比如前面提到的,在char数组中保存int变量,如果我们还是用数组的`下标方式`去写入和读取数据的话,将会造成char类型与int类型的数据转换,不是真正一样上的,在char数组中保存int变量,这种,只能说是char数组中保存由int变量转换而来的char类型数据(char和int互转:https://blog.csdn.net/smilesundream/article/details/77881995 )。我们知道,char类型和int类型所占内存大小不同,int需要4字节,char只需要1个字节,由int转为char,就不可避免的将会丢掉int的高位字节的信息,这可并不是我们想要的。 既然采取下标的方式不行,那么就只能采取指针的方式了,所以,我们应该很好理解书上的很含糊的解释了:`4字节int型数据要保存到char数组,因而转换成int指针类型`。 我们看代码: `scanf("%d",(int*)&opmsg[i*OPSZ+1]);` `OPSZ`大小为4,也就是每隔4个`char`类型的大小的地址,也即每隔4个字节,取一下地址,传进去`scanf`函数内部,然后转换为指针。4字节,刚好是`int`类型所需的内存大小。 这里如果不转换成int指针类型,传入`scanf`函数内部作为指针后,偏移量还是不变,还是`char`类型决定的偏移量1。从而导致,输入进去的int类型数据,会被截断,会被隐式转换成char类型,从而导致原来数组下标`i*OPSZ+2,i*OPSZ+3,(i+1)*OPSZ`位置的数据还是为空,然后在`(i+1)*OPSZ+1`的位置,再接着传入下一个被截断的int变量... 但是,如果转换成int指针类型,再传入`scanf`函数内部,指针每次加1的地址偏移量已经变为4,char类型所需1字节,每个下标增量对应地址增加1字节;所以输入进去`scanf` 的int型变量,会直接占4个下标的位置。也就是说,原来数组下标`i*OPSZ+1,i*OPSZ+2,i*OPSZ+3,(i+1)*OPSZ`对应的内存位置,将直接存储一个int型的变量。同样的,如果直接输出`opmsg[i*OPSZ+1]`,将只会输出该int变量的低位地址的存储的值,要想完整的显示整个int变量,依然需要对`opmsg[i*OPSZ+1]`取地址,转换为int指针,然后解引用,最后再输出才行。 以上的分析,都可以在vs中,打开调试->窗口->内存,在内存窗口中分析就很好得到结论了。 ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include<iostream> using namespace std; int main() { char str[3] = { '0' }; //str[2] = 16; //printf("%c \n", str[0]); //printf("%d \n", sizeof(str));//输出1 scanf_s("%d", (int*)&str[2]); printf("%c \n", str[0]); printf("&str[0]+1%x \n", &str[0]+1); printf("&str+1 %x \n", &str+1); printf("%c \n", str[2]); cout << *((int*)(str+2)) <<endl; system("pause"); return 0; } ```
上一篇:
C中的结构体
下一篇:
C++中NULL指针引发的一个小问题
0
赞
323 人读过
新浪微博
微信
腾讯微博
QQ空间
人人网
提交评论
立即登录
, 发表评论.
没有帐号?
立即注册
0
条评论
More...
文档导航
没有帐号? 立即注册