C语言数据类型及存储

C语言数据类型分类

C语言数据类型分为内置类型和自定义类型

内置类型

内置类型是C语言自带的数据类型,整形,浮点型,字符型,指针,空类型等都属于内置类型,他们的意义比较单一,往往用来表示一个含义,比如整形,可以用来记录年龄,次数等数据,浮点类型可以用来存放身高,成绩等实数类型。

void(空类型)不做变量的定义,他的作用大部分场景在函数返回值类型(可以参考C语言函数),还有部分场景会使用到void*的指针。
他们在C语言中可以如下表示

int         //整形数据类型
short int   //短整型数据类型
char        //字符型数据类型
int*        //整形指针数据类型
double      //双精度浮点型
float       //单精度浮点型
void        //空类型

自定义类型

自定义类型是描述复杂的含义,用单一的内置类型无法描述时,所使用的类型,是由用户自己来定义的,通俗一点说就是DIY。

结构体类型

我们需要描述一个人,他有身高,有体重,有性别,有年龄,用单纯的int或者char无法全部描述,因此我们可以进行如下操作

#include<stdio.h>
#include<string.h>
struct people{   //"struct people"为我们自定义的类型,下列name,height等成为成员变量
   //下列name,height等成为成员变量   
     char name[10]; //姓名
     float height;  //身高
     float weight;  //体重
     char sex;      //性别
     int age;       //年龄
};//声明结构体类型


int main()
{
    struct people pe; //定义结构体,
   //为其成员变量赋值
     strcpy(pe.name,"张三");  //字符串拷贝
     pe.height = 183.5;
     pe.weight = 130.5;
     pe.sex = 'm';
     pe.age= 18;
   //打印信息
   printf("姓名:%s 身高:%f 体重:%f 性别:%c 年龄:%d",pe.name,pe.height,pe.weight,pe.sex,pe.age);
    return 0;
}   

结构体类型可以将内置类型和自定义类型组合,构成一个类型,访问其成员时,可以进行如下操作

struct people pe;

pe.age = 18; //结构体直接访问

struct people* ppe = &pe;

ppe->age = 18; //指针间接访问 

枚举类型

生活中一些可以一一举例的数据类型可以成为枚举类型

比如性别:男,女;季节:春,夏,秋,冬;

可以定义枚举类型

enum Season // 枚举类型 季节
{
    Spring,
    Summer,
    Autumn,
    Winter    //最后一个没有逗号
};
  
enum Season now = Autumn; //定义枚举变量 now ,初始化为Autumn

可以理解为我们自己定义枚举常量,用这个常量来给我们的枚举变量赋值,类型是相对应的 。

枚举类型中的枚举常量都是有自己的值得,默认从0开始,依次递增,我们也可以给他赋初始值。

enum Season // 枚举类型 季节
{
    Spring = 1,
    Summer = 3,
    Autumn = 4,
    Winter = 5   //最后一个没有逗号
};
  
enum Season now = Autumn; //定义枚举变量 now ,初始化为Autumn, now的值为4

可以用枚举类型替换掉我们平时用的#define,有很多优点,可以一次定义多个常量,不是单纯的替换,可以避免很多问题,便于调试等。

联合体类型

联合体类型是一种特殊的自定义类型,类似于结构体,但是与结构体不同的是,联合体的成员变量共用一块空间,因此联合体也称做共用体

union Un  //联合体的声明
{
   int a;
   char c;
};

union Un x; //联合体的定义

x.a = 5;     //访问联合体成员
x.c = 'p';

C语言数据在内存中的存储

声明和定义的区别

内置类型

告诉编译器我有这个变量,而他的值未知,这是声明。

声明后给变量赋值,系统会为变量开辟空间来存储数据,这是定义。

也就是说,声明和定义的区别就是有没有分配内存

变量的声明和定义方法如下

int   age;          //  数据类型    变量名称;  这一步是声明

       age = 0;   //分配内存空间,存储数据,这一步是定义

int   cnt = 0;   //声明的时候给他初始化,分配内存空间了,是定义

结构体类型

结构体类型声明和定义在介绍类型的时候已经展示,但也有一些特殊的声明和定义

//匿名结构体:省去struct后面的标签,并且直接定义x
struct
{
    int a;
    int b;
}x;  
//匿名结构体定义时可以定义多个,也可以定义指针
struct
{
    int a;
    int b;
}x1,x2[10],*x; 

*******************************************************************************************
//结构体自引用:在结构体中成员变量可以是自身的指针类型
struct Node
{
    int a;
    struct Node* next; //如果不是指针,就会递归定义,无休无止,所以定义成指针, 
                       //就可以通过自身找到下一个结构体
}

stuct people
{
    char name[10];
    int age;
    float height;
}p1;     // 声明类型的同时定义结构体p1

