目录
81. Formating Output
82. Functions for I/O Stream
83. File Open Mode
84. Introduction to Binary IO
85. How to Do Binary Input/Output
86. File Positioner
87. Random Access File
88. Operators and Functions
89. 2D Vector Class
90. C++ Operator Functions
91. C++11: Left Value, Pure Right Value and eXpiring Value
92. Overloading General Binary Arithmetic Operators
93. Overloading Shorthand Binary Arithmetic Operators
94. Overloading [] Operator
95. Overloading the Unary Operator
96. Overloading the - Operators
97. Overloading the ++ and -- Operators
98. Overloading << / >> Operator
99. Overloading Object Converting Operator
100. Overloading the = Operator
101. More on Operator Overloading
102. More Coding Guidelines
103. Overview of Exception-Handling
104. Exception-Handling Advantages
105. Exception Match and Exception Classes
106. Build-in Exception Classes
107. Custom Exception Classes
109. Catch Derived Exceptions
110. C++11:noexcept
111. Exception Propagation
112. Rethrowing Exceptions
113. Rethrowing Exceptions
114. When Do We Use Exceptions?
115. Meta-Programming and Generic Programming
116. Template Basics
117. Function Template
118. Function Template Instantiation
119. Make a Function Generic
120. Class Template
格式化输出
setw manipulator(“设置域宽”控制符)
要包含头文件
1.1. setw(n) 设置域宽,即数据所占的总字符数
std::cout << std::setw(3) << 'a' << std::endl;
输出:
_ _a
1.2. setw()控制符只对其后输出的第一个数据有效,其他控制符则对其后的所有输入输出产生影响。
std::cout << std::setw(5) << 'a' << 'b' << std::endl;
输出:
_ _ _ _ab
1.3. setw()的默认为setw(0),按实际输出
1.4. 如果输出的数值占用的宽度超过setw(int n)设置的宽度,则按实际宽度输出。
float f=0.12345; std::cout << std::setw(3) << f << std::endl;
输出:
0.12345
setprecision manipulator(“设置浮点精度”控制符)
2.1. setprecision(int n)
(1) 控制显示浮点数的有效位
(2) n代表数字总位数
#include
#include
using namespace std;
int main() {
float f = 17 / 7.0;
cout << f << endl;
cout << setprecision(0) << f << endl;
cout << setprecision(1) << f << endl;
cout << setprecision(2) << f << endl;
cout << setprecision(3) << f << endl;
cout << setprecision(6) << f << endl;
cout << setprecision(8) << f << endl;
return 0;
}
Visual Studio输出:
2.42857
2.42857
2
2.4
2.43
2.42857
2.4285715
Eclipse CDT + GCC 8.2输出:
2.42857
2
2
2.4
2.43
2.42857
2.4285715
setfill manipulator(“设置填充字符”控制符)
3.1. setfill(c)
设置填充字符,即“<<"符号后面的数据长度小于域宽时,使用什么字符进行填充。
std::cout << std::setfill('*') << std::setw(5) << 'a' << std::endl;
输出:
****a
Formatting Output in File Operation(在文件操作中格式化输入/输出)
The stream manipulator also works to format output to a file(流控制符同样可以用于文件输入/输出)
控制符 | 用途 |
---|---|
setw(width) | 设置输出字段的宽度(仅对其后第一个输出有效) |
setprecision(n) | 设置浮点数的输/入出精度(总有效数字个数等于n) |
fixed | 将浮点数以定点数形式输入/出(小数点后有效数字个数等于setprecision指定的n) |
showpoint | 将浮点数以带小数点和结尾0的形式输入/出,即便该浮点数没有小数部分 |
left | 输出内容左对齐 |
right | 输出内容右对齐 |
hexfloat/defaultfloat | C++11新增;前者以定点科学记数法的形式输出十六进制浮点数,后者还原默认浮点格式 |
get_money(money)put_money(money) | C++11新增;从流中读取货币值,或者将货币值输出到流。支持不同语言和地区的货币格式https://en.cppreference.com/w/cpp/io/manip/get_moneyhttps://en.cppreference.com/w/cpp/io/manip/put_money |
get_time(tm, format)put_time(tm,format) | C++11新增;从流中读取日期时间值,或者将日期时间值输出到流。https://en.cppreference.com/w/cpp/io/manip/get_timehttps://en.cppreference.com/w/cpp/io/manip/put_time |
用于输入/输出流的函数
getline()
1.1. When using (>>), data are delimited by whitespace. (>>运算符用空格分隔数据)
对于文件内容:
Li Lei#Han Meimei#Adam
如下代码只能读入“Li”
ifstream input("name.txt");
std::string name;
input >> name;
1.2. Read in "Li Lei" with member function getline(char* buf, int size, char delimiter)
constexpr int SIZE{ 40 };
std::array
while (!input.eof()) {// not end of file
input.getline(&name[ 0 ] , SIZE , '#');
std::cout << &name[ 0 ] << std::endl;
}
1.3. Read in "Li Lei" with non-member function std::getline(istream& is, string& str, char delimiter)
std::string name2{};
while (!input.eof()) {
std::getline(input, name2, '#');
std::cout << n << std::endl;
}
get() and put()
Two other useful functions are get and put.
2.1. get: read a character
int istream::get();
istream& get (char& c);
2.2. put write a character.
ostream& put (char c);
flush()
3.1. Flush output stream buffer (将输出流缓存中的数据写入目标文件)
ostream& flush();
3.2. 用法
cout.flush(); // 其它输出流对象也可以调用 flush()
cout << "Hello" << std::flush; // 与endl类似作为manipulator的调用方式
文件的打开模式
fstream and File Open Modes (fstream与文件打开模式)
ofstream : 写数据; ifstream : 读数据
fstream = ofstream + ifstream
When opening an fstream object, a "file open mode" should be specified(创建fstream对象时,应指定文件打开模式)。
Mode(**模式****)** | Description(**描述****)** |
---|---|
ios::in | 打开文件读数据 |
ios::out | 打开文件写数据 |
ios::app | 把输出追加到文件末尾。app = append |
ios::ate | 打开文件,把文件光标移到末尾。ate = at end |
ios::trunc | 若文件存在则舍弃其内容。这是ios::out的默认行为。trunc = truncate |
ios::binary | 打开文件以二进制模式读写 |
Combining Modes (模式组合)
2.1. Open Mode的定义
// std::ios_base::openmode 被ios继承
typedef /implementation defined/ openmode;
static constexpr openmode app = /implementation defined/
2.2. Combine several modes (几种模式可以组合在一起)
using the | operator (bitwise inclusive OR) (用“位或”运算符)
2.3. To open a file "name.txt" for appending data (打开文件name.txt追加数据)
stream.open("name.txt", ios::out | ios::app);
二进制输入输出简介
Text File vs Binary File (文本文件与二进制文件)
1.1. TEXT file vs BINARY file (not technically precise) (文本文件与二进制文件)
(1) Both stores as a sequence of bits (in binary format) (都按二进制格式存储比特序列)
(2) text file : interpreted as a sequence of characters (解释为一系列字符)
(3) binary file : interpreted as a sequence of bits. (解释为一系列比特)
1.2. For example, the decimal integer 199 (对于十进制整数199)
(1) 在文本文件中存为3个字符: '1', '9', '9';三个字符的ASCII码占3个字节:0x31, 0x39, 0x39
(2) 在二进制文件中存为字节类型的值:C7;十进制 199 = 十六进制 C7
Text I/O vs Binary I/O (文本读写与二进制读写)
2.1. 文本读写:Windows文件的换行(CRLF) vs *nix文件的换行(LF)
在Windows上,'\n'输出到文件中会自动编码为'\r' '\n' 两个字符
在*nix上,'\n' 字符输出到文件中不变
2.2. Text I/O is built upon binary I/O to provide a level of abstraction for character encoding and decoding. (文本模式的读写是建立在二进制模式读写的基础上的,只不过是将二进制信息进行了字符编解码)
File Open Mode: ios::binary
3.1. Binary I/O does not require conversions. (二进制读写无需信息转换)
numeric value è write (bin I/O) è file
value in memory è copy (no conversion) è file
3.2. How to perform binary I/O ? (如何进行二进制读写)
By default, a file is opened in text mode.(文件默认以文本模式打开)
open a file using the binary mode ios::binary.(用ios::binary以二进制模式打开文件)
Text I/O (**文本模式****)** | Binary I/O function:(**二进制模式****)** | |
---|---|---|
读 | operator >>; get(); getline(); | read(); |
写 | operator <<; put(); | write(); |
如何实现二进制读写
The write Function (write函数)
1.1. prototype (函数原型)
ostream& write( const char* s, std::streamsize count )
1.2. 可直接将字符串写入文件
fstream fs("GreatWall.dat", ios::binary|ios::trunc);
char s[] = "ShanHaiGuan\nJuYongGuan";
fs.write(s, sizeof(s));
1.3. 如何将非字符数据写入文件
(1) Convert any data into a sequence of bytes (byte stream) (先将数据转换为字节序列,即字节流)
(2) Write the sequence of bytes to file with write() (再用write函数将字节序列写入文件)
How to convert any data into byte stream? (如何将信息转换为字节流)
2.1. reinterpret_cast
该运算符有两种用途:
(1) cast the address of a type to another type (将一种类型的地址转为另一种类型的地址)
(2) cast the address to a number, i.e. integer (将地址转换为数值,比如转换为整数)
2.2. 语法: reinterpret_cast
address is the starting address of the data (address是待转换的数据的起始地址)
dataType is the data type you are converting to. (dataType是要转至的目标类型)
For binary I/O, dataType is char . (对于二进制I/O来说,dataType是 char)
2.3. 例子
long int x {0};
int a[3] {21,42,63};
std::string str{"Hello"};
char* p1 = reinterpret_cast
char* p2 = reinterpret_cast
char* p3 = reinterpret_cast
The read Function (read成员函数)
3.1. prototype (函数原型)
istream& read ( char* s, std::streamsize count );
3.2. 例子
// 读字符串
fstream bio("GreatWall.dat", ios::in | ios::binary);
char s[10];
bio.read(s, 5);
s[5] = '\0';
cout << s;
bio.close();
// 读其它类型数据(整数),需要使用 reinterpret_cast
fstream bio("temp.dat", ios::in | ios::binary);
int value;
bio.read(reinterpret_cast
cout << value;
文件位置指示器
File Positioner (文件位置指示器)
1.1. file positioner (fp):
A file consists of a sequence of bytes.(文件由字节序列构成)
File positioner is a special marker that is positioned at one of these bytes. (一个特殊标记指向其中一个字节)
1.2. A read or write operation takes place at the location of the file positioner. (读写操作都是从文件位置指示器所标记的位置开始)
When a file is opened, the fp is set at the beginning. (打开文件,fp指向文件头)
When you read or write data to the file, the file pointer moves forward to the next data item. (读写文件时,文件位置指示器会向后移动到下一个数据项)
1.3. File Positioner(文件位置指示器)的其它说法
File Pointer(文件指针):易与C语言的FILE* 混淆
File Cursor(文件光标):借用数据库中的“光标”概念
Example of File Positioner
aFileStream.get() ---> fp = fp + 1
随机访问文件
Random Access (随机访问)
1.1. Random Access means one can read/write anywhere inside a file(随机访问意味着可以读写文件的任意位置)
1.2. How?
We are able to know where the file positioner is. (我们能知道文件定位器在什么位置)
We are able to move the file positioner inside the file (我们能在文件中移动文件定位器)
Maybe we need two file positioners : one for reading, another for writing
1.3. 相关函数
· | For reading (**读文件时用)** | For writing(**写文件时用)** |
---|---|---|
获知文件定位器指到哪里 | tellg(); tell是获知,g是get表示读文件 | tellp(); tell是获知,p是put表示写文件 |
移动文件定位器到指定位置 | seekg(); seek是寻找,g是get表示读文件 | seekp(); seek是寻找,p是put表示写文件 |
seek的用法
2.1. seek的原型
xxx_stream& seekg/seekp( pos_type pos );
xxx_stream& seekg/seekp( off_type off, std::ios_base::seekdir dir);
seekdir 文件定位方向类型 | 解释 |
---|---|
std::ios_base::beg | 流的开始;beg = begin |
std::ios_base::end | 流的结尾 |
std::ios_base::cur | 流位置指示器的当前位置;cur = current |
例子 | 解释 |
---|---|
seekg(42L); | 将文件位置指示器移动到文件的第42字节处 |
seekg(10L, std::ios::beg); | 将文件位置指示器移动到从文件开头算起的第10字节处 |
seekp(-20L, std::ios::end); | 将文件位置指示器移动到从文件末尾开始,倒数第20字节处 |
seekp(-36L, std::ios::cur); | 将文件位置指示器移动到从当前位置开始,倒数第36字节处 |
运算符与函数
Special Operators Usage with Objects (与对象一起用的运算符)
1.1. string类:使用“+”连接两个字符串
string s1("Hello"), s2("World!");
cout << s1 + s2 << endl;
1.2. array 与 vector类:使用[] 访问元素
array
vector
a[0] v[1] = 'b';
1.3. path类:使用“/”连接路径元素
std::filesystem::path p{};
p = p / "C:" / "Users" / "cyd";
The operator vs function (运算符与函数的异同)
2.1. 运算符可以看做是函数
2.2. 不同之处
2.2.1. 语法上有区别
3 * 2 //中缀式
*3 2 //前缀式
multiply ( 3, 2) ; //前缀式
3 2 * //后缀式(RPN)
2.2.2. 不能自定义新的运算符 (只能重载)
3 ** 2 // C/C++中错误
pow(3, 2) // 3的平方
2.2.3. 函数可overload, override产生任何想要的结果,但运算符作用于内置类型的行为不能修改
multiply (3, 2) // 可以返回1
3 * 2 // 结果必须是6
2.3. 函数式编程语言的观念
一切皆是函数
Haskell中可以定义新的运算符
平面向量类
How to describe 2D vector in C++(如何在C++中描述平面向量)
1.1. C++ STL vector: 变长数组
1.2. 向量数据成员
double x, double y
或者 std::array
1.3. 运算 1.3.1. +, -, 数乘, 点积
(1, 2) + (3, 4) : (1+3, 2+4)
(1, 2) – (3, 4) : (1-3, 2-4)
(1, 2) * 3 : (13, 23)
(1, 2) * (3, 4) : ( 13, 24)
1.3.2. 求长度(magnitude)和方向(direction)
| (1, 2)| : √ ( 11 + 22)
dir (1, 2) : arctan ( 1/2 )
1.4. ==, !=
1.5. <, <=, >, >=
比较两个向量的长度
1.6. 类型转换:double (即求向量长度), string
1.7. 负值(Negative value)
-(1, 2) : (-1, -2)
1.8. 自加1,自减1
Vec2D类
The steps of creating Vec2D class(创建平面向量类的步骤)
3.1. Test-Driven Development (TDD),测试驱动开发
不是软件测试方法,而是开发设计方法
Kent Beck 《测试驱动开发》
3.2. Steps(步骤)
(1) 先编写测试代码,而不是功能代码
(2) 编译运行测试代码,发现不能通过
(3) 做一些小小的改动(编写功能代码),尽快地让测试程序可运行
(4) 重构代码,优化设计
Some Functions
4.1.
double atan(double x); //返回x的反正切值,以弧度为单位。有其他类型重载
double sqrt(double x); //返回x的平方根。有其它类型重载
double pow (double b, double exp); // 返回b的exp次方。有其它类型重载
编码规范
Generic variables should have the same name as their type. Non‐generic variables have a role. These variables can often be named by combining role and type.
一般变量的名字应该与变量的类型相同;特定变量都是有角色的,这类变量经常用角色加上类型命名
void setTopic(Topic* topic)
// NOT: void setTopic(Topic* value) // NOT: void setTopic(Topic* aTopic) // NOT: void setTopic(Topic* t)
void connect(Database* database)
// NOT: void connect(Database* db) // NOT: void connect (Database* oracleDB)
Point startingPoint, centerPoint;
Name loginName;
C++运算符函数
Why operator overloading (为何要用运算符重载)
1.1. Which is more intuitive? (哪个更直观)
1.2. Compare the magnitudes of two 2D-Vectors(比较两个平面向量的大小)
cout << "v1.compareTo(v2) is " << v1.compareTo(v2) << endl;
cout << "v1 < v2 is " << v1 < v2 << endl;
Operators: Which can be Overloaded (可重载的运算符) 2.1. Overloadable (可重载)
(1) 类型转换运算符:double, int, char, ……
(2) new/delete, new []/delete[]
(3) ""_suffix 用户自定义字面量运算符(自C++11起)
(4) 一般运算符:
2.2. 不可重载的运算符
Operator | Name |
---|---|
. | 类属关系运算符 |
.* | 成员指针运算符 |
:: | 作用域运算符 |
? : | 条件运算符 |
# | 编译预处理符号 |
2.3. Restrictions for operator overloading (运算符重载的限制)
(1) Precedence and Associativity are unchangeable (优先级和结合性不变)
(2) NOT allowing to create new operator (不可创造新的运算符)
Operator Functions(运算符函数)
原型与调用
4. 确定运算符函数的调用形式
“谁”的运算符?“谁”是运算符函数的参数?
“谁”的运算符?“谁”是运算符函数的参数?
表达式 | 作为成员函数 | 作为非成员函数 | 示例 |
---|---|---|---|
@a | (a).operator@ ( ) | operator@ (a) | ![std::cin](https://zh.cppreference.com/w/cpp/io/cin) 调用 std::cin.operator!() |
a@b | (a).operator@ (b) | operator@ (a, b) | std::cout << 42 调用 std::cout.operator<<(42) |
a=b | (a).operator= (b) | 不能是非成员 | std::string s; s = "abc"; 调用 s.operator=("abc") |
a(b...) | (a).operator()(b...) | 不能是非成员 | std::random_device r; auto n = r(); 调用 r.operator()() |
a[b] | (a).operator[](b) | 不能是非成员 | std::map |
a-> | (a).operator-> ( ) | 不能是非成员 | auto p = std::make_unique |
a@ | (a).operator@ (0) | operator@ (a, 0) | std::vector |
此表中,@ 是表示所有匹配运算符的占位符:@a 为所有前缀运算符,a@ 为除 -> 以外的所有后缀运算符,a@b 为除 = 以外的所有其他运算符 |
C++11: 左值、纯右值与将亡值
C++03 lvalue and rvalue (C++03的左值和右值)
通俗理解
(1) 能放在等号左边的是lvalue
(2) 只能放在等号右边的是rvalue
(3) lvalue可以作为rvalue使用
C++11: Left Value
2.1. An lvalue designates a function or an object, which is an expression whose address can be taken (左值指定了一个函数或者对象,它是一个可以取地址的表达式)
int lv1{ 42 }; // Object
int main() {
int& lv2{ lv1 }; // Lvalue reference to Object
int* lv3{ &lv1 }; // Pointer to Object
}
int& lv4() { return lv1; } // Function returning Lvalue Reference
2.2. 左值例子
(1) 解引用表达式*p
(2) 字符串字面量"abc"
(3) 前置自增/自减表达式 ++i / --i
(4) 赋值或复合运算符表达式(x=y或m*=n等)
àhttps://www.geeksforgeeks.org/understanding-lvalues-prvalues-and-xvalues-in-ccwith-examples/
C++11: Pure Right Value
3.1. prvalue(Pure Right Value,纯右值):是不和对象相关联的值(字面量)或者其求值结果是字面量或者一个匿名的临时对象
3.2. 纯右值例子
(1) 除字符串字面量以外的字面量,比如 32, 'a'
(2) 返回非引用类型的函数调用 int f() { return 1;}
(3) 后置自增/自减表达式i++/i--
(4) 算术/逻辑/关系表达式(a+b、a&b、a<=b、a
(5) 取地址(&x)
3.3. 左值可以当成右值使用
C++11: eXpiring Value
xvalue(eXpiring Value,将亡值):将亡值也指定了一个对象,是一个将纯右值转换为右值引用的表达式
int prv(int x) { return 6 * x; } // pure rvalue
int main() {
const int& lvr5{ 21 }; // 常量左值引用可引用纯右值
int& lvr6{ 22 }; // 错!非常量左值引用不可引用纯右值
int&& rvr1{ 22 }; // 右值引用可以引用纯右值
int& lvr7{ prv(2) }; // 错!非常量左值引用不可引用纯右值
int&& rvr2{ prv(2) }; // 右值引用普通函数返回值
rvr1 = ++rvr2; // 右值引用做左值使用
}
重载一般二元算术运算符
Overloading General Binary Arithmetic Operators(重载一般二元算术运算符)
1.1. 调用一般二元运算符
Vec2D a{1,2}, b{3, 6}; double z {1.3};
Vec2D c = a + b; // a.operator+(b); à Vec2D Vec2D::operator+(Vec2D);
Vec2D d = a + z; // a.operator+(z); à Vec2D Vec2D::operator+(double);
Vec2D e = z + b; // z.operator+(b); à Vec2D double::operator+(Vec2D); 错误!
1.2. 函数原型
struct Vec2D {
Vec2D operator +(Vec2D); //成员函数
Vec2D operator +(double); //成员函数
friend Vec2D operator +(double, Vec2D); //非成员(友元)函数
};
Vec2D operator +(double, Vec2D) { // Do something
}
Example
Vec2D v1(2, 4);
Vec2D v2 = v1 + Vec2D(2, 3)
cout << "v1 is " << v1.toString() << endl;
cout << "v2 is " << v2.toString() << endl;
重载复合二元算术运算符
Overloading the Shorthand Operators (简写/复合运算符重载)
1.1. shorthand operators: +=, -=, *=, and /=
v1 += v2; // 语句执行后,v1的值被改变了
v1 = v1 + v2;
Vec2D Vec2D::operator +=(const Vec2D& secondVec2D ) {
*this = this->add(secondVec2D);
return (*this);
}
Vec2D Vec2D::add(const Vec2D& secondVec2D) { //prvalue
double m = x_ + secondVec2D.getX()
double n = y_ + secondVec2D.y_;
return Vec2D(m, n); //临时的匿名对象
}
Example
Vec2D v2(2, 4);
v2 += Vec2D(2, 3);
cout << "v2 is " << v2.toString() << endl;
重载[]运算符
Overloading the [] Operators (重载[]运算符)
array subscript [] can be overloaded to access the contents of the object using the array-like syntax ([]重载后可用类似数组的语法格式访问对象内容)
Vec2D v {8, 9};
cout << "v.x: " << v[0] << "v.y: " << v[1] << endl;
double Vec2D::operator {
if (index == 0)
return x_;
else if (index == 1)
return y_;
else {
cout << "index out of bound" << endl; exit(0);
}
}
[] accessor and mutator (数组下标运算符作为访问器和修改器)
2.1. The [] operator functions as both accessor and mutator.([]运算符可既读又写)
accessor: double a = v2[ 0 ];
mutator : v2[ 1 ]=3.0; //compile error:Lvalue required in function main()
2.2. How to make v2[] an Lvalue? (如何使r2[]成为左值)
declare the [] operator to return a reference (使[]返回一个引用)
double& Vec2D::operator { //lvalue
if (index == 0)
return x_; //x_ can be modified
//...... Now, the Vec2D class is mutable.
}
重载一元运算符
Overloading the Unary Operators
1.1. Let @ be a unary operator (@代表单目运算符)
--, ++, -(负号)
1.2. prepositive unary operators(前置单目运算符): –(负), *(dereference, n解引用)
1.3. 当编译器遇到 @obj; 时,
若operator @是在obj的类中的成员,则调用
obj.operator @()
若operator @是obj的类的 friend 函数,则调用
operator @(obj)
重载负号运算符
Overloading the - Operators (重载负号运算符)
1.1. + and - are unary operators. (正号和负号都是一元运算符)
only operates on the calling object itself (只对调用该运算符的对象起作用)
the unary function operator has NO parameters. (作为对象成员的一元运算符无参数)
1.2. 调用 - 运算符
Vec2D v1(2, 3);
Vec2D v2 = -v1; // 向量v1求负值;v1的值不变
cout << v1.toString();
1.3. 重载 - 运算符: -(a, b) à (-a, -b)
Vec2D Vec2D::operator-(){
return Vec2D(-this->x, -this->y); // 返回匿名临时对象
}
重载++和--运算符
++ and – Operators(自增自减运算符)
1.1. ++ and -- may be prefix or postfix. (自增/减运算符可前置也可后置)
prefix ++var or --var : 前置:先增减后取值,表达式是lvalue
postfix var++ or var-- : 后置:先取值后增减,表达式是prvalue
1.2. 规则:++(a, b) à (++a, ++b); (a, b)++ (a++, b++)
Vec2D v1(2, 3);
cout << "v1: " << v1.toString() << endl; //v1: (2, 3)
Vec2D v2 = ++v1;
cout << "v2: " << v2.toString() << endl; // v2: (3, 4)
Vec2D v3(2, 3);
Vec2D v4 = v3++;
cout << "v3: " << v3.toString() << endl; // v3: (3, 4)
cout << "v4: " << v4.toString() << endl; // v4: (2, 3)
prefix ++/-- v.s. postfix ++/-- (前置与后置的差别)
前置++/--重载无参数,返回引用类型
后置++/--重载带参数--"dummy(大米)"参数)
运算符名 | 语法 | 可重载 | 原型示例(对于类 class T**)** | |
---|---|---|---|---|
类内定义 | 类外定义 | |||
前自增 | ++a | 是 | T& T::operator++(); | T& operator++(T& a); |
前自减 | --a | 是 | T& T::operator--(); | T& operator--(T& a); |
后自增 | a++ | 是 | T T::operator++(int dummy); | T operator++(T& a, int dummy); |
后自减 | a-- | 是 | T T::operator--(int dummy); | T operator--(T& a, int dummy); |
Overloading ++/-- Example (重载实例) 3.1. Prefix ++/--
Vec2D& Vec2D::operator++(){
x_ += 1; y_ += 1; return *this;
}
3.2. Postfix ++/--
Vec2D Vec2D::operator++(int dummy){
Vec2D temp(this->x_, this->y_); x_ += 1; y_ += 1; return temp;
}
重载流插入/提取运算符
Overloading the << and >> Operators(重载流操作运算符)
1.1. << 和 >> 是在 ostream 和istream类中定义(重载)的
(<<) : send values to cout (将信息输出到ostream对象);比如 cout << vec2d;
(>>) : read values from cin. (从istream对象中读取信息);比如 cin >> vec2d;
1.2. << and >> are binary operator (流提取/插入运算符是二元运算符)
Overloading << and >> as friend (重载为友元)
2.1. 使用<<和>>运算符时,第一个参数是流类的实例:cout << x< 运算符重载为类成员函数后,当调用该运算符时,左操作数必须是该类的实例。若<<和>>重载为成员函数,则只能用 v1< << (>>) should be overloaded as "friend function" (只能重载为友元函数) class Vec2D { //重载为成员函数 public: ostream &operator<<(ostream &stream); istream &operator>>(istream &stream); Vec2D v1; v1 << cout; //Vec2D对象只能作为第一个操作数 struct Vec2D { //重载为友元函数 friend ostream &operator<<(ostream &stream, Vec2D &v); friend istream &operator>>(istream &stream, Vec2D &v); }; Vec2D v1; cout << v1; 重载对象转换运算符 Object Conversion (对象转换) when converting a Vec2D object to a double value (将Vec2D对象转换为double数时),我们可以求该对象的范数,也就是向量长度(magnitude) define a function operator to convert an object into int or double. (定义一个运算符函数执行类型转换) Vec2D::operator double() { return magnitude(); } Vec2D v1(3, 4); double d = v1 + 5.1; // d: 10.1 double e = static_cast 重载赋值运算符 The Default Behavior of the = Operators (赋值运算符的默认行为) By default, the = operator performs a memberwise copy from one object to the other. (默认情况下,赋值运算符执行对象成员的一对一拷贝) For example, the following code copies v2 to v1(例如,下面的代码将v2的值拷贝给v1) Vec2D v1(1, 2); Vec2D v2(4, 5); v1 = v2; cout << "v1 is " << v1.toString() << endl; cout << "v2 is " << v2.toString() << endl; Overloading the = Operators To change the way the default assignment operator = works, you need to overload the = operator. (重载赋值运算符,会改变其默认工作方式) 一般情况下,如果拷贝构造函数需要执行深拷贝,那么赋值运算符需要重载 const Employee operator=( employee1 = employee1 = employee1; 默认赋值运算符的 Shallow Copy (浅拷贝) Employee e1{"Jack", Date(1999, 5, 3), Gender::male}; Employee e2{"Anna", Date(2000, 11, 8), Gender:female}; e2 = e1; //默认赋值运算符,执行一对一成员拷贝 Overloading Assignment Operator(定制赋值运算符) e2 = e1; class Employee { public: Employee& operator =(const Employee& e){ name = e.name; *birthdate = *(e.birthdate); } // ... }; Employee e1{"Jack", Date(1999, 5, 3), Gender::male}; Employee e2{"Anna", Date(2000, 11, 8),, Gender:female}; e2 = e1; // 调用重载赋值运算符 运算符重载的更多说明 能重载运算符操作基础数据类型吗 1.1. 重载的运算符必须和用户定义的class类型一起使用 1.2. 重载的运算符的参数至少应有一个是类对象(或类对象的引用) 参数不能全部是C++的标准类型 以防止用户修改用于标准类型数据的运算符的性质。 以友元函数实现部分运算符 class Vec2D { public: friend Vec2D operator+(Vec2D &firstVec, Vec2D &secondVec); // friend double& operator[](Vec2D &v, const int &index); // operator[] 不能重载为友元函数,只能重载为成员函数 friend Vec2D operator++(Vec2D &v); friend Vec2D operator++(Vec2D &v, int dummy); } class Vec2D { public: Vec2D operator+(Vec2D &secondVec); double& operator[](const int &index); Vec2D operator++(); Vec2D operator++(int dummy); } 更多编码规范 variable names for (int i = 1; i < 5; i++){ for (int j = 1; j <= n1; j++){ } } Iterator variables should be called i, j, k etc. Variables named j, k etc. should be used for nested loops only 迭代变量应使用i,j,k等字母。j,k等应仅用于嵌套循环中 Variables with a large scope should have long names, variables with a small scope can have short names [1]. 大作用域的变量应使用长名字,小作用域的变量应使用短名字 2.Group the Header Files 41.Include statements should be sorted and grouped. Sorted by their hierarchical position in the system with low level files included first. Leave an empty line between groups of include statements. 42.包含语句应排序并分组。排序时,按其在系统中的层次位置,较低层的包含语句在前。用空行分隔分组的包含语句 #include #include #include #include #include "com/company/ui/PropertiesDialog.h" #include "com/company/ui/MainWindow.h" 包含文件的路径中,一定不要使用绝对路径 Include statements must be located at the top of a file only. 包含语句必须放在文件的顶部 将包含语句放到源代码的其它位置,可能会导致预期之外的编译副作用 异常处理概览 Necessity of Exception Handling (错误处理的必要性) 1.1. Example reads in two integers (读二数) and displays their quotient.(求商) #include using namespace std; int main() { // Read two intergers cout << "Enter two integers: "; int number1, number2; cin >> number1 >> number2; cout << number1 << " / " << number2 << " is "; cout << (number1 / number2) << endl; return 0; } 1.2. If you enter 0 for the second number: (若输入0做除数) a runtime error would occur (会出现运行时错误) because you cannot divide an integer by 0. (0不能做除数) Exception-Handling Overview (异常处理概览) 2.1. TWO way to deal with " divide an integer by 0 " (两种处理被0除的方法) (1) use if statement (使用if语句) (2) use exception handling (使用异常处理) int main() { // Read two intergers cout << "Enter two integers: "; int number1, number2; cin >> number1 >> number2; try { } catch (int e) { } cout << "Execution continues ..." << endl; } Example Snippet for try-throw-catch (踹扔抓的代码块示例) try { Code to try; throw an exception (1) with a throw statement More code to try; } catch (type e) { Code to process the exception; } 异常处理机制的优点 Exception-Handling, Simple ? (异常处理机制,简单吗?) 1.1. TWO ways to deal with " divide an integer by 0 " use if statement (simple?) use exception handling (complex?) // if statement #include using namespace std; int main() { // Read two intergers cout << "Enter two integers: "; int number1, number2; cin >> number1 >> number2; if (number2 != 0) { } else { } } // Exception Handling #include using namespace std; int main() { // Read two intergers cout << "Enter two integers: "; int number1, number2; cin >> number1 >> number2; try { } catch (int e) { } cout << "Execution continues ..." << endl; } Exception-Handling Advantages (异常处理机制的优点) Advantages: bring the exceptional information in callee to the caller (优点:可将异常信息从被调函数带回给主调函数) //用异常处理 int quotient(int number1, if (number2 == 0) return number1 / number2; } int main() { try { } catch (int) { } } 若不用异常处理: quotient()如何告诉 main() "number2 有问题"? (1) 用返回值? if(number2 == 0) return x; //x应该是0还是1? (2) 用指针/引用类型的参数? int quotient(int n1, int n2, int &s){ if(n2 == 0) s=-1; //求商,函数要3个参数? } (3) 如果采用 f(g(h(quotient(x,y)))); 怎样将错误从quotient() 传到 f()? 异常匹配与异常类 catch: Match with Exception Type (catch: 按异常类型匹配) catch ( ExceptionType& parameter ) { /* 处理异常 */ } 若try{}中所抛异常类型与catch()的参数类型(ExceptionType)匹配,则进入catch块 若对异常对象的内容不感兴趣,可省略catch参数,只保留类型 void f1() { throw 100; } void f2() { for (int i = 1; i <= 100; i++) new int[70000000]; } try { f1(); } catch (int) { //仅有类型 cout << "Error" << endl; } try { f2(); } catch (bad_alloc) { cout << "new failed" << endl; } try { f1(); } catch (int& e) { //类型+参数 cout << e << endl; } try { f2(); } catch (bad_alloc &e) { cout << "Exception: " << e.what() << endl; } Why Exception Class (为何要使用异常类) 不使用异常类,则捕获的异常所能传递的信息量很少 try { // ... } catch (int e) { //do something with e } 使用异常类,则可以在抛出异常时传递很多信息,在捕获异常时接收这些信息 class Beautiful { string name; string hometown; int salary; string misdoing; // ………… } try { // ... } catch (Beautiful object) { //do something with object } 内建异常类 Base Class of Exception in Standard Library(标准库中的异常基类) 1.1. #include 1.2. class exception; exception(); // 构造函数 virtual const char* what(); //返回解释性字符串 what()返回的指针指向拥有解释信息的空终止字符串的指针。该指针保证在获取它的异常对象被销毁前,或在调用该异常对象的非静态成员函数前合法 1.3. exception 是标准库所有异常类的基类 Exception Classes in Standard Library(标准库中的异常类) 注意,在使用所有标准库异常类的时候,都必须附加std名字空间。 例如,在使用 bad_alloc 异常类时: std::bad_alloc ex{"Out of memory"}; throw ex; 自定义异常类 Custom Exception Classes 1.1. How to create user-defined exception class?(如何创建自定义的异常类) This class is just like any C++ class (这个类与其它C++ class是类似的) often it is derived from exception or its descendants (通常由exception或其后代类派生) 1.2. Why to derive from exception ? To utilize the common features in the exception class (使用exception类的公共特征) e.g., the what() function (比如, what()函数) Vec3D类 2.1. DIMENSION: 向量维度。本例为3 2.2. vec:数组,存放向量元素 2.3. operator [] 通过数组下标形式读取或者修改向量的元素 需检查下标是否属于区域[0, DIMENSION-1] 如果越界,抛出异常 What do the shoes look like? (鞋长什么样) Priority of using exception classes (优先使用哪种异常类?) 4.1. Predefined exception classes of C++. (whenever possible) (C++预定义的) 用C++已经造好的轮子 4.2. Creating your own exception classes. (the less, the better) (你自己定义的) merely when you run into a problem, and the predefined exception classes cannot describe it adequately 捕获多种无关异常 Catch Various Exceptions (不同的异常的捕获) The exception threw by a try block can be of differing types (try块中的代码可能会抛出不同类型的异常) class EA: public exception { }; class EB: public exception { }; class C { public: void foo(int x) { if (x == 1) throw EA(); else if (x == 2) throw EB(); } }; One catch block can catch only one type of exception. (一个catch块只能捕获一种异常) int main() { C c { }; try { } catch (EA& a) { } catch (EB& b) { } } 捕获派生异常 catch : Derive Exception Class (catch块中的派生异常类) 1.1. Derived exception classes (派生异常类) class MyException: public logic_error { }; 1.2. catch( logic_error ) will match: (catch参数类型为基类异常类型,则可以匹配:) catch exception objects of a base class (能捕获基类对象) catch exception objects of the derived classes (也能捕获派生类对象) try { throw MyException(); // 抛出派生异常对象 } catch (logic_error& e) { // catch参数为基类异常,但可以捕获所有派生类异常对象 MyException* p = dynamic_cast if (p != nullptr) else } U dynamic_cast 若转型失败且NewType是指针类型,则返回nullptr。 若转型失败且NewType是引用类型,则抛出std::bad_cast类型的异常 Order of exception handlers (异常处理的次序) 2.1. The CORRECT order when catching exceptions: (捕获异常的正确次序) 1st appear: A catch block for a derived class type (派生类的catch块在前) 2nd appear: A catch block for a base class type (基类的catch块在后) 2.2. Which one is wrong, (a) or (b)? (a代码和b代码哪个有错?) // (a) try { ... } catch (logic_error& e) { ... } catch (MyException& e) { ... } // (b) try { ... } catch (MyException& e) { ... } catch (logic_error& e) { ... } C++11的noexcept Why noexcept 1.1. C++03将throw(ExceptionType)放到函数后面,说明函数会抛出什么类型的异常,也被称为“异常规约” java用 throws关键字做同样的事情 C++中基本没人用“异常规约” 1.2. C++11使用noexcept指明函数是否抛出异常 若函数不抛异常,则可做编译优化 即便函数抛异常,也不再说明所抛异常类型(简化) Usage of noexcept specifier(noexcept声明符的用法) 2.1. noexcept声明符的语法:noexcept或者 noexcept(布尔表达式) void foo() noexcept {} void foo() noexcept(true) {} // noexcept(true)等价于noexcept void foo() {} // 可能会抛出异常 void foo() noexcept(false) {} // noexcept(false)等价于什么也不写,可能会抛出异常 2.2. noexcept不能用于区分重载函数 (对比:函数名后面的const可区分重载) 2.3. noexcept函数中抛出异常,等于调用std::terminate() void f() { /* 潜在抛出异常 */ } void g() noexcept { f(); // 合法,即使 f 抛出 throw 42; // 合法,等效于调用 std::terminate } Usage of noexcept operator(noexcept运算符的用法) bool noexcept( expression ) noexcept 运算符进行编译时检查,若表达式声明为不抛出任何异常则返回 true void may_throw(); void no_throw() noexcept; int main() { std::cout << std::boolalpha } 异常传播 Exception Propagation (异常传播) 1.1. 什么是异常传播? 1.2. Nested Function Call (嵌套的函数调用) 每个函数中都有 try-catch 块 内层函数抛出异常 1.3. which "catch" can do? U 关注2点 U \1. try块中抛出异常的语句后面的语句 \2. catch的异常类型与所需匹配的异常实例的类型 The rules in Exception Propagation (异常传播中的规则) try-catch的执行规则 \1. try**块中的异常**:抛异常的语句后的块代码都被跳过,并开始找exception handler的代码 \2. 找**exception handler****的流程**:沿函数调用的链反向寻找 (1) 按catch块的顺序对比,找到则执行catch块代码 (2) 找不到,退出当前函数,将异常传给调用当前函数的函数 Quiz: function3抛出 \1. Exception3,执行哪些statement? (1-6) \2. Exception2,执行哪些statement? (1-6) \3. Exception1,执行哪些statement? (1-6) \4. Exception0,执行哪些statement? (1-6) 重抛异常 Rethrowing Exceptions (重抛出异常) 1.1. An exception handler can rethrow the exception (异常处理程序可以重新抛出异常) 1.2. When to rethrow an exception? (1) if the handler cannot process the exception (当它无法处理该异常) (2) the handler simply wants to let its caller be notified (或想通知它的调用者发生了一个异常) try { // statements; } catch (TheException &ex) { // Do something; throw; } Rethrow Another Exception An exception handler can throw another exception other than the captured exception (异常处理程序可以重新抛出另一个不同于已经捕获异常) class MyException: public logic_error { }; try { throw logic_error(); // 抛出派生异常对象 } catch (logic_error& e) { // catch参数为基类异常,但可以捕获所有派生类异常对象 //MyException* p = dynamic_cast MyException& p = dynamic_cast< MyException&>(e); // 引用转换失败会抛 std::bad_cast异常 cout << p.what() << endl; // 上面抛异常,本语句被跳过 } 重抛异常 Rethrowing Exceptions (重抛出异常) 1.1. An exception handler can rethrow the exception (异常处理程序可以重新抛出异常) 1.2. When to rethrow an exception? (1) if the handler cannot process the exception (当它无法处理该异常) (2) the handler simply wants to let its caller be notified (或想通知它的调用者发生了一个异常) try { // statements; } catch (TheException &ex) { // Do something; throw; } Rethrow Another Exception An exception handler can throw another exception other than the captured exception (异常处理程序可以重新抛出另一个不同于已经捕获异常) class MyException: public logic_error { }; try { throw logic_error(); // 抛出派生异常对象 } catch (logic_error& e) { // catch参数为基类异常,但可以捕获所有派生类异常对象 //MyException* p = dynamic_cast MyException& p = dynamic_cast< MyException&>(e); // 引用转换失败会抛 std::bad_cast异常 cout << p.what() << endl; // 上面抛异常,本语句被跳过 } 何时应该使用异常? When to Use Exceptions (何时使用异常机制) 1.1. Throw an exception when your program can identify an external problem that prevents execution. (当一个外部的问题阻止你的程序运行时,抛异常) (1) 从服务器接收到非法数据 (2) 磁盘满了 (3) 宇宙射线阻止你查询数据库 1.2. Throw an exception if the function couldn’t do what it advertised, and establish its postconditions (如果函数无法完成它所告知的功能并建立其正常的后置状态,抛异常) 构造函数失败。例如vector的构造函数应创建一个对象,但对象占内存太大导致无法构建,那么应该抛异常 参考资料 àhttps://stackoverflow.com/a/4506872/3242645 àhttps://isocpp.org/wiki/faq/exceptions Not to Use Exceptions (何时不用异常) 2.1. Simple errors that may occur in individual functions are best handled locally without throwing exceptions. (只发生在单独函数中的简单错误不要用异常处理) 2.2. Do not use throw to indicate a coding error in usage of a function (不要用异常处理编码错误) 可以用assert()中断程序执行然后调试 2.3. Do not use exceptions for control flow (不要用异常来控制程序流程) 不要用throw来结束循环 2.4. 适度:不可不用,不可多用 实时系统中不用异常(航天飞机控制程序、生命维持系统等) 变量/对象命名的2个编码规范 \23. The prefix n should be used for variables representing a number of objects. \23. 表示对象数量的变量名应加前缀 n 例如:nPoints, nLines 24.The suffix No should be used for variables representing an entity number. \24. 代表实体编号的变量应加后缀 No 例如:tableNo, employeeNo 另外一种比较优雅的替代方法是,给这种变量加上前缀 i,例如: iTable, iEmployee. 这样让他们的名字变成一个迭代器 这个作业的难点在于处理虚部的正负号。相关的技术如下 提示10:流插入/提取运算符的原型 friend ostream& operator <<(ostream& os, const MyComplex& z); friend istream& operator >>(istream& is, MyComplex& z); 提示20:重载流插入/提取运算符时,类内和类外的原型写法一样吗? 你说那能一样吗?能一样吗?一样吗?样吗?吗? 外面写的时候,是不用 friend 这个关键字的 提示30:输出时,怎么弄出虚部前面的“+”符号 例如,输入 3 1.0,那么输出为 3+1i,其中1.0前面的那个加号该怎么搞出来? 有2个方法: ==法1== 判断虚部是否为非负,若非负的话,在输出虚部之前,先输出一个“+”字符 ==法2== ostream这个类提供了setf()函数,可以设置数值输出时是否要带标志位。【具体可以查阅 setf 函数】 ostream os; os.setf(std::ios::showpos); os << 12; //输出 +12 ostream这个类还提供了unsetf()函数,可以将某个标志位取消。 当你使用了setf,输出复数 1+2i 时 os.setf(std::ios::showpos); os << real << image << "i"; 就会显示“+1+2i”。怎样把实部前面的那个正号去掉呢? 此时,你需要用两条语句,分别输出复数的实部和虚部。输出实部前,使用 os.unsetf(std::ios::showpos); 输出虚部前,使用 os.setf(std::ios::showpos); ==法3== 与法2类似,但是使用flags()这个函数来读/写当前所有的格式化标志位,具体方法,请查阅 flags() 函数(https://cppreference.com,或者 https://cplusplus.com ) MyComplex MyComplex::operator * (const MyComplex& z){ //(a+bi)·(c+di)=(ac-bd)+(bc+ad)i MyComplex t; // 做乘法,结果存入t中 return t; } MyComplex MyComplex::operator / (const MyComplex& z){ //(a+bi)/(c+di)=[(ac+bd)/(c²+d²)]+[(bc-ad)/(c²+d²)]i if ((z.real_ == 0) && (z.image_ == 0)) { cout << "Divisor can not be 0"; exit(0); } MyComplex t; t.real_ = // 求实部 t.image_ = // 求虚部 return t; } 元编程与泛型编程 Concepts of X-Programming (“编程”的概念) Programming(编程): Writing a program that creates, transforms, filters, aggregates and otherwise manipulates data. (写一个程序去处理数据) Metaprogramming(元编程): Writing a program that creates, transforms, filters, aggregates and otherwise manipulates programs.(写个程序去处理程序) Generic Programming(泛型编程): Writing a program that creates, transforms, filters, aggregates and otherwise manipulates data, but makes only the minimum assumptions about the structure of the data, thus maximizing reuse across a wide range of datatypes.(写个程序去处理数据,但是只对数据的结构做最小假设以使该程序能重用于处理广泛的数据类型) C++: Meta / Generic Programming (C++的元编程和泛型编程) 2.1. C++ implements MetaProgramming with "template" to produce template instance, i.e. programs, in compiling time. (C++用模板实现元编程,由编译器在编译期根据模板生成模板实例,也就是程序) Generic programming in C++ (i.e. compile-time polymorphism) is accomplished by metaprogramming (i.e. code generation from templated code).(C++的泛型编程,即编译时多态,是藉由元编程实现的,也就是由代码模板生成代码) 初识模板 Templates Basics (模板基础) 1.1. Why Templates? (用一个例子来看为何需要模板) 1.2. To find the maximum of two integers, two doubles, and two characters (求二整数、二双精度浮点数、二字符的最大者) three different functions (在C中,用三个函数) int maxInt(int x, int y); double maxDouble(double x, char maxChar(char x, char y); three overloaded functions: (C++中,用重载函数) int maxValue(const int &value1, const int &value2) { if (value1 > value2) else } double maxValue(const double &value1, const double &value2){ if (value1 > value2) else } char maxValue(const char &value1, const char &value2) { if (value1 > value2) else } Function with generic type (带有泛型的函数) We Expect: GenericType maxValue (const GenericType &x, if (x > y) else } ...... int main() { cout << maxValue(1, 3); cout << maxValue(1.0, 3.0); cout << maxValue('a', 'x'); return 0; } U Advantages U save typing; save space; easy to maintain; 函数模板 Function template (函数模板) 1.1. C++ introduces function template with generic types. (C++引入了带有泛型的函数模板) 1.2. How to specify a type parameter? (如何声明类型参数)? Multiple type parameters (多个类型参数) 2.1. What if a function template has more than one parameter? (若函数模板有多于一个类型参数该怎么处理?) 类型参数间用逗号分隔 template < typename T, typename S > auto add (T x1, S x2) { //C++14 return (x1 + x2); } 2.2. 编码规范 \8. Names representing template types should be a single uppercase letter. \8. 用于表示模板类型的名字应该使用一个大写字母 例如:template template Using Function Template (使用函数模板) template auto add (T x1, S x2) { //C++14 return (x1 + x2); } int main () { auto y = add ( 1, 2.0 ); return 0; } 编译器根据函数调用的实参,生成函数模板的实例: 函数模版实例化 What is function template instantiation (什么是函数模板实例化) 1.1. Function Template & Instantiation(实例化): A function template is just a blueprint, not a type, or a function. (函数模板只是蓝图,本身不是不是类型、函数) 编译器扫描代码,遇到模版定义时,并不立即产生代码 The template arguments must be determined so that the compiler can generate an actual function (确定模板实参后,编译器生成实际函数代码) 1.2. 两种实例化方法 (确定模板实参的方法) Explicit instantiation (显式实例化) Implicit instantiation (隐式实例化) Explicit instantiation (显式实例化) 强制某些函数实例化,可出现于程序中模板定义后的任何位置。 template < typename T > void f( T s ){ std::cout << s << '\n'; } template void f // void f(double s) { // T: double template void f<>(char); // 实例化 f template void f(int); // 实例化 f Implicit instantiation (隐式实例化) 编译器查看函数调用,推断模版实参,实现隐式实例化。 #include template void f(T s) { } int main(){ } 4.Instantiated function/class (实例函数/实例类) 4.1. C++11 (Section:14.7) A function instantiated from a function template is called an instantiated function. A class instantiated from a class template is called an instantiated class.(由函数模板实例化得到的函数叫做“实例函数”,由类模板实例化得到的类叫做“实例类”) 4.2. 非正规称呼 template function / template class Example: Selection Sort 例子:选择排序 Example: a function template "sort" 选择排序(list[], size) { for (i : 0 to size-2) { // 初始化 list[i] -> min i -> index // 在 list[i..size-1]**中找到最小的 for (j : i+1 to size-1) { if (min > list[j]) { list[j]-> min j ->index } } // list[i] ß-> list[index] if (index != i) { list[i] -> list[index]; min -> list[i] } } 将一个函数泛型化 Developing generic function (设计泛型函数) 1.1. Steps (步骤) (1) To start with non-generic function (先设计/编写一个非泛型函数) (2) To debug and test it (调试/测试该函数) (3) To convert it to a generic function (将上述非泛型函数转换为泛型函数) 1.2. How to transform(如何泛型化) (1) What DATA does the function process? (函数处理哪些数据) (2) What are the TYPEs of the DATA?(数据的类型是什么) (3) Transform the TYPEs to TYPE Parameters Generic Sort Function (泛型排序函数) 2.1. 非泛型函数原型 void selectionSort(double list[] , const int size); 2.2. 泛型函数原型 template void selectionSort(T list[] , const int size); 类模板 Class Template (类模板) 1.1. 类模板是将类中某些类型变为泛型,从而定义一个模板 1.2. Generic types of class members (类成员的泛型) Data field member(数据域成员) : can be of a generic data type (可以成为泛型数据) Function member(函数成员): return type, parameter type, local var type (返回值类型、参数类型、局部变量欸行可以成为泛型) 2.Class Templates 3.Syntax for class template (类模板的语法) template class Stack { public: Stack(); bool empty(); T peek(); T push(T value); T pop(); int getSize(); private: T elements[100]; int size; }; //**模板前面放 //**类型名后跟 template<typename T> bool Stack<T>::empty() { return (size == 0); } //**对比 non-template class // int Stack::peek() template<typename T> T Stack<T>::peek() { return elements[size - 1]; }
};99. Overloading Object Converting Operator
100. Overloading the = Operator
const Employee& e);
101. More on Operator Overloading
102. More Coding Guidelines
if (n1 % j == 0)
g = j;
103. Overview of Exception-Handling
if (number2 == 0)
throw number1;
cout << number1 << " / " << number2 <<
" is " << (number1 / number2) << endl;
cout << "Exception: an integer " << e <<
" cannot be divided by zero" << endl;
(2) or from function;
104. Exception-Handling Advantages
cout << number1 << " / " << number2 << " is ";
cout << (number1 / number2) << endl;
cout << "Divisor cannot be zero" << endl;
if (number2 == 0)
throw number1;
cout << number1 << " / " << number2 <<
" is " << (number1 / number2) << endl;
cout << "Exception: an integer " << e <<
" cannot be divided by zero" << endl;
int number2) {
throw number1;
int x = quotient(1, 0);
std::cout << "除数为0!";
105. Exception Match and Exception Classes
106. Build-in Exception Classes
107. Custom Exception Classes
108. Catch Multiple Unrelated Exceptions
c.foo(1);
c.foo(2);
cout << a.what() << endl;
cout << b.what() << endl;
109. Catch Derived Exceptions
cout << p->what() << endl;
cout << e.what() << endl;
110. C++11:noexcept
<< "Is may_throw() noexcept? "
<< noexcept (may_throw()) << '\n'
<< "Is no_throw() noexcept? "
<< noexcept (no_throw()) << '\n';
111. Exception Propagation
112. Rethrowing Exceptions
113. Rethrowing Exceptions
114. When Do We Use Exceptions?
115. Meta-Programming and Generic Programming
116. Template Basics
double y);
return value1;
return value2;
return value1;
return value2;
return value1;
return value2;
const GenericType &y) {
return x;
return y;
117. Function Template
118. Function Template Instantiation
// std::cout << s << '\n';
// }
std::cout << s << '\n';
f
119. Make a Function Generic
120. Class Template