c++

18 文件操作
文件-指存储在外部介质上的数据集合,文件按照数据的组织形式不一样,分为两种:ASCII文件(文本/字符),二进制文件(内部格式/字节)

ASCII文件输出还是二进制文件,数据形式一样,对于数值数据,输出不同
18.1 文件类和对象
C++ 标准类库中有三个类可以用于文件操作,它们统称为文件流类。这三个类是:

ifstream:输入流类,用于从文件中读取数据。
ofstream:输出流类,用于向文件中写人数据。
fstream:输入/输出流类,既可用于从文件中读取数据,又可用于 向文件中写人数据。
文件流对象定义:
#include
ifstream in;
ofstream out;
fstream inout;
1
2
3
4
18.2 打开文件
打开文件的目的:建立对象与文件的关联,指明文件使用方式
打开文件的两种方式:open函数和构造函数
open函数:void open(const char* szFileName, int mode);

模式标记 适用对象 作用
ios::in ifstream fstream 打开文件用于读取数据。如果文件不存在,则打开出错。
ios::out ofstream fstream 打开文件用于写入数据。如果文件不存在,则新建该文件;如 果文件原来就存在,则打开时清除原来的内容。
ios::app ofstream fstream 打开文件,用于在其尾部添加数据。如果文件不存在,则新建该文件。
ios::ate ifstream 打开一个已有的文件,并将文件读指针指向文件末尾(读写指 的概念后面解释)。如果文件不存在,则打开出错。
ios:: trunc ofstream 单独使用时与 ios:: out 相同。
ios::binary ifstream ofstream fstream 以二进制方式打开文件。若不指定此模式,则以文本模式打开。
ios::in | ios::out fstream 打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::out ofstream 打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::out | ios::trunc fstream 打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。
ios::binary 可以和其他模式标记组合使用,例如:

ios::in | ios::binary表示用二进制模式,以读取的方式打开文件。
ios::out | ios::binary表示用二进制模式,以写入的方式打开文件。
流类的构造函数
eg:ifstream::ifstream (const char* szFileName, int mode = ios::in, int);

