2024-2025-1 20241307《计算机基础与程序设计》第九周学习总结
作业信息
这个作业属于哪个课程 | (2024-2025-1-计算机基础与程序设计) |
---|---|
这个作业要求在哪里 | (2024-2025-1计算机基础与程序设计第九周作业) |
这个作业的目标 | |
作业正文 | (2024-2025-1 学号20241307《计算机基础与程序设计》第九周学习总结) |
教材学习内容总结
《计算机科学概论》第七版第十章和第十一章更为详细的内容总结:
第十章:操作系统基础
- 操作系统概述:操作系统是一种特殊的程序,它具有操作硬件的权限,并负责运行和管理其他程序,是用户与计算机硬件之间的接口。操作系统的主要责任包括管理计算机资源,如CPU、内存、输入/输出设备等,以及为人机交互提供界面.
- 操作系统的启动和管理:操作系统通常是开机后第一个启动的程序,它掌控着整个计算机系统的运行。它加强了程序加载方式,支持批处理,即可以一次给多个程序运行,提高了系统资源的利用率.
- 设备驱动程序:操作系统充当软件和硬件的媒介,提供应用程序编程接口(API)来抽象硬件,这些API被称为设备驱动程序。通过设备驱动程序,程序员可以以标准化的方式与硬件进行交互,而无需了解硬件的具体细节,方便了软件的开发和硬件的升级与替换.
- 多任务处理:操作系统具备使多个程序可以同时运行的能力,即使在单个CPU上,也能通过分时复用等技术让多个程序共享时间片,实现宏观上的同时运行。每个程序会分配有专属的内存块,这些内存块可能不连续,操作系统通过虚拟内存技术隐藏了这种复杂性,使得程序在运行时仿佛拥有连续的内存空间.
- 进程管理 :
- 进程的概念:进程是程序的一个执行实例,它拥有独立的内存空间和执行状态。一个程序可以对应多个进程,每个进程都有自己的数据、堆栈和程序计数器等。
- 进程的状态:包括运行态、就绪态、阻塞态等。运行态表示进程正在CPU上执行;就绪态表示进程已准备好运行,只要CPU空闲就可获得执行权;阻塞态则是进程因等待某些事件发生,如等待I/O操作完成,而暂时无法继续执行。
- 调度算法:操作系统依据特定的调度算法来决定进程的运行顺序。常见的调度算法有先来先服务,即按照进程到达的先后顺序进行调度;短作业优先,优先调度执行时间短的进程,以提高系统的整体效率;优先级调度,根据进程的优先级高低来分配CPU时间,优先级高的进程先执行;时间片轮转,每个进程轮流在CPU上执行一个固定的时间片,当时间片用完后,进程会被切换到就绪队列末尾等待下一次调度。
- 内存管理 :
- 内存分配方式:常见的有连续内存分配,即把内存划分为连续的区域分配给进程,但容易产生内存碎片;分页系统,将内存和进程的地址空间划分为固定大小的页面,通过页表实现逻辑地址到物理地址的映射,提高了内存的利用率;分段系统,按程序的逻辑结构将其划分为不同的段,如代码段、数据段等,每个段有自己的基址和长度,便于程序和数据的共享与保护。
- 虚拟内存:虚拟内存技术可通过硬盘空间模拟出比物理内存更大的内存空间。当物理内存不足时,操作系统会将暂时不用的内存数据交换到硬盘上的虚拟内存中,需要时再调入物理内存,使程序能够使用更多内存,从而运行更大的程序或同时运行多个程序。
- 内存保护:操作系统给每个程序分配专用的内存范围,并将其隔离起来,以防止不同程序之间相互影响和防止病毒等恶意程序的侵害,确保系统的稳定性和安全性.
- 文件系统 :
- 文件的概念:文件是数据存储的基本单位,它可以存储各种类型的数据,如文本、图像、音频、视频等。
- 文件系统的管理:操作系统负责管理文件的创建、删除、读取和写入等操作,并通过目录结构组织文件,形成层次化的文件系统,方便用户对文件进行分类和查找。常见的目录结构有树形目录结构,如Windows系统中的文件夹结构。
- 文件权限控制:操作系统还可对文件进行权限控制,规定不同用户或用户组对文件的访问权限,如读、写、执行等权限,确保文件的安全性和保密性。
- 文件存储设备:文件通常存储在硬盘等外部存储设备上,硬盘通过磁头在盘片上的读写操作来存储和检索数据,具有大容量、非易失性等特点,是计算机系统中主要的存储设备之一。
- 输入输出管理 :
- 输入输出设备:包含键盘、鼠标、显示器、硬盘、打印机等,这些设备是用户与计算机进行交互和数据传输的重要媒介。
- 缓冲区和I/O调度:操作系统通过提供缓冲区来临时存储输入输出数据,减少设备与CPU之间的速度差异对系统性能的影响。同时,采用I/O调度算法,如先来先服务、优先级调度等,对设备的I/O请求进行合理调度,提高数据传输效率,减少等待时间。
第十一章:数据库系统
- 数据库系统概述:数据库是有组织的数据集合,它由数据库管理系统进行管理。数据库管理系统是一种软件系统,可提供高效的数据存储、查询、更新和管理功能,能够确保数据的完整性、一致性和安全性。通过数据库系统,用户可以方便地对大量的数据进行组织、管理和共享,提高数据的利用价值.
- 数据模型 :
- 层次模型:以树形结构组织数据,每个节点表示一个记录类型,有且仅有一个父节点,除根节点外,其他节点都有且仅有一个父节点。层次模型适用于表示具有一对多关系的数据,如组织机构、家族关系等,但对于多对多关系的表示较为复杂。
- 网状模型:以图形结构组织数据,节点之间可以有多种连接方式,比层次模型更为灵活,能够更自然地表示多对多关系。但网状模型的结构复杂,数据操作和维护难度较大。
- 关系模型:以二维表格形式组织数据,由行和列组成,行表示记录,列表示属性。关系模型具有简单、直观、易于理解和操作等优点,是目前最为常用的数据模型,大多数数据库管理系统都采用关系模型,如MySQL、Oracle等。
- 关系数据库 :
- 表的结构:表是关系数据库的基本存储单位,由列(属性)和行(记录)构成。每一列都有特定的数据类型,如整数、字符、日期等,用于定义该列所存储的数据特征。每行代表一条记录,包含了各个属性的值。
- 主键和外键:主键用于唯一标识每条记录,确保表中数据的唯一性。外键用于建立表之间的联系,通过外键可以将不同表中的相关数据关联起来,实现数据的一致性和完整性约束。
- 数据完整性:包括实体完整性,要求表中的每一行都有唯一的主键值;参照完整性,规定外键的值必须是另一个表中主键的有效值或为空;域完整性,确保列中的数据符合定义的数据类型和取值范围。
- SQL语言:SQL是用于访问和操作数据库的标准语言,具有简洁、灵活、功能强大等特点,涵盖了数据查询、数据操作、数据定义和数据控制等功能.
- 数据查询:使用SELECT语句从一个或多个表中检索数据,可以根据条件筛选数据、对数据进行排序、分组等操作,以满足不同的查询需求。
- 数据操作:INSERT语句用于向表中插入新的记录;UPDATE语句用于更新表中已存在记录的某些列的值;DELETE语句用于删除表中的记录。
- 数据定义:CREATE语句用于创建数据库、表、视图、索引等数据库对象;ALTER语句用于修改数据库对象的结构,如添加或删除列、修改列的数据类型等;DROP语句用于删除数据库对象。
- 数据控制:GRANT语句用于授予用户对数据库对象的访问权限,如查询、插入、更新、删除等权限;REVOKE语句用于收回用户的权限,确保数据库的安全性和保密性。
- 数据库设计 :
- 实体-关系模型:是一种图形化的数据库设计工具,用于描述数据库中的实体及其关系。实体表示现实世界中的对象或概念,如学生、课程等,用矩形框表示;关系表示实体之间的联系,如学生选课、教师授课等,用菱形框表示,通过线条和箭头连接实体和关系,并标注关系的类型和基数。
- 规范化:规范化是为了消除数据冗余、避免更新异常等问题,提高数据库的性能和数据一致性。常见的规范化范式有第一范式,要求每个属性都是不可再分的原子值;第二范式,在满足第一范式的基础上,要求非主键属性完全依赖于主键;第三范式,在满足第二范式的基础上,要求非主键属性之间不存在传递依赖。通过逐步规范化,可以使数据库结构更加合理、高效。
以下是更为详细的对常见《C 语言程序设计》教材第八章(指针相关内容)的总结:
一、指针基础
- 指针变量的声明与初始化
- 在 C 语言中,声明指针变量的语法形式为:
类型标识符 *指针变量名;
,例如int *p;
声明了一个名为p
的指针变量,它被指定用于存放int
类型变量的地址。这里的*
仅是一个标识,表明所声明变量为指针类型。指针变量在声明时可以进行初始化,将其初始化为某个已有变量的地址,像int num = 10; int *p = #
,通过取地址运算符&
获取num
的地址并赋给p
,使得p
初始就指向num
。
- 在 C 语言中,声明指针变量的语法形式为:
- 指针的取值与间接访问
- 取地址运算符
&
和取值运算符*
是与指针密切相关的两个操作符。&
作用于变量时返回该变量在内存中的地址,而*
作用于指针变量时,是对指针所指向内存地址存储的值进行访问(即间接访问)。例如,对于上述的p
和num
,*p
就等同于num
,对*p
进行赋值操作,如*p = 20;
,实际上就是改变num
的值为20
,因为p
指向num
,*p
代表取p
所指内存处的值。
- 取地址运算符
二、指针运算
- 算术运算
- 指针自增自减:指针变量进行自增(
++
)或自减(--
)运算时,其地址改变量取决于它所指向的数据类型大小。以指向int
类型(通常占 4 字节)的指针p
为例,执行p++
后,p
的地址值会在原基础上加 4 字节,使其指向下一个int
类型存储单元。同样,p--
会让指针地址回退 4 字节。这种自动适配数据类型字节大小的运算方式,保证了指针能按顺序正确访问同类型的数据序列,比如遍历数组元素。 - 指针加减整数:除自增自减外,指针还能和整数进行加减运算,语法形式如
p + n
或p - n
(n
为整数)。其实际地址变化为指针当前地址加上或减去n
乘以指针所指数据类型的字节大小。例如,p
指向int
数组arr
的首元素,p + 2
得到的地址就是指向arr[2]
的地址,即跳过两个int
元素位置后的地址,对*(p + 2)
取值就能获取arr[2]
的值。
- 指针自增自减:指针变量进行自增(
- 关系运算
- 指针间可以进行关系比较运算,像
==
(判断是否指向同一地址)、!=
(判断指向不同地址)、<
、>
、<=
、>=
等。常用于判断指针是否指向同一个内存区域、在遍历数组场景中确定指针是否超出数组边界等。比如在遍历数组arr
时,用指针p
作为迭代工具,while (p < arr + sizeof(arr)/sizeof(arr[0]))
语句可确保p
在数组有效范围内移动,避免越界访问,其中arr + sizeof(arr)/sizeof(arr[0])
表示指向数组末尾元素下一个位置的指针。但要注意,参与比较的指针通常需指向同一数据类型且处于同一数据块(如同一数组)才有实际意义与确定性结果。
- 指针间可以进行关系比较运算,像
三、指针与数组
- 数组名与指针的等价性
- 在 C 语言里,数组名在多数情况下等同于指向数组首元素的指针常量(不可修改指向)。对于数组
int arr[5];
,arr
和&arr[0]
在值上相等,都代表数组首元素的地址,从编译器底层实现看,对arr
进行取值操作(*arr
)等价于arr[0]
。基于此等价性,指针和数组在代码书写与操作逻辑上有诸多相似与关联之处。
- 在 C 语言里,数组名在多数情况下等同于指向数组首元素的指针常量(不可修改指向)。对于数组
- 通过指针遍历数组
- 借助指针遍历数组是常见编程实践,示例代码如下:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 让指针 p 指向数组 arr 首元素
for (int i = 0; i < 5; i++) {
printf("%d ", *p); // 输出指针 p 指向的当前元素值
p++; // 指针后移,指向下一个元素
}
return 0;
}
上述代码中,初始 p
指向 arr[0]
,循环里每次输出当前指向元素值后,指针 p
向后移动一个 int
类型单元,直至遍历完整个数组。此外,还能用指针反向遍历数组,将指针初始化为指向数组末尾元素,再不断向前移动指针并取值。
3. 指针与二维数组
- 二维数组可看作是“数组的数组”。对于二维数组 int matrix[3][4];
,matrix
是指向包含 4 个 int
元素数组的指针(可理解为行指针),matrix[0]
、matrix[1]
等是指向每行首元素的指针(与一维数组名类似)。若定义 int (*p)[4] = matrix;
,这里 p
就是和 matrix
类似的行指针,通过 p
能按行访问二维数组,像 *(p + 1)[2]
可访问 matrix[1][2]
,需注意运算符优先级与结合性确保指针运算和解引用操作正确组合来定位目标元素。
四、指针与函数
- 函数参数传递指针
- 将指针作为函数参数传递,打破了函数参数“单向传递”局限,实现类似“双向”数据交互效果。示例如下:
#include <stdio.h>
// 交换两个整数的函数,参数为指针
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int num1 = 5, num2 = 10;
swap(&num1, &num2); // 传递 num1 和 num2 的地址给 swap 函数
printf("num1 = %d, num2 = %d\n", num1, num2);
return 0;
}
在 swap
函数中,通过指针 *a
和 *b
间接访问 main
函数里的 num1
和 num2
,修改它们的值,改变在函数调用结束后依然生效,因为传递的是地址,操作的是对应内存数据。
2. 函数返回指针
- 函数可以返回指针类型值,但要谨慎对待返回指针指向内存的有效性。若返回指向局部变量的指针,函数结束后局部变量内存释放,返回指针就成“悬空指针”,后续访问该指针指向数据会引发错误(如访问违规、读取不确定值等)。正确示例如动态内存分配函数(malloc
、calloc
等)返回指向新分配内存块的指针,这些内存块在堆上分配,生命周期由程序员手动管理(需 free
释放),不会随函数结束而自动回收,保障指针返回后的可用性与安全性。
五、多级指针
- 多级指针的概念与声明
- 多级指针即指向指针的指针,声明语法类似普通指针,如
int **pp;
声明了一个二级指针pp
,意味着pp
所存储的值是另一个指针(该指针指向int
类型变量)的地址。同理可拓展到三级指针(int ***ppp;
)甚至更多级,不过实际编程中,二级指针较为常用。
- 多级指针即指向指针的指针,声明语法类似普通指针,如
- 多级指针的应用场景
- 指针数组处理:当有数组元素都是指针类型时(如
int *arr[5];
,这是一个指针数组,每个元素指向int
类型数据),要操作整个数组或传递数组相关信息,二级指针就派上用场。例如在函数参数传递场景,若要修改指针数组内容或对数组整体把控,函数参数可设为二级指针形式(void func(int **p);
)接收指针数组首地址,实现深层次数据操控。 - 动态二维数组模拟:在动态分配二维数组时,利用二级指针配合
malloc
等函数能灵活构建类似二维数组结构且按需分配内存。先分配外层指针数组内存,再为每个内层指针(对应二维数组每行)分配内存块用于存储列数据,后续通过多级指针运算和解引用精确访问各元素,这种方式在处理规模不定二维数据场景下,有效节省内存且贴合复杂数据管理需求。
- 指针数组处理:当有数组元素都是指针类型时(如
教材学习中的问题和解决过程(先问 AI)
- 问题1:指针变量的初始化及赋值理解混乱
问题表现:对于指针变量如何正确获取有效地址进行初始化,以及后续赋值操作时出现混淆。例如,不清楚何时该使用取地址运算符 &,什么时候直接赋值地址,可能会写出类似 int num = 10; int *p; *p = num; 这样错误的代码(试图直接给未初始化的指针所指向的内存赋值,这是非常危险的操作,会导致程序崩溃或出现不可预期的结果),或者错误地认为只要声明了指针就能随意赋值其他变量的地址而忽略了类型匹配等问题。 - 问题1解决方案:牢记指针声明、初始化及赋值的基本语法规则。声明指针时,像 int *p; 只是告诉编译器 p 是一个用来存放 int 类型变量地址的指针变量,但此时它并没有指向任何有效的内存地址,需要通过合适的方式进行初始化,如 int num = 10; int *p = #,使用取地址运算符 & 获取已定义变量 num 的地址赋给 p,让 p 指向 num。
理解指针类型的重要性,指针类型决定了它能指向什么样的数据类型以及进行指针运算时的步长等特性。比如 int * 类型的指针只能存放 int 类型变量的地址,不能将其他类型(如 float、char 等)变量的地址随意赋给它,否则会引发类型不匹配错误。在后续对指针重新赋值时,同样要遵循这个原则,保证赋给指针的地址对应的变量类型和指针本身声明的类型一致。 - 问题2:指针运算导致越界访问数组或出现错误结果
问题表现:在利用指针遍历数组或者进行指针算术运算时,容易出现数组越界访问的情况,导致程序崩溃或者得到不符合预期的结果。例如,在使用指针遍历数组时,循环条件设置错误,使得指针超出了数组的有效范围;或者在进行指针加减整数运算时,对运算结果理解有误,算错了要访问的元素位置。 - 问题2解决方案:清晰掌握数组的边界概念以及指针运算与数组下标的对应关系。在遍历数组时,无论是使用下标还是指针方式,都要确保访问的元素始终在数组的有效范围内。对于上述遍历数组的例子,正确的循环条件应该是 i < sizeof(arr)/sizeof(arr[0])(通过计算数组元素个数来确定循环次数),这样能保证指针 p 在移动过程中不会超出数组最后一个元素的位置。
当进行指针算术运算时,要时刻清楚运算的实际含义和对指针指向位置的影响。例如,若指针 p 指向数组 arr 的首元素,p + n(n 为整数)得到的是指向数组中第 n 个元素(下标为 n,从 0 开始计数)的地址,但要注意 n 的取值范围不能让指针超出数组边界。在实际编程中,可以添加一些边界检查的代码逻辑,如使用条件判断语句来确认指针是否在合法范围内,避免意外越界。 - 问题3:函数中使用指针作为参数或返回指针时出现错误
问题表现:当把指针作为函数参数传递时,可能错误地认为在函数内部修改了指针本身(而不是指针所指向的值)就会影响到函数外部的指针变量,导致达不到期望的数据修改效果。 - 问题3解决方案:对于函数参数传递指针的情况,要明确理解传递的是指针变量的副本到函数内部(就像普通变量传递一样,只是这里传递的是地址值),函数内对指针所指向的值进行操作(通过解引用 * 操作符)才能改变函数外部对应变量的值,而直接修改函数内指针变量本身的指向并不会影响外部的指针。所以如果想在函数内改变外部指针的指向,需要传递指针的指针(二级指针)作为参数来实现深层次的修改。
当函数返回指针时,务必保证返回的指针指向的是有效的、生命周期合适的内存空间。尽量避免返回指向局部变量的指针,若确实需要返回动态分配内存的指针(比如通过 malloc 等函数分配的内存),要记得在合适的时候释放该内存(一般由调用函数的地方负责释放,遵循谁申请谁释放的原则),同时要对返回的指针进行合法性检查,确保不是空指针等异常情况后再进行后续操作。
基于AI的学习
代码调试中的问题和解决过程
- 问题1:程序崩溃或出现异常提示,原因是指针未初始化就使用
问题表现:当运行程序时,可能会直接崩溃,或者系统弹出类似 “段错误”“访问冲突” 等异常提示。这通常是因为声明了指针变量后,没有给它赋初值(即让它指向一个有效的内存地址)就直接对其进行解引用操作(使用 * 运算符访问它所指向的内存内容),导致程序尝试访问非法的内存地址,进而引发错误。 - 问题1解决方案:在使用指针之前,务必确保其已经被正确初始化,指向一个合法的内存地址。可以通过取地址运算符 & 将指针指向已定义的变量,如 int num = 10; int *p = #,这样 p 就指向了 num,后续对 *p 的操作就是对 num 的操作,是合法的。
如果暂时不确定指针要指向哪里,也可以先将其初始化为 NULL(空指针),像 int *p = NULL;,这样在后续代码中对指针进行操作前,先通过条件判断 if (p!= NULL) 来确认指针是否有效,避免因空指针解引用导致的错误。 - 问题2:数组越界访问导致结果错误或程序异常,在利用指针操作数组时出现
问题表现:在使用指针遍历数组或者通过指针进行数组元素的读写操作时,可能会出现数组越界的情况。比如,循环条件设置错误,使得指针超出了数组实际元素的范围,访问到了数组之外的内存区域。以下是一个简单的示例,本打算遍历数组输出每个元素,但循环次数设置多了,导致越界 - 问题2解决方案:仔细检查遍历数组时的循环条件,确保指针始终在数组的有效范围内移动。对于上述数组遍历的例子,正确的循环控制应该是 i < sizeof(arr)/sizeof(arr[0]),这样可以根据数组的实际元素个数来确定循环次数,避免指针超出边界。
- 问题3:函数返回的指针指向无效内存,尤其是返回指向局部变量的指针
问题表现:在函数中返回指针时,如果返回的是指向函数内部局部变量的指针,当函数执行完毕后,该局部变量所占用的内存空间会被系统自动释放,这时再通过返回的指针去访问数据,就会出现错误。 - 问题3解决方案:避免返回指向函数内部局部变量的指针。如果需要从函数返回数据的地址,应当使用动态内存分配函数(如 malloc、calloc 等)在堆上分配内存,然后将数据存储在这块内存中,并返回指向该内存的指针。在接收函数返回的指针后,要对指针进行合法性检查,确保它不是空指针(NULL),然后再进行后续的操作,避免因指针无效而引发错误。如上述代码中在 main 函数里通过 if (p!= NULL) 判断后再使用 *p 取值以及最后记得释放动态分配的内存(遵循谁申请谁释放的原则)。
其他(感悟、思考等,可选)
学习《计算机科学概论》第十章、第十一章与《C语言程序设计》第八章后,感悟颇深。操作系统作为软硬件的“大管家”,复杂却精妙,进程调度、内存管理等机制宛如城市交通管控,合理分配资源、协调运行,保障计算机高效运转,也让我意识到日常流畅用机背后藏着这般精细逻辑。数据库系统似有序仓库,SQL 语言是灵活钥匙,开启数据存储、检索大门,规范设计确保信息完整可靠,为海量数据管理筑牢根基。C 语言指针则像神秘魔法棒,赋予直接操控内存魔力,虽初期晦涩易错,但掌握后能极大提升程序效率、拓展功能,深化对计算机底层运行的理解,编程之路由此迈向新高度。
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | |
第二周 | 300/500 | 4/4 | 18/38 | |
第三周 | 500/1000 | 5/7 | 22/60 | |
第四周 | 500/1300 | 6/9 | 30/90 | |
第五周 | 1000/1400 | 7/9 | 60/90 | |
第六周 | 1200/1500 | 8/9 | 70/90 | |
第七周 | 1400/1600 | 9/10 | 80/100 | |
第八周 | 1600/1700 | 10/11 | 100/100 | |
第九周 | 1900/1900 | 11/11 | 110/110 |