struct people p2;    // 定义结构体变量p2
struct people p3 = {"张三",21,180};  //定义p3的同时初始化p3

struct Node
{
    int a;
    struct people p;   //结构体嵌套
    struct Node* next; //结构体自引用
}p4 = {1, {"李四", 20, 175 }, &p3};  //结构体嵌套定义初始化

p5 = {2, {"王五", 20, 178 }, &p4};   //结构体嵌套定义初始化

数据存储

 整数类型

在32位机器下,整数会分配4个字节的大小来存放数据,64位机器下是8个字节,每个字节是8个bit位,一个bit就是我们内存的最小单位,只有2种状态,我们可以用它存储2进制代码。

32位机器下,int类型的数据会有4个字节,也就是32个bit位来存放数据。存放时有符号位和数值位两部分。

符号位都是用0表示“正”,用1表示“负”;

原码

直接将整数按照正负数的形式翻译成二进制代码

反码

原码的符号位不变,其他位依次按位取反

补码

反码+1就是补码

正数的原码,补码,反码都是相同的。

在计算机中,整形存储都是存储补码

这样存储补码的原因是:使用补码可以统一处理符号位和数值位。可以统一处理加减法,补码与原码转换,其运算过程是相同的,不需要额外的硬件电路

浮点类型

浮点类型包括float,double,long double类型

存储规则

根据国际标准(IEEE)规定,任意一个二进制浮点数V可以表示成下面形式

其中

  1. 符号位(S):1位,用来表示这个数是正数还是负数。0表示正数,1表示负数。
  2. 指数位(E):8位,用来表示数值的范围。它存在一个偏移值,通常偏移量是127。
  3. 尾数位(M):23位,用来表示数值的精度。

例如,十进制20.0,写成二进制是10100.0 = 1.01 * 2^4.  则S = 0, M = 1.01, E = 4;

 根据IEEE规定,32位浮点数最高位的1位存储符号位S,接着8位是指数E,剩下的23位是有效数字M。

对于64位,最高位为符号位,接着11位是指数E,剩下的52位为有效数字

对于M和E,有特殊规定

M:介于1到2之间,即1.***,默认第一个数总是1,因此可以将1省略,在内存中只存入小数部分,这样做的好处是存储有效数字的位数可以从23变到24

E:E是一个无符号的整数,8个bit位,意味着他的存储范围为0~255,但是科学计数法中E可以为负数,因此我们给E加上127存到内存里,就可以表示-127~128之间的数,对于11位的E,这个数是1023

例如,如果有一个32位的浮点数01000000 00000000 00000000 00000000,那么:

  • 符号位是0,表示这是一个正数。
  • 指数位是10000000,转换为十进制是128,减去偏移量127,得到实际指数是1。
  • 尾数位是00000000 00000000 00000000 00000000,表示小数部分是0。

所以这个数表示的是1 * 2^1 = 2

自定义类型

结构体在内存中存储是,其分配的空间不是各成员分配的空间之和,他还需遵守一个规则,结构体内存对齐

结构体内存对齐

对齐规则:

1.第一个成员在与结构体变量偏移量为0的位置

2.其他成员变量要对齐到某个数字(对齐数)的整数倍地址初,

3.结构体总大小为最大对齐数(每个成员对齐数的最大值)的整数倍。

4.如果有嵌套结构体的情况,成员结构体对齐到自己的最大对齐数整数倍,外结构体对齐到所有对齐数(包括成员结构体)的最大对齐数的整数倍

对齐数的计算

 对齐数 = 编译器默认的对齐数 与 该成员大小的较小值,vs中默认对齐数是8

默认对齐数可以进行修改

#pragma pack(2) // 修改默认对齐数为2

struct Example {
    char a;  // 1 字节
    int b;   // 4 字节
    char c;  // 1 字节
};

#pragma pack( )  // 恢复默认对齐数

举例
struct Example {
    char a;  // 1 字节
    int b;   // 4 字节
    char c;  // 1 字节
};

 如果没有对齐,那他的内存分布是

根据对齐规则后,应该是

a是结构体的第一个成员,在偏移量为0的位置

b是int类型的数据,大小为4个字节,与默认对齐数8相比较小,所以b的对齐数是4,他的位置必须在4的整数倍

c类似,对齐数为1,位置在1的整数倍

整个结构体对齐数位成员对齐数1,4,1 的最大值4,所以他的大小为4的整数倍,所以他的总大小为12个字节

对齐的意义

编译器读取内存可能不是一位一位读取,是同时读取4个字节或者其他整数个字节,以4为例,如果不对齐的话,可能读取一个整数会需要访问两次,因为在他之前可能读过不是4的倍数大小的空间,而对齐后则是可以一次读取,尽管浪费了空间,但是换来了时间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值