C语言入门——第四课

目录

一、数组

1.基本概念

2.数组的描述

3.计算数组所占字节个数

4.数组下标

5.数组之间传值

6.示例代码

7.不同数组的表示

8.数组作为形参

9.数组的访问

10.Q&A

二、const在C++和C语言的差异

1.C语言的const

2.C++中的const

3.作业

三、C++编译链接过程 

四、运算符

1.最高优先级的运算符

(1)数组下标

(2)圆括号

2.最低优先级的运算符

(1)逗号表达式

​编辑

3.单目运算符

简洁与(截断与)

简洁或(截断或)

4.三目运算符

5.取余运算符

(1)概念

(2)应用

(3)例题

①判断是否是闰年

​编辑

②输出'A-Z'到'Z-A'

​编辑 (4)取余和取模

 (5)作业

​编辑 ​编辑

 6.运算符优先级

五、左值、右值、将亡值

1.左值

2.右值

3.将亡值

(1)用户自定义数据类型

(2)右值引用

六、指针

1.概念

2.符号 * 的几种用法 

3.代码示例

4.指针为空判断

断言

1.概念

2.示例代码

3.注意事项

5.指针的地址大小

七、地址

1.使用%p输出地址

2.小端存放

八、计算机的存储设备


一、数组

1.基本概念

是一个固定长度存储相同数据类型数据结构,数组中的元素被存储在一段连续的内存空间中。

2.数组的描述

描述应该包含:类型和大小,不能只包含一个,只包含一个就是错误说法。

3.计算数组所占字节个数

使用sizeof()函数,sizeof()函数是用来计算类型或者变量的字节个数,1字节=>8bit。

4.数组下标

数组的下标必须是常量,也必须是整型量

在这个例子中,n必须是常量,必须是整型量。

const int n = 3;
int ar[n] = {1,2,3};

如果数组下标是变量的话,不同的编译器的情况不同,c99可以允许这种现象,vs2019不通过。一般情况下,我们将数组下标设置为整型常量。

 

5.数组之间传值

Q:可不可以将一个数组的值给另一个数组?

A:不可以。

在下面代码中,我们可以看到br = ar这一行代码报错,原因是此时的ar代表的是数组第一个元素的地址(首地址),br代表的也是数组第一个元素的地址,由于地址是常量,所以不能够被改变,也就不能进行赋值操作,所以br = ar报错。

Q:数组怎么传值?

A:循环。

int main()
{
	const int n = 5;
	int ar[n] = { 1,2,3,4,5 };
	int br[n];
	for (int i = 0; i < n; i++)
	{
		br[i] = ar[i];
	}
}

6.示例代码

示例代码一:

sizeof(stra)用来计算数组的大小,sizeof(stra[0])用来计算数组中元素的字节大小。两者相除得出数组元素的数量。

int main()
{	
	char stra[] = { 'y','h','p','i','n','g' };
	char strb[] = { "yhping" };
	int an = sizeof(stra) / sizeof(stra[0]);
	int bn = sizeof(strb) / sizeof(strb[0]);
	printf("stra_size:%d strb_size:%d", an, bn);
	return 0;
}

代码输出的结构为:

Q:为什么这里元素个数不一样?

A:因为对于字符串而言,它的结尾会被加上'\0',所以相对于字符输入,字符串输入多了一个字符'\0'。

示例代码二:

int main()
{	
	char stra[] = { 'y','h','p','i','n','g' };
	char strb[] = { "yhping" };
	int an = sizeof(stra) / sizeof(stra[0]);
	int bn = sizeof(strb) / sizeof(strb[0]);
	//printf("stra_size:%d strb_size:%d", an, bn);
	printf("%s\n", stra);
	printf("%s\n", strb);
	return 0;
}

输出结果如下: 

Q:产生该现象的原因是什么?

A:printf("%s",变量),输出时遇到第一个‘\0’时才输出。因为数组没有‘\0’,所以没有办法输出。

7.不同数组的表示

