C语言数据的存储(上)

简介: C语言数据的存储

1.数据类型介绍

C语言的内置类型

char 字符数据类型 1字节(8bit位)

short 短整型 2字节(16bit位)

int 整型 4字节(32bit位)

long 长整型 4/8字节

long long 更长的整型 8字节

float 单精度浮点型 4字节

double 双精度浮点型 8字节

其实char类型也可以归到整型里面,因为字符在存储表示的时候都用的它的ASCII值.

1.1 类型的基本归类

整型家族:

char

字符存储和表示的时候本质上使用的是ASCII值,ASCII值是整数,字符类型也归类到整型家族.

unsighed char
signed char

short

unsigned short[int]

signed short[int]

int

unsigned int

signed int

long

unsigned long[int]

signed long[int]

对于有符号数和无符号数:

温度:由正负

年龄:正数

在C语言中表示有符号的数,用signed,可以是正数,可以是负数;unsigned表示无符号的数,只能是正数.

注意✨:在使用int类型的时候,我们写的int,实际上等价于signed int,在我们书写代码的时候,signed可以省略,但是unsigned不可以省略,必须写出来.

以下这几种定义是等价的:

int可以省略,signed可以省略:

short num;

short int num;

signed short num;

signed short int num;

unsigned short num;

unsigned short int num;

特殊注意的是😶‍🌫️😶‍🌫️:

1.char是否等价于signed char 取决于编译器

但是大多数编译器,char就是signed char

2.unsigned类型的数据要用%u来打印,%d是用来打印有符号的数据.比如unsigned int num=-1;如果用%d来打印,打印的是-1,用%u来打印,打印的是1

3.%d 打印有符号的数,结果是10进制

%u 打印无符号数,结果是10进制

4. 如果是%u打印,%u就直接把内存里面的值(也就是补码))当成原码打印出来

5. 不管变量的类型是什么,只要存这个值,就是把它的二进制的补码存进去,最终展示出来的结果是多少按照它的解读方式来定.

浮点型家族

float

double

构造类型(创造出来的类型,也叫自定义类型)

数组类型

结构体类型struct

枚举类型enum

联合类型union

1.去掉数组名,剩下的就是数组类型

指针类型

用这些指针创建一些变量,这些变量专门存储地址

int* pi;

char* pc;

float* pf;

void* pv;

有了地址就可找到内存空间的位置,从这个位置开始向后访问数据,按照什么样的节奏访问取决于数据类型.+1跳过几个字节,取决于指针类型.解引用访问几个字节,也取决于指针类型.

空类型

void表示空类型(无类型)

通常应用于函数的返回类型,函数的参数,指针类型.

函数的返回类型

void test(…){} //函数不需要返回值

void test(coid){} //函数不需要参数

void* p; //无具体类型的指针

2.整型在内存中的存储

一个变量的创建是要在内存中开辟空间的,空间的大小是根据不同的类型决定的.

比如:int a=10;

像内存申请4个字节的空间,把10存储进去

知识要点

1.内存中存储的都是二进制数据

2.计算机中的整数有三种2进制表示方法:原码,反码,补码

3.三种表示方法均有符号位,数值位两部分组成.

符号位0表示正数,1表示负数.

数值位: 正数的原反补码都相同

负整数的三种表示方法各不相同

负整数:

原码:

直接将数值按照正负数的形式翻译成二进制就可以得到原码

反码:

将原码的符号位不变,其他位依次按位取反就可以得到反码

补码:

反码+1得到补码

通过代码的内存窗口验证内存中整型数据存的是补码

#include<stdio.h>
int main() {
  int a = 20;
  //32个bit位:最高位表示符号位,剩下的31bit位表示数值位
  //20的二进制序列:
  //0 0000000 00000000 00000000 00010100  --原码
  //正数的原码反码补码全都相同
  int b = -10;
  // 1 0000000 00000000 00000000 00001010    -10的原码
  // 1 1111111 11111111 11111111 11110101    -10的反码
  // 1 1111111 11111111 11111111 11110110    -10的补码
}

结论: 对于整型来说,数据存放内存中其实存放的是补码

为什么整型数据在内存中存储的是补码呢?

