前言
每个人的记忆是有限的,学过的东西很快就会遗忘,因此,在即将升大二之际,对大一学习的C++的基础语法进行整理归纳,并附上一年里写过的一些重要代码,方便今后回顾!
声明:本文参考教材提供的网络学习资料(非常感谢,网址已注明),代码为博主本人大一时自己写的代码(作业、实验报告的题目),本人水平有限,代码不一定完美,欢迎帮忙指正!!!
教材
C++程序设计基础(第5版)(上)
附带学习网站:http://cs.scutde.net/Courses/course_10/index.html
其他参考网站:C++ 教程 | 菜鸟教程 https://www.runoob.com/cplusplus/cpp-tutorial.html
目录
目录
前言
教材
目录
正文
预备章节
第一章 基本数据与表达式
第二章 程序控制结构
第三章 函数
第四章 数组
第五章 类与对象
第六章 运算符重载
第七章 继承
第八章 虚函数与多态性
第九章 模板
第十章 输入/输出流
第十一章 异常处理
第1章 基本数据与表达式
1.1 概述
1.2 C++的字符集与词汇
1.3 C++的基本数据类型与存储形式
1.4 常量与变量
1.5 内存访问
1.6 表达式
1.7 数据输入和输出 |
第2章 程序控制结构
2.1 选择控制
2.2 循环控制
2.3 判断表达式的使用
2.4 转向语句 |
第3章 函 数
3.1 函数的定义与调用
3.2 函数参数的传递
3.3 函数调用机制
3.4 函数指针
3.5 内联函数和重载函数
3.6 变量存储特性与标识符作用域
3.7 多文件程序结构
3.8 终止程序执行 |
第4章 数组
4.1 一维数组
4.2 指针数组
4.3 二维数组
4.4 数组作函数参数
4.5 动态存储
4.6 字符数组与字符串
4.7 string类 |
第5章 类与对象
5.1 结构
5.2 类与对象
5.3 构造函数和析构函数
5.4 静态成员
5.5 友员 |
第6章 运算符重载
6.1 运算符重载规则
6.2 用成员或友员函数重载运算符
6.3 几个典型运算符重载
6.4 类类型转换 |
第7章 继承
7.1 类之间的关系
7.2 基类和派生类
7.3 基类的初始化
7.4 继承的应用实例
7.5 多继承 |
第8章 虚函数与多态性
8.1 静态联编
8.2 类指针的关系
8.3 虚函数和动态联编
8.4 纯虚函数和抽象类
8.5 虚函数和多态性的应用 |
第9章 模板
9.1 什么是模板
9.2 函数模板
9.3 类模板 |
第10章 输入/输出流
10.1 流类和流对象
10.2 标准流和流操作
10.3 格式控制
10.4 串流
10.5 文件处理 |
第11章 异常处理( ** )
11.1 C++的异常处理机制
11.2 异常处理的实现 |
|
|
正文
预备章节
1.C++两种编程方法:
1).用结构化方法编程(函数)
2).用面向对象方法编程(类)
2.程序的编译执行
编辑——编译——运行
3.常用软件环境:Visual Studio、Dev C++、Visual C++等
第一章 基本数据与表达式
1.C++的数据类型
2.引用与指针(难点)
(1)引用
C++允许为对象定义别名,称为"引用"。定义引用的说明语句格式如下。
类型 & 引用名 = 对象名 ;
其中,"&"为引用说明符。
引用说明为对象建立引用名,即别名。引用在定义初始化时与对象名绑定,程序中不能对引用重定义。一个对象的别名,从使用方式和效果上,与使用对象名一致。
|
|
|
|
(2)指针
C++中,关键字const可以约束对象的访问性质,使对象值只读 。
1.指向常量的指针
定义形式:
const 类型 * 指针
或者 类型 const * 指针
图 1.7 指向常量的指针访问特性
2.指针常量
指针常量的定义方式:
类型 * const 指针
图 1.8 指针常量的访问特性
3.指向常量的指针常量
指向常量的指针常量的定义:
const 类型 * const 指针
或者 类型 const * const 指针
图 1.9 指向常量的指针常量的访问特性
4.常引用
冠以const定义的引用,将约束对象用别名方式访问时为只读。常引用的定义方式为:
const 类型 & 引用名 = 对象名
|
|
|
|
3.基本数据类型
类型名
|
说 明
|
字 节
|
示数范围,精度
|
char
|
字符型
|
1
|
-128 ~ 127
|
signed char
|
有符号字符型
|
1
|
-128 ~ 127
|
unsigned char
|
无符号字符型
|
1
|
0 ~ 255
|
short [int]
|
短整型
|
2
|
-32768 ~ 32767
|
signed short [int]
|
有符号短整型
|
2
|
-32768 ~ 32767
|
unsigned short [int]
|
无符号短整型
|
2
|
0 ~ 65535
|
int
|
整型
|
4
|
-2147483648 ~ 2147483647
|
signed [int]
|
有符号整型
|
4
|
-2147483648 ~ 2147483647
|
unsigned [int]
|
无符号整型
|
4
|
0 ~ 4294967295
|
long [int]
|
长整型
|
4
|
-2147483648 ~ 2147483647
|
signed long [int]
|
有符号长整型
|
4
|
-2147483648 ~ 2147483647
|
unsigned long [int]
|
无符号长整型
|
4
|
0 ~ 4294967295
|
float
|
单精度浮点型
|
4
|
-3.4 × 10 38 ~ 3.4 × 10 38 ,约 6 位有效数字
|
double
|
双精度浮点型
|
8
|
-1.7 × 10 308 ~ 1.7 × 10 308 ,约 12 位有效数字
|
long double
|
长双精度浮点型
|
8
|
-3.4 × 10 4932 ~ 1.1 × 10 4932 ,约 15 位有效数字
|
|
|
|
|
4.C++常用的转义符
名 称
|
字符形式
|
值
|
空字符 (Null) |
\0 |
0X00 |
换行 (NewLine) |
\n |
0X 0A |
换页 (FormFeed) |
\f |
0X 0C |
回车 (Carriage Return) |
\r |
0X0D |
退格 (BackSpasc) |
\b |
0X08 |
响铃 (Bell) |
\a |
0X07 |
水平制表 (Horizontal Tab) |
\t |
0X09 |
垂直制表 (Vertical Tab) |
\v |
0X0B |
反斜杠 (backslash) |
\\ |
0X 5C |
问号 (question mark ) |
\? |
0X 3F |
单引号 (single quote) |
\ ′ |
0X27 |
双引号 (double quote) |
\ 〞 |
0X22 |
|
|
|
|
5.运算符
优先级
|
运算符
|
功 能
|
结合性
|
1 |
()
|
改变优先级
|
左→右 |
::
|
作用域运算
|
[]
|
数组下标
|
. ->
|
成员选择
|
.* ->*
|
成员指针选择
|
2 |
++ --
|
自增,自减
|
右→左 |
&
|
取地址
|
*
|
取内容
|
!
|
逻辑反
|
~
|
按位反
|
+ -
|
取正,取负(单目运算)
|
()
|
强制类型
|
sizeof
|
求存储字节
|
new delete
|
动态分配,释放内存
|
3 |
* / %
|
乘,除,求余
|
左→右 |
4 |
+ -
|
加,减(双目运算)
|
5 |
<< >>
|
左移位,右移位
|
6 |
< <= > >=
|
小于,小于等于,大于,大于等于
|
7 |
== !=
|
等于,不等于
|
左→右 |
8 |
&
|
按位与
|
9 |
^
|
按位异或
|
10 |
|
|
按位或
|
11 |
&&
|
逻辑与
|
12 |
||
|
逻辑或
|
13 |
?:
|
条件运算
|
右→左 |
14 |
= += -= *= /= %= &= ^=
|
赋值,复合赋值
|
15 |
,
|
逗号运算
|
左→右 |
|
|
|
|
6.表达式
1)算术表达式
2)关系表达式
3)逻辑表达式
4)赋值表达式
5)条件表达式
6)逗号表达式
第二章 程序控制结构
(一) 选择语句
1.if语句
(1)一个分支的if语句:if (表达式) 语句;
(2)if (表达式) 语句1;
else 语句2;
(3)if (表达式) 语句1;
else if(表达式) 语句2;
else 语句3;
2.switch语句
switch语句形式为:
switch( 表达式 )
{ case 常量表达式1 :语句1 ;
case 常量表达式2 :语句2 ;
:
case 常量表达式n :语句n;
[ default : 语句n+1 ;]
}
|
switch语句的执行流程
△要在某个语句中断,需使用break语句!
|
|
|
|
|
(二)循环语句
1.while语句
(1)do-whlie
(2)while-do
2.for语句
for(表达式1;表达式2;表达式3)
{
循环体;
}
(3)判断表达式的使用
1.算术表达式用于判断
算术表达式表达一个结果值,如果这个结果值可以用于0值或非0值判断,则可以直接用作判断表达式。
2.赋值表达式用于判断
赋值表达式的值是被赋值变量的值,用于判断表达式中,首先完成赋值运算,然后以被赋值变量的值作判断。
3.对输入作判断
可以用组合键Ctrl_z(同时按Ctrl和z键)结束cin输入。cin的输入函数返回一个0值。
|
|
|
|
(4)转向语句
①break ②continue ③return ④goto
第三章 函数
1.函数
自定义函数的形式一般为:
类型 函数名 ( [ 形式参数表 ] )
{
语句序列
}
函数定义的第一行是函数首部(或称函数头);以花括号相括的语句序列为函数体。
"形式参数表"的一般形式为:
类型 参数1 ,类型 参数2 ,… ,类型 参数n
|
函数调用的一般形式为:
函数名 ( [ 实际参数表 ] )
可以用两种形式调用:
(1)函数语句
(2)函数表达式
|
|
|
|
|
函数原型
|
说明
|
int abs( int n );
|
n 的绝对值
|
double cos( double x );
|
x (弧度)的余弦
|
double exp( double x );
|
指数函数 e x
|
double fabs( double x );
|
x 的绝对值
|
double fmod( double x , double y );
|
x / y 的浮点余数
|
double log( double x );
|
x 的自然对数(以 e 为底)
|
double log10( double x );
|
x 的对数(以 10 为底)
|
double pow( double x , double y );
|
x 的 y 次方( x y )
|
double sin( double x );
|
x (弧度)的正弦
|
double sqrt( double x );
|
x 的平方根
|
double tan( double x );
|
x (弧度)的正切
|
|
|
|
2.传值参数
(1)值传递机制
实际参数的表达式的值复制到由对应的形参名所标识的一个对象中,作为形参的初始值。
(2)形式参数类型:
①指针参数(指针类型):被调用函数可以在函数体内通过形参指针简介访问实参所指对象。
!!!使用const限定指针,保护实参对象
#include <iostream.h>
int func( const int * p)
{ int a = 10 ;
a += *p ;
//*p = a ; 错误,不能修改const对象
// p = &a ; 错误
return a ;
}
void main()
{ int x = 10 ;
cout << func( &x ) << endl ;
}
②引用参数(引用类型):形式参数名作为引用(别名)绑定与实际参数标识的对象。
!!!使用const引用参数
#include<iostream.h>
#include<iomanip.h>
void display( const int & rk )
{
cout << rk << " :\n";
cout<< "dec : " << rk << endl;
cout<< "oct : " << oct << rk << endl;
cout<< "hex : " << hex << rk << endl;
}
void main()
{ display( 4589 ) ; }
3.函数返回类型
C++函数可以通过return语句也可以返回表达式的执行结果。return语句的一般格式为:
return ( 表达式 );
1.返回基本类型
如果函数定义的返回类型为基本数值类型,执行return语句时,首先计算表达式的值,然后匿名对象,把数值带回函数的调用点。
2.返回指针类型
函数被调用之后返回一个对象的指针值(地址表达式)。指针函数的函数原型一般为:
关联类型 * 函数名( 形式参数表 );
【例3-15】定义一个函数,返回较大值变量的指针。
3.返回引用类型
C++函数返回对象引用时,不产生返回实际对象的副本,返回时的匿名对象是实际返回对象的引用。
【例3-16】定义一个函数,返回较大值变量的引用。
【例3-17】输入一系列正整数和负整数,以0结束,统计其中正整数和负整数的个数。
|
|
|
|
4.函数的地址
1)函数指针
函数定义后,函数名表示函数代码在内存的直接地址。我们可以用一个指针变量获取函数的地址,通过指针变量的间址方式调用函数。指向函数的指针变量简称为函数指针。
1.函数的类型
函数的类型是指函数的接口,包括函数的参数定义和返回类型。
函数类型定义的一般形式为:
类型 函数类型 ( 形式参数表 ) ;
其中,"函数类型"是用户定义标识符。
2.函数指针
要定义指向某一类函数的指针变量,可以用以下两种说明语句:
类型 ( * 指针变量名 )( 形式参数表 ) ;
或 函数类型 * 指针变量名 ;
还可以用关键字typedef定义指针类型。函数指针类型定义的一般形式为:
typedef 类型( * 指针类型 )( 形式参数表 ) ;
或 typedef 函数类型 * 指针类型 ;
3.用函数指针调用函数
使用函数指针调用函数的一般形式为:
( * 指针变量名 )( 实际参数表 )
或 指针变量名 ( 实际参数表 )
【例3-24】用函数指针调用不同函数。
当函数指针作为函数参数时,可以传递函数的地址,通过参数调用不同函数。
【例3-25】使用函数指针参数调用函数。该程序与例3-24的功能相同。
【例3-26】使用函数名作为函数参数,调用库函数sin(x)和cos(x),计算指定范围内间隔为0.1的函数值之和。
2)函数重载
C++允许定义多个同名函数,每个函数有不同的参数集,称为函数重载。
|
|
|
|
5.递归函数
递归定义能够用有限的语句描述一个无穷的集合。一个函数体中出现调用自身的语句,称为直接递归调用。函数体中调用另一个函数,而该函数又反过来调用原函数,称为间接递归调用。
【例3-19】使用递归函数编程序求n!。
递归形式定义阶乘:
递归函数执行由递推和回归两个过程完成。假如执行main函数时,输入n值为3,则函数调用fact(3)的递推和回归过程如图3.7所示。
图 3.7 函数调用fact(3)的递推过程和回归过程
【例3-20】求正整数a和b的最大公约数。
a和b的最大公约数,就是能同时整除a和b的最大整数。从数学上知道,求a和b的最大公约数等价于求b与(a % b)的最大公约数。具体的算法可以用递归公式表示为:
【例3-21】斐波那契数列。
斐波那契(Fibonacci)数列的第1项为0,第2项为1,后续的每一项是前面两项的和。数列两项的比例趋于一个常量:1.618…,称为黄金分割。数列形如
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, …
斐波那契数列可以用递归定义:
Fibonacci(0) = 0
Fibonacci(1) = 1
Fibonacci(n) = Fibonacci(n-1) + Fibonacci(n-2)
图3.8表示fibonacci函数求fibonacci(3)的值,图中将fibonacci缩写为fib。
图 3.8 fibonacci数列的递归调用
【例3-22】汉诺塔问题。
传说印度的主神梵天做了一个塔,它在一个黄铜板上插3根宝石针,其中一根针上从上到下按从小到大的顺序串上了64个金片。梵天要求僧侣们把金片全部移动到另一根针上去,规定每次只能移动一片,且不许将大片压在小片上。移动时可以借助第三根针暂时存放盘子。梵天说如果这64片金片全部移至另一根针上时,世界就会在一声霹雳之中毁灭。图3.9是8个盘子的汉诺塔示意。
图 3.9 8个盘子的汉诺塔
|
|
|
|
|
|
第四章 数组
1.简介
·从程序员看,数组是相关数据对象集的一种组织形式。数组以线性关系组织对象,对应于数学的向量、矩阵的概念。数组结构是递归的,一个 n 维数组的每个元素是 n-1 维数组。数组元素以下标表示在它数组中的位置,称为下标变量。使用指针数组,可以管理复杂的对象,例如字符串、函数以及类对象。
· 从机器系统看,数组占有一片连续的存储空间。数组名表示这片存储空间的首地址。一旦说明一个数组,不带下标的数组名是一个常指针。可以用下标变量形式和指针形式访问已说明的内存空间。
· 使用 new和delete算符能够动态地申请和收回内存空间。申请的存储空间同样用下标形式和指针形式访问。
· C语言没有字符串类型,但在输入输出操作中可以把字符数组名和字符指针作为“字符串变量”使用,实现对字符串的“名”访问。
· string是C++预定义的类,它提供了对字符串更安全和方便的操作。
· 排序、查找、比较、复制等是数组的常用操作。
|
|
2.数组的定义和初始化及访问
一维数组的说明格式为:
类型 标识符[表达式];
例如,有以下说明:
int a[10] ; //长度为10的整型数组
double b[5] ; //长度为5的浮点型数组
char s['a'] ; //长度为97的字符型数组
二维数组的说明格式为:
类型 数组名 [表达式1][表达式2];
例如,有以下说明:
int a[3][4] ; //3行4列的整型数组
double b[10][10] ; //10行10列的浮点型数组
char s[40][40] ; //40行40列的字符型数组
C++的高维数组在内存中以高维优先的方式存放。具体如图4.6所示。
高维数组可以按照两种方式初始化。
例如,
int am[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
或 int am[2][3] = { 1, 2, 3, 4, 5, 6 };
一维数组的访问形式:下标方式和指针方式。
1.以下标方式访问数组
下标方式引用数组元素格式:
数组名[表达式]
其中:"表达式"表示数组元素的下标,要求为整型表达式。
2.以指针方式访问数组
数组名是数组的指针,即数组的首地址。
例如,说明数组
int a[5];
若内存分配如图4.2所示,则a的值是地址00B4(十六进制数)。a的偏移量以数组元素类型长度为单位,a+1的值是00B8,a+2的值是00BC等。
二维数组也可以用下标方式和指针方式访问。
1. 以下标方式访问二维数组
二维数组元素带有两个下标表达式:
数组名 [表达式1][表达式2]
2. 以指针方式访问二维数组
以指针方式访问二维数组,可以从一维数组的结构推导出来。我们还是以int a[2][3]为例说明。从图4.7中看到,a是由元素a[0]、a[1]、a[2]组成的一维数组,所以,a是a[0]、a[1]、a[2]的首地址(指针),即:
a == &a[0] a+1 == &a[1] a+2 == &a[2]
*a == a[0] *(a+1) == a[1] *(a+2) == a[2]
对于二维数组,不带下标的数组名是一个逻辑上的二级指针,所指对象是行向量,偏移量是一行元素的存储长度。带一个下标的数组名是一级指针,偏移量是一个元素的长度,它所指的对象的数组元素。
3.动态数组
C++使用new和delete操作符动态分配存储空间和动态释放已分配的存储空间。new和delete的一般语法形式为:
指针变量 = new 类型(初始化值表)
指针变量 = new 基本类型 [长度]
delete 指针变量
delete [] 指针变量
new操作符返回所分配空间的首地址。由new分配的堆空间没有名字,只能通过间址方式访问。
delete收回由"指针变量"所指的内存空间。
例如,以下语句申请如图4.11所示的内存空间:
int *p1 = new int; //动态分配一个整型单元
char *p2 = new char; //动态分配一个字符型单元
float *p3 = new float; //动态分配一个浮点型单元
int *p4 = new int [4] ; //动态分配整型数组
使用完毕的内存,应该用delete释放:
delte p1;
delete p2;
delete p3;
delete []p4;
|
|
|
|
//定义动态数组:
//一维:
类型 *a=nullptr;
a = new 类型[n];
//二维:
类型**a=new 类型*[n];
for(int i=0;i<n;++i)
{
a[i]=new 类型[m]; //m可为常量或变量,[m]可不写,也可写为(y),y为常数,给数组赋值初始化
}
4.使用数组处理字符串
4.6.1 字符串存储
1.用字符数组存放字符串
例如:
char str[10]
表示str是一个字符型数组,可以存放10个字符。为了表示一个字符串的结束位置,可以用'\0'作标志。
说明一个字符数组时,有不同的初始化方式:
① 逐个字符对数组元素赋初始值:
char str1[10] = { 'S', 't', 'u', 'd', 'e', 'n', 't' };
② 用串常量初始化
char str2[10] = { "Student" };
char str3[] = { "Student" };
或者省略{ }:
char str3[] = "Student" ;
2.用字符指针管理串
字符指针作为串地址,为管理字符串提供了方便。
例如:
char *string = "Student";
一个一维数组可以存放一个字符串,二维数组可以对串进行管理。例如,
char *name[5] = { "Chen", "Li", "Zhang", "Huang", "He" };
存储状态如图4.12所示,name的每一个元素是一个字符串的首地址。
图 4.12 用指针数组管理字符串
4.6.2 字符串的访问
当字符串以普通数组形式管理时,访问方法与前面讨论过的数组访问形式一致。
用cin或cout输入输出字符串时,字符数组名和字符指针可以像一个"串变量名"那样使用。
【例4-22】字符数组的访问。
【例4-23】用字符指针对字符串的输入输出。
【例4-24】包含空格的字符串输入输出。
【例4-25】测试字符输出。
【例4-26】指针数组表示的字符串。
【例4-27】串的赋值。
4.6.3 字符串处理函数
C++提供一系列对字符串操作的相关函数。这些函数在string.h的头文件中声明。下面介绍一些常用的字符串处理函数的原型、功能和使用方法。
1. 字符串长度函数
原型 int strlen ( const char * s );
功能 返回字符指针s所指的字符串有效长度。
【例4-28】测试字符串长度。
2. 字符串复制 函数
原型 char * strcpy ( char *s1 , const char * s2 );
功能 将s2所指的字符串复制到s1所指的字符数组中,函数返回值是s1串的地址。
【例4-29】复制字符串。
3.字符串连接函数
原型 char * strcat( char * s1 , char * s2 ) ;
功能 把s2所指的字符串添加到s1所指的字符串之后。函数返回s1串的地址。
【例4-30】字符串连接。
4.字符串比较函数
原型 int strcmp( const char * s1 , const char * s2 );
int strncmp( const char * s1 , const char * s2 , int n );
功能 以字典顺序方式比较两个字符串是否相等。如果两个串相等,函数返回值为0;如果s1串大于s2串,返回值大于0;如果s1串小于s2串,返回值小于0。函数strcmp用于对两个串的完全比较;函数strncmp用于比较两个串的前n个字符。
【例4-31】查找姓名。
【例4-32】字符串排序。
5.输入串
用cin流输入字符串时,C++把键盘操作的空格或回车都视为串结束,因此无法输入带空格的字符串。C语言的gets函数接受键盘输入的空格,以回车作结束。
puts函数输出字符串。gets和puts函数在stdio.h文件声明。
【例4-33】输入带空格的字符串。
|
|
|
|
5.数组作函数参数
当数组元素作函数参数时,性质与简单变量相同;当数组名作参数时,实现地址传送。
4.4.1 向函数传送数组元素
数组元素是下标变量,可以用不同形式参数向函数传递。
【例4-10】数组元素作传值参数。
【例4-11】数组元素作引用参数。
4.4.2 数组名作函数参数
当数组名作为函数参数时,C++做传址处理。
【例4-12】数组名作函数参数。
这是因为实参是编译时建立的数组,形式参数是一个指针类型的临时变量,存放实参数组的地址(见图4.9 所示)。
图 4.9 实参数组和形参数组
【例4-13】修改形参数组指针。
【例4-14】数组的降维处理。
4.4.3 应用举例
排序是计算机程序设计中的一种重要算法。排序方法有很多,这里仅介绍两种简单排序法:选择排序和冒泡排序法。
【例4-15】选择排序。
算法可以描述为:
for ( i = 0 ; i < n - 1 ; i ++ )
{ 从a[i]到a[n-1]找最小元素a[t]
把a[t]与a[i] 交换
}
细化寻找最小元素算法。每一趟寻找中,设一个变量t,记录当前最小元素的下标:
for( j = i+1; j<n; j++ )
if( a[j]<a[t] ) t = j;
对数组一趟搜索完成后,找到当前最小值a[t],然后执行a[i]与a[t]交换。
程序用随机函数初始化数组。
【例4-16】冒泡排序。
冒泡排序法的过程是相邻元素比较。图4.10展示了一个冒泡排序实例。
图 4.10 冒泡排序示例
【例4-17】矩阵相乘。
求两矩阵的乘积C = A×B。设A、B分别为m×p和p×n的矩阵,则C是 m×n的矩阵。按矩阵乘法的定义有:
|
|
|
|
第五章 类与对象
1.简介
·结构类型用struct定义,是用户自定义数据类型,由不同类型的数据成员组成。结构变量在内存占有一片连续的存储区间。结构变量成员用圆点运算符和箭头运算符访问。
·链表是一种重要的动态数据结构。动态数据的组织特点是可以在程序运行时创建或撤消数据元素。为了描述动态数据结构中元素之间的关系,数据元素类型定义必须包含表示数据关系的指针。我们详细讨论了最简单的动态数据结构——单向链表的操作。
·类类型是结构类型的拓展,通常用关键字class定义。类是数据成员和成员函数的封装。类的实例称为对象。
·数据成员是类的属性,可以为各种合法的C++类型,包括类类型。
·成员函数用于操作类的数据或在对象之间发送消息。
·类成员由private, protected, public决定访问特性。public成员集称为类的接口。不能在类的外部访问private成员。
·构造函数是特殊的成员函数,在创建和初始化对象时自动调用。析构函数则在对象作用域结束时自动调用。
·重载构造函数和复制构造函数提供了创建对象的不同初始化方式。当一个对象拥有的资源是由指针指示的堆时,必须定义深复制方式的复制构造函数。
·静态成员是局部于类的成员,它提供一种同类对象的共享机制。静态数据成员在编译时建立并初始化存储空间。静态数据成员和静态成员函数依赖于类而使用,与是否建立对象无关。
·友员是类对象操作的一种辅助手段。一个类的友员可以访问该类各种性质的成员。
·从编译器的观点看,类是一个程序包。定义什么类成员和如何声明成员的访问性质,取决于问题的需要。 |
|
2.结构struct
1.定义结构
结构类型以关键字struct标识,结构说明语句形式为:
struct <标识符>
{ 类型 成员1 ;
类型 成员2 ;
…
类型 成员n ;
} ;
例如,定义职工档案的结构类型:
struct Employee1
{ char name[10] ;
long code ;
double salary ;
char *address ;
char phone[20] ;
} ;
2.访问结构
对结构变量成员访问用圆点运算符:
结构变量名 . 成员
【例5-1】访问结构变量。
如果用指针访问结构,访问形式为:
*(指针). 成员
或 指针 -> 成员
【例5-2】用指针访问结构。
【例5-3】结构变量赋值。
|
|
|
3.链表
程序设计中处理的数据对象每个元素之间往往存在某种关系,要求在数据表示上,不但要存放基本的信息,还要表示与其它元素的连接。这一节仅从程序设计语言的角度,通过最简单的链表结构,介绍对数据关系的存储和操作。
1.动态链表存储 最简单的数据组织形式是线性表。表中除了第一个和最后一个元素外,每一个元素都有一个前驱和一个后继元素。若数据元素是在程序运行中需要动态插入或删除,数组操作显然不方便。可以用如图5.1 所示的"单向链表"表示。
图 5.1 单向链表
图5.1的链表可用以下的结构类型存放数据: struct node { char name[20]; float salary; node * next;
};
2.建立和遍历链表
设有说明: struct node
{ int data; node * next; }; node *head, *p;
建立链表的过程可以描述为:
生成头结点; while(未结束)
{ 生成新结点; 把新结点插入链表;
}
图5.2所示操作建立第一个结点:
图 5.2 建立第一个结点
然后建立后续结点。图5.3所示操作把生成结点插入表尾:
图 5.3 建立后续结点
一旦生成头结点,头指针就不应该移动。p称为跟踪指针。
【例5-4】建立单向链表。
【例5-5】遍历链表。
3.插入结点
以下讨论各种情况的插入。
(1)在表头插入结点
在表头插入结点是要使被插结点成为第一个结点,步骤是: ① 生成新结点; ② 把新结点连接上链表; ③ 修改表头指针。
具体操作见图5.4。
图 5.4 在表头插入结点
(2)在*p之后插入*s 在*p之后插入结点与在表头插入结点的操作相似,不过首先要查找p的位置(见图5.5)。
图 5.5 在*p之后插入*s
(3)在*p之前插入*s 在*p之前插入*s,需要找到*p的前驱结点的地址,所以查找过程要定位于*p的前驱。图5.6的操作设q指针指向*p的前驱结点。
图 5.6 在*p之前插入*s
【例5-6】用插入法生成一个有序链表。 为了找到第一个大于num的结点时完成前插,程序用双跟踪指针查找。q是p的前驱指针,开始查找时指针的初始状态(图5.7)是:
图 5.7 开始查找
若在链表中找不到大于num的结点,说明num是当前最大值,应该插入表尾。这时,指针的状态(图5.8)如下:
图 5.8 查找结束
4.删除结点
删除结点也要根据结点的位置作不同处理,还要注意释放被删结点。
(1)删除头结点 操作见图5.9。
图 5.9 删除头结点
(2)删除 *p 删除结点*p,需要知道其前驱结点指针。操作见图5.10。
图 5.10 删除结点 *p
【例5-7】从头指针为head的链表中删除值等于key的结点。
【例5-8】约瑟夫(Jonsephus)问题。
如图5.11所示,n个人围成一个环,从第i个开始,由1至interval不断报数,凡报到interval的出列,直到环空为止。出列的人按先后顺序构成一个新的序列。例如,n=8,i=2,interval=3,则输出序列为: 4 7 2 6 3 1 5 8
编程模拟这个游戏。
程序的运行情况如图5.12所示。
图 5.12 约瑟夫环操作示意
|
|
|
4.类
类是在结构的基础上发展而来的。类定义解决了对数据和操作的封装及对对象的初始化。除此之外,面向对象方法的还支持继承、多态机制,为大型软件的复杂性和可重用性提供了有效的途径。
1)定义类和对象
C++中,属性以数据的存储结构实现,称为类的数据成员;方法用函数实现,称为成员函数。它们都是类的成员。 C++中,类定义的说明语句一般形式为:
class <类名>
{ public:
公有段数据成员和成员函数 ;
protected:
保护段数据成员和成员函数 ;
private:
私有数据成员和成员函数 ;
} ;
其中,class是定义类的关键字。 类成员用关键字指定不同的访问特性,决定其在类体系中或类外的可见性。 关键字private用于声明私有成员。 protected声明保护成员。 public声明公有成员。
例如,一个日期类的定义如下:
class Date
{ public:
void SetDate( int y, int m, int d );
int IsLeapYear() ;
void PrintDate() ;
private:
int year, month, day ;
} ;
2)this指针
3)构造函数和析构函数
a.构造函数和析构函数的原型是:
类名::类名( 参数表 );
类名 ::~ 类名();
构造函数和析构函数不能定义在私有部分。
b.带参数的构造函数
c.重载构造函数
d.复制构造函数
4)静态数据成员static
5)友元函数与友元类
|
|
|
|
//题目:定义向量并实现向量的简单运算(经典例题)
// P275Ex3改模板类(多维向量).cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//代码涉及模板类(后续内容)
#include "pch.h"
#include <iostream>
using namespace std;
template <typename T>
class Vector
{
public:
Vector(int msize);
Vector(int msize, T*p);
Vector(const Vector&); //复制构造函数***
~Vector();
template <typename T>
friend Vector<T> operator+(const Vector<T> &A, const Vector<T> &B);
template <typename T>
friend Vector<T> operator-(const Vector<T> &A, const Vector<T> &B);
template <typename T>
friend Vector<T> operator*(const Vector<T> &A, const Vector<T> &B);
Vector<T>& operator=(const Vector<T>A); //加const
template <typename T>
friend istream& operator>>(istream& input, Vector<T>& A);
template <typename T>
friend ostream& operator<<(ostream &output, const Vector<T>& A); //一定要加const
public:
T *x;
int size;
};
template <typename T>
Vector<T>::Vector(int msize)
{
size = msize;
x = new T[size];
for (int i = 0; i < size; ++i)
{
x[i] = 0;
}
}
template <typename T>
Vector<T>::~Vector()
{
delete[]x;
}
template <typename T>
Vector<T>::Vector(int msize,T*p)
{
size = msize;
x = new T[size];
for (int i = 0; i < size; ++i)
{
x[i] = p[i];
}
}
template <typename T>
Vector<T>::Vector(const Vector&A)
{
delete []x;
x = new T[A.size];
for (int i = 0; i < A.size; ++i)
{
x[i] = A.x[i];
}
}
template <typename T>
Vector<T> operator+(const Vector<T> &A, const Vector<T> &B)
{
T* a = new T[A.size];
for (int i = 0; i < A.size; ++i)
{
a[i] = A.x[i] + B.x[i];
}
return Vector<T>(A.size,a);
}
template <typename T>
Vector<T> operator-(const Vector<T> &A, const Vector<T> &B)
{
T* a = new T[A.size];
for (int i = 0; i < A.size; ++i)
{
a[i] = A.x[i] - B.x[i];
}
return Vector<T>(A.size, a);
}
template <typename T>
Vector<T> operator*(const Vector<T> &A, const Vector<T> &B)
{
T* a = new T[A.size];
for (int i = 0; i < A.size; ++i)
{
a[i] = A.x[i] * B.x[i];
}
return Vector<T>(A.size, a);
}
template <typename T>
Vector<T>& Vector<T>::operator=(const Vector<T>A) //复制构造函数
{
for (int i = 0; i < A.size; ++i)
{
x[i] = A.x[i];
}
return *this;
}
template <typename T>
istream& operator>>(istream& input, Vector<T>& A)
{
for (int i = 0; i < A.size; ++i)
{
input >> A.x[i];
}
return input;
}
template <typename T>
ostream& operator<<(ostream &output, const Vector<T>& A)
{
output << '(';
for (int i = 0; i < A.size; ++i)
{
output << A.x[i];
if (i != A.size - 1) output << ",";
}
output << ')';
return output;
}
int main()
{
int size;
cout << "请输入向量维数:";
cin >> size;
Vector<int> A(size);
Vector<int> B(size);
cout << "请输入向量A:";
cin >> A ;
cout << "请输入向量B:";
cin >> B;
cout << "A=" << A << endl;
cout << "B=" << B << endl;
cout << "A+B=" << A+B << endl;
cout << "A-B=" << A - B << endl;
cout << "A*B=" << A * B << endl;
Vector<int> D(size);
D = A + B; //必须写复制构造函数才能这样写
cout <<"D=A+B="<< D;
}
第六章 运算符重载
1.基本语法
6.1.1 重载运算符的限制
C++语言中大部分预定义的运算符都可以被重载。以下列出可以重载的运算符:
+ - * / % ^ & | ~
! = < > += -= *= /= %=
^= &= |= << >> >>= <<= == !=
<= >= && || ++ -- ->* ' ->
[] () new delete new[] delete[]
只有几个运算符不能被重载:
. .* :: ?: sizeof
重载运算符函数可以对运算符做出新的解释,即定义用户所需要的各种操作。但运算符重载后,原有的基本语义不变。
6.1.2 运算符重载的语法形式
运算符函数是一种特殊的成员函数或友员函数。成员函数的语法形式为
类型 类名 :: operator op ( 参数表 )
{
//相对于该类定义的操作
}
通常重载算符用成员函数或友员函数。它们的关键区别在于成员函数具有this指针,而友员函数没有this指针。
1.一元运算符
一元运算符不论前置或后置,都要求一个操作数:
Object op
或 op Object
当重载为成员函数时,参数表为空。当重载为友员函数时,操作数由参数表的参数提供。
2.二元运算符
任何二元运算符要求左、右操作数:
ObjectL op ObjectR
当重载为成员函数时,左操作数由对象ObjectL通过this指针传递,右操作数由参数ObjectR传递。
重载为友员函数时,左右操作数都由参数传递。
6.2.1 用成员函数重载算符
当一元运算符的操作数,或者二元运算符的左操作数是该类的一个对象时,重载算符函数一般定义为成员函数。
6.2.2 用友员函数重载
当函数左右操作数类型不同时,用友员函数重载运算符,因为左右操作数都由参数传递,可以通过构造函数实现数据类型隐式转换。
当一个运算符的操作需要修改类对象状态时,应该以成员函数重载。例如,需要左值操作数的运算符(如 =,*=,++ 等)应该用成员函数重载。如果以友员函数重载,可以使用引用参数修改对象。
当运算符的操作数(尤其是第一个操作数)希望有隐式转换,则重载算符时必须用友员函数或普通函数。 C++中不能用友员函数重载的运算符有
= () [] ->
|
|
|
6.3.1 重载 ++ 与 --
自增和自减运算符有前置和后置两种形式。每个重载运算符的函数都必须有明确的特征,使编译器确定要使用的版本。C++规定,前置形式重载为一元运算符函数,后置形式重载为二元运算符函数。
【例6-4】例6-2中使用了成员函数重载++和-运算符。本例用友员函数重载++运算符。
6.3.2 重载赋值运算符
赋值运算符重载用于对象数据的复制。重载函数原型为:
类型 & 类名::operator= ( const 类名 & ) ;
【例6-5】定义Name类的重载赋值函数。
运算符函数operator= 必须重载为成员函数,而且不能被继承。
6.3.3 重载运算符[]和()
运算符"[]"和"()"只能用成员函数重载,不能用友元函数重载。
1.重载下标算符 [] []是二元运算符,用于访问数据对象的元素。重载函数调用的一般形式为:
对象 [表达式]
2.重载函数调用符 ()
函数调用操作符() 可以看作一个二元运算符。重载函数调用的一般形式为:
对象 ( 表达式表 )
【例6-6】定义一个向量类,用重载[]算符函数访问向量元素,重载()算符函数返回向量长度。
6.3.4 重载流插入和流提取运算符
运算符"<<" 和">>"在C++的流类库中重载为插入和提取操作。程序员重载这两个算符,通常用于传输用户自定义类型的数据。这两个算符重载函数必须为非成员函数。
【例6-7】为vector类重载流插入运算符和提取运算符,用于输出和输入数据元素。
|
|
|
|
C++中,类是用户自定义的类型,类之间,类与基本类型之间可以像系统预定义基本类型一样进行类型转换。实现这种转换使用构造函数和类型转换函数。
6.4.1 构造函数进行类类型转换
具有一个非默认参数的构造函数实现一种从参数类型到该类类型的转换。构造函数的形式为:
ClassX :: ClassX ( arg ,arg1 = E1 ,…, argn = En ) ;
其中
ClassX 用户定义的类类型名;
arg 基本类型或类类型参数,是将被转换成ClassX类的参数;
arg1~argn 默认参数;
E1~En 默认参数的默认值。
6.4.2 类型转换函数
具有一个非默认参数的构造函数能够把某种类型对象转换成指定类对象,但不能将一个类对象转换为基本类型数据。为此,C++引入一种特殊的成员函数--类型转换函数。
类型转换函数的形式为 :
ClassX :: operator Type ()
{
…
return Type_Value ;
}
其中
ClassX 是类类型标识符;
Type 是类型标识符。可以是基本类型或类类型;
Type_Value 是Type类型的表达式。
这个函数的功能是把ClassX类型的对象转换成Type类型的对象。函数没有参数,没有返回类型,但必须有一个返回Type类型值的语句。
类型转换函数只能定义为一个类的成员函数,不能定义为类的友员。
【例6-8】有理数计算。
|
|
|
|
2.代码实例
参考上一章代码
第七章 继承
1.简介
·继承是面向对象程序设计实现软件重用的重要方法。程序员可以在已有类的基础上定义新的数据成员和成员函数。原有类称为基类,新的类称为派生类,这种程序设计方法称为继承。一个操作的特殊实现,用继承方法增加新的类,不会影响原有的类层次结构。派生类成员由基类成员和自身定义的成员组成。
·单继承的派生类只有一个基类。多继承的派生类有多个基类。
·类成员的访问特性和类的继承性质决定类成员的作用域和可见性。类的公有成员称为接口,可以在类外访问。派生类不能访问基类的私有(private)成员,但可以访问基类的公有(public)和保护(protected)成员。对基类成员的访问性质还受继承方式影响。公有(public)继承方式,基类的public和protected成员在派生类中性质不变;保护(protected)继承,基类的public和protected成员都成为派生类的protected成员;私有(private)继承,基类的public和protected成员都成为派生类的private成员。
派生类中不可见基类的私有数据成员,但这些数据存储单元依然被建立。创建派生类对象时,派生类的构造函数总是先调用基类构造函数来初始化派生类中的基类成员。调用基类构造函数可以通过初始化列表实现数据成员的初始化。调用析构函数的次序和调用构造函数的次序相反。
·类继承关系中,覆盖成员出现访问的二义性,可以用作用域符显示指定类成员。
·为了避免多继承类格中的汇点类在派生类对象中产生不同副本,C++提供虚继承机制。多继承提供了软件重用的强大功能,也增加了程序的复杂性。 |
|
C++中,描述类继承关系的语法形式是:
class 派生类名 : 基类名表
{
数据成员和成员函数说明
};
其中"基类名表"由以下语法形式构成:
访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n
"访问控制"是表示继承权限的关键字,称为访问描述符:
public 公有继承
private 私有继承
protected 保护继承
|
|
|
|
派生类的构造函数使用冒号语法的初始化式,用指定的参数传递给基类的构造函数。
构造函数名(变元表):基类(变元表),数据成员1(变元表),…,数据成员(变元表)
{ /* …… */ }
构造函数的执行顺序是:首先基类;然后类对象成员;最后派生类。
|
|
|
|
2.代码实例
//销售人员的继承关系
#include "pch.h"
#include <iostream>
#include <string>
using namespace std;
class Employee
{public:
Employee()
:basicSalary(2000)
{
}
Employee(int mNum, string mName)
:basicSalary(2000)
{
number = mNum;
}
double pay()
{
totalSalary = basicSalary;
return totalSalary;
}
void print()
{
cout << number << '\t' << name << '\t' << totalSalary << endl;
}
friend istream& operator>>(istream&input, Employee &A);
protected:
int number;
string name;
double basicSalary;
double totalSalary;
};
class Salesman:public Employee
{
public:
Salesman()
:commrate(5.0/1000),sales(0)
{
}
Salesman(int mNum,string mName,double mSales)
:commrate(5.0 / 1000), sales(0)
{
sales = mSales;
}
double pay()
{
totalSalary = basicSalary+sales*commrate;
return totalSalary;
}
void print()
{
cout << number << '\t' << name << '\t' << totalSalary << endl;
}
double Getsales()
{
return sales;
}
friend istream& operator>>(istream&input, Salesman &A);
protected:
double sales;
double commrate;
};
class Salesmanager:public Salesman
{
public:
Salesmanager()
{
basicSalary = 2000;
jobSalary = 3000;
}
Salesmanager(int mNum, string mName, double mSales)
{
basicSalary = 2000;
jobSalary = 3000;
sales = mSales;
}
double pay()
{
totalSalary = basicSalary + jobSalary+sales * commrate;
return totalSalary;
}
void print()
{
cout << number << '\t' << name << '\t' << totalSalary << endl;
}
friend istream& operator>>(istream&input, Salesmanager &A);
protected:
double jobSalary;
};
istream& operator>>(istream&input, Employee &A)
{
input >> A.number >> A.name;
A.pay();
return input;
}
istream& operator>>(istream&input, Salesman &A)
{
input >> A.number >> A.name>>A.sales;
A.pay();
return input;
}
istream& operator>>(istream&input, Salesmanager &A)
{
input >> A.number >> A.name >> A.sales;
A.pay();
return input;
}
int main()
{
cout << "输入普通员工信息(工号、姓名、销售额)\n";
Employee A;
cin >> A;
A.print();
cout << "输入销售员信息(工号、姓名、销售额)\n";
Salesman B;
cin >> B;
B.print();
cout << "sales=" << B.Getsales() << endl;
cout << "输入销售经理信息(工号、姓名、销售额)\n";
Salesmanager C;
cin >> C;
C.print();
}
第八章 虚函数与多态性
1.简介
·虚函数和多态性使软件设计易于扩充。
·冠以关键字virtual的成员函数称为虚函数。派生类可以重载基类的虚函数,只要接口相同,函数的虚特性不变。
·如果通过对象名和点运算符方式调用虚函数,则调用关联在编译时由引用对象的类型确定,称为静态联编。
·基类指针可以指向派生类对象;以及基类中拥有虚函数,是支持多态性的前提。当通过基类指针或引用使用虚函数时,C++在程序运行时根据所指对象类型在类层次中正确选择重定义的函数。这种运行时的晚期匹配称为动态联编。
·如果一个基类中包含虚函数,通常把它的析构函数说明为虚析构函数。这样,它的所有派生类析构函数也自动成为虚析构函数(即使它们与基类析构函数名字不相同)。虚析构函数使得用delete算符删除对象时,系统可以正确地调用析构函数。
·纯虚函数是在说明时代码“初始化值”为0的虚函数。纯虚函数本身没有实现,由它的派生类定义实现版本。
·具有纯虚函数的类称为抽象类。抽象类只能作为基类,不能建立实例化对象。如果抽象类的派生类不提供纯虚函数的实现,则它依然是抽象类。定义了纯虚函数实现的派生类,即可以建立对象的类称为具体类。
·尽管不能建立抽象类对象,但抽象类机制提供的软件抽象和可扩展性的手段;抽象类指针使得派生的具体类对象具有多态操作能力。异质链表是多态应用的一个实例。 |
|
基类指针和派生类指针与基类对象和派生类对象有4种可能匹配使用方式:
(1)直接用基类指针引用基类对象;
(2)直接用派生类指针引用派生类对象;
(3)用基类指针引用派生类对象;
(4)用派生类指针引用基类对象。
第(1)、(2)种情况的使用无疑是安全的。下面讨论第(3)和第(4)种情况。
8.2.1 基类指针引用派生类对象
派生类是基类的变种。一般情况下,基类指针指向派生类对象时,只能引用基类成员。
【例8-1】使用基类指针引用派生类对象。
8.2.2 派生类指针引用基类对象
派生类指针只有经过强制类型转换之后,才能引用基类对象。
【例8-2】日期时间程序。
【例8-3】重写例8-2,在派生类中调用基类同名成员函数。
|
|
|
|
冠以关键字virtual的成员函数称为虚函数。
实现运行时多态的关键是先要说明虚函数,用同一个基类指针访问虚函数,才能实现运行时的多态。
8.3.1 虚函数和基类指针
基类指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员。以下的简单例子演示这种情况。
【例8-4】演示基类指针的移动。
【例8-5】虚函数应用。把例8-3中的成员函数who()说明为虚函数,可以看到不同的运行效果。
定义虚函数时注意:
(1)一旦一个成员函数被说明为虚函数,所有派生类的重载函数都保持虚特性。
(2)虚函数必须是类的成员函数。
(3)不能将友员说明为虚函数,但虚函数可以是另一个类的友员。
(4)析构函数可以是虚函数,但构造函数不能是虚函数。
8.3.2 虚函数的重载特性
重载一个虚函数时,要求函数名、返回类型、参数个数、参数类型和顺序完全相同。
【例8-6】虚函数的重载特性。
8.3.3 虚析构函数
虚析构函数用于动态分配类层次结构对象时,指引delete运算符选择正确的析构调用。
【例8-7】普通析构函数在删除动态派生类对象的调用情况。
如果用基类指针指向由new运算建立的派生类对象,而delete运算又显式地作用于指向派生类对象的基类指针,那么,不管基类指针所指对象是何种类型,也不管每个类的析构函数名是否相同,系统都只调用基类的析构函数。
解决办法是将基类析构函数说明为虚析构函数。这样,使用delete运算符时系统会调用相应类的析构函数,删除派生类对象时,同时删除派生类对象的基类部分。
【例8-8】用虚析构函数删除派生类动态对象。
为基类提供一个虚析构函数,能够使派生类对象在不同状态下正确调用析构函数。
|
|
|
|
基类往往用于表达一些抽象的概念,仅说明一个公共的界面,而由各派生类提供各自的实现版本。这种情况下,基类的有些函数没有定义是很正常的,但要求派生类必须重定义这些虚函数,以使派生类有意义。
一个具有纯虚函数的基类称为抽象类。抽象类机制支持一般概念的表示,也可以用于定义接口。
8.4.1 纯虚函数
纯虚函数在该基类中没有实现定义,要求所有派生类都必须定义自己的版本。
纯虚函数的说明形式如下:
virtual 类型 函数名 ( 参数表 ) = 0 ;
其中函数用赋值为0表示没有实现定义。
【例8-9】简单图形类,以说明虚函数的使用。
8.4.2 抽象类
抽象类至少有一个纯虚函数。定义了纯虚函数实现版本的派生类称为具体类。
对抽象类的使用,可以说明抽象类的指针和引用。但是有以下限制:
·抽象类只能用作其他类的基类;
·抽象类不能建立对象;
·抽象类不能用作参数类型、函数返回类型或显式类型转换。
【例8-10】使用抽象类指针。
【例8-11】使用抽象类引用,以不同数制形式输出正整数。
|
|
|
|
2.代码实例
#include "pch.h"
#include <iostream>
using namespace std;
const double pi = 3.1415926;
class Circle
{
public:
virtual double area()=0; //虚函数
virtual double volume()=0; //虚函数
protected:
double radius;
};
double Circle::area()
{
double S;
S = pi * radius*radius;
return S;
}
class Sphere:public Circle //球体
{
public:
Sphere(double r);
virtual double area();
virtual double volume();
};
Sphere::Sphere(double r)
{
radius = r;
}
double Sphere::area()
{
double S;
S = 4 * Circle::area();
return S;
}
double Sphere::volume()
{
double V;
V = 4.0 / 3.0*pi*radius*radius*radius;
return V;
}
class Column :public Circle //圆柱体
{
public:
Column(double r,double h);
virtual double area();
virtual double volume();
private:
double height;
};
Column::Column(double r, double h)
{
radius = r;
height = h;
}
double Column::area()
{
double S;
S = 2 * Circle::area()+2*pi*radius*height;
return S;
}
double Column::volume()
{
double V;
V = Circle::area()*height;
return V;
}
int main()
{
int count = 0;
Circle *p,*q;
p = new Sphere(3.2);
q = new Column(3.3, 5.2);
cout << "球体p:\n 面积:"<<p->area();
cout << "\t体积:"<< p->volume();
cout << endl;
cout << "圆柱体q:\n 面积:"<< q->area();
cout << "\t体积:"<< q->volume();
}
第九章 模板
1.简介
·模板是C++类型参数化的多态工具。所谓类型参数化,是指一段程序可以处理在一定范围内各种类型的数据对象,这些数据对象呈现相同的逻辑结构。由于C++程序的主要构件是函数和类,所以,C++提供了两种模板:函数模板和类模板。
·所有模板定义都以模板说明开始。模板说明以关键字template开始,以尖括号相括说明类属参数。每个类属参数之前冠以关键字typename或class。类属参数必须在模板定义中至少出现一次。同一个类属参数可以用于多个模板。类属参数可以用于指定函数的参数类型、函数返回类型和说明函数中的变量。
·模板由编译器通过使用时的实际数据类型实例化,生成可执行代码。实例化的函数模板称为模板函数;实例化的类模板称为模板类。
·函数模板可以用多种方式重载。可以用不同类属参数重载函数模板,也可以用普通参数重载为一般函数。
·类模板可以从模板类派生;类模板可以从非模板类派生;模板类可以从类模板派生;非模板类可以从类模板派生。
·函数模板和类模板可以声明为非模板类的友员。使用类模板,可以声明各种各样的友员关系。
·类模板可以声明static数据成员。实例化的模板类的每个对象共享一个模板类的static数据成员。 |
|
2.代码实例
//1.求最大值
#include "pch.h"
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class find_max
{
public:
find_max(T ma, T mb, T mc);
T max();
void print();
private:
T a, b, c;
};
template<typename T>
find_max<T>::find_max(T ma, T mb, T mc)
{
a = ma, b = mb, c = mc;
}
template<typename T>
T find_max<T>::max()
{
T d=(a > b ? a : b);
return c > d ? c : d;
}
template<typename T>
void find_max<T>::print()
{
cout << a <<" "<< b<<" " << c<<endl;
}
int main()
{
find_max<int>A(3, 6, 1);
A.print();
cout<<"三个整数的最大值为:"<<A.max()<<endl;
find_max<double>B(3.4, 6.3, 1.7);
B.print();
cout << "三个小数的最大值为:" << B.max()<<endl;
find_max<string>C("Guangzhou", "Beijing", "Zhongguo");
C.print();
cout << "三个字符串的最大值为:"<<C.max()<<endl;
}
//2、数组计算
#include <iostream>
#include <string>
using namespace std;
template<typename T>
class Array
{
public:
Array(T *mp, int msize);
Array(int msize);
~Array();
void input();
T sum();
T average();
void print();
private:
int size;
T *p;
};
template<typename T>
Array<T>::Array(T *mp, int msize)
{
size = msize;
p = new T[msize];
for (int i = 0; i < msize; ++i)
{
p[i] = mp[i];
}
}
template<typename T>
Array<T>::Array(int msize)
{
size = msize;
}
template<typename T>
Array<T>::~Array()
{
delete[]p;
}
template<typename T>
void Array<T>::input()
{
p = new T[size];
cout << "请输入" << size << "个数据:";
for (int i = 0; i < size; ++i)
{
cin >> p[i];
}
}
template<typename T>
T Array<T>::sum()
{
T s = 0;
for (int i = 0; i < size; ++i)
{
s += p[i];
}
return s;
}
template<typename T>
T Array<T>::average()
{
T s = sum();
return s / size;
}
template<typename T>
void Array<T>::print()
{
cout << "数组数据如下:\n";
for (int i = 0; i < size; ++i)
{
cout << p[i] << ' ';
if ((i + 1) % 10 == 0) cout << '\n';
}
}
int main()
{
char op;
while (true)
{
cout << "选择默认初始化or自行添加数据(1 or 2): ";
cin >> op;
if (op == '1')
{
int p[] = { 1,2,3,4,5,6,7,8,9,10 };
Array<int>A(p, sizeof(p) / sizeof(int));
cout << "总和=" << A.sum() << endl;
cout << "平均值=" << A.average() << endl;
A.print();
double q[] = { 1.5,2.4,3.3,4.2,5.1,6.0,7.6,8.7,9.8,10.9 };
Array<double>B(q, sizeof(q) / sizeof(double));
cout << "总和=" << B.sum() << endl;
cout << "平均值=" << B.average() << endl;
B.print();
}
else if (op == '2')
{
cout << "请输入要添加的数据类型(int or double):";
string type;
cin >> type;
if (type == "int")
{
int msize;
cout << "请输入数组元素个数:";
cin >> msize;
Array<int>C(msize);
C.input();
cout << "总和=" << C.sum() << endl;
cout << "平均值=" << C.average() << endl;
C.print();
cout << endl;
}
if (type == "double")
{
int msize;
cout << "请输入数组元素个数:";
cin >> msize;
Array<double>D(msize);
D.input();
cout << "总和=" << D.sum() << endl;
cout << "平均值=" << D.average() << endl;
D.print();
cout << endl;
}
}
else
{
cout << "输入有误!\n";
continue;
}
}
}
第十章 输入/输出流
1.简介
·C++流库是用继承方法建立起来的一个输入输出类库。流是对字节序列从一个对象移动到另一个对象的抽象。
·流对象是内存与文件(或字符串)之间数据传输的信道。标准流与系统预定义的外部设备连接;串流与串对象或字符数组连接;文件流与用户定义的外部文件连接。一旦流对象和通信对象关联,程序就可以使用流的操作方式传输数据。
·标准流与外设的连接是C++预定义的。其他流类对象与通信对象的连接使用流类构造函数实现。
·数据流本身没有逻辑格式。数据的解释方式由应用程序的操作决定。流类库提供了格式化和非格式化的I/O功能。
·文本流I/O提供内存基本类型数据与文本之间的格式转换。包括重载插入和提取运算符,字符输入输出函数,以及各种格式控制。标准流、串流都是文本流。
·处理用户定义的文件I/O要用文件流对象。根据代码方式分为文本文件和二进制文件,根据数据存取方式分为顺序存取文件和随机存取文件。文本文件是顺序存取文件,二进制文件是随机存取文件。
·文件操作的三个主要步骤是:打开文件;读/写文件;关闭文件流。
·文件的性质由打开文件的方式决定。文本文件流提供数据格式化I/O;二进制文件流提供字节流无格式I/O。移动流指针,可以对文件的任意位置进行读/写操作。 |
|
10.1.1 流类库
C++的流库(stream library)是用继承方法建立起来的一个输入输出类库,它主要有两个平行的基类:streambuf类和ios类。它们是所有流类的基类。还有一个独立的类iostremm_init用于流类的初始化操作。
图 10.1 streambuf类及其派生类
streambuf主要负责缓冲区的处理,类结构见图10.1。
图 10.2 ios的类层次结构
ios类层次提供流的高级I/O操作。类层次见图10.2所示。
10.1.2 头文件
C++的iostream类库提供了数百种I/O功能,iostream类库的接口部分包含在几个头文件中。例如,常用以下几个头文件:
iostream.h 包含了操作所有输入/输出流所需的基本信息。该文件含有cin(标准输入流)、cout(标准输出流)、cerr(非 缓冲错误输出流)、clog(缓冲错误输出流)四个对象,提供了无格式和格式化的I/O功能。
iomanip.h 包含格式化I/O的带参数操纵算子。这些算子用于指定数据输入输出的格式。
fstream.h 包含处理文件的有关信息,提供建立文件,读/写文件的各种操作接口。
|
|
|
标准流是C++预定义的对象,主要提供内存外部设备进行数据交互的功能,包括数据提取、插入、解释以及格式化处理等。流的操作是流类的公有成员函数。
10.2.1 标准流
标准流对象(常简称为标准流)是为用户常用外部设备提供的与内存之间通信的通道。它们对数据进行解释和传输,提供必要数据缓冲等。标准流与外部设备之间的关系见图10-3所示。
图10.3 标准流
(1)cin
istream类的对象,标准输入流。通常连向键盘,可以重定向。输入流的提取运算符可以识别输入字节序列类型的数据。
(2)cout
ostream类的对象,标准输出流。通常连向显示器,可以重定向。输出流的插入运算把内存的基本类型数据转换成相应的字符序列,输出到标准输出设备上。
(3)cerr
ostream类的对象,标准错误输出流,连向显示器。
(4)clog
ostream类的对象,标准错误输出流,连向打印机。不能重定向。
标准流连接的外部设备都是文本形式的设备,标准流的主要工作是内存基本类型数据与文本之间的翻译和传输。能够处理基本类型数据与文本之间I/O的流类称为文本流。
|
|
|
|
10.2.2 输入流操作
流操作主要是提取和插入数据。输入操作指输入流对象从流中提取数据,写入内存。最常用的方法是使用流提取运算符" >>"输入基本类型数据。
提取运算符具有类型转换功能,将输入流中的空白、Tab键、换行符等特殊字符作为分隔符。表10-1列出了istream类的一些公有成员函数,它们以不同的方法提取输入流中的数据。
函数
|
功能
|
read
|
无格式输入指定字节数
|
get
|
从流中提取字符,包括空格
|
getline
|
从流中提取一行字符
|
ignore
|
提取并丢弃流中指定字符
|
peek
|
返回流中下一个字符,但不从流中删除
|
gcount
|
统计最后输入的字符个数
|
eatwhite
|
忽略前导空格
|
seekg
|
移动输入流指针
|
tellg
|
返回输入流中指定位置的指针值
|
operator>>
|
提取运算符
|
表10-1中的函数,C++会提供不同的重载版本。
【例10-1】用get函数从键盘输入字符。
10.2.3 输出流操作
输出操作是把内存的数据插入流中。常用的方法是使用流插入运算符"<<",输出C++基本类型的数据项。
表10-2列出了ostream提供的主要公有成员函数。
函数
|
功能
|
put
|
无格式 , 插入一个字节
|
write
|
无格式 , 插入一字节序列
|
plush
|
刷新输出流
|
seekp
|
移动输出流指针
|
tellp
|
返回输出流中指定位置的指针值
|
operator<<
|
插入运算符
|
10.2.4 流错误状态
ios类中,定义了一个记录流错误状态的数据成员,称为状态字。错误状态字描述见表10-3。
标识常量
|
值
|
意义
|
goodbit
|
0x00
|
状态正常
|
eofbit
|
0x01
|
文件结束符
|
failbit |
0x02 |
I/O 操作失败,数据未丢失,可以恢复 |
badbit
|
0x04
|
非法操作,数据丢失,不可恢复
|
ios有几个与流错误状态有关的公有成员函数。
int eof() const;
返回eofbit状态值。当遇到文本文件结束符时,输入流中自动设置eofbit。
int fail() const;
返回failbit状态值,判断流操作是否失败。failbit发生流格式错误,字符没有丢失。这种错误通常是可以修复的。
int good() const;
int operator void *();
上述两个函数,如果bad、fail和eof全部返回false,即eofbit、failbit和badbit都没有被设置,则返回1(true),否则返回0(false)。
int bad() const;
int operator !();
上述两个函数,只要eofbit、failbit或badbit其中一个被设置,则返回1(true),否则返回0(false)。
int rdstate() const;
返回状态字。例如,函数调用
cout.rdstate()
将返回流的当前状态。随后可以用位测试的方法检查各种错误状态。
void clear( int nState = 0 );
恢复或设置状态字。默认参数为0,即ios::goodbit,对状态字清0,把流状态恢复为正常。例如:
cin.clear();
清除cin,并为流设置goodbit。
cin.clear(ios::failbit) ;
给流设置了failbit。有时程序操作遇到某些问题时可能需要这样做,当问题解决后再恢复。
|
|
|
|
ios提供直接设置标志字的控制格式函数,iostream和iomanip库还提供了一批控制符简化I/O格式化操作。
10.3.1 设置标志字
1.标志常量
ios类中说明了一个数据成员,用于记录当前流的格式化状态,称为标志字。标志字的每一位用于记录一种格式。为便于记忆,每一种格式定义了对应的枚举常量。程序中,可以使用标志常量或直接用对应的十六进制值设置输入输出流的格式。表10-4列出主要标志常量名及其意义。
标志常量
|
值
|
意义
|
输入 / 输出
|
ios::skipws
|
0X0001
|
跳过输入中的空白
|
I
|
ios::left
|
0X0002
|
按输出域左对齐输出
|
O
|
ios::right
|
0X0004
|
按输出域右对齐输出
|
O
|
ios::internal
|
0X0008
|
在符号位或基指示符后填入字符
|
O
|
ios::dec
|
0X0010
|
转换基制为十进制
|
I/O
|
ios::oct
|
0X0020
|
转换基制为八进制
|
I/O
|
ios::hex
|
0X0040
|
转换基制为十六进制
|
I/O
|
ios::showbase
|
0X0080
|
在输出中显示基指示符
|
O
|
ios::showpoint
|
0X0100
|
输出时显示小数点
|
O
|
ios::uppercase
|
0X0200
|
十六进制输出时一律用大写字母
|
O
|
ios::showpos
|
0X0400
|
正整数前加“ + ”号
|
O
|
ios::scientific
|
0X0800
|
科学示数法显示浮点数
|
O
|
ios::fixed
|
0X1000
|
定点形式显示浮点数
|
O
|
ios::unitbuf
|
0X2000
|
插入操作后立即刷新流
|
O
|
ios::stdio
|
0X4000
|
插入操作后刷新 stdout 和 stdree
|
O
|
2.ios中控制格式的函数
ios有几个直接操作标志字的公有成员函数:
(1)设置和返回标志字
long flags( long lFlags );
用参数lFlags更新标志字,返回更新前的标志字。
long flags() const;
返回标志字。
(2)操作标志字
long setf( long lFlags );
设置参数lFlags指定的标志位,返回更新前的标志字。
long setf( long lFlags, long lMask );
将参数lMask指定的标志位清0,然后设置参数lFlags指定的标志位,返回更新前的标志字。
(3)清除标志字
long unsetf( long lFlags );
将参数lFlags指定的标志位清0,返回更新前的标志字。
ios还有几个设置输出数据宽度、填充字符和置输出显示精度的函数:
(4)设置和返回输出宽度
int width( int nw );
设置下一个输出项的显示宽度为nw。如果nw大于数据所需宽度,在没有特别指示时,数据以右对齐方式显示。如果nw小于数据所需宽度,则nw无效,数据以默认格式输出。该函数的设置没有持续性,输出一个项目之后,恢复系统的默认设置。
int width() const;
返回当前的输出宽度值。
(5)设置填充字符
char fill( char cFill );
当设置宽度大于数据显示需要宽度时,空白位置以字符参数cFill填充。若数据在宽度域左对齐,在数据右边填充,否则在左边填充。默认填充符为空白符。
char fill() const;
返回当前使用的填充符。
(6)设置显示精度
int precision( int np );
用参数np设置数据显示精度。如果浮点数以定点形式输出,np表示有效位数。如果浮点数设置为科学示数法输出,则np表示小数点后的位数。
系统提供显示精度的默认值是6。float类型最大设置示数精度为6位,double类型最大设置示数精度为15位。
int precision() const;
返回当前示数精度值。
【例10-2】设置输出宽度。
【例10-3】不同基数形式的输入输出。
【例10-4】格式化输出浮点数。
10.3.2 格式控制符
C++在ios的派生类istream和ostream中定义了一批函数,作为重载插入运算符<<或提取运算符>>的右操作数控制I/O格式,称为控制符(或操作算子),简化了格式化输入输出代码编写。这些控制符在iostream.h或iomanip.h文件中声明。 1.iostream中的控制符
iostream.h几个常用的控制符见表10-5。
控制符
|
功能
|
输入 / 输出
|
Endl
|
输出一个新行符,并清空流
|
O
|
Ends
|
输出一个空格符,并清空流
|
0
|
Flush
|
清空流缓冲区
|
0
|
Dec
|
用十进制表示法输入或输出数值
|
I/O
|
Hex
|
用十六进制表示法输入或输出数值
|
I/O
|
Oct
|
用八进制表示法输入或输出数值
|
I/O
|
Ws
|
提取空白字符
|
I
|
【例10-5】不同基数形式的输入输出。
2.iomanip中的控制符
iomanip文件在namespace std中定义中定义了若干控制符,用于设置ios的标志字。见表10-6。
控制符
|
功能
|
输入 / 输出
|
resetiosflags ( ios:: lFlags )
|
清除 lFlags 指定的标志位
|
I/O
|
setiosflags ( ios:: lFlags )
|
设置 lFlags 指定的标志位
|
I/O
|
setbase ( int base )
|
设置基数, base = 8 , 10 , 16
|
I/O
|
setfill ( char c )
|
设置填充符 c
|
O
|
setprecision ( int n )
|
设置浮点数输出精度
|
O
|
setw ( int n )
|
设置输出宽度
|
O
|
表10-6中lFlags参数定义见表10-4,格式控制常量。注意,setiosflags不能代替setbase的作用。
【例10-6】整数的格式化输出。
【例10-7】格式化输出浮点数。
|
|
|
|
串流提取数据时对字符串按变量类型解释;插入数据时把类型数据转换成字符串。串流I/O具有格式化功能。在程序设计中,串流通常利用string对象或字符串作为与外部设备交换数据的缓冲区。
串流类是 ios 中的派生类。strstream,istrstream,ostrstream类库中定义的istrstream类和ostrstream类支持从C的字符串输入/输出。使用这些串流类要包含strstream头文件。
stringstream,istringstream,ostringstream类库中定义的istringstream类和ostringstream类支持从string对象输入/输出,并且更加安全。使用这些串流类要包含sstream头文件。
【例10-8】用输入串流对象从string对象提取数据。
【例10-9】用输出串流对象向string对象插入数据。
|
|
|
|
C++把文件看成无结构的字节流,根据文件数据的编码方式分为文本文件和二进制文件。根据存取方式分为顺序存取文件和随机存取文件。
流库的ifstream、ofstream和fstream类用于内存与文件之间的数据传输。
10.5.1 文件和流
C++文件是顺序排列的字节序列,长度为n的文件,字节号从0~n-1。见图10.4所示。
图 10.4 C++文件
可以用ASCII码解释的文件称为文本文件。文本文件是一种解释方式最简单的文件。
文本文件除了可以表示可见字符外,还可以表示一些控制设备动作的特殊符号。详细请参阅ASCII字符集。
对于文本文件,iostream定义了一个表示文件结束的标识常量EOF (End Of File),它是值为0x1A的字符。关闭文件流时,该字符被自动添加到文件尾部。键盘上,可以同时按下Ctrl键和Z键,在标准输入流cin中输入文件结束符。
C++把非文本文件都称为二进制文件。二进制文件不能方便地识别文件结束符,读写文件时要根据流的长度或流指针的位置判断是否已到达流的结束。
顺序存取文件指对文件的元素顺序操作。
随机存取文件通过文件指针在文件内移动,可以查找到指定位置进行读写。
进行文件的读写,首先必须建立一个文件流对象,然后把这个流对象与实际文件相关联(称为打开文件)一个文件用完后,需要关闭文件。
C++有三种文件流:ifstream.h头文件包含文件输入流类ifstream;ofstream.h头文件包含文件输出流类ofstream;fstream.h头文件包含文件输入/输出流类fstream。
10.5.2 打开和关闭文件
文件操作总是包含三个基本步骤:
打开文件——读/写文件——关闭文件
1. 打开文件
打开文件操作包括建立文件流对象;与外部文件关联;指定文件的打开方式。打开文件有两种方法:
(1)首先建立流对象,然后调用open函数连接外部文件。
流类 对象名 ;
对象名.open( 文件名 , 方式 ) ;
(2)调用流类带参数的构造函数,建立流对象的同时连接外部文件。
流类 对象名 ( 文件名 , 方式 ) ;
其中:
"流类"是C++流类库定义的文件流类,为ifstream、ofstream或fstream。
"对象名"是用户定义标识符,流对象名。
"文件名"是用字符串表示外部文件的名字。要求使用文件全名。
"方式"是ios定义的标识常量(见表10-7),表示文件的打开方式。
例如,用第一种方式打开文件。
打开一个已有文件datafile.dat,准备读:
ifstream infile ; //建立输入文件流对象
infile.open( "datafile.dat" , ios::in ) ; //连接文件,指定打开方式
打开(创建)一个文件newfile.dat,准备写:
ofstream outfile ; //建立输出文件流对象
outfile.open( "d:\\newfile.dat" , ios::out ) ; //连接文件,指定打开方式
第二种方法是调用fstream带参数构造函数,在建立流对象的同时,用参数形式连接外部文件和指定打开方式。
例如
ifstream infile ( "datafile.dat" , ios::in ) ;
ofstream outfile ( "d:\\newfile.dat" , ios::out );
fstream rwfile ( "myfile.dat" , ios::in | ios::out ) ;
文件打开方式见表10-7。
标识常量
|
值
|
意义
|
ios::in
|
0x0001
|
读方式打开文件
|
ios::out
|
0x0002
|
写方式打开文件
|
ios::ate
|
0x0004
|
打开文件时,文件指针指向文件末尾
|
ios::app
|
0x0008
|
追加方式,向文件输出的内容追加到文件尾部
|
ios::trunc
|
0x0010
|
删除文件现有内容( ios::out 的默认操作)
|
ios::nocreate
|
0x0020
|
如果文件不存在,则打开操作失败
|
ios::noreplace
|
0x0040
|
如果文件存在,则打开操作失败
|
ios::binary
|
0x0080
|
二进制方式打开,默认为文本方式
|
2.关闭文件
关闭文件操作包括把缓冲区数据完整地写入文件,添加文件结束标志,切断流对象和外部文件的连接。
关闭文件使用fstream的成员函数close。
例如:
ifstream infile ;
infile.open( "file1.txt" , ios::in ) ;
//读文件
infile.close() ; //关闭file1.txt
infile.open("file2.txt" , ios::in ) ; //重用流对象
用close关闭文件后,若流对象的生存期没有结束,即流对象依然存在,还可以与其他文件连接。上述语句关闭file1.txt后,重用流infile打开文件file2.txt。
当一个流对象的生存期结束,系统也会自动关闭文件。
10.5.3 文本文件
文本文件是顺序存取文件,用默认方式打开,用文本文件流进行读/写操作。
文本文件本身是没有什么记录逻辑结构。为了便于识别,文本文件中通常用换行符分隔逻辑行记录,用空白符、换行符、制表符等分隔数据项。
【例10-10】建立一个包含学生学号、姓名、成绩的文本文件。
【例10-11】读文本文件。
【例10-12】浏览文件。
【例10-13】向文件追加记录。
【例10-14】复制文本文件。
10.5.4 二进制文件
二进制文件以基本类型数据在内存的二进制表示形式存放数据。写操作时,从内存的指定位置开始的若干个字节插入到流中;读操作时,从流的指定位置开始送若干字节赋给指定对象。数据的解释由内存对象的类型决定。因此,二进制文件又称为类型文件。
二进制文件的读写方式完全由程序控制。
打开二进制文件用binary方式。从二进制文件流读取数据用istream的read函数;向二进制文件流写入数据用 ostream的write函数。
二进制文件是随机存取文件。C++的流指针在字节流中移动。指针所指向的位置,对数据既可读又可写。
文件打开以后,系统自动生成两个隐含的流指针--读指针和写指针。istream、ostream的成员函数可以返回指针的值(指向),或移动指针。用追加方式(ate, app)打开文件时,流指针指向文件末尾;用其他方式打开时,流指针指向文件的第一个字节。文件的读/写从流指针所指的位置开始,每完成一次读/写操作后,流指针自动移动到下一个读/写分量的起始位置。
除了读/写操作自动移动流指针外,istream和ostream分别提供对读指针和写指针的操作函数。
istream类操作读指针的函数:
(1)移动读指针
istream& seekg( long pos );
istream& seekg( long off, ios::seek_dir dir );
参数说明:
pos 指针的新位置值
off 指针偏移量
dir 参照位置
带一个参数的seekg函数,直接由参数pos指定指针的新位置。
带两个参数的seekg函数以dir为参照位置移动指针,偏移量为off。seek_dir是一个枚举类型,在ios定义为:
enum seek_dir { beg = 0, cur = 1, end = 2 } ;
seek_dir的常量值有以下含义(见图10.5所示):
ios::cur 当前流指针位置
ios::beg 流的开始位置
ios::end 流的尾部
图 10.5 流指针位置
(2)返回读指针当前指向的位置值
long tellg();
返回值是表示存储地址的长整型值。
ostream类操作写指针的函数:
(1)移动写读指针
ostream& seekp( long pos );
ostream& seekp( long off, ios::seek_dir dir );
(2)返回写指针当前指向的位置值
long tellp();
函数参数意义与读指针操作函数相同。
【例10-15】简单事务处理。本程序模拟一个书店的销售账目。程序能够添加、修改书目,根据进货和销售数目更新库存数。
|
|
|
|
2.代码实例
// P408Ex1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//以表格的形式输出:当x=1°,2°,……,10°时sinx,cosx和tanx的值。要求:输出时,数据的宽度为10,左对齐,保留小数点后5位。
#include "pch.h"
#include <iostream>
#include <iomanip>
using namespace std;
const double pi = 3.1415926;
int main()
{
cout << setw(10) << setiosflags(ios::left) << "度数(°)" << setw(10) << setiosflags(ios::left) << "sin" << setw(10) << setiosflags(ios::left) << "cos" << setw(10) << setiosflags(ios::left) << "tan" << endl;
for (int i = 1; i <= 10; ++i)
{
cout << setw(10)<< i;
cout << setw(10) << setprecision(5) << fixed << sin(i*pi / 180.0) << setiosflags(ios::left); //计算sin,注意弧度制与角度制转化
cout << setw(10) << setprecision(5) << fixed << cos(i*pi / 180.0) << setiosflags(ios::left);
cout << setw(10) << setprecision(5) << fixed << tan(i*pi / 180.0) << setiosflags(ios::left);
cout << endl;
}
}
//读出一个作业.cpp文件,删除全部注释内容,即以"/*...*/"相括法文本和以"//"开始到行末的文本,生成一个新的.cpp文件。
#include "pch.h"
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
ifstream file("test.cpp", ios::in);
ofstream newfile("newtest.cpp", ios::out);
char a = '\0', b = '\0';
while (!file.eof())
{
b = file.get();
if (a == '/'&& b == '/')
{
for (;;)
{
a = b;
b = file.get();
if (a == '\n')
{
cout << '\n';
newfile << '\n';
break;
}
}
}
else if (a == '/'&&b == '*')
{
while (true)
{
a = b;
b = file.get();
if (a == '*'&&b == '/')
{
a = b;
b=file.get();
break;
}
}
}
else
{
char ch = a;
cout << ch;
newfile<<ch;
}
a = b;
}
file.close();
newfile.close();
printf("完成!\n");
}
#include "pch.h"
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
//写一个二进制文件用于测试
//fstream file1;
//file1.open("test1.dat", ios::out | ios::binary);
//fstream file2;
//file2.open("test2.dat", ios::out | ios::binary);
//int s[] = { 1,3,6,9,14,18,23,34,45 };
//int f[] = { 2,4,5,6,13,17,19,30 };
//file1.write((char*)&s, sizeof(s));
//file2.write((char*)&f, sizeof(f));
//file1.close();
//file2.close();
//文件合并成新的文件
fstream file1("test1.dat", ios::in | ios::binary);
fstream file2("test2.dat", ios::in | ios::binary);
fstream file3("test3.dat", ios::out | ios::binary);
int a,b;
file1.read((char*)&a, sizeof(a));
file2.read((char*)&b, sizeof(b));
while (true)
{
if (file1&&file2)
{
if (a <= b)
{
file3.write((char*)&a, sizeof(int));
file1.read((char*)&a, sizeof(a));
}
else
{
file3.write((char*)&b, sizeof(int));
file2.read((char*)&b, sizeof(int));
}
}
else if (file1&&!file2)
{
while (file1)
{
file3.write((char*)&a, sizeof(int));
file1.read((char*)&a, sizeof(int));
}
}
else if (!file1&&file2)
{
while (file2)
{
file3.write((char*)&b, sizeof(int));
file2.read((char*)&b, sizeof(int));
}
}
else
{
break;
}
}
file1.close();
file2.close();
file3.close();
cout << "文件合并完成!" << endl;
//输出显示
cout << "显示file1!\n";
fstream file4("test1.dat", ios::in | ios::binary);
while (file4)
{
int a;
file4.read((char*)&a, sizeof(a));
if (!file4) break; //增加这一条件,避免最后一个元素重复输出
cout << a << ' ';
}
file4.close();
cout << endl;
cout << "读取完成!";
cout << "显示file2!\n";
fstream file5("test2.dat", ios::in | ios::binary);
while (file5)
{
int a;
file5.read((char*)&a, sizeof(a));
if (!file5) break;
cout << a << ' ';
}
file5.close();
cout << endl;
cout << "读取完成!";
cout << "显示file3!\n";
fstream file6("test3.dat", ios::in | ios::binary);
while (file6)
{
int a;
file6.read((char*)&a, sizeof(a));
if (!file6) break;
cout << a << ' ';
}
file6.close();
cout << endl;
cout << "读取完成!";
}
第十一章 异常处理
参考:https://www.runoob.com/cplusplus/cpp-exceptions-handling.html