const int ar[5] = {1,2,3,4,5};//常性数组,只可读,不可写,包含5个整型类型的元素
int *par[10] ={NULL};//10个整型类型的指针

解释的方法都是从右向左解释。

数组的定义必须注意两个要素:数组大小和数组类型。 

8.数组作为形参

  Q:下面代码的形参是数组,它的输出结果为多少?

void func(int br[10])
{
	printf("br_size:%d\n",sizeof(br));
}
int main()
{
	const int n = 10;
	int ar[n] = { 12,23,34,45,56,67,78,89,90,100 };
	func(ar);
	return 0;
}

A: 在函数 func 中,参数 br 是一个指向整型数组的指针。当你使用 sizeof(br) 来获取 br 的大小时,它会返回指针的大小,而不是数组的大小。在大多数系统上,指针的大小通常是 4 字节或 8 字节,具体取决于系统的架构(32位或64位)。

如果数组作为形参,那么它会退化为指针。

上述代码等价于下面代码:

void func(int *br,int n)
{
	printf("br_size:%d\n",sizeof(br));
}
int main()
{
	const int n = 10;
	int ar[n] = { 12,23,34,45,56,67,78,89,90,100 };
	func(ar,n);
	return 0;
}

在数组进行函数调用时,并不应该把所有的值传递,而是应该传递地址和数组中元素的个数。这样的话讲问题模型简化,无论其他的影响因素怎样变化,我们自始至终只用传递这两个属性就能找到对应的值。

9.数组的访问

下标方式和指针方式都可以访问数组。只不过下标方式会自动转换成指针方式 。

void func(int *br,int n)
{
	printf("br_size:%d\n",sizeof(br));
	for (int i = 0; i < n; i++)
	{
		printf("%d %d\n", br[i],*(br+i));
	}
}
int main()
{
	const int n = 10;
	int ar[n] = { 12,23,34,45,56,67,78,89,90,100 };
	func(ar,n);
	return 0;
}

数组下标方式访问:br[i]

指针方式访问:*(br+i), 含义是数组的首地址+偏移量

Q:请问可以i[br]这样输出吗?

A:可以,因为会自动转化为*(i+br)的形式,所以无论是br[i]还是i[br],在运行的时候都会转化为*(br+i),输出对应的值。

void func(int *br,int n)
{
	printf("br_size:%d\n",sizeof(br));
	for (int i = 0; i < n; i++)
	{
		printf("%d %d %d\n", br[i],*(br+i),i[br]);
	}
}
int main()
{
	const int n = 10;
	int ar[n] = { 12,23,34,45,56,67,78,89,90,100 };
	func(ar,n);
	return 0;
}

10.Q&A

Q:如果数组定义的长度大于你所初始化的数组元素,数组会怎样存储?

示例代码如下:n=10,数组长度为10,但是集合里只3个元素

const int n = 10;
int ar[n] = {1,2,3};

A:此时会将剩余的位置补0。

Q:ar是一个数组,ar++;的表述正确吗?

A: 不正确。ar此时代表的是一个地址,地址不能++。

等价与如下代码:

int a = 10;

&a;

a++;

直接将地址++是错误的。

二、const在C++和C语言的差异

const在C语言和C++中的区别_c语言和c++ 的const的区别_Mr.LeoLu的博客-CSDN博客

在C++和C语言中,他们执行const的方式有一定的差异。

1.C语言的const

用const修饰的变量本质上还是个变量,所以如果对被修饰的变量进行不法修改的话,变量的值是可以被修改的。

示例代码如下:

int main()
{
	const int n = 10;//定义一个常变量n = 10
	int b = 0;//定义一个整型变量b的值为0
	int* ip = (int*)&n;//定义一个可以存储整型类型的指针变量*ip,将n的地址强转成int类型赋给*ip
	*ip = 100;//*ip指的是存储地址对应的变量,此时它是变量n,这里想要改变n的值为100
	b = n;//再将n的值赋值给b
	printf(" n = %d b = %d *ip = %d\n", n, b, *ip);
	return  0;
}

编译器得到的结果是