int main() {
  1 - 1;
  //怎么算?CPU中只有加法器---->转换成加法计算
  1 + (-1);
  //00000000 00000000 00000000 00000001   1的补码
  //10000000 00000000 00000000 00000001   -1的原码
  //11111111 11111111 11111111 11111110   -1的反码
  //11111111 11111111 11111111 11111111   -1的补码
  // 00000000 00000000 00000000 00000001   1的补码
  // 11111111 11111111 11111111 11111111   -1的补码
  //相加之后的结果:
  // 1 00000000 00000000 00000000 00000000 
  //对于一个整型来说是32比特位,现在是33位,最高位就丢了
  //丢了之后的结果:
  //00000000 00000000 00000000 00000000    结果是0,证明可以通过补码算出来
  //可以通过原码算出来吗?
  //00000000 00000000 00000000 00000001   1的原码
  //10000000 00000000 00000000 00000001   -1的原码
  //相加之后的结果:
  //10000000 00000000 00000000 00000010   结果是-2,我们发现用原码计算不对,而且在用原码进行计算的时候,符号位是否进行相加也不知道
}

大小端存储

在上面的内存图中,我们可以发现在内存中存储的数据是倒着放的,倒着往回读刚好是补码

这是为什么呢?😶‍🌫️😶‍🌫️😶‍🌫️

任何一个数据在存储的时候,它如果大于一个字节,就会有存储顺序的问题.

大端,小端:来自于<<格列夫游记>>中鸡蛋大头小头剥的说法

字节序:注意是以字节为单位,把数据划分成一个一个字节之后讨论字节为单位在内存中存储的顺序

我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

设计一个小程序判断当前机器是大端存储还是小端存储

int main() {
  int a = 1;
  char* p = (char*)&a;\
    if (*p == 1) {
      printf("小端\n");
    }
    else {
      printf("大端\n");
    }
  return 0;
}

是否可以将刚刚判断大小端的代码封装成一个函数呢?

可以

int check_sys() {
  int a = 1;
  char* p = (char*)&a; //int*
    if (*p == 1) {
      return 1;
    }
    else {
      return 0;
    }
}
int main() {
  if (check_sys() == 1)
    printf("小端\n");
  else
    printf("大端\n");
  return 0;
}

优化一下

int check_sys() {
  int a = 1;
  if (*(char*)&a== 1) {  //直接解引用
    return 1;
  }
  else {
    return 0;
  }
}
int main() {
  if (check_sys() == 1)
    printf("小端\n");
  else
    printf("大端\n");
  return 0;
}

再优化

int check_sys() {
  int a = 1;
  return *(char*)&a;  //直接返回
}
int main() {
  if (check_sys() == 1)
    printf("小端\n");
  else
    printf("大端\n");
  return 0;
}

练习

1.输出什么?

//输出什么?
#include <stdio.h>
int main()
{
  char a = -1;
  signed char b = -1;
  unsigned char c = -1;
  printf("a=%d,b=%d,c=%d", a, b, c);
  return 0;
}

从运行的结果来看:

char a;就是signed char a;因为两个的结果是一样的

详解:

char a = -1;

signed char b = -1;

unsigned char c = -1;

关于有符号数和无符号数的补充

char c;

char类型是1byte=8bit位

8bit位放到二进制序列所有的可能性:

00000000 0

00000001 1

00000010 2

00000011 3

00000100 4

00000101 5

01111111 127

10000000 没有办法-1,直接被翻译成-128 -128

10000001 ->原码11111111 -127

11111110 ->原码 10000010 -2

11111111 ->原码 10000001 -1

总共有2^8个这样的二进制序列,即255个
最左边那一列数字是最高位,是符号位,符号位是0表示正数,1表示负数

总结发现:

char类型变量的取值范围是: -128~127

简单记忆成: -2 ^ 7~2 ^ 7-1

知道char类型变量的取值范围的意义是什么?

比如说char c=200;这样是错的,因为char类型能表示的最大整数是127

unsigned char

二进制序列是:

00000000 1

00000001 2

00000010 3

00000011 4

01111111 127

10000000 128

10000001 129

11111111 255

对于无符号数,最高位不是符号位,但是最高位是有效位

总结发现:

无符号数的char取值范围是0~255

简单记忆成0~2^8-1

推广

short

16个bit位

有符号数:1位是符号位,15位是有效位

取值范围:

-2 ^ 15~2 ^15-1

unsigned short

无符号数:

取值范围: 0~2^16-1

2.输出的是什么?

#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}

详解:

-128

10000000 00000000 00000000 10000000 原码

11111111 11111111 11111111 01111111 反码

11111111 11111111 11111111 10000000 补码

a是char类型,只能放低八位

10000000 a

现在以%u的形式打印,a是char类型,现在需要进行整形提升(注意对a进行整形提升的时候要看a的类型)

提升的时候,a的类型是有符号char,所以最高位是符号位,整形提升的时候高位全部补1

