首页 /  技术专区  /  C 屏幕太窄?试试伸展一下吧 >

数据类型

1、 数据的类型

(1)空间第一个字节的地址

        代码、变量、常量空间,往往都是连续的一片字节空间,到底哪一个字节的地址才代表整个空间的地址(指针)呢,我们前面说过,第一个字节的地址是整个空间的指针。

(2)如何访问一片连续字节空间

        只要知道如下几件事即可。

        1)知道第一个字节的地址

        2)知道访问到第几个字节结束

        3)知道如何解析里面存放的数据

如此就能正确访问连续字节中的数据,不管是访问函数、变量、常量,都是这样来访问的。

例子1:

访问int变量的空间,int是四个字节组成的一片连续空间,如何访问呢?

        1)知道第一个字节的地址

        2)int的大小为4个字节,显然就知道了访问到那个字节结束

        3)数据类型为整型,数据的存储结构就是整形的存储结构,访问空间时,需要按照“整形存储结构”去解析空间。

例子2:

访问float变量的空间,float也是四个字节组成的一片连续空间,如何访问呢?

        1)知道第一个字节的地址

        2)知道大小为四个字节

        3)知道是float类型,那么数据就是以浮点形式存储的,读写空间时就必须按照浮点存储方式来解析空间。

疑问:浮点数是如何存储的?

是以科学计数法来存储的,比如

                                转为科学计数法就是                           指数

12.3  ——————————————————————>  123 * 10的-1次方

疑问float为例,float空间总共32位:

31 30 29 28 27 26 252 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 0

 

· 最高位(31):符号位,0表示正数,1表示负数

· 23~30:指数(1)

· 0~22:整数(123)

比如向float的空间写数据时,按照这个结构来存储12.3的。

(3)回顾C语言的数据类型

        对于绝大多数高级语言来说,都是有数据类型的,以c为例,就有大量的数据类型。

        凡是有数据类型的语言,我们都成为强类型语言。

1)基础数据类型

(a)整形:char short int long,这些还可以与unsigned进行组合,unsigned char,... 

(b)浮点:float double,注意浮点都是有符号的,浮点不能和unsigned进行组合,不存在无符号浮点数一说,

2)组合数据类型

由基础型演变而来。

(a)结构体、联合体类型

/* 结构体类型 */
struct Student
{
    int num;  //基础类型
    char name; //基础类型
    ...
};
/* 联合体类型 */
union Student
{
    int num;  //基础类型
    char name; //基础类型
    ...
};

(b)指针类型

int *,float *,struct Student *, union Student *。
int **,float **,...。

疑问:有联合体指针吗?

答:有,不过用的很少,后面课程讲联合体时,还会提到的。

(4)数据类型的意义

        1)给程序员看到

        比如你一看到变量类型,就知道应该放什么类型的数据到变量里面。

        2)数据类型决定了空间大小和存储结构

比如:

(a)例子1

int a;
a = 100;

空间大小:4字节

存储结构:整形存储结构

编译器在编译时,就知道给a预安排多大的空间,存储结构是怎样的。

float a;
a = 100.7;

空间大小:四个字节

存储方式:浮点存储方式

(b)例子2:

struct Student 
{
    int num;
    char name;
    float score;
    ...
};
struct Student stu;

空间大小:基本>=各成员之和,我们后面学习了结构体的对齐后,其实是能算出具体大小的。

存储结构:属于结构体存储结构

结构体存储时,是按照“构体成员的顺序”来进行存储的,每个成员的存储方式则又由成员的类型来决定。

在很多同学的想象中,总认为成员之间是紧挨着的,其实不是的,中间其实可能有间隔,这个涉及到结构体的对齐,这个我们以后再讲。

不同结构体类型的成员类型、成员数量不同,所以不同结构体类型的存储结构是不一样的,就算是同一个结构体类型,将成员顺序改变后,也不一样。

3)让编译器帮忙检查类型错误

如果编译器检查到类型有问题,就会帮我们报错或者报警告,提醒我们数据的使用可能有问题。

报错、报警告是好事,因为通过这些信息可以快速的帮我们排查问题,解决问题。

如果没有类型检查的话,编译器就不会进行类型检查,那么与数据类型相关的错误,就只能由程序员自己来预防,当时显然这种方式很不靠谱。


(5)=两边的类型问题  

        等号的左边叫左值,右边叫右值,不管=是用于初始化,还是用于赋值,要求右值必须与左值类型一致。

        左值:必须是一个“可写”的变量空间

        右值:不定,可以是一个数,一个变量,一个常量

int a = 0;  //初始化:定义时就给值的就是初始化。 
a = 100;  //赋值:定义之后给值的,就是赋值

类型一致有两种情况,第一种是天然一致,第二种是强行一致。

1)天然一致

int a = 100;

变量a的类型:int

数据100的类型:100

天然一致,其实就是什么类型的数据,就应该放到对应类型的空间中。

2)强制转换,强行一致

(a)显式强制转换

int a = (int)12.45;

float被强制转为int后再,在将值给a。

float a = 100.0;
int b = (int)a;