我们可以看到n的值从10被修改到了100,因此,我们需要知道,const修饰的变量的值在C语言中并非是无法修改的,我们可以通过指针来修改该变量的值。

2.C++中的const

在C++中,程序执行时,const修饰的变量是常变量,会直接在使用到该常变量的时候将常变量的值替换到该位置,即使在程序有对常变量值的修改操作,该值实际上也不会发生变化,替换值还是会按照常变量定义处的初始值。

同样运行上述代码:

int main()
{
	const int n = 10;
	int b = 0;
	int* ip = (int*)&n;
	*ip = 100;
	b = n;
	printf(" n = %d b = %d *ip = %d\n", n, b, *ip);
	return  0;
}

运行结果如下:

这里我们可以看到C++程序下的n值没有被修改。

在C++中编译器直接将变量的值和变量的符号对应起来,存储到符号表中。在编译时,调用变量符号,会直接将变量的值替换掉该变量符号。

在C++中,使用const修饰的变量不会被分配存储空间,但是在C语言中会被分配空间,因此,在C语言中可以通过指针修改const修饰的变量的值。

3.作业

尝试int类型换成double类型,看看在C语言和C++下有没有差异。

将以下代码在c++和c程序下运行

#include<stdio.h>

int main()
{
	const double n = 10;
	double b = 0;
	double*ip = (double*)&n;
	*ip = 100;
	b = n;
	printf(" n = %lf b = %lf *ip = %lf\n", n, b, *ip);
	return  0;
}

C语言下的const运行结果

C++下的运行结果

可以看出,将常变量的类型改变,在C++和C语言下的运行结果仍旧不同。 

三、C++编译链接过程 

首先.cpp文件生成.i文件,再生成.s文件,.cpp、.i、.s文件都是文本文件。

接下来.s文件生成.obj文件,这是一个二进制文件,也是不能够执行的。

最后生成的.exe文件才能执行。

 在.obj到.exe的过程我们称为链接过程

首先我们需要理解一个概念,就是所有的函数声明都被存放在一块,但是它并不能执行函数的功能,如果想要执行函数的功能就需要将函数声明链接到对应的函数库中。

.obj到.exe的链接过程就是将函数链接到静态库,从而实现函数的功能。这也进一步说明了,为什么.obj文件依旧不能被执行。

四、运算符

操作数是指数据实体,运算符是指各种运算的符号。

只需要一个操作数:单目运算符

需要两个操作数:双目运算符

需要三个操作数:算目运算符

运算符需要记住优先级最高的和最低的。优先级最高的运算符包括:数组下标[ ],圆括号(),点 .  以及 ->。优先级最低的运算符是逗号表达式。

1.最高优先级的运算符

优先级别最高的运算符包括:数组下标[ ],圆括号(),点 .  以及 ->

(1)数组下标

数组下标必须是整数 ,而且必须是常数。

常见的常整型都有:宏常量、枚举、const修饰的常变量以及常数。

(2)圆括号

 一般用来写表达式,例如:

for(int i = 0 ; i < 10 ; i ++)
{
    //循环体
}

圆括号还会在函数调用时写出形参位置对应的实参。

int add(int a, int b)
{
    return a+b;
}
int main()
{
    int c = 0;
    c= add(2,3);//调用函数时给对应的实参
    printf("%d",c)
}

2.最低优先级的运算符

(1)逗号表达式

逗号表达式:优先级最低

表达式,表达式

运行过程从左到右依次运行

a=(1,2,3)

最终把最右边的值给a

总结:提供逗号表达式的目的是为了在C语言要求只能有一个表达式的情况下可以使用两个或多个表达式,换句话说,逗号表达式可以将两个表达式粘贴在一起。

3.单目运算符

凡是单目运算符,都是从右向左进行结合。

数学不要和编程混淆。因为有时候16<age<28可以运行,有些编译器会通过,但是有些会报错,所以不要这样写。 

简洁与(截断与)

参与运算的表达式都为真时,结果才为真,否则为假。

常考题如下:问输出的结果是多少?

结果为3,因为a>b为假,所以不执行后面的++c操作,所以c值不变。

