目录
62. C++11:Constructors in Inheritance
63. Default Constructor in Inheritance
64. Chain of Constructors and Destructors
68. Implementation of Run-time Polymorphism
69. Summarize of Run-time Polymorphism
70. C++11: Using override and final
71. Accessibility (Visibility)
72. Abstract Class and Pure Virtual Function
76. C++17: About File System Library
78. Introduction to the Input and Output Classes
61. Inheritance
继承
-
Inheritance (继承)
-
1.1. 自行车的例子
继承链上的类的对应叫法 | |
---|---|
基类 / Base Class | 派生类 / Derived Class |
父类 / Parent Class | 子类 / Child Class |
超类 / SuperClass | 子类 / SubClass |
继承 vs 泛化 | |
---|---|
继承/Inherit | 子继承父 |
泛化/Generalize | 父泛化子 |
1.2. Example: Shape (形状)
-
避免一个类被继承( C++11 )
C++11引入final特殊标识符,可以使得类不能被继承
对于代码:
class B final {};
class D : public B {};
编译后的输出是 (Visual Studio)
error C3246: “D”: 无法从“B”继承,因为它已被声明为“final”这是程序输出
62. C++11:Constructors in Inheritance
C++11:继承中的构造函数
-
派生类继承的成员
1.1. C++11:派生类不继承的特殊函数
(1) 析构函数
(2) 友元函数
1.2. 继承基类构造函数
(1) using A::A; 继承所有基类ctor
(2) 不能仅继承指定的某个基类ctor
1.3. 调用继承的构造函数
struct A { // 等价于 class A { public:
A(int i) {}
A(double d, int i) {}
// ...
};
struct B : A { // C++11
using A::A; // 继承基类所有构造函数
int d{0}; // 就地初始化
};
int main() {
B b(1); // 调A(int i)
}
-
Calling Base Class Constructors (调用基类构造函数)
若派生类成员也需要初始化,则可以在派生类构造函数中调用基类构造函数
struct A { // 等价于 class A { public:
A(int i) { cout << "A(int i)" << endl; }
A(double d , int i) {}
// ...
};
struct B : A { // C++11
using A::A; // 继承基类ctor,除了A(int i)
int d{ 0 }; // 就地初始化
B(int i) : A{ i } , d{ i } {
std::cout << "B(int i)" << std::endl;
}
};
int main() {
B b(1); // 调用 B(int i)
std::cin.get();
}
63. Default Constructor in Inheritance
继承中的默认构造函数
-
No-Arg Constructor in Base Class(基类的无参构造函数)
If a base constructor is not invoked explicitly, the base class’s no-arg constructor is invoked by default. (若基类ctor未被显式调用,基类的默认构造函数就会被调用)
要考虑给基类提供默认构造函数
-
编码规范
-
文件扩展名:头文件用.h,源文件用 .cpp (c++, cc也可)
-
A class should be declared in a header file and defined in a source file where the name of the files match the name of the class.类应该在头文件中声明并在源文件中定义,俩文件名字应该与类名相同
例如:MyClass.h, MyClass.c++
例外的是,模板类的声明和定义都要放在头文件中
-
Class variables should never be declared public.
类成员变量不可被声明为public
说明:公有变量违背了C++的信息隐藏原则。例外的是,如果class只是一个数据结构,类似C语言中的struct,则可将类变量声明为公有
64. Chain of Constructors and Destructors
构造链和析构链
-
constructor chaining (构造函数链)
Constructing an instance of a class invokes all the base class along the inheritance chain. (构造类实例会沿着继承链调用所有的基类ctor)
调用次序: base first, derive next (父先子后)
-
destructor chaining (析构函数链)
Conversely, the destructors are automatically invoked in reverse order(dtor与ctor正好相反)
调用次序: derive first, base next (子先父后)
65. Name Hiding in Inheritance
继承中的名字隐藏
-
Homonymous Function in Base Class is Hidden (基类同名函数被隐藏)
对于下面的代码
class P {
public:
void f() {}
};
class C :public P {
public:
void f(int x) {}
};
int main() {
C c;
c.f();
}
Visual C++编译结果:
namehiding.cpp(13): error C2660: “C::f”: 函数不接受 0 个参数
g++编译结果:
NameHiding.cpp:13:7: error: no matching function for call to 'C::f()'
1.1. Names in inner scopes hide names in outer scopes. (内部作用域的名字隐藏外部作用域的(同名)名字)
(1) The derived class acts as an inner scope (派生类视作内部作用域)
(2) The base class as an outer scope(基类视作外部作用域)
-
Why?
(1) To avoid certain potentially dangerous behavior (避免某些潜在的危险行为)
(2) Each class starts with a "clean sheet" with respect to each method name it declares (每个类在创建时,它的函数名都是写在一张干净的白纸上面,不会被基类函数名干扰)
https://stackoverflow.com/questions/32946364
https://stackoverflow.com/questions/1628768
-
How to unhiding base class members? (如何取消隐藏基类同名成员)
using-declaration : introduce base class members into derived class definitions (using 声明语句可以将基类成员引入到派生类定义中)
class P {
public:
void f() {}
};
class C :public P {
public:
using P::f; //此处不带小括号
void f(int x) {}
};
int main() {
C c;
c.f();
}
66. Redefining Functions
重定义函数
-
Redefining Functions (重定义函数)
Shape::toString() 被Circle继承,因此可以通过Circle对象调用该函数。
std::cout << circle.toString();
但基类的toString()无法输出派生类对象信息
You can redefine toString() in Circle and Rectangle to return a string describe Circle or Rectangle object. (你可以重定义派生类的toString()以描述派生类对象)
#include <string>
std::string Shape::toString() {
using namespace std::string_literals;
return "Shape color "s + color
+ ((filled) ? " filled"s : " not filled"s);
}
string Circle::toString() {
return "Circle color " + color +
" filled " + ((filled) ? "true" : "false");
}
string Rectangle::toString() {
return "This is a rectangle object");
}
2. Redefine (重定义)
.3.Redefine v.s. Overload (重定义与重载)
3.1. Overload Functions (重载函数)
3.1.1. more than one function with the same name (多个函数名字相同)
3.1.2. But different in at least one of the signatures: (但至少一个特征不同)
(1) parameter type (参数类型)
(2) parameter number (参数数量)
(3) parameter sequence (参数顺序)
3.2. Redefine Functions (重定义函数)
3.2.1. The functions have the same signature (函数特征相同)
(1) Name (同名)
(2) Parameters (including type, number and sequence) (同参数:类型,数量和顺序)
(3) Return type (返回值类型)
3.2.2. Defined in base class and derived class, respectively (在基类和派生类中分别定义)
67. Concept of Polymorphism
多态的概念
面向对象的多态性可以分为4类:重载多态,强制多态,包含多态,参数多态。前面两种统称专用多态,后面两种统称通用多态。
-
What is Polymorphism?
1.1. 广义的多态:不同类型的实体/对象对于同一消息有不同的响应,就是OOP中的多态性。
1.2. 截止目前:多态性有两种表现的方式
1.2.1. 重载多态:
class C {
public:
int f(int x);
int f( );
};
1.2.2. 子类型多态:不同的对象调用同名重定义函数,表现出不同的行为
class A { virtual int f() {return 1;} };
class B: public A { virtual int f() {return 8;} };
A a; B b;
A* p = &b;
a.f() // call A::f()
b.f() // call B::f()
p->f(); // call B::f()
-
Binding
2.1. 联编(Binding): 确定具有多态性的语句调用哪个函数的过程。
2.2. Static Binding (静态联编)
在程序编译时(Compile-time)确定调用哪个函数
例:函数重载
2.3. Dynamic Binding (动态联编)
在程序运行时(Run-time),才能够确定调用哪个函数
用动态联编实现的多态,也称为运行时多态(Run-time Polymorphism)。
68. Implementation of Run-time Polymorphism
实现运行时多态
-
Why Run-time Polymorphism (为何要使用运行时多态?)
print()调用toString()输出信息,需要写三个重载函数
void print( A obj );
void print( B obj );
void print( C obj );
-
How do we implement run-time polymorphism (如何实现运行时多态)
实现运行时多态有两个要素:
(1) virtual function (虚函数)
(2) Override (覆写) : redefining a virtual function in a derived class. (在派生类中重定义一个虚函数)
struct A {
virtual std::string toString() {
return "A";
}
};
-
Example of polymorphism (多态的例子)
3.1. 调用哪个同名虚函数?
(1) 不由指针类型决定;
(2) 而由指针所指的【实际对象】的类型决定
(3) 运行时,检查指针所指对象类型
3.2. 用途:可以用父类指针访问子类对象成员
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
转存失败重新上传取消
转存失败重新上传取消
-
虚函数的传递性
4.1. If a function is defined virtual in a base class, it is automatically virtual in all its derived classes. (基类定义了虚同名函数,那么派生类中的同名函数自动变为虚函数)
4.2. 虚函数
类中保存着一个Virtual function table (虚函数表)
Run-time binding (运行时联编/动态联编)
More overhead in run-time than non-virtual function (比非虚函数开销大)
69. Summarize of Run-time Polymorphism
运行时多态的总结
-
Summary: static binding v.s. dynamic binding
基类与派生类中有同名函数
(1) 通过派生类对象访问同名函数,是静态联编
(2) 通过基类对象的指针访问同名函数,是静态联编
(3) 通过基类对象的指针或引用访问同名虚函数,是动态联编
-
Summary: 静态联编的简单示例
class P { public: f(){…} }; //父类
class C: public P { public: f(){…} }; //子类
main () {
P p; C c;
p.f(); //调用P::f()
c.f(); //调用C::f()
}
说明: 对象是什么类型,就调什么类型
-
Summary: 通过基类指针访问同名函数的示例
class P { public: f(){…} }; //父类
class C: public P { public: f(){…} }; //子类
main () {
P* ptr; P p; C c;
ptr = &p;
ptr->f(); // 调用P::f()
ptr=&c;
ptr->f(); // 调用P::f()
}
说明: 指针是什么类型,就调什么类型
-
Summary:动态联编的简单示例-指针形式
class P { public: virtual f(){…} }; //父类
class C: public P { public: f(){…} }; //子类,f自动virtual
main () {
P* ptr; P p; C c;
ptr = &p;
ptr->f(); //调用P::f()
ptr=&c; ptr->f(); //调用C::f()
}
说明: 函数虚,不看指针看真对象
-
Summary:动态联编的简单示例-引用形式
class P { public: virtual f(){…} }; //父类
class C: public P { public: f(){…} }; //子类,f自动virtual
main () {
P p; C c;
P& pr1 = p;
pr1.f(); //调用P::f()
P& pr2 = c;
pr2.f(); //调用C::f()
}
说明: 函数虚,不看引用看真对象
70. C++11: Using override and final
C++11:使用override和final
-
override显式声明覆写
C++11引入override标识符,指定一个虚函数覆写另一个虚函数。
class A {
public:
virtual void foo() {}
void bar() {}
};
class B : public A {
public:
void foo() const override { // 错误: B::foo 不覆写 A::foo
} // (签名不匹配)
void foo() override; // OK : B::foo 覆写 A::foo
void bar() override {} // 错误: A::bar 非虚
};
void B::foo() override {// 错误: override只能放到类内使用
}
U Note U
override的价值在于:避免程序员在覆写时错命名或无虚函数导致隐藏bug
àhttps://zh.cppreference.com/w/cpp/language/override
-
final 显式声明禁止覆写
C++11引入final特殊标识符,指定派生类不能覆写虚函数
struct Base {
virtual void foo();
};
struct A : Base
{
void foo() final; // A::foo 被覆写且是最终覆写
void bar() final; // 错误:非虚函数不能被覆写或是 final
};
struct B final : A // struct B 为 final,不能被继承
{
void foo() override; // 错误: foo 不能被覆写,因为它在 A 中是 final
};
U Note U
struct可与class互换;差别在于struct的默认访问属性是public
71. Accessibility (Visibility)
访问控制 (可见性控制)
-
The protected Keyword
1.1. the private and public keywords
(1) To specify whether data fields and functions can be accessed from the outside of the class. (说明数据及函数是否可以从类外面访问)
(2) Private members can only be accessed from the inside of the class (私有成员只能在类内的函数访问)
(3) Public members can be accessed from any other classes. (公有成员可被任何其他类访问)
1.2. A protected data field or a protected function in a base class can be accessed by name in its derived classes. (保护属性的数据或函数可被派生类成员访问)
-
访问属性示例
#include <iostream>
using namespace std;
class A {
public: // 访问属性
int i;
protected:
int j;
private:
int k;
};
class B: public A { // 此public为派生方式
public: // 访问属性
void display() {
cout << i << endl; // OK, can access i
cout << j << endl; // OK, can access j
cout << k << endl; // Error! cannot access k
}
};
int main() {
A a;
cout << a.i << endl; // OK, can access a.i
cout << a.j << endl; // Error, cannot access a.j
cout << a.k << endl; // Error, cannot access a.k
}
-
公有继承
公有继承的派生类定义形式:
class Derived:public Base{
派生类新成员定义;
};
(1) 基类成员 在派生类中的访问属性不变。
(2) 派生类的成员函数 可以访问基类的公有成员和保护成员,不能访问基类的私有成员;
(3) 派生类以外的其它函数 可以通过派生类的对象,访问从基类继承的公有成员, 但不能访问从基类继承的保护成员和私有成员。
-
私有继承
私有继承的派生类定义形式:
class Derived:private Base{
派生类新成员定义;
};
(1) 基类成员 在派生类中都变成 private。
(2) 派生类的成员函数 可以访问基类的公有成员和保护成员,不能访问基类的私有成员;
(3) 派生类以外的其它函数 不能通过派生类的对象,访问从基类继承的任何成员。
-
保护继承
私有继承的派生类定义形式:
class Derived:protected Base{
派生类新成员定义;
};
(1) 基类成员 公有成员和保护成员变成protected,私有成员不变。
(2) 派生类的成员函数 可以访问基类的公有成员和保护成员,不能访问基类的私有成员;
(3) 派生类以外的其它函数 不能通过派生类的对象,访问从基类继承的任何成员。
-
私有继承和保护继承的区别
72. Abstract Class and Pure Virtual Function
抽象类与纯虚函数
-
Abstract Classes (抽象类)
Sometimes a base class is so abstract that it cannot have any specific instances. Such a class is referred to as an abstract class (类太抽象以至于无法实例化就叫做抽象类)
-
Abstract Functions / Pure Virtual Function (抽象函数/纯虚函数)
2.1. 成员函数应出现在哪个继承层次?
问题:Shape类层次中,getArea()函数放在哪个层次
选择1:放哪儿都行:Shape中或子类中定义getArea()
选择2:强制要求Shape子类必须实现getArea()
2.2. 抽象函数(abstract functions)要求子类实现它
virtual double getArea() = 0; // 在Shape类中
Circle子类必须实现getArea()纯虚函数才能实例化
2.3. Abstract class: the class which contains abstract functions (包含抽象函数的类被称为抽象类)
抽象类不能实例化(创建对象)
3. Abstract Class Example
73. Dynamic Cast
动态类型转换
-
Dynamic Casting – Why (为何需要动态类型转换)
void printObject(Shape& shape)
// shape是派生类对象的引用
{
cout << "The area is "
<< shape.getArea() << endl;
// 如果shape是Circle对象,就输出半径
// 如果shape是Rectangle对象,就输出宽高
}
Suppose you wish to modify this function to display the radius if the object is a circle. How can this be done? (如果你要修改函数,让它显示圆的半径该怎么办?)
-
Dynamic Casting Example
2.1. dynamic_cast 运算符
(1) 沿继承层级向上、向下及侧向转换到类的指针和引用
(2) 转指针:失败返回nullptr
(3) 转引用:失败抛异常
2.2. 例子
先将Shape对象用dynamic_cast转换为派生类Circle对象
然后调用派生类中独有的函数
// A function for displaying a Shape object
void printObject(Shape &shape)
{
cout << "The area is "
<< shape.getArea() << endl;
Shape *p = &shape;
Circle c = dynamic_cast<Circle>(p);
// Circle& c = dynamic_cast<Circle&>(shape);
// 引用转换失败则抛出一个异常 std::bad_cast
if (c != nullptr) // 转换失败则指针为空
{
cout << "The radius is "
<< p1->getRadius() << endl;
cout << "The diameter is "
<< p1->getDiameter() << endl;
}
}
74. Upcasting and Downcasting
向上转换和向下转换
-
Upcasting and Downcasting (向上/向下 转型)
1.1. upcasting : Assigning a pointer of a derived class type to a pointer of its base class type (将派生类类型指针赋值给基类类型指针)
1.2. downcasting : Assigning a pointer of a base class type to a pointer of its derived class type. (将基类类型指针赋值给派生类类型指针)
Upcast | Downcast |
---|---|
computer = pc | pc = computer |
computer = desktop | desktop = computer |
pc = desktop | desktop = pc |
-
Upcasting and Downcasting (向上/向下 转型 续)
2.1. Upcasting can be performed implicitly without using the dynamic_cast operator. (上转可不使用dynamic_cast而隐式转换)
Shape* s = nullptr;
Circle *c = new Circle(2);
s = c; //OK,隐式上转
2.2. Downcasting must be performed explicitly. (下转必须显式执行)
Shape* s = new Circle(1);
Circle *c = nullptr;
c = dynamic_cast <Circle*> (s); //显式下转
U Note U
父上,子下
上转隐,下转显
-
基类对象和派生类对象的互操作
3.1. 对象内存布局
Shape S;
Circle C;
U Warning U
可将派生类对象截断,只使用继承来的信息
但不能将基类对象加长,无中生有变出派生类对象
75. typeid
运行时查询类型的信息
-
typeid operator (typeid运算符)
-
typeid is used to obtain the information about the class of the object (typeid用于获取对象所属的类的信息)
(1) typeid returns a reference to an object of class type_info. (typeid运算符返回一个type_info对象的引用)
(2) typeid(AType).name() 返回实现定义的,含有类型名称的C风格字符串(char *)
#include <typeinfo> //使用typeid,需要包含此头文件
class A {};
A a{};
// ……
auto& t1 = typeid(a);
if (typeid(A) == t1) {
std::cout << "a has type "
<< t1.name() << std::endl;
}
可能的输出(visual studio)
class A
可能的输出(g++)
1A
76. C++17: About File System Library
C++17的文件系统库简介
-
C++17引入的filesystem这套新东西,提供了path这个类,使得我们可以直接处理很多与文件路径相关的操作,也可以获取磁盘的相关信息。但是这一部分的重点是path类。
-
创建文件的输入流和输出流对象,并且使用流提取运算符“>>”从文件中读数据,使用流插入运算符“<<”向文件中写数据。这部分很好理解,因为这些用法与我们使用cin和cout没有太多不同。
-
格式化输入输出。有些时候,我们要控制输出数据所占的位宽,或者控制数据的精度。这些都依赖于一些操纵符。你不一定要掌握我们介绍的所有I/O操纵符,但是你需要记住有哪些操纵符。这样等你编程序的时候,根据程序输入输出的需要去查手册。
-
二进制输入输出。我们不需要纠结于文本或者二进制的区别。这里最重要的是记住用write()和read()这两个函数。而且这两个函数接收的第一个参数是 char类型的指针。为了将数组、整型变量等等与char类型指针转换,我们还必须学会 reinterpret_cast这个运算符的用法
-
那么最后就是随机文件访问。这一节最重要的是知道如何使用seekx移动文件位置指示器。
由于C++的I/O流是带有缓冲的,而缓冲这个机制对于程序员来说是不透明的,因此,缓冲会给我们带来一些麻烦。所以我们也要尝试使用 get/getline()等函数编写一些例程,了解缓冲对输入的影响。
-
About std::filesystem(std::filesystem简介)
1.1. C++17 std::filesystem provides facilities for performing operations on file systems and their components, such as paths, regular files, and directories。(标准库的filesystem提供在文件系统与其组件,例如路径、常规文件与目录上进行操作的方法)
1.2. Some terms(一些术语)
(1) File(文件):持有数据的文件系统对象,能被写入或读取。文件有名称和属性,属性之一是文件类型
(2) Path(路径):标识文件所处位置的一系列元素,可能包含文件名
1.3. 路径类:
namespace fs = std::filesystem;
fs::path p{ "CheckPath.cpp" };
-
Path (路径)
2.1. Absolute Path (platform dependent) (绝对路径):An absolute path contains a file name with its complete path and drive letter.(包含完整的路径和驱动器符号)
2.2. Relative Path (相对路径)
(1) Contains NO drive letter or leading "/" (不包含驱动器及开头的/符号)
(2) The file stores in the path Relative to "Current Path" (文件存在相对于“当前路径”的位置)
OS Type | Absolute path | Directory path |
---|---|---|
Windows(case insensitive) | c:\example\scores.txt | c:\example |
Unix/Linux(case sensitive) | /home/cyd/scores.txt | /home/cyd |
-
Differences between Windows and Linux(两种操作系统的不同)
Windows | Linux | C++ | java | |
---|---|---|---|---|
行结束 字符 | \r\n | \n | - | System.getProperty("line.separator"); |
路径名分隔符 | '' | '/' | std::filesystem::path ::preferred_separator | java.io.File.separator |
路径名 | a:\b\c 或\host\b\c | /a/b/c | std::filesystem::path | - |
U 为名字空间起个别名 U
namespace fs = std::filesystem;
// The directory separator for Windows is a backslash (), which needs special treat
namespace fs = std::filesystem;
fs::path p1("d:\cpp\hi.txt"); // 字符串中的反斜杠要被转义
fs::path p2("d:/cpp/hi.txt"); // Windows也支持正斜杠
fs::path p3(R"(d:\cpp\hi.txt)");// 使用原始字符串字面量
77. Path class
路径类及操作
-
Members functions of path class(path类的成员函数)
部分重要的成员函数 | 说明 | |
---|---|---|
+path(string) | 构造函数 | |
+assign(string): path& | 为路径对象赋值 | |
连接 | +append(type p): path& | 将p追加到路径后。type是string、path或const char*。等价于 /= 运算符;自动添加目录分隔符 |
+concat(type p): path& | 将p追加到路径后。type是string、path或const char*。等价于+=运算符;不自动添加目录分隔符 | |
修改器 | +clear(): void | 清空存储的路径名 |
+remove_filename(): path& | 从给定的路径中移除文件名 | |
+replace_filename(const path& replacement): path& | 以 replacement 替换文件名 | |
分解 | +root_name(): path | 返回通用格式路径的根名 |
+root_directory(): path | 返回通用格式路径的根目录 | |
+root_path(): path | 返回路径的根路径,等价于 root_name() / root_directory(),即“路径的根名 / 路径的根目录” | |
+relative_path(): path | 返回相对于 root-path 的路径 | |
+parent_path(): path | 返回到父目录的路径 | |
+filename(): path | 返回路径中包含的文件名 | |
+stem(): path | 返回路径中包含的文件名,不包括文件的扩展名 | |
+extension(): path | 返回路径中包含的文件名的扩展名 | |
查询 | +empty(): bool | 检查路径是否为空 |
+has_xxx(): bool | 其中“xxx”是上面“分解”类别中的函数名。这些函数检查路径是否含有相应路径元素 |
-
Non-member functions (非成员函数)
部分重要的非成员函数 | 说明 | |
---|---|---|
operator/( const path& lhs, const path& rhs ) | 以偏好目录分隔符连接二个路径成分 lhs 和 rhs。比如 path p{"C:"}; p = p / "Users" / "batman"; | |
operator <<, >> (path p) | 进行路径 p 上的流输入或输出 | |
文件类型 | s_regular_file( const path& p ): bool | 检查路径是否是常规文件 |
is_directory( const path& p ): bool | 检查路径是否是目录 | |
is_empty( const path& p ): bool | 检查给定路径是否指代一个空文件或目录 | |
查询 | current_path(): pathcurrent_path( const path& p ): void | 返回当前工作目录的绝对路径(类似linux指令 pwd)更改当前路径为p (类似linux指令 cd) |
file_size( const path& p ): uintmax_t | 对于常规文件 p ,返回其大小;尝试确定目录(以及其他非常规文件)的大小的结果是由编译器决定的 | |
space(const path& p): space_info | 返回路径名 p 定位于其上的文件系统信息。space_info中有三个成员:capacity ——文件系统的总大小(字节),free ——文件系统的空闲空间(字节),available ——普通进程可用的空闲空间(小于或等于 free ) | |
status(const path& p): file_status | 返回 p 所标识的文件系统对象的类型与属性。返回的file_status是一个类,其中包含文件的类型(type)和权限(permissions) | |
修改 | remove(const path& p): boolremove_all(const path& p): uintmax_t | 删除路径 p 所标识的文件或空目录递归删除 p 的内容(若它是目录)及其子目录的内容,然后删除 p 自身,返回被删文件及目录数量 |
rename(const path& old_p, const path& new_p): void | 移动或重命名 old_p 所标识的文件系统对象到 new_p(类似linux指令mv) | |
copy( const path& from, const path& to ): void | 复制文件与目录。另外一个函数 bool copy_file(from, to) 拷贝单个文件 | |
create_directory( const path& p ): boolcreate_directories( const path& p ): bool | 创建目录 p (父目录必须已经存在),若 p 已经存在,则函数无操作创建目录 p (父目录不一定存在),若 p 已经存在,则函 |
78. Introduction to the Input and Output Classes
输入输出类介绍
-
Comparision of File Manipulation between C and C++ (文件操作对比)
C++ | C | ||
---|---|---|---|
Header File (**头文件)** | file input | ifstream (i: input; f:file) | stdio.h |
file output | ofstream (o: ouput; f:file) | ||
file input & output | fstream | ||
Read/**Write (读写操作)** | read from file (**读文件)** | >>;get(); get(char); get(char);getline();read(char,streamsize); | fscanf();fgets(char, size_t , FILE);fread(void ptr*, size, nitems, FILE stream*); |
write to file (**写文件)** | <<;put(char), put(int);write (const char*, streamsize);flush() | fprintf();fwrite(const void ptr*, size, nitems, FILE stream);fputs(const char, FILE *); | |
Status test (**状态测试)** | eof(); bad(); good(); fail() | feof(); ferror(); |
2. What is a stream? (什么是流?)
A stream is a sequence of data(流是一个数据序列) (https://docs.oracle.com/javase/tutorial/essential/io/streams.html )
3. Hierarchy of C++ I/O Stream Classes(C++ I/O流类层次)
C++的流类主要有五类:
\1. 流基类(ios_base和ios)
\2. 标准输入输出流类(istream/ostream/iostream)
\3. 字符串流类(istringstream/ostringstream)
\4. 文件流类(ifstream/ofstream/fstream)
\5. 缓冲区类(streambuf/stringbuf/filebuf)
标准输入输出流对象 cin 和 cout 分别是类 istream 和 ostream 的实例
字符串流:将各种不同的数据格式化输出到一个字符串中,可以使用I/O操纵器控制格式;反之也可以从字符串中读入各种不同的数据
-
Buffered Input and Output (带缓冲的输入输出)
C++的I/O流是有内部缓冲区的
#include <iostream>
using namespace std;
int main() {
char c;
int i = 0;
do {
c = cin.get();
cout << ++i << " : " <<
static_cast**<int>(c)** << endl;
} while (c != 'q');
return 0;
}
U Note U
c = cin.get(void)每次读取一个字符并把由Enter键生成的换行符留在输入队列
上述代码运行结果
在使用Gcc编译器的时候,存在这样一种意外的情况:
无论输入多少个字符, cin.in_avail()函数返回值永远是0。
这是由于GCC编译器中配套的libstdc++实现中的问题(可以看作是标准库gcc实现的一个bug,或者一个特性)。
解决办法是,在使用cin之前,插入下面一行代码
-
cin.sync_with_stdio(false);
简单解释原因:GCC的libstdc++默认是保证C++的cin与C的stdin同步的。
79. Write data to a file
向文件写入数据
-
Writing Data to a File
ofstream class can be used to write data to a text file. (ofstrem可向文本文件中写数据)
输出数据的流程:
What if the file exists? (文件已存在怎么办?) The contents of the file will be destroyed without warning.(内容被直接清除)
-
Writing Data to a File – Auto type recognition (自动类型识别)
writes two lines to the file “scores.txt”
Double data is converted to ascii by "<<" operator
80. Read data from a file
从文件读数据
-
Reading Data from a File
ifstream class can be used to read data from a text file. (ifstrem可从文本文件中读数据)
Testing if the file is opened successfully (检测文件是否成功打开)
-
Knowing data format (了解数据格式)
To read data correctly, you need to know exactly how data is stored.(若想正确读出数据,必须确切了解数据的存储格式)
For example, the program in previous would not work if the score is a int value.
-
Testing if a file is successfully opened (检测文件是否成功打开)
3.1. Errors may occur (可能出现错误):
the file does not exist when reading a file (读文件时文件不存在)
the media is ReadOnly when writing a file (e.g. write to a CD) (写文件时介质只读)
3.2. To detect if a file is successfully opened: (检测文件是否正确打开的方法)
invoke fail() immediately after open(). (open()之后马上调用fail()函数)
If fail() returns true, the file is not opened (does not exist). (fail()返回true, 文件未打开)
ofstream output("scores.txt");
if (output.fail()) {
cout << R"(Can't open file "scores.txt"!)";
}
-
Testing End of File (检测是否已到文件末尾)
What if you don’t know how many lines are in the file (若你不知道文件有多少行), and You want to read them all (还想把他们全读出来)
Use eof() function to detect the end of file (用eof()函数检查是否是文件末尾)
ifstream in("scores.txt");
while (in.eof() == false) {
cout << static_cast<char>(in.get());
}