#include
#include
using namespace std;
int main()
{
ifstream inFile(“c:\tmp\test.txt”, ios::in);
if (inFile)
inFile.close();
else
cout << “test.txt doesn’t exist” << endl;
ofstream oFile(“test1.txt”, ios::out);
if (!oFile)
cout << “error 1”;
else
oFile.close();
fstream oFile2(“tmp\test2.txt”, ios::out | ios::in);
if (!oFile2)
cout << “error 2”;
else
oFile.close();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
18.3 文本文件的读写
对于文本文件,可以使用 cin、cout 读写。
eg:编写一个程序,将文件 i.txt 中的整数倒序输出到 o.txt。(12 34 56 78 90 -> 90 78 56 34 12)

#include
#include
using namespace std;
int arr[100];
int main()
{

int num = 0;
ifstream inFile("i.txt", ios::in);//文本模式打开
if (!inFile)
	return 0;//打开失败
ofstream outFile("o.txt",ios::out);
if (!outFile)
{
	outFile.close();
	return 0;
}
int x;
while (inFile >> x)
	arr[num++] = x;
for (int i = num - 1; i >= 0; i--)
	outFile << arr[i] << " ";
inFile.close();
outFile.close();
return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
18.4 二进制文件的读写
用文本方式存储信息不但浪费空间,而且不便于检索。
二进制文件中,信息都占用 sizeof(对象名) 个字节;文本文件中类的成员数据所占用的字节数不同,占用空间一般比二进制的大。
ostream::write 成员函数:ostream & write(char* buffer, int count);

class Person
{
public:
char m_name[20];
int m_age;
};
int main()
{

Person p;
ofstream outFile("o.bin", ios::out | ios::binary);
while (cin >> p.m_name >> p.m_age)
	outFile.write((char*)&p, sizeof(p));//强制类型转换
outFile.close();
//heiren 烫烫烫烫烫烫啼  
return 0;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
istream::read 成员函数:istream & read(char* buffer, int count);

Person p;
ifstream inFile(“o.bin”, ios::in | ios::binary); //二进制读方式打开
if (!inFile)
return 0;//打开文件失败
while (inFile.read((char *)&p, sizeof§))
cout << p.m_name << " " << p.m_age << endl;
inFile.close();
1
2
3
4
5
6
7
文件流类的 put 和 get 成员函数

#include
#include
using namespace std;
int main()
{

ifstream inFile("a.txt", ios::binary | ios::in); 
if (!inFile)
    return 0;
ofstream outFile("b.txt", ios::binary | ios::out);
if (!outFile) 
{
    inFile.close();
    return 0;
}
char c;
while (inFile.get(c))  //每读一个字符
    outFile.put(c);  //写一个字符
outFile.close();
inFile.close();
return 0;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
一个字节一个字节地读写,不如一次读写一片内存区域快。每次读写的字节数最好是 512 的整数倍

18.5 移动和获取文件读写指针
ifstream 类和 fstream 类有 seekg 成员函数,可以设置文件读指针的位置;
ofstream 类和 fstream 类有 seekp 成员函数,可以设置文件写指针的位置。
ifstream 类和 fstream 类还有 tellg 成员函数,能够返回文件读指针的位置;
ofstream 类和 fstream 类还有 tellp 成员函数,能够返回文件写指针的位置。
函数原型

ostream & seekp (int offset, int mode);
istream & seekg (int offset, int mode);
//mode有三种:ios::beg-开头往后offset(>=0)字节 ios::cur-当前往前(<=0)/后(>=0)offset字节 ios::end-末尾往前(<=0)offect字节
int tellg();
int tellp();
//seekg 函数将文件读指针定位到文件尾部,再用 tellg 函数获取文件读指针的位置,此位置即为文件长度
1
2
3
4
5
6
举个栗子:折半查找文件,name等于“Heiren”

#include
#include
//#include
//#include
using namespace std;

class Person
{
public:
char m_name[20];
int m_age;
};
int main()
{

Person p;
ifstream ioFile("p.bin", ios::in | ios::out);//用既读又写的方式打开
if (!ioFile) 
	return 0;
ioFile.seekg(0, ios::end); //定位读指针到文件尾部,以便用以后tellg 获取文件长度
int L = 0, R; // L是折半查找范围内第一个记录的序号
			  // R是折半查找范围内最后一个记录的序号
R = ioFile.tellg() / sizeof(Person) - 1;
do {
	int mid = (L + R) / 2; 
	ioFile.seekg(mid *sizeof(Person), ios::beg); 
	ioFile.read((char *)&p, sizeof(p));
	int tmp = strcmp(p.m_name, "Heiren");
	if (tmp == 0)
	{ 
		cout << p.m_name << " " << p.m_age;
		break;
	}
	else if (tmp > 0) 
		R = mid - 1;
	else  
		L = mid + 1;
} while (L <= R);
ioFile.close();

system("pause");
return 0;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
18.6 文本文件和二进制文件打开方式的区别
UNIX/Linux 平台中,用文本方式或二进制方式打开文件没有任何区别。
在 UNIX/Linux 平台中,文本文件以\n(ASCII 码为 0x0a)作为换行符号;而在 Windows 平台中,文本文件以连在一起的\r\n(\r的 ASCII 码是 0x0d)作为换行符号。
在 Windows 平台中,如果以文本方式打开文件,当读取文件时,系统会将文件中所有的\r\n转换成一个字符\n,如果文件中有连续的两个字节是 0x0d0a,则系统会丢弃前面的 0x0d 这个字节,只读入 0x0a。当写入文件时,系统会将\n转换成\r\n写入。
用二进制方式打开文件总是最保险的。

19 泛型和模板
泛型程序设计在实现时不指定具体要操作的数据的类型的程序设计方法的一种算法,指的是算法只要实现一遍,就能适用于多种数据类型,优势在于代码复用,减少重复代码的编写。
模板是泛型的基础,是创建泛型类或函数的蓝图或公式。
19.1 函数模板
函数模板的一般形式:

template或template
函数类型 函数名(参数列表)
{
函数体;
}
template<class T1,class T2,…>//class可以换成typename
函数类型 函数名(参数列表)
{
函数体;
}
//举个栗子
template T max(T a, T b)
{
return a > b ? a : b;
}
int main()
{
cout <<"max value is "<< max(12,34) << endl;//34
cout << "max value is " << max(12.4, 13.6) << endl;//13.6
cout << "max value is " << max(12.4, 13) << endl;//error 没有与参数列表匹配的 函数模板 “max” 实例参数类型为:(double, int)
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
19.2 类模板
声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。
类模板的一般形式:
template//class可以换成typename 模板头
class 类名
{
函数定义;
};
//多个类型参数和函数模板类似,逗号隔开
1
2
3
4
5
6
当类中的成员函数在类的声明之外定义时,它必须定义为函数模板,带上模板头,定义形式如下:

template//class可以换成typename
函数类型 类名::函数名(形参列表)
{
函数体;
}
1
2
3
4
5
举个栗子:

template<typename T1, typename T2> // 模板头 没有分号
class Point {
public:
Point(T1 x, T2 y) : x(x), y(y) { }
public:
T1 getX() const; //成员函数后加const,声明该函数内部不会改变成员变量的值
void setX(T1 x);
T2 getY() const;
void setY(T2 y);
private:
T1 x;
T2 y;
};
template<typename T1, typename T2> //模板头
T1 Point<T1, T2>::getX() const {
return x;
}
template<typename T1, typename T2>
void Point<T1, T2>::setX(T1 x) {
x = x;
}
template<typename T1, typename T2>
T2 Point<T1, T2>::getY() const {
return y;
}
template<typename T1, typename T2>
void Point<T1, T2>::setY(T2 y) {
y = y;
}
int main()
{
Point<int, double> p1(66, 20.5);
Point<int, char*> p2(10, “东经33度”);
Point<char*, char*> p3 = new Point<char, char*>(“西经12度”, “北纬66度”);
cout << “x=” << p1.getX() << “, y=” << p1.getY() << endl;
cout << “x=” << p2.getX() << “, y=” << p2.getY() << endl;
cout << “x=” << p3->getX() << “, y=” << p3->getY() << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
19.3 typename 和 class 的区别
在模板引入 c++ 后,采用class来定义模板参数类型,后来为了避免 class 在声明类和模板的使用可能给人带来混淆,所以引入了 typename 这个关键字。

模板定义语法中关键字 class 与 typename 的作用完全一样。
不同的是typename 还有另外一个作用为:使用嵌套依赖类型(nested depended name)
class MyClass
{
public:
typedef int LengthType;
LengthType getLength() const
{
return this->length;
}
void setLength(LengthType length)
{
this->length = length;
}

private:
LengthType length;
};
template
void MyMethod(T myclass)
{
//告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量
typedef typename T::LengthType LengthType; //
LengthType length = myclass.getLength();
cout << "length = " <<length<< endl;

}
int main()
{
MyClass my;
my.setLength(666);
MyMethod(my);//length = 666
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
19.4 强弱类型语言和c++模板的那点猫腻
计算机编程语言可以根据在 "定义变量时是否要显式地指明数据类型"可以分为强类型语言和弱类型语言。

强类型语言-在定义变量时需要显式地指明数据类型,为变量指明某种数据类型后就不能赋予其他类型的数据了,除非经过强制类型转换或隐式类型转换。典型的强类型语言有 C/C++、Java、C# 等。
int a = 123; //不转换
a = 12.89; //隐式转换 12(舍去小数部分)
a = (int)“heiren,HelloWorld”; //强制转换(得到字符串的地址) 不同类型之间转换需要强制
1
2
3
//Java 对类型转换的要求比 C/C++ 更为严格,隐式转换只允许由低向高转,由高向低转必须强制转换。
int a = 100; //不转换
a = (int)12.34; //强制转换(直接舍去小数部分,得到12)
1
2
3
弱类型语言-在定义变量时不需要显式地指明数据类型,编译器(解释器)会根据赋给变量的数据自动推导出类型,并且可以赋给变量不同类型的数据。典型的弱类型语言有 JavaScript、Python、PHP、Ruby、Shell、Perl 等。
var a = 100; //赋给整数
a = 12.34; //赋给小数
a = “heiren,HelloWorld”; //赋给字符串
a = new Array(“JavaScript”,“React”,“JSON”); //赋给数组
1
2
3
4
强类型语言在编译期间就能检测某个变量的操作是否正确,因为变量的类型始终哦都市确定的,加快了程序的运行;对于弱类型的语言,变量的类型可以随时改变,编译器在编译期间能确定变量的类型,只有等到程序运行后、赋值后才能确定变量当前是什么类型,所以传统的编译对弱类型语言意义不大。

解释型语言-弱类型往往是解释型语言,边执行边编译
编译型语言-先编译后执行。

  强类型语言较为严谨,在编译时就能发现很多错误,适合开发大型的、系统级的、工业级的项目;而弱类型语言较为灵活,编码效率高,部署容易,学习成本低,在 Web 开发中大显身手。另外,强类型语言的 IDE 一般都比较强大,代码感知能力好,提示信息丰富;而弱类型语言一般都是在编辑器中直接书写代码。

  C++模板退出的动力来源是对数据结构的封装:数据结构关注的是数据的存储以及对其的增删改查操作,C++开发者们想封装这些结构,但是这些结构中数据成分的类型无法提前预测,于是模板诞生了。

STL(Standard Template Library,标准模板库)就是c++对数据结构封装后的称呼。

20 命名空间和异常处理
20.1 命名空间
命名空间实际上是由用户自己命名的一块内存区域,用户可以根据需要指定一个有名字的空间区域,每个命名空间都有一个作用域,将一些全局实体放在该命名空间中,就与其他全局实体分割开来。
命名空间定义的一般形式:

namespace [命名空间名]//名省略时,表示无名的命名空间
{
命名空间成员;
}
1
2
3
4
命名空间成员的引用:命名空间名::命名空间成员名
使用命名空间别名:namespace 别名 = 命名空间名
使用using声明命名空间成员的格式:using 命名空间名::命名空间成员名;
使用using声明命名空间的全部成员:using namespace 命名空间名;

using声明后,在using语句所在的作用域中使用该命名空间成员时,不必再用命名空间名加以限定。
标准C++库的所有标识符(包括函数、类、对象和类模板)都是在一个名为std的命名空间中定义的。
无名的命名空间,只在本文件的作用域内有效。
20.2 异常处理
异常就是程序在执行过程中,由于使用环境变化和用户操作等原因产生的错误,从而影响程序的运行。

程序中常见的错误:语法错误,运行错误
异常处理机制的组成:检查异常(try)、抛出异常(throw)、捕获并处理异常(catch)
异常处理语句:

被检查的语句必须放在try后面的{}中,否则不起作用,{}不能省略。
进行异常处理的语句必须放在catch后面的{}中,catch后()中的异常信息类型不能省略,变量名可以省略。
catch语句块不能单独使用,必须和try语句块作为整体出现。
在try-catch结构中,只能有一个try,但可以有多个catch.
catch(…)通常放在最后,可以捕获任何类型的异常信息。
try
{
被检查的语句(可以有多个throw语句);
}
catch(异常信息类型 [变量名])
{

}
1
2
3
4
5
6
7
8
throw语句:thow 变量或表达式;

throw放在try中,也可以单独使用,向上一层函数寻找try-catch,没有找到系统就会调用系统函数terminate,使程序终止运行。
try
{
throw 123;
}
catch (int a)
{
cout << a << endl;//123
}
1
2
3
4
5
6
7
8
c++中没有finally
类的异常处理,当在try中定义了类对象,在try中抛出异常前创建的对象将被自动释放。
异常规范-描述了一个函数允许抛出那些异常类型。

异常规范应同时出现在函数声明和函数定义中。
如果没有异常规范,可以抛出任何类型的异常。
异常规范的一般形式:函数类型 函数名(参数类型)throw ([异常类型1,异常类型2,…])
float fun(float float)throw(int,float,double);
1
C++标准异常
在这里插入图片描述

异常 描述
std::exception 该异常是所有标准 C++ 异常的父类。
std::bad_alloc 该异常可以通过 new 抛出。
std::bad_cast 该异常可以通过 dynamic_cast 抛出。
std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid 该异常可以通过 typeid 抛出。
std::logic_error 理论上可以通过读取代码来检测到的异常。
std::domain_error 当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument 当使用了无效的参数时,会抛出该异常。
std::length_error 当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator。
std::runtime_error 理论上不可以通过读取代码来检测到的异常。
std::overflow_error 当发生数学上溢时,会抛出该异常。
std::range_error 当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error 当发生数学下溢时,会抛出该异常。
21 STL
C++标准模板库(Standard Template Library,STL)是泛型程序设计最成功的实例。STL是一些常用数据结构和算法的模板的集合,由Alex Stepanov主持开发,于1998年被加入C++标准。

C++ 标准模板库的核心包括三大组件:容器,算法,迭代器
21.1 容器
顺序容器:可变长动态数组Vector、双端队列deque、双向链表list
关联容器:set、multliset、map、multimap

关联容器内的元素是排序的,所以查找时具有非常好的性能。
容器适配起:栈stack、队列queu、优先队列priority_queue
所有容器具有的函数:
int size();
bool empty();
1
2
顺序容器和关联容器函数:

begin()
end()
rbegin()
erase(…)
clear()
1
2
3
4
5
顺序容器独有的函数:

front()
back()
push_back();
pop_back();
insert(…);
1
2
3
4
5
21.2 迭代器
迭代器是一种检查容器内元素并遍历元素的数据类型。C++更趋向于使用迭代器而不是下标操作,因为标准库为每一种标准容器(如vector)定义了一种迭代器类型,而只用少数容器(如vector)支持下标操作访问容器元素。按照定义方式分为以下四种。
1.正向迭代器:容器类名::iterator 迭代器名;
2.常量正向迭代器:容器类名::const_iterator 迭代器名;
3.反向迭代器:容器类名::reverse_iterator 迭代器名;
4.常量反向迭代器:容器类名::const_reverse_iterator 迭代器名

21.3 算法
STL 提供能在各种容器中通用的算法(大约有70种),如插入、删除、查找、排序等。算法就是函数模板。算法通过迭代器来操纵容器中的元素。
STL 中的大部分常用算法都在头文件 algorithm 中定义。此外,头文件 numeric 中也有一些算法。
许多算法操作的是容器上的一个区间(也可以是整个容器),因此需要两个参数,一个是区间起点元素的迭代器,另一个是区间终点元素的后面一个元素的迭代器。
会改变其所作用的容器。例如:

copy:将一个容器的内容复制到另一个容器。
remove:在容器中删除一个元素。
random_shuffle:随机打乱容器中的元素。
fill:用某个值填充容器。
不会改变其所作用的容器。例如:

find:在容器中查找元素。
count_if:统计容器中符合某种条件的元素的个数。
#include
#include
#include
using namespace std;
int main() {
vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4); //1,2,3,4
vector::iterator p;
p = find(v.begin(), v.end(), 3); //在v中查找3 若找不到,find返回 v.end()
if (p != v.end())
cout << "1) " << *p << endl; //找到了
p = find(v.begin(), v.end(), 9);
if (p == v.end())
cout << "not found " << endl; //没找到

p = find(v.begin() + 1, v.end() - 1, 4); //在2,3 这两个元素中查找4
cout << "2) " << *p << endl; //没找到,指向下一个元素4

int arr[10] = { 10,20,30,40 };
int * pp = find(arr, arr + 4, 20);
if (pp == arr + 4)
	cout << "not found" << endl;
else
	cout << "3) " << *pp << endl;
return 0;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值