float的100被强制转为int后再,然后再给b。

int a = 103435455;
int b = (int)&a;

int *的&a被强制转换int,然后在给b。

当然,并不是所有类型之间都可以强制转换的,比如:

float a= 134.454
int *p = (int *)a;

将浮点强转为指针,这是毫无意义的,所以c不允许这种强制转换。

(b)隐式强制转换

编译器在编译时,会默认将右值类型强制转换左值的类型,这就是隐式强制转换。

int a = 12.56;

float 12.56被隐式转为int后,再给a。

int a = 12.56;
char b = a;

int a被隐式转为char后,在都给b。

int a = 10;
char *p = a;

int被隐式转为char *。

int a = 10;
char *p = &a;

int *被隐式强制转为char *。


像int a = 12.56这种基本类型的隐式强制转换,编译器在编译时是不会报警告的,但是像指针这种很重要的类型,编译时一定会报警告提醒你,两边类型不一致,让你警惕,因为编译器很担心这是你的误写。

实际上我们并不提倡使用隐式强制转换,如果你确实需要进行“强制转换”,我们应该使用()进行显式强制转换,显式强制转换的目的:

        · 让代码意思更明确,具有更强的可读性

        否则其他程序员在看到你的代码时,可能认为因为误写才导致类型不一致的,而()可以明确表明,我这里就是要进行强制转换,程序有这个需求。

        · 不要让编译器误解

        对于指针等重要的类型来说,虽然它会帮助你进行隐式强制转换,但是它一定会提警告,提醒你这里类型不一致,你需要注意。

我们说编译器提警告是好事,因为这有利于程序员排查错误,为了不要让编译器报一堆的警告,干扰我们排查其它错误,我们也应该使用(),明确的告诉编译器,这里就是要进行强制准换,此时编译器就不要再报警告。

总之在编程时,如果能够让类型“天然一致”的话最好,否则就应该进行显式强制转换。

(6)函数传参和返回值的类型问题

        1)传参

        传参时也有一个“=”的存在,编译器在翻译传参时,与翻译=是一样的,所以传参等效于有一个=。

        形参 = 实参

        形参为左值,实参为右值。

        如果传参时类型不一致的话,最好使用显式强制转换,不要使用隐式的。

        一般时,都要求实参和形参类型一致。

        2)返回值:

        返回值与返回类型要一致,否者要强转,我们最好使用显式强制转换,不要使用隐式的。

int fun(void)
{
    return (int)123.56;
}

(7)强制转换时,到底干了什么事

        不管是隐式还是显式强制转换,强制转换时,其实就是强行的采用某类型去解释数据,然后将解释后的数据copy给左值。



1)例子1

float a = 134.57
short b = (short)a;

按照short类型要求的“空间大小、存储结构”去解释a中的数据,然后copy给b。

short只有2个字节,float为4个字节,按照short类型解释时,只会强行解释a中头两个字节的空间,所以会丢失两个字节空间的内容。

而且存储类型也会从float转变为short(短整形)。


疑问:a的内容会发生改变吗?

答:不会,只是读取a的内容时,会进行类型的强制解释,但是a中原有的内容不会改变,改变的只是复制的副本。


2)例子2:

char a = 'a';
int b = (int)a;

强行按照int去解释a中数据,然后copy给b。

a只有一个字节,b有四个字节,按照int型强行解释a空间时,a的一个字节是不够的,所以会把后面紧挨着的其它的三个字节也会解释进来。

这一个强制转换不会丢失数据,但是数据也可能会发生改变,因为会多解释了其它空间的内容。

3)例子3:

float a = 100.12;
int b = (int)a;

强行按照int去解释a中数据,然后copy给b。

a和b都是四个字节,空间大小都是一致的,但是存储结构不一致,此时数据也会发生变化,最终b中数据放的是100。


4)例子4

struct Student 
{
    int num;
    char name;
    float score;
};

struct Student stu = {100, "zhangsan", "98.0"};

struct Teacher
{
    char name;
    int num;
};
struct Teacher tea;

tea = (struct Teacher)stu;

强行按照struct Teacher类型去解释stu空间的内容,然后copy给tea。

struct Teacher的空间大小<struct Student,而且存储结构体也不一样的,所以强行按照struct Teacher去解释stu时,不仅数据会丢失,数据的存储结构也改变。

在struct Student类型中,头四个字节解释为一个成员,但是按照struct Teacher解释时,第一个字节为一个成员,其实每个字节的内容并没有改变,只是各自的解释方式不同了。

5)总结强制类型转换

通过前面的例子可以发现,进行强制转换时,并没有改变空间每个字节中的01011001这些内容,只是各自按照不同的类型去解释而已。



强制解释时,会出现如下问题:

        (a)可能丢失部分空间中数据

        (b)可能会多包含其它空间的数据

        (c)存储结构会发生变化 

        

所以进行强制转换时,一定要小心,只有当以上可能结果都被我们接受时,而且确实需要强制转换时,我们才会进行强制转换。

在正常情况下,我们更多是还是要,尽量的让类型天然的一致。



0/200
图片验证码