补完之后的结果如下(内存提升之后是个补码)

11111111 11111111 11111111 10000000

以无符号数%u打印,对于无符号数,原码反码补码全都一样

所以原码也是

11111111 11111111 11111111 10000000

直接打印出来

结果是个很大的数字

3…2的变形

#include <stdio.h>
int main()
{
  char a = 128;
  printf("%u\n", a);
  return 0;
}

128

00000000 00000000 00000000 10000000

放到a里面的二进制序列 10000000

与-128时放到a里面的二进制序列一样

所以接下来的运算和之前的一样

所得到的结果也和-128的一样

对于char类型的二进制序列图记忆:

4.输出是什么?

int main() {
  int i = -20;
  unsigned int j = 10;
  printf("%d\n", i + j);
  //按照补码的形式进行运算,最后格式化成为有符号整数
}

详解:

int类型里面存的下-20和10,只要存的下,这两个变量里面的值就是-20和10,但从值的角度来讲,加起来一定等于-10,又以有符号的形式打印,结果就是-10

如果不敢确定的话,我们来详细分析一下:

就是把这两个数值的原码写出来,换算成补码,相加之后,再换算成原码的形式打印出结果

-20

10000000 00000000 00000000 00010100 原码

11111111 11111111 11111111 11101011 反码

11111111 11111111 11111111 11101100 补码

10

00000000 00000000 00000000 00001010 原码

正数的原码补码反码相同

-20+10

11111111 11111111 11111111 11101100 补码

00000000 00000000 00000000 00001010 补码

相加之后:

11111111 11111111 11111111 11110110

(计算的结果是存在内存中的,是补码)

求结果的原码

11111111 11111111 11111111 11110101 反码

10000000 00000000 00000000 00001010 原码

最后打印的就是原码 -10

注意😶‍🌫️

不需要考虑强制类型转换成相同的数据类型再进行相加.因为转换成相同的类型之后,存原来的数据还是能存的下的只要是4个直接能存的下就可以,与是否为int类型还是unsigned类型没有关系,只要能存的下,存的数值是不变的.

存的下的情况下不会因为存到别的类型里面值发生变化.

类型转换的用途是什么?

1.相同数据类型的变量进行计算,更方便计算

2.当算术转换一个变量里面的数字为无符号数字时,最好用%u打印.注意算数转换是临时的,变量还是原来的类型.

相关文章
|
2月前
|
存储 编译器 C语言
C语言存储类详解
在 C 语言中,存储类定义了变量的生命周期、作用域和可见性。主要包括:`auto`(默认存储类,块级作用域),`register`(建议存储在寄存器中,作用域同 `auto`,不可取地址),`static`(生命周期贯穿整个程序,局部静态变量在函数间保持值,全局静态变量限于本文件),`extern`(声明变量在其他文件中定义,允许跨文件访问)。此外,`typedef` 用于定义新数据类型名称,提升代码可读性。 示例代码展示了不同存储类变量的使用方式,通过两次调用 `function()` 函数,观察静态变量 `b` 的变化。合理选择存储类可以优化程序性能和内存使用。
156 82
|
1月前
|
存储 C语言 C++
深入C语言,发现多样的数据之枚举和联合体
深入C语言,发现多样的数据之枚举和联合体
深入C语言,发现多样的数据之枚举和联合体
|
1月前
|
存储 C语言
深入C语言内存:数据在内存中的存储
深入C语言内存:数据在内存中的存储
|
1月前
|
C语言
回溯入门题,数据所有排列方式(c语言)
回溯入门题,数据所有排列方式(c语言)
|
2月前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
385 8
|
1月前
|
存储 C语言
C语言中的浮点数存储:深入探讨
C语言中的浮点数存储:深入探讨
|
2月前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
2月前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
2月前
|
C语言
C语言程序设计核心详解 第二章:数据与数据类型 4种常量详解 常见表达式详解
本文详细介绍了C语言中的数据与数据类型,包括常量、变量、表达式和函数等内容。常量分为整型、实型、字符型和字符串常量,其中整型常量有十进制、八进制和十六进制三种形式;实型常量包括小数和指数形式;字符型常量涵盖常规字符、转义字符及八进制、十六进制形式;字符串常量由双引号括起。变量遵循先定义后使用的规则,并需遵守命名规范。函数分为标准函数和自定义函数,如`sqrt()`和`abs()`。表达式涉及算术、赋值、自增自减和逗号运算符等,需注意运算符的优先级和结合性。文章还介绍了强制类型转换及隐式转换的概念。
|
2月前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。