姓名 | 徐秀 | 周次 | 4 | 方向 | 3G |
内 容 | 本周学习知识点: (1)多维数组的指针 (2)C语言结构体和联合体 (3)内存管理 (4)文件操作 | ||||
本周学习收获: (5)(1)多维数组、const指针的使用 (6)指向数组指针的使用 (7)指向函数的指针的使用 (8)内存区域的划分标准,以及各个区域数据的使用 (9)静态分配和动态分配 (10)野指针的概念以及程序中避免出现野指针的方法 (11)内存泄露的概念,malloc函数原型的含义,功能和使用方式 (12)使用free进行动态内存的释放工作 (13)结构对象创建方式、初始化和赋值 (14)嵌套结构的定义,以及访问 (15)结构数组的使用意义,以及对结构数组的初始化、对数组成员的访问方式 (16)结构作为函数参数时,值传递和地址传递的区别,以及对程序带来的影响 (17)C文件的有关基本知识 (18)C语言文件操作中FILE指针的意义,以及使用方式 (19)使用fopen函数进行文件的打开,主要的四个文件打开方式的使用:“r”, “w” , “a”, “b” 文件处理函数的使用:fclose,feof,fputc,fputs,fgetc,fgets,fwrite,fread,fprintf,fscanf | |||||
学习总结: 1. 用指向数组的指针作函数参数 一维数组名可以作为函数参数,多维数组名也可作函数参数。 用指针变量作形参,以接受实参数组名传递来的地址。 可以有两种方法: ①用指向变量的指针变量 ②用指向一维数组的指针变量 2.const int* p 和int const* p 1)两者意义是相同的 *p是const 类型的常量,值不可再改变 p是一个指针变量,因为p是变量,因此p的值(即指针的指向)可以被改变 2)p可以指向什么类型的对象? p可以指向const对象也可以指向非const的对象 3)通过p间接访问对象 可访问*p,但p不能修改所指向对象的值 int* const 1)p本身是什么类型的对象? p是一个被const修饰的指针变量,其值不可修改,在定义p时必须初始化 const* int a;或int * const a; //a是const,但*a可变 2) p可以指向什么类型的对象? p只能指向int对象,不能指向const int的对象 3)通过p间接访问对象 通过p可以修改所指向对象的值 3.指向数组的指针: 数组指针(也称行指针) 定义: 类型名称 (*指针名)[数组长度]; ()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。 4.指针数组的基本概念: 一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。 定义一维指针数组的一般形式为 类型名 *数组名[数组长度]; 指针数组作用: 指针数组比较适合用来指向若干个字符串,使字符串处理更加方便灵活 可以分别定义一些字符串,然后用指针数组中的元素分别指向各字符串 由于各字符串长度一般是不相等的,所以比用二维数组节省内存单元 5.数组指针和指针数组区别: 数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。 还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。 比如要表示数组中i行j列一个元素: *(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j] 6.指针的指针 指针的指针:指向指针变量的指针变量。指针的指针存放的是指针变量地址. 指针变量的指针变量(指针的指针)的定义: 类型 **指针变量名; 数组的指针是指向数组元素的指针; 指针数组的指针,也是指向其数组元素的指针。 指针数组的数组元素是指针,所以指向指针数组的指针就是指针的指针。也就是说,可以使用“指针的指针”指向指针数组。 7.函数的指针:函数的入口地址(函数的首地址)。C语言规定函数的首地址就是函数名,所以函数名就是函数的指针。 指向函数的指针变量:存放函数入口地址(函数指针)的变量,称为指向函数的指针变量。简称函数的指针变量。 函数可以通过函数名调用,也可以通过函数指针调用。 通过函数指针实现函数调用的步骤: 1)指向函数的指针变量的定义: 类型 (* 函数指针变量名)(); 例如 int (*p)(); 注意:两组括号()都不能少。int表示被指向的函数的类型,即被指向的函数的返回值的类型。 2)指向函数的指针变量的赋值,指向某个函数: 函数指针变量名=函数名; 3)利用指向函数的指针变量调用函数: (* 函数指针变量名)(实参表) 8.返回指针值的函数 一个函数可以带回一个整型值、字符值、实型值等,也可以带回指针型的数据,即地址。其概念与以前类似,只是带回的值的类型是指针类型而已。 这种带回指针值的函数,一般定义形式为 类型名 *函数名(参数表列); 9. C语言程序中少量变化的数据用变量来处理。数量不宜多。 批量同类型数据的处理用数组。 不同类型的数据的集合用什么数据结构来存放呢?这就是本单元要介绍的内容:用结构体类型处理不同类型数据的集合。 用户自己建立由不同类型数据组成的组合型的数据结构,它称为结构体 10.定义结构体变量: 先声明结构体类型,再定义该类型变量 声明结构体类型struct Student,可以用它来定义变量 不指定类型名而直接定义结构体类型变量 其一般形式为: struct { 成员表列 }变量名表列; 指定了一个无名的结构体类型 。 11.结构体变量初始化 结构变量初始化的格式,与一维数组相似: 结构变量={初值表} 不同的是:如果某成员本身又是结构类型,则该成员的初值为一个初值表。 注意:初值的数据类型,应与结构变量中相应成员所要求的一致,否则会出错。 12.结构体变量访问 表示结构变量成员的一般形式 结构变量名.成员名 int nNo = stBoy1.nNo; 嵌套结构变量成员的表示形式 结构变量名.成员名.成员名 int nMonth = stBoy1.stBirthday.nMonth; 13.结构体数组 (1)定义结构体数组一般形式是 ① struct 结构体名 {成员表列} 数组名[数组长度]; ② 先声明一个结构体类型,然后再用此类型定义结构体数组: 结构体类型 数组名[数组长度]; 对结构体数组初始化的形式是在定义数组的后面加上: ={初值表列}; 14.指向结构体的指针 指向结构变量的指针的定义 struct 结构名 *结构指针变量名 = &结构变量; struct STUDENT *pStu = &boy; 通过指针访问结构变量的成员 结构指针变量名->成员名 int nNo = pStu->nNo; (*结构指针变量名).成员名 int nNo = (*pStu). nNo; 结构体数组 将整个结构作为参数传递(值传递) void Show(struct STUDENT stuObj); 将结构的地址作为参数传递(地址传递) void Show(struct STUDENT *pStu); 15.共用体(联合体) 有时想用同一段内存单元存放不同类型的变量。 使几个不同的变量共享同一段内存的结构,称为 “共用体”类型的结构。 定义一个共用体的一般形式: union 联合名 { 数据类型 成员名1; 数据类型 成员名2; … 数据类型 成员名n; }; 16.使用共用体注意以下特点 (1) 同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一个成员,而不是同时存放几个。 (2)可以对共用体变量初始化,但初始化表中只能有一个常量。 (3)共用体变量中起作用的成员是最后一次被赋值的成员,在对共用体变量中的一个成员赋值后,原有变量存储单元中的值就取代。 (4) 共用体变量的地址和它的各成员的地址都是同一地址。 (5) 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。 (6) 以前的C规定不能把共用体变量作为函数参数,但可以使用指向共用体变量的指针作函数参数。C99允许用共用体变量作为函数参数 (7) 联合体和结构体体可以互相嵌套 (8) 同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一个成员,而不是同时存放几个。 (9)可以对共用体变量初始化,但初始化表中只能有一个常量。 (10)共用体变量中起作用的成员是最后一次被赋值的成员,在对共用体变量中的一个成员赋值后,原有变量存储单元中的值就取代。 17.文件 文件有不同的类型,在程序设计中,主要用到两种文件: (1) 程序文件: 包括源程序文件(后缀为.c)、目标文件(后缀为.obj)、可执行文件(后缀为.exe)等。这种文件的内容是程序代码。 (2) 数据文件: 文件的内容不是程序,而是供程序运行时读写的数据,如在程序运行过程中输出到磁盘(或其他外部设备)的数据,或在程序运行过程中供读入的数据。如一批学生的成绩数据,或货物交易的数据等。 18.输入输出流 输入输出是数据传送的过程,数据如流水一样从一处流向另一处,因此常将输入输出形象地称为流(stream),即数据流。流表示了信息从源到目的端的流动。 文件标识符 文件要有一个唯一的文件标识,以便用户识别和引用。 文件标识包括三部分: (1)文件路径 (2)文件名主干 (3)文件后缀 19.文件的分类 ●从用户观点: 特殊文件(标准输入输出文件或标准设备文件)。 普通文件(磁盘文件)。 ●从操作系统的角度看,每一个与主机相连的输入 输出设备看作是一个文件。 例:输入文件:终端键盘 输出文件:显示屏和打印机 从文件编码的方式来看 ASCII文件 ASCII文件也称为文本文件,这种文件在磁盘中存放时每个字符对应一个字节,用于存放对应的ASCII码。 二进制文件 二进制文件是按二进制的编码方式来存放文件的。 例如:mp3、rar 等。 20.文件操作的基本步骤 引入头文件(stdio.h ) 定义文件指针 打开文件 文件读写 关闭文件 21.文件类型指针 缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针” 每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等) 这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名为FILE。 声明FILE结构体类型的信息包含在头文件“stdio.h”中 一般设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量 22.用fopen打开文件 对文件读写之前应该“打开”该文件,在使用结束之后应“关闭”该文件。 所谓“打开”是指为文件建立相应的信息区(用来存放有关文件的信息)和文件缓冲区(用来暂时存放输入输出的数据) 在编写程序时,在打开文件的同时,一般都指定一个指针变量指向该文件,也就是建立起指针变量与文件之间的联系,这样就可以通过该指针变量对文件进行读写 所谓“关闭”是指撤销文件信息区和文件缓冲区 fopen函数说明 计在打开一个文件时,通知编译系统以下3个信息: ①需要访问的文件的名字 ②使用文件的方式(“读”还是“写”等) ③让哪一个指针变量指向被打开的文件 23.格式化的方式读写文件 一般调用方式为: fprintf(文件指针,格式字符串,输出表列); fscanf (文件指针,格式字符串,输入表列); 如: fprintf (fp,”%d,%6.2f”,i,f); fscanf (fp,”%d,%f”,&i,&f); 24.使用二进制的方式读写一组数据: 一般调用形式为: fread(buffer,size,count,fp); fwrite(buffer,size,count,fp); 使用说明: buffer:是一个地址 对fread来说,它是用来存放从文件读入的数据的存储区的地址 对fwrite来说,是要把此地址开始的存储区中的数据向文件输出 size:要读写的字节数 count:要读写多少个数据项 fp:FILE类型指针 说明: fwrite是按一个字节块一个字节块的方式写到文件。 完成一次写操(fwrite())作后必须关闭流(fclose()); 完成一次读操作(fread())后,如果没有关闭流(fclose()),则指针(FILE * fp)自动向后移动前一次读写的长度,不关闭流继续下一次读操作则接着上次的输出继续输出; 25.静态和动态的内存分配 静态分配 编译器在处理程序源代码时分配 动态分配 程序执行时按动态要求来分配 26.动态分配和静态分配的区别: 静态内存分配是在程序执行之前进行的, 因而效率比较高,但是它缺少灵活性,要求在程序执行之前就知道所需内存的类型和数量 静态对象是有名字的变量,我们直接对其进行操作. 而动态对象是没有名字的变量,我们通过指针间接地对它进行操作. 静态对象的分配与释放由编译器自动处理.动态对象的分配与释放, 必须由程序员显式地管理, 相对来说比较容易出错 27. 动态内存分配 stdlib.h里面定义了五种类型、一些宏和通用工具函数。 类型例如size_t、wchar_t、div_t、ldiv_t和lldiv_t; 宏例如EXIT_FAILURE、EXIT_SUCCESS、RAND_MAX和MB_CUR_MAX等等; 常用的函数如malloc()、calloc()、realloc()、free()、system()、atoi()、atol()、rand()、srand()、exit()等等。 对内存的动态分配是通过系统提供的库函数来实现的 主要有malloc,calloc,free,realloc这4个函数。 动态内存分配 1.malloc函数 其函数原型为 void *malloc(unsigned int size); 1)size这个参数的含义是分配的内存的大小(以字节为单位) 2)返回值:失败,则返回值是NULL(空指针)。 成功,则返回值是一个指向空类型(void)的指针 (即所分配内存块的首地址) 用malloc申请0字节的问题 另外还有一个问题,用malloc函数申请0字节内存会返回NULL指针吗? 可以测试一下,也可以与去查找关于malloc函数的说明文档。申请0字节内存,函数并不返回NULL,而是返回一个正常的内存地址,但是你却无法使用这块大小为0的内存。这好比尺子上的某个刻度,刻度本身并没有长度,只有某两个刻度一起才能量出长度。对于这点一定要小心,因为这时候if(p!= NULL)将起作用。 说明: 第一、malloc 函数返回的是 void * 类型,如果你写成:p = malloc (sizeof(int)); 则程序无法通过编译,报错:“不能将 void* 赋值给 int * 类型变量”。所以必须通过 (int *) 来将强制转换。 第二、函数的实参为 sizeof(int) ,用于指明一个整型数据需要的大小。如果你写成 int* p = (int *) malloc (1); 如果往里头存入一个整数,就会有3个字节无家可归,而直接“住进邻居家”!造成的结果是后面的内存中原有数据内容全部被清空。 28.free函数 既然有分配,那就必须有释放。不然的话,有限的内存就会用光,而没有释放的内存却在空闲。 其函数原型为 void free(void *p); 其作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。p应是最近一次调用calloc或malloc函数时得到的函数返回值。 free函数看上去挺狠的,但他到底做了什么呢?其实他就做了一件事:斩断指针变量和这块内存的关系。从此p和那块内存之间再无瓜葛。 即指针变量p本身保存的地址并没有改变,但是他对这个地址的那块内存已经没有所有权了。 如果对p连续2次使用free函数,肯定会发生错误。因为第一次使用free函数时,p所属的内存已经被释放,第2次使用时,已经没有内存可以释放了。所以一夫一妻制。一个malloc,一个free。 29.内存泄露问题 计算机中最宝贵的资源就是内存。因此需要动态分配内存的程序一定要坚持“好借好还,再借不难”的原则 内存泄露(memory leak) 指一块动态分配的内存, 我们不再拥有指向这块内存的指针, 因此我们没有办法将它返还给程序供以后重新使用. 1)重新赋值 2)首先释放父块 30.野指针概念 “野指针”不是NULL指针,是指向“垃圾”内存的指针 成因主要有三种: 1)指针变量没有被初始化 2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针 3)指针操作超越了变量的作用范围 (不易发现) 31.野指针成因:访问越界 内存读取越界 (overread) 是指所读取的字节数多于它们应有的字节数。这个问题并不太严重,在此就不再详述了。下面的代码提供了一个示例。 野指针成因:指针释放后,没有设置为null 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。 | |||||
对授课教师意见建议: |
周记(四)
最新推荐文章于 2023-11-11 21:02:12 发布