简洁或(截断或)

参与运算的表达式只要有一个为真,结果就为真;两个表达式都为假,才是假的。

4.三目运算符

在C语言中唯一一个三目运算符?::

在下面示例中,它表达的意思是:

如果a>b的表达式为真,就把a的值赋给c,否则,把b的值赋给c。

5.取余运算符

(1)概念

在C++和许多其他编程语言中,取余运算符(Modulus Operator)用于计算一个数除以另一个数后的余数。在C++中,取余运算符使用百分号(%)来表示。

(2)应用

取余运算在C语言中的应用:

①判断某个数能否被整除

②判断奇偶性,判断素数或质数

③计算一个范围。形成循环,如例2

④求最大公约数

(3)例题

判断是否是闰年
#define  _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<math.h>

int main()
{
	int year = 0;
	bool tag = false;
	scanf("%d", &year);
	if (year % 4 == 0 && year % 100 == 0 || year % 400 == 0)
	{
		tag = true;
		printf("%s", "yes");
	}
	else
	{
		printf("%s", "no");
	}
	return 0;
}

输出结果如下: 

②输出'A-Z'到'Z-A'

要求最终控制台输出的结果如下所示:

代码如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<math.h>

int main()
{
	const int n = 27;//取27的原因是,最后一个位置要存储空字符串
	char str[n] = { "ABCDEFGHIGHLMNOPQRSTUVWXYZ" };
	for (int i = 0; i < n; i++)
	{
		int k = i;
		for (int j = 0;j < n; j++)
		{
			printf("%c", str[k]);
			k = (k + 1) % n;
		}
		printf("\n");//每一行遍历完,换行
	}
}

 第一次遍历是确定有多少行,第二次遍历是确定每一行有多少元素,关键代码为k=(k+1)%n,这行代码的实现效果可以对应输出后面的元素。例如,k=23,也就是w,k+1=24,再对26取余,还是等于24,所以再次被遍历输出的下一个元素就是k=24的X啦!简单理解,就是没有到一个回合,继续向下读取元素。

输出结果如下所示:

 (4)取余和取模

Q:为什么C语言中的运算叫取余,其他语言会把这种运算叫做取模,二者有什么差异?

A:C语言中的 % 运算符执行的操作与数学中的余数运算一致。例如,如果你计算 10 % 3,结果将是 1,因为 10 除以 3 的余数是 1。这与数学中的余数运算一致。

而"取模"运算通常用来确保结果为正数,不管被除数是正数还是负数。某些编程语言中的取模运算符(如Python中的 %)确保结果始终为正数。

例如,Python 中的 (-10) % 3 结果为 2,而不是 -1。这种行为使得取模运算更适合某些数学和算法应用。

Q:震惊:为什么(-10) % 3 结果为 2

A:符号问题:在Python中,取模运算的规则是:a % b 的结果始终具有与 b 相同的符号。也就是说,结果的符号与除数 b 的符号相同。

数值原因:因为Python在除法运算中是向上取整的

C语言中,10 % (-3)= 1 的计算结果如下:r = 10 - (10 / -3) * -3 = 10 - (-3 x -3) = 1

Python中,10 % (-3)= -2 的计算结果如下:r = 10 - (10 / -3) * -3 = 10 - (-4 x -3) = -2

主要原因是:(10 / -3) 向上取整,使其等于-4。

负数取模怎么算, -10 % 3 = ?-CSDN博客

 (5)作业

求最大公约数,求最大公约数的常见方法是欧几里得算法,又称辗转相除法,其计算原理依赖于定理:gcd(a,b) = gcd(b,a mod b)。

#include <stdio.h>

int gcd(int a, int b) {
    if (b == 0) {
        return a; // 当b为0时,a就是最大公约数
    } else {
        return gcd(b, a % b); // 否则,递归调用gcd函数
    }
}

int main() {
    int num1, num2;

    printf("请输入两个整数:");
    scanf("%d %d", &num1, &num2);

    int result = gcd(num1, num2);

    printf("最大公约数是:%d\n", result);

    return 0;
}

