第 6 章 C++流类库 (WHUT版)_第1页
第 6 章 C++流类库 (WHUT版)_第2页
第 6 章 C++流类库 (WHUT版)_第3页
第 6 章 C++流类库 (WHUT版)_第4页
第 6 章 C++流类库 (WHUT版)_第5页
已阅读5页,还剩70页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第六章 C 的I O流类库 主要内容 C 的流及流类库重载I O运算符 stdio h中定义的输入 输出库函数 printf scanf 完成输入 输出工作有较严重的缺点 1 在C 语言环境中 这样的输入 输出设施缺乏完备性 不能把自定义类型的数据作为一个整体进行输入或输出 2 使用库函数时需要写出比较繁琐的格式说明 3 不同的库函数在参数次序和语义等方面存在不一致 因此 C 语言使用继承机制创建出了自己特有的方便 一致 安全而且可扩充的输入 输出系统 这就是通常所说的流库 6 1C 的流及流类库 1 C 的流输入和输出是数据传送的过程 数据如流水从一处流向另一处 C 形象地将此过程称为流 stream 所谓 流 stream 就是数据从源 数据的生产者 漏 数据的消费者 的流动 C 的输入输出流是指由若干字节组成的字节序列 这些字节中的数据按顺序从一个对象传送到另一个对象 流表示了信息从源到目的端的流动 1 输入流在输入操作时 字节流从输入设备 如键盘 磁盘 流向内存 称为输入流 2 输出流在输出操作时 字节流从内存流向输出设备 如显示器 打印机 磁盘等 称为输出流 流中的内容可以是ASCII字符 二进制形式的数据 图形图像 数字音频视频或其它形式的信息 3 输入输出流的本质 程序运行时 在内存中为每一个数据流开辟一个内存缓冲区 用来存放流中的数据 输出数据 当用cout和插入运算符 从输入缓冲区中提取数据送给程序中的有关变量 4 流类 在C 中 输入输出流被定义为类 C 的IO库中的类称为流类 streamclass 用流类定义的对象称为流对象 例如 cin和cout就是iostream withassign类的对象 2 流类库 C 流类库是C 语言为完成输入 输出工作而预定义的类的集合 C 的I O流类库是用继承方法建立起来的一个输入输出类库 这些类构成一个层次结构的系统 流类库具有两个平行类 即streambuf类和ios类 这两个类是基本类 所有的流类都可以由它们派生出来 1 streambuf类 streambuf类提供物理设备的接口 它提供缓冲或处理流的通用方法 几乎不需要任何格式 streambuf类提供对缓冲区的低级操作 如设置缓冲区 对缓冲区指针进行操作 从缓冲区取字符 向缓冲区存储字符 缓冲区由一个字符序列和两个指针组成 这两个指针是输入缓冲区指针和输出缓冲区指针 它们分别指向字符要被插入或取出的位置 streambuf有三个派生类 即filebuf strstreambuf和conbuf 其成员函数大多采用内联函数方式定义 以提高效率 下图为它们之间的层次关系 filebuf类使用文件来保存缓冲区中的字符序列 当读文件时 实际是将指定文件中的内容读到缓冲区中来 当写文件时 实际是将缓冲区的字符写到指定的文件中 strstreambuf类扩展了streambuf类的功能 增加了动态内存管理功能 提供了在内存中进行提取和插入操作的缓冲区管理 conbuf类扩展了streambuf类的功能 用于处理输出 它提供了控制光标 设置颜色 定义活动窗口 清屏等功能 为输出操作提供缓冲区管理 2 ios类 ios类及其派生类为用户提供了使用流类的接口 主要派生类 输出流 ostream 针对系统全部预定义类型重载了输出运算符 它提供了流的主要输出操作 文件流 fstreambase 提供了文件流的公共操作 串流 strstreambase 专门处理串流 ios作为流类库中的一个基类 可以派生出许多类 ios流类和它的派生类的结构关系如图12 1所示 从图12 1中可以看出ios类有四个直接派生类 即输入流类 输出流类 文件流类和字符串流类 这四种流作为流库中的基本流类 从该图12 1中还可以看出各个类的继承关系 例如 输入文件流类ifstream是由输入流类istream和文件流类fstreambase派生而来 输入字符串流类istrstream是由输入流类istream和字符串流类strstreambase派生而来的 输入输出流类iostream是由输入流类istream和输出流类ostream派生而来的 输入流类 输出流类 文件流类 字符串流类 常用ios流类的简要说明和类声明所在的头文件名如表12 1所示 从表中可以看出 C 流类库中 streambuf ios istream ostream iostream istream withassign和ostream withassign这些基本I O流类和预定义的cin cout cerr和clog在iostream h文件中说明 ifstream ofstream和fstream在fstream h中说明 istrstream ostrstream和strstream在strstream h中说明 如果使用标准输入 输出 控制台I O 只要包含iostream h头文件即可由于fstream h和strstream h中都包含了iostream h 所以 如果使用fstream或者strstream 只要包含相应的fstream h和strstream h即可 在头文件iostream h中 提供了4个预定义的标准流对象 externistream withassigncin 预定义代表标准输入设备键盘externostream withassigncout 预定义代表标准输出设备显示器externistream withassigncerr 预定义代表标准出错输出设备显示器externistream withassignclog 预定义代表标准出错输出设备显示器 说明 这四个标准流对象是程序中进行标准输入输出要用到的 cin对象是从标准输入设备 键盘 流到内存的数据流 称为cin流或标准输入流 cout对象是从内存流到标准输出设备 显示器 的数据流 称为cout流或标准输出流 cerr对象和clog对象均为向输出设备 显示器 输出出错信息的数据流 当开始执行C 程序时 系统将自动打开四个标准流对象 用户可以在程序中直接使用它们 它们是 cin 称为标准输入流 缺省时为键盘 如 intm n cin m n 10022输入时数据间加以空格cout 称为标准输出流 缺省时为显示器 如 intx 9 cout x x endl x 9cerr和clog都是ostream withassign类的对象 称为标准错误输出流 固定关联到显示器 6 2 实现输入 输出 预定义类型数据的输入 输出 istream流类 对于系统预定义类型 把运算符 重载为输入运算符 ostream流类 对于系统预定义类型 把运算符 重载为输出运算符 1 istream流类 istream类中有如下的主要成员函数 1 get函数在istream类中重载了多个get函数 istreamistream istream get unsignedchar intlen char n 从流中把字符输入到给定的char 直到遇到分界符 文件结束符或已读完 len 1 个字符为止 istream从流中输入下一个字符或EOF 2 getline函数在istream类中重载了两个getline函数 istreamistream istream getline unsignedchar int char n 它们的功能与相应的get函数十分类似 只是把分界符也一起读进来 3 read函数istream它们的功能是 从流中提取给定数目的字符到数组char 中 出错时可以用函数gcount得到实际读入的字符数 2 ostream流类 ostream类中有如下主要成员函数 1 flush函数ostream它们的功能是向流中输出几个字符 第一个参数指向待输出的字符串 3 输入运算符 的工作 1 在缺省情况下 运算符 跳过空白符 然后读入与输入变量类型相对应的值 2 当输入字符串 即 类型为char 的变量 时 运算符 的作用是跳过空白 读入以下的非空白字符 直到遇到另一个空白字符为止 并在串尾放一个字符 0 3 不同类型的变量一起输入时 系统除了检查是否含有空白符之外 还完成输入数据与变量类型的匹配 4 输入运算符 采用左结合方式工作 并返回它的左操作数 因此 可以把多个输入操作组合到一个语句中 4 输出运算符 的工作 使用输出运算符输出预定义类型数据时的注意事项 输出运算符 也采用左结合方式工作 并且返回它的左操作数 因此 可以把多个输出操作组合到一个语句中 使用起来很方便 使用输出运算符 进行输出操作时 不同类型的数据也可以组合在一条语句中 例如可把整型变量和双精度型变量的输出放在同一条语句中 重载并不能改变运算符的优先级 因此 必须特别注意表达式的求值顺序 6 2 实现输入 输出 自定义类型数据的输入 输出 1 重载输入运算符 1 重载输入 输出运算符时必须使用友元函数 重载输入运算符的友元函数的定义格式如下 istream 第一个参数是 对istream对象的引用 这意味着in必须是输入流 它可以是其它任何合法的标识符 但必须与return后面的标识符相同 第二个参数是 接收数据的对象 其中user type是用户自定义类型名 obj为该类型的对象名 1重载输入运算符 2 几点注意事项 该重载函数的返回类型是istream类对象的引用 istream operator 返回引用的目的在于 把几个输入运算符 放在同一条输入语句中时 该重载函数仍能正确工作 2 该运算符函数有两个参数 第一个参数是对istream类对象的引用 它出现在运算符 的左边 第二个参数是出现在运算符右边的自定义类型对象 3 重载运算符函数operator 的第二个参数必须是一个引用 例exam12 6输入运算符的重载 includeclassPoint public Point inti 0 intj 0 x i y j friendistream ostream 在这个程序中 定义了重载输入运算符和输出运算符 它们都是类Point的友元函数 分别完成对该类对象的输入和输出操作 在定义重载输入运算符时 参数a前面的 一定不能省略 否则将得到错误的结果 2重载输出运算符 用友元函数重载输出运算符 来实现用户自定义类型对象的输出 定义运算符函数的格式如下 ostream 与重载输出运算符函数一样 重载输出运算符也不能是成员函数法 但可以是该类的友元函数或独立函数 includeusingnamespacestd classPoint public intx y Point inti 0 intj 0 x i y j ostream 例exam12 4输出运算符 的重载 6 3格式化I O控制 C 语言提供了两种格式控制方法 一种方法是使用ios类中有关格式控制的成员函数 另一种方法是使用称为操纵符的特殊类型的函数 1方法 1用ios类成员函数控制格式 1 用成员函数操作状态标志 p36912 2 1 2 1 设置状态标志 setfios中定义了用于设置状态标志的成员函数setf 它的原型为 longios setf longflags 2 清除状态标志 unsetf 3 取状态标志 flags 2 状态标志的值 在ios类中说明了数据成员longx flags 它存放控制输入 输出格式的状态标志 每个状态标志占一位 bit 状态标志的值只能是ios类中定义的下述枚举量 enum skipws 0 x0001 跳过输入中的空白 可用于inputleft 0 x0002 左对齐输出 可用于outputright 0 x0004 右对齐输出 可用于outputinternal 0 x0008 在符号位和基指示符后填入字符 可用于outputdec 0 x0010 转换基制为十进制 可用于input outputoct 0 x0020 转换基制为八进制 可用于input outpnthex 0 x0040 转换基制为十六进制 可用于input outupshowbase 0 x0080 输出时显示基指示符 可用于input outputshowpoint 0 x0100 输出时显示小数点 可用于outputuppercase ox0200 十六进制输出时 表示制式的和表示数值的字母 一律为大写 可用于outputshowpos 0 x0400 对正整数显示正号 可用于outputscientific 0 x0800 用科学表示法显示浮点数 可用于0utputfixed 0 x1000 用定点形式显示浮点数 可用于outputunitbuf 0 x2000 在输出操作后立即刷新所有流 可用于outputstdio 0 x4000 在输出操作后刷新stdout和stderr 可用于output 如果设定了某个状态标志 则x flags中对应位为1 否则为0 这些状态标志之间是或的关系 可以几个标志并存 例如 如果设定了状态标志dec和scientific 其他标志都未设定 则x flags的值应为0000100000010000 即十六进制的0 x0810 要设置多个标志时 彼此用或运算符 分隔 例如 cout setf ios dec ios scientific 例exam12 1说明了上述各种成员函数的使用方法 2 设置域宽 填充字符和精度的成员函数 p36912 2 1 2 1 设置域宽 intios width 返回当前的域宽值 intios width intwid 此函数用于设置域宽 并返回域宽值 注意 所设置的域宽仅对下一个流输出操作有效 当一次输出操作完成之后 域宽又恢复为0 只对最接近它的第一个输出有影响 第一个输出完成后立即把域宽设置为0 原样输出 2 设置填充字符 ios fill char 3 设置显示精度 有效数字 ios precision int 调用fill 函数和precision 函数 设置了fill和precision后 在程序中一直有效 除非它们被重新设置 例exam12 2说明了上述各种成员函数的使用方法 includevoidmain cout defaultwidthis cout width n cout defaultfillis cout fill n cout defaultprecisionis cout precision n cout 666 123 45678 n cout precision 3 cout width 8 所设置的域宽仅对下一个流输出操作有效 cur cout cur 111 cout width n cout cout width currentprecisionis cout precision n cout 666 123 45678 456 78 n cout currentwidthis cout width n cout fill cout width 8 cout 666 123 45678 n 综合分析 VC 6 0 该程序的输出结果为 defaultwidthis0defaultfillisdefaultprecisionis6666123 457cur1118 cout width 8 不足补cout fill0currentprecisionis3666123457 precisionis3currentwidthis0 666123 cout width 8 不足补cout fill precisionis3 分析如下 从上列输出结果可以看出 缺省域宽为0 即用密集状态输出 原样输出 缺省的填充字符为空格 缺省的输出精度为6 即按照数据的实际精度输出 设置了显示精度之后 若数据的实际精度与设置的精度不一致 则输出方法如下 实际精度大于设置的精度时 四舍五入后按照设置的精度输出 实际精度小于设置的精度时 按照实际精度输出 设置域宽之后 只对其后最接近它的第一个输出有影响 第一个输出完成后系统立即把域宽置为0 总结 使用ios类的成员函数进行输入输出格式控制时 每个函数的设置需要写一条语句 而且不能将它们直接嵌入到输入输出语句中 使人感到不够简洁 方便 C 提供另一种进行输入输出格式控制的方法 这一方法使用一种称为操纵符的特殊函数 在很多情况下 使用操纵符进行格式化控制比用ios格式标志和成员函数要方便 方法 2用IO操纵符控制格式 1 标准控制符C 预定义的操纵符分为带参数的操纵符和不带参数的操纵符 通常 不带参数的操纵符在iostream文件中定义 而带参数的操纵符在iomanip文件中定义 定义在iostream头文件中的操纵符有 dec设置十进制转换基格式标志 用于输入 输出 hex设置十六进制转换基格式标志 用于输入 输出 oct设置八进制转换基格式标志 用于输入 输出 ws提取空白字符 仅用于输入 endl插入换行符并刷新流 仅用于输出 ends在串后插入终止空字符 仅用于输出 flush刷新输出流 仅用于输出 6 3 2用控制符控制格式 2 定义在iomanip头文件中的操纵符有 setbase intn 设置转换基格式为n 取值0 8 10 或16 缺省时为0表示采用十进制 仅用于输出 resetiosflags longf 清除由参数f指定的格式位 用于输入输出 setiosflags longf 用参数f设置格式位 用于输入 输出 setfill intc 设置填充字符 用于输入 输出 setprecision intn 设置浮点数精度为n 用于输入 输出 setw intn 设置域宽为n 用于输入 输出 setw只对最靠近它的输出起作用 也就是说 它的作用是 一次性 的上面这些操纵符大致可以代替ios的格式成员函数的功能 且使用比较方便 例exam12 3操纵符的使用 6 3 2用控制符控制格式 3 include includevoidmain cout 123 setw 5 456 88 n cout 123 setw 5 setfill 456 setw 5 88 n 输出结果为 12345688123 456 88在第一个输出语句中 setw 5 把下一次输出整数456的域宽设置为5 但输出后域宽马上恢复为0 故输出的整数88与前面输出的456连在一起了 在第二个输出语句中为后两个整数都设置了域宽5 并把填充字符设置为 故得到了预想的格式 可见设置域宽的控制函数与成员函数ios width完全相同 仅对最靠近它的下一个流输出操作有影响 综合分析 VC 6 0 6 4文件的输入 输出 C 把文件当作是字符序列 C 把文件看作是字符 字节 的序列 即由一个一个字符 字节 的数据顺序组成 按照数据的组织形式 可以把文件分成ASCII文件和二进制文件两种 ASCII文件也叫文本文件 其每个字节放一个ASCII代码 表示一个字符 二进制文件 就是把内存中的数据按其在内存中的存储形式原样写到外存储器中 C 中如何进行文件操作 在C 中 进行文件操作首先建立一个文件流 然后把这个流和实际的文件相关联 这称为打开文件 文件打开后 可以按要求进行读写操作 一般来说 在主存与外设的数据传输中 由主存到外设叫做输出或写 而由外设到主存叫做输入或读 完成输入输出后 还必须将已打开的文件关闭 即取消文件和流的关联 1 文件的打开 文件使用时 必须先打开 后使用 打开文件时必须指定文件的打开方式 C 为文件输入输出提供了3个类 ifstream ofstream和fstream 与这三个类相对应 C 为文件输入输出提供了三种类型的流 即输入流 输出流以及输入 输出流 也就是说 为了打开一个输入流 必须把它定义为类ifstream的对象 要打开一个输出流 必须把它定义为类ofstream的对象 而为了打开既有输入 又有输出的流 则必须把它定义为类fstream的对象 例如 ifstreamin 输入ofstreamout 输出fstreamboth 输入和输出将产生一个输入流 in 个输出流 out 和一个既可输入 又可输出的流 both 文件的打开有两种方式 一种是用输入输出流的成员函数打开另一种是用构造函数打开 open 函数是这三个流类的成员函数 其原型为 voidopen constunsignedchar intmode intaccess filebuf openprot 其中第一个参数用来传递文件名 第二个参数mode的值决定文件的打开方式 第三个参数决定文件的保护方式 用户通常只使用缺省值 1 用流ifstream ofstream和fstream类对象的成员函数方式打开文件 打开方式是已经在ios类中定义了的枚举常量 常用的有 ios in打开一个文件用于输入 读 操作 ios out打开一个文件用于输出 写 操作ios ate打开一个已有的文件 文件指针指向文件尾 ios app以输出方式打开文件 写入的数据添加在文件末尾ios trunc打开一个文件 如果文件已存在 则删除其中全部数据 如果文件不存在 则建立新文件 如果已指定了ios out 而未指定ios app ios ate ios in 则同时默认此方式 ios nocreate打开一个已有的文件 如果文件不存在 则打开文件失败 即不创建一个新文件 ios noreplace如果文件不存在则建立新文件 如果文件存在 则打开失败 noreplace是不更新原有文件的意思 ios binary指定文件以二进制方式打开 而不是缺省说明的文本方式 2 用构造函数打开文件 用流对象的构造函数打开文件的格式是 ifstream流对象名 文件名 ofstream流对象名 文件名 fstream流对象名 文件名 打开方式 例如 语句ofstreamfout mytest txt 它相当于语句 ofstreamfout fout open mytest txt 只有在打开文件之后 才能对文件进行读写操作 如果由于某些原因打不开文件 即执行函数open 失败 则流变量的值将为0 因此 在对文件进行操作前 必须确保文件已打开 否则要进行相应的处理 为了确认打开一个文件是否成功 可以使用类似下面的办法进行检测 ifstreamfin mytest txt 打开输入文件if fin cout Cannotopenfile n 错误处理代码 为了确保文件操作的顺利进行 在打开文件时通常都要判断是否成功 VC C Java用try catch finally语句 2 文件的关闭 当对一个打开的文件操作完毕后 应及时调用文件流的成员函数来关闭相应的文件 具体格式为 流对象名 close 其中流对象名为待关闭的文件流的对象名 Close 函数不带任何参数 也不返回任何值 文件流的对象通过文件打开函数 建立起文件名与该对象的联系后 就可对文件进行读 写操作 文件关闭后 就断开了文件流与对象间的联系 如果要对文件再次进行读 写 就要重新打开文件 3 文件的读写 当文件打开以后 即文件与流建立了联系后 就可以进行读写操作了 1 文本文件的读写对文本文件的读写操作可以用以下两种方法 1 用流输出运算符 输入输出标准类型的数据 2 用文件流的put get和getline等成员函数进行字符的输入输出 1 用流输出运算符 输入输出标准类型的数据 都已在iostream中被重载为能用于ostream和istream类对象的标准类型的输入输出 由于ifstream和ofstream分别是istream和ostream类的派生类 因此它们从ostream和istream类继承了公用的成员函数 所以在对磁盘文件的操作中 可以通过文件流对象和运算符 实现对磁盘文件的读写 如同用cin cout和 对标准设备进行读写一样 只是必须用与文件相连接的文件流代替cin和cout 例exam12 7生成一个sqrtable txt文件 该文件内容为100以内整数的平方根表 结果精确到小数点后4位 include include include includeusingnamespacestd intmain ofstreamtable table open sqrtable txt inti j if table cout Cannotopenoutputfile n return1 table for i 0 i 10 i table setw 5 i table endl table for i 0 i 10 i table table fixed setprecision 4 for i 0 i 10 i table endl table setw 2 i for j 0 j 10 j table setw 8 sqrt 10 i j table close return0 2 用文件流的put get和getline等成员函数进行字符的输入输出 成员函数put用于输出一个字符 格式为 输出流对象 put ch 调用该函数的结果是向输出流中插入 输出 一个字符ch 成员函数get用于读入一个字符 它有3种形式 无参数的 有一个参数的 有三个参数的 不带参数的调用形式为 输入流对象 get 用来从指定的输入流中提取一个字符 包括空白字符 函数的返回值就是读入的字符 若遇到输入流中的文件结束符 则函数值返回文件结束标志EOF EndOfFile 有一个参数的调用形式为 输入流对象 get ch 成员函数getline用于读入一行字符 其用法与带3个参数的get函数类似 即 输入流对象 getline 字符数组或字符指针 字符个数n 终止字符 例exam12 8将文件sqrtable txt复制为文件mttable txt 例exam12 8将文件sqrtable txt复制为文件mttable txt include includeusingnamespacestd intmain charc ifstreamfin sqrtable txt if fin cout Cannotopeninputfile n return1 ofstreamfout mytable txt if fout cout Cannotopenoutputfile n return1 while fin get c fout put c return0 又例如 实现任意名称文件复制的程序如下 include include 为了使用exit includevoidmain intargc char argv charch if argc 3 cerrdcopyfile1file2 2 二进制文件的读写 二进制文件不是以ASCII代码存放数据的 它将内存中数据存储形式不加转换地传送到磁盘文件 因此它又称为内存数据的映像文件 对二进制文件的读写主要用istream类的成员函数read和ostream类的成员函数write来实现的 这两个函数的原型如下 istream read是流类istream中的成员函数 其功能为 从相应的流中读取len个字节 并把它们放入指针buffer所指向的缓冲区中 该函数有两个参数 第一个参数buffer是一个指针 它是读入数据的存放地址 起始地址 第二个参数len是一个整数值 它是要读入的数据的字节数 调用格式为 read 缓冲区首址 读入的字节数 其中 缓冲区首址 的数据类型为char 当输入其他类型数据时 必须进行类型转换 write是流类ostream的成员函数 利用该函数 可以从buffer所指的缓冲区把len个字节写到相应的流上 参数的含义及调用注意事项与read函数类似 例exam12 9将1 1000之间的所有素数以二进制形式存放在磁盘文件Prime dat中 include include includeusingnamespacestd intmain fstreamtable table open Prime dat ios binary ios out if table coutsqrt i table write char 例exam12 10将例exam12 9得到的Prime dat中的数据读入内存并在显示器上显示 include include includeusingnamespacestd intmain fstreamtable table open Prime dat ios binary ios in if table cout Cannotopeninputfile n return1 inti while table eof table read char 3 文件的随机读写 上面介绍的文件操作都是按一定顺序进行读写的 因此称为顺序文件 对于顺序文件来说 只能按实际排列的顺序 一个一个地访问文件中的各个元素 也就是说 在访问完第I个元素之后 只能访问第I 1个元素 既不能访问第I 2个元素

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论