输出结果为:

 

 6.运算符优先级

优先级如下:不需要刻意记忆,直接按照正常思维做就好了。

五、左值、右值、将亡值

1.左值

能够取地址的就是左值。

下面的例子中,const的修饰的变量是可以取地址的,所以被称为左值。

const int n = 10;

&n;

2.右值

不能取地址的就是右值 。

下面的示例尝试对常数取地址,常数没有地址,只有变量才有地址。

&10;

3.将亡值

只存在在计算过程中,计算结束就会消亡。在C++中,将亡值(Rvalue)是一种表示临时对象或表达式的值,通常用于右值引用(Rvalue reference)的上下文中。将亡值通常是一些临时生成的中间结果,其生命周期通常很短暂,可以被移动而不是复制。

计算机的内置类型有:int,char,long,short……,凡是由内置类型产生的将亡值,都是右值,不可被取地址,不可被改变。

(1)用户自定义数据类型

与内置类型相对的是用户自己定义的值:

除了C语言中的内置基本数据类型(整数、浮点数、字符等),C语言还提供了一些其他的数据类型和数据结构,这些类型和结构通常需要通过用户定义来创建。以下是一些常见的C语言中的其他数据类型和数据结构:

  1. 数组:数组是一组相同类型的元素的集合,可以通过下标访问每个元素。例如,int myArray[5] 定义了一个包含5个整数的数组。

  2. 指针:指针是变量,用于存储其他变量的内存地址。指针允许对内存进行间接访问。例如,int *ptr 定义了一个指向整数的指针。

  3. 结构体:结构体是一种用户定义的数据类型,可以包含多个不同类型的成员变量。结构体允许将多个相关数据项组合成一个单独的数据结构。例如,struct Point { int x; int y; } 定义了一个表示二维点的结构体。

  4. 联合体:联合体是一种数据类型,允许在相同的内存位置存储不同类型的数据。与结构体不同,联合体的成员共享相同的内存空间,只能同时包含一个成员值。例如,union Data { int i; float f; } 定义了一个整数或浮点数的联合体。

  5. 枚举:枚举是一种用户定义的数据类型,用于创建具有一组可能值的类型。例如,enum Days { SUN, MON, TUE, WED, THU, FRI, SAT } 定义了一个表示星期几的枚举类型。

  6. 指向函数的指针:C语言允许创建指向函数的指针,这使得可以在运行时动态选择要调用的函数。例如,int (*funcPtr)(int, int) 定义了一个指向接受两个整数参数并返回整数的函数的指针。

  7. typedeftypedef关键字允许用户为已有的数据类型创建新的名称,以增加代码的可读性和可维护性。例如,typedef int Length; 可以为int创建一个新的名称Length

  8. 位字段:位字段是一种用于表示数据的特殊数据类型,允许将多个位字段组合成一个整数,以节省内存空间。例如,struct Flags { unsigned int flag1: 1; unsigned int flag2: 1; } 定义了一个包含两个位字段的结构体。

举例说明:在a+b运算过程时,10 + 20-->30存储的这30就是一个将亡值,是个常量,是不能取地址的,也就是说它是右值。

int a  =10,b = 20;
int c = 0;
c = a+ b ;

这个例子告诉我们为什么在C语言中不能写a+b = c,因为在运算过程时,a+b会产生一个将亡值,不能被更改,所以无法将c的值给他们。 

(2)右值引用

右值引用是C++11引入的一个重要特性,用于处理将亡值(Rvalue)和支持移动语义。它可以显著提高程序的性能,特别是在处理大量数据或大型数据结构时。通过有效地管理资源,可以避免不必要的资源分配和释放开销

&&x 表示一个名为 x 的变量,它是一个右值引用。右值引用可以绑定到将亡值,也可以用于实现移动构造函数和移动赋值运算符,以避免不必要的数据复制,从而提高程序性能。

以下是一个示例,演示如何使用右值引用:

int main() {
    int a = 42; // 普通整数变量
    int &&b = 10; // 右值引用绑定到将亡值 10

    int c = std::move(b); // 使用 std::move 将将亡值 b 移动给 c

    std::cout << "a: " << a << std::endl; // 输出 a 的值
    std::cout << "b: " << b << std::endl; // 输出 b 的值
    std::cout << "c: " << c << std::endl; // 输出 c 的值

    return 0;
}

在这个示例中,b 是一个右值引用,绑定到将亡值 10。然后,使用 std::moveb 的值移动给 c,而不是复制它。这可以提高性能,特别是当操作大型数据结构时。在移动之后,b 不再拥有有效的值。

当使用右值引用移动数据时,源对象的资源通常会被"窃取",不会占据额外的数据空间,因为没有发生数据的复制。在C++中,通常会有大量的对象复制操作,这可能会导致性能问题,尤其是在处理大型数据结构时。右值引用引入了移动语义,允许将资源的所有权从一个对象转移到另一个对象,而不进行数据复制。

六、指针

1.概念

是一种用于存储和管理内存地址的变量类型。指针一定要记住,它有两个值,一个是自身的值,一个是所指之物的值。指针所指之物的值,也被称为解引用。

int *ip:开辟一个可以用来存储int类型地址的指针变量,变量名称为ip

*ip:指的是在ip指针下存储的地址所对应的变量 (*ip是指存储地址对应的变量 )

2.符号 * 的几种用法 

①乘法

②修饰指针变量

③单目运算符:指针

 

3.代码示例

示例一:

int main()
{
    int a = 10,b = 20;
    int *ip = NULL;
    ip =&a;
    *ip = 100;
    ip = &b;
    *ip = 200;
    return 0;
}

上述代码主要进行的操作为:

第一行代码:定义两个整型类型的变量a和b,a = 10,b=20。

第二行代码:定义一个可以存储整型地址的指针变量ip。

第三行代码:将a的值给变量ip。

第四行代码:*ip指的是a,将100赋值给a。

第五行代码:此时将b的地址存储到ip指针变量下。

第六行:此时的*ip为b,将200赋值给b。

输出:此时a = 100,b = 200。

几个概念

&*ip=>&b:*ip指的是b,再加上&,就是&b

*&ip=>ip:&ip是ip的地址,ip地址对应的变量还是ip 

测试下面代码是否可以执行

&*ip = &a;

*&ip=&a;

第一个不能通过,左边是个地址,地址是个常量,只有变量可以被赋值。

第二个可以通过,左边是个变量,其实就是ip变量,可以把a地址给左边变量 

示例二:

int main()
{
	int a = 10, b = 20;
	int* ap = &a;
	int* bp = &b;
	if (ap > bp)
	{
		
	}
	if (*ap > *bp)
	{

	}

}

上述代码中,第一个if比的是a的地址和b的地址。第二个if比的是a和b两个变量的值。因为*ap指的是a,*bp指的是b。ap指的是这个指针变量内部存储的地址,bp也一样存储的b的地址。 

 上述代码也告诉我们,首先要明确指针自身的值,才能明确指针所指向的值。也就是说,首先要知道指针变量里存储的地址是谁,才能知道地址指向的变量。

4.指针为空判断

在指针使用时,我们需要判断指针是否为空

判断方式有两种,一种是直接通过if语句判断,另外一种是通过断言的方式判断。

断言
1.概念

C语言中的断言是一种用于在程序运行时检查条件是否满足的机制。

如果 assert()中止了程序, 它会显示失败的测试、 包含测试的文件名和行号

C语言中的断言由<assert.h>头文件提供支持。

#include <assert.h>

assert(expression);

断言表达式是bool表达式,为真向下执行,为假不执行,为假会终止程序,并且提示。 

2.示例代码
#include<assert.h>

int main()
{
	int x = -5;
	assert(x > 0);
	printf("x is positive.\n");
}

3.注意事项

断言只在debug版本起作用,release版本不起作用。

debug是调试版本,不会对代码进行优化。

release是交付版本,会对代码进行优化。

5.指针的地址大小

x86地址(32位):4字节

64位地址:8字节

七、地址

举两个例子:

有一栋宿舍,房号依次为301、302--305,房号相加没有任何意义。

有两个人的出生日期,将他们相加也没有任何意义。

 但是房号相减有意义,可以知道中间有几间房,日期相减也有意义,可以知道相差几年、几月、几日。所以地址也是一样,相加没有任何意义,但是相减可以知道中间相差几个存储空间。因此,我们说地址不能相加,但是可以相减。

1.使用%p输出地址

当使用 %x 格式化字符串来输出内存地址时,输出的位数可能会受到影响,具体取决于操作系统和编译器的架构。在不同的架构下,内存地址的位数可能会有所不同。

在x86架构下,一个内存地址通常是32位的,因此 %x 可以正常地将这个32位的内存地址显示为16进制数,不会丢失信息。

但在64位架构下,一个内存地址通常是64位的,超过了 %x 的默认宽度。因此,如果你使用 %x 来输出64位地址,可能只会显示其中的一部分,这会导致信息丢失。

因为4个2进制占16进制的一个字符,%x只能表示8个16进制字符,也就是32位2进制,但是一般64位系统地址是64位2进制表示的,超出了%x的默认长度,因此使用%x输出地址会显示不全,示例如下所示:

为了正确地显示64位地址,你可以使用  %p,这将会显示完整的64位地址。

2.小端存放

小端存放中,最低有效字节(即最右边的字节)存储在最低内存地址处,而最高有效字节(即最左边的字节)存储在最高内存地址处。

 inter处理器使用小端存放,而且将低地址作为首地址,作为地址存放。(因为四个字节会被分配4个存储空间,都会有地址,但是只取低地址作为地址存放。)

八、计算机的存储设备

计算机的处理数据的方式:计算机从磁盘读取到内存,CPU在内存里读取和计算,原因内存读取速度快,如果直接在磁盘处理的话,比较慢,CPU将长期处于闲置状态。

寄存器、高速缓存、内存和磁盘都是计算机系统中用于存储数据的不同层次的存储设备,它们按照存取速度和容量从高到低排列。越往上速度越快,价格越贵,空间越小。

每个层次都有不同的特点和用途。

  1. 寄存器(Registers)

    • 寄存器是位于CPU内部的非常小型的存储单元,用于存储临时数据和执行计算
    • 寄存器的访问速度非常快,通常比其他存储设备快几个数量级。
    • 寄存器的数量非常有限,通常只有几十个,用于存储CPU指令和数据。
  2. 高速缓存(Cache)

    • 高速缓存是位于CPU和主内存之间的中间层次存储器,用于存储最近使用的数据和指令。
    • 高速缓存的目标是加速对内存的访问,因为它的访问速度介于寄存器和内存之间。
    • 高速缓存通常分为多个级别(L1、L2、L3),根据距离CPU的远近以及容量大小不同。
  3. 主内存(Memory)

    • 主内存是计算机中的主要存储设备,用于存储程序代码和数据
    • 主内存的容量通常较大,可以容纳程序的所有数据和指令。
    • 主内存的访问速度介于高速缓存和磁盘之间,通常以纳秒级别计算访问时间。
  4. 磁盘(Disk)

    • 磁盘是计算机的永久性存储设备,用于存储操作系统、应用程序、文件和数据。
    • 磁盘的容量通常非常大,可以存储大量的数据。
    • 磁盘的访问速度相对较慢,通常以毫秒级别计算访问时间。

这些存储设备在计算机系统中协同工作,构成存储层次结构。寄存器和高速缓存用于提供快速的数据访问,以满足CPU的要求。主内存用于存储正在运行的程序和数据。磁盘用于永久性存储,以便长期保存数据。

在计算机系统中,数据通常从磁盘加载到主内存,然后从主内存加载到高速缓存和寄存器,以供CPU进行处理。这个层次结构的设计旨在实现高性能和有效的数据访问。

我们需要记住数据存储容量单位的常见定义:

1K = 2^10

1M = 2^20

1G = 2^30

1T = 2^40 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值