C++ 学习指南基础(四)

目录

 

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

108. Catch Multiple Unrelated Exceptions

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


81. Formating Output

格式化输出

  1. setw manipulator(“设置域宽”控制符)

要包含头文件 <iomanip>

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

  1. setprecision manipulator(“设置浮点精度”控制符)

2.1. setprecision(int n)

(1) 控制显示浮点数的有效位

(2) n代表数字总位数

#include <iostream>

#include <iomanip>

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

  1. setfill manipulator(“设置填充字符”控制符)

3.1. setfill(c)

设置填充字符,即“<<"符号后面的数据长度小于域宽时,使用什么字符进行填充。

std::cout << std::setfill('*') << std::setw(5) << 'a' << std::endl;

输出:

****a

  1. 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

82. Functions for I/O Stream

用于输入/输出流的函数

  1. 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<char , SIZE> name{};

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;

}

  1. 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);

  1. flush()

3.1. Flush output stream buffer (将输出流缓存中的数据写入目标文件)

ostream& flush();

3.2. 用法

cout.flush(); // 其它输出流对象也可以调用 flush()

cout << "Hello" << std::flush; // 与endl类似作为manipulator的调用方式

83. File Open Mode

文件的打开模式

  1. 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 打开文件以二进制模式读写
  1. 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);

84. Introduction to Binary IO

二进制输入输出简介

  1. 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

  1. 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. (文本模式的读写是建立在二进制模式读写的基础上的,只不过是将二进制信息进行了字符编解码)

  1. 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();

85. How to Do Binary Input/Output

如何实现二进制读写

  1. 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函数将字节序列写入文件)

  1. 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<dataType>(address)

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*>(&x); // variable address

char* p2 = reinterpret_cast<char*>(a); // array address

char* p3 = reinterpret_cast<char*>(&str); // object address

  1. 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<char *>(&value), sizeof(value));

cout << value;

86. File Positioner

文件位置指示器

  1. 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(文件光标):借用数据库中的“光标”概念

  1. Example of File Positioner

aFileStream.get() ---> fp = fp + 1

87. Random Access File

随机访问文件

  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表示写文件
  1. 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字节处

88. Operators and Functions

运算符与函数

  1. Special Operators Usage with Objects (与对象一起用的运算符)

1.1. string类:使用“+”连接两个字符串

string s1("Hello"), s2("World!");

cout << s1 + s2 << endl;

1.2. array 与 vector类:使用[] 访问元素

array<char, 3> a{};

vector<char> v(3, 'a'); //'a', 'a', 'a'

a[0] v[1] = 'b';

1.3. path类:使用“/”连接路径元素

std::filesystem::path p{};

p = p / "C:" / "Users" / "cyd";

  1. 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中可以定义新的运算符

89. 2D Vector Class

平面向量类

  1. How to describe 2D vector in C++(如何在C++中描述平面向量)

1.1. C++ STL vector: 变长数组

1.2. 向量数据成员

double x, double y

或者 std::array<double, 2> v_;

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

  1. Vec2D类

  1. The steps of creating Vec2D class(创建平面向量类的步骤)

3.1. Test-Driven Development (TDD),测试驱动开发

不是软件测试方法,而是开发设计方法

Kent Beck 《测试驱动开发》

3.2. Steps(步骤)

(1) 先编写测试代码,而不是功能代码

(2) 编译运行测试代码,发现不能通过

(3) 做一些小小的改动(编写功能代码),尽快地让测试程序可运行

(4) 重构代码,优化设计

  1. Some Functions

4.1. <cmath>

double atan(double x); //返回x的反正切值,以弧度为单位。有其他类型重载

double sqrt(double x); //返回x的平方根。有其它类型重载

double pow (double b, double exp); // 返回b的exp次方。有其它类型重载

编码规范

  1. 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.

  2. 一般变量的名字应该与变量的类型相同;特定变量都是有角色的,这类变量经常用角色加上类型命名

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;

90. C++ Operator Functions

C++运算符函数

  1. 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;

  1. 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 (不可创造新的运算符)

  1. 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<int, int> m; m[1] = 2; 调用 m.operator[](1)
a-> (a).operator-> ( ) 不能是非成员 auto p = std::make_unique<S>(); p->bar() 调用 p.operator->()
a@ (a).operator@ (0) operator@ (a, 0) std::vector<int>::iterator i = v.begin(); i++ 调用 i.operator++(0)
此表中,@ 是表示所有匹配运算符的占位符:@a 为所有前缀运算符,a@ 为除 -> 以外的所有后缀运算符,a@b 为除 = 以外的所有其他运算符      

91. C++11: Left Value, Pure Right Value and eXpiring Value

C++11: 左值、纯右值与将亡值

  1. C++03 lvalue and rvalue (C++03的左值和右值)

通俗理解

(1) 能放在等号左边的是lvalue

(2) 只能放在等号右边的是rvalue

(3) lvalue可以作为rvalue使用

  1. 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/

  1. 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&&b、a||b、~a)(a==b、a>=b、a<b)

(5) 取地址(&x)

3.3. 左值可以当成右值使用

  1. 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; // 右值引用做左值使用

}

92. Overloading General Binary Arithmetic Operators

重载一般二元算术运算符

  1. 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

}

  1. Example

Vec2D v1(2, 4);

Vec2D v2 = v1 + Vec2D(2, 3)

cout << "v1 is " << v1.toString() << endl;

cout << "v2 is " << v2.toString() << endl;

93. Overloading Shorthand Binary Arithmetic Operators

重载复合二元算术运算符

  1. 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); //临时的匿名对象

}

  1. Example

Vec2D v2(2, 4);

v2 += Vec2D(2, 3);

cout << "v2 is " << v2.toString() << endl;

94. Overloading [] Operator

重载[]运算符

  1. 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);

}

}

  1. [] 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.

}

95. Overloading the Unary Operator

重载一元运算符

  1. 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)

96. Overloading the - Operators

重载负号运算符

  1. 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); // 返回匿名临时对象

}

97. Overloading the ++ and -- Operators

重载++和--运算符

  1. ++ 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)

  1. 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);
  1. 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;

}

98. Overloading << / >> Operator

重载流插入/提取运算符

  1. 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 (流提取/插入运算符是二元运算符)

  1. Overloading << and >> as friend (重载为友元)

2.1. 使用<<和>>运算符时,第一个参数是流类的实例:cout << x<<y;

运算符重载为类成员函数后,当调用该运算符时,左操作数必须是该类的实例。若<<和>>重载为成员函数,则只能用 v1<<cout;

<< (>>) 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;

99. Overloading Object Converting Operator

重载对象转换运算符

  1. 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<double>(v1); // e: 5.0

100. Overloading the = Operator

重载赋值运算符

  1. 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;

  1. Overloading the = Operators

To change the way the default assignment operator = works, you need to overload the = operator. (重载赋值运算符,会改变其默认工作方式)

一般情况下,如果拷贝构造函数需要执行深拷贝,那么赋值运算符需要重载

const Employee operator=(

    const Employee& e);

employee1 = employee1 = employee1;

  1. 默认赋值运算符的 Shallow Copy (浅拷贝)

Employee e1{"Jack", Date(1999, 5, 3), Gender::male};

Employee e2{"Anna", Date(2000, 11, 8), Gender:female};

e2 = e1; //默认赋值运算符,执行一对一成员拷贝

  1. 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; // 调用重载赋值运算符

 


101. More on Operator Overloading

运算符重载的更多说明

  1. 能重载运算符操作基础数据类型吗

1.1. 重载的运算符必须和用户定义的class类型一起使用

1.2. 重载的运算符的参数至少应有一个是类对象(或类对象的引用)

参数不能全部是C++的标准类型

以防止用户修改用于标准类型数据的运算符的性质。

  1. 以友元函数实现部分运算符

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); }

102. More Coding Guidelines

更多编码规范

  1. variable names

for (int i = 1; i < 5; i++){

for (int j = 1; j <= n1; j++){

 if (n1 % j == 0)
 ​
   g = j;

}

}

  1. Iterator variables should be called i, j, k etc. Variables named j, k etc. should be used for nested loops only

  2. 迭代变量应使用i,j,k等字母。j,k等应仅用于嵌套循环中

  1. Variables with a large scope should have long names, variables with a small scope can have short

names [1].

  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 <fstream>

#include <iomanip>

#include <qt/qbutton.h>

#include <qt/qtextfield.h>

#include "com/company/ui/PropertiesDialog.h"

#include "com/company/ui/MainWindow.h"

包含文件的路径中,一定不要使用绝对路径

  1. Include statements must be located at the top of a file only.

  2. 包含语句必须放在文件的顶部

将包含语句放到源代码的其它位置,可能会导致预期之外的编译副作用

103. Overview of Exception-Handling

异常处理概览

  1. Necessity of Exception Handling (错误处理的必要性)

1.1. Example

reads in two integers (读二数)

and displays their quotient.(求商)

#include <iostream>

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不能做除数)

  1. 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 {

 if (number2 == 0)
 ​
   throw number1;
 cout << number1 << " / " << number2 <<
 ​
         " is "  << (number1 / number2) << endl;

}

catch (int e) {

 cout << "Exception: an integer " << e <<
 ​
         " cannot be divided by zero" << endl;

}

cout << "Execution continues ..." << endl;

}

  1. Example Snippet for try-throw-catch (踹扔抓的代码块示例)

try {

Code to try;

throw an exception (1) with a throw statement

                       (2) or from function;

More code to try;

}

catch (type e) {

Code to process the exception;

}

104. Exception-Handling Advantages

异常处理机制的优点

  1. 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 <iostream>

using namespace std;

int main() {

// Read two intergers

cout << "Enter two integers: ";

int number1, number2;

cin >> number1 >> number2;

if (number2 != 0) {

 cout << number1 << " / " << number2 << " is ";
 ​
 cout << (number1 / number2) << endl;

}

else {

 cout << "Divisor cannot be zero" << endl;

}

}

// Exception Handling

#include <iostream>

using namespace std;

int main() {

// Read two intergers

cout << "Enter two integers: ";

int number1, number2;

cin >> number1 >> number2;

try {

 if (number2 == 0)
 ​
   throw number1;
 cout << number1 << " / " << number2 <<
 ​
         " is "  << (number1 / number2) << endl;

}

catch (int e) {

 cout << "Exception: an integer " << e <<
 ​
         " cannot be divided by zero" << endl;

}

cout << "Execution continues ..." << endl;

}

  1. Exception-Handling Advantages (异常处理机制的优点)

Advantages: bring the exceptional information in callee to the caller (优点:可将异常信息从被调函数带回给主调函数)

//用异常处理

int quotient(int number1,

          int number2) {

if (number2 == 0)

 throw number1; 

return number1 / number2;

}

int main() {

try {

 int x = quotient(1, 0);

} catch (int) {

 std::cout << "除数为0!";

}

}

若不用异常处理:

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()?

105. Exception Match and Exception Classes

异常匹配与异常类

  1. 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;

}

  1. 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

}

106. Build-in Exception Classes

内建异常类

  1. Base Class of Exception in Standard Library(标准库中的异常基类)

1.1. #include <exception>

1.2. class exception;

exception(); // 构造函数

virtual const char* what(); //返回解释性字符串

what()返回的指针指向拥有解释信息的空终止字符串的指针。该指针保证在获取它的异常对象被销毁前,或在调用该异常对象的非静态成员函数前合法

1.3. exception 是标准库所有异常类的基类

  1. Exception Classes in Standard Library(标准库中的异常类) 注意,在使用所有标准库异常类的时候,都必须附加std名字空间。

例如,在使用 bad_alloc 异常类时:

  1. std::bad_alloc ex{"Out of memory"};

  2. throw ex;

107. Custom Exception Classes

自定义异常类

  1. 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()函数)

  1. Vec3D类

2.1. DIMENSION: 向量维度。本例为3

2.2. vec:数组,存放向量元素

2.3. operator [] 通过数组下标形式读取或者修改向量的元素

需检查下标是否属于区域[0, DIMENSION-1]

如果越界,抛出异常

  1. What do the shoes look like? (鞋长什么样)

  1. 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

108. Catch Multiple Unrelated Exceptions

捕获多种无关异常

  1. 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 {

 c.foo(1);
 ​
 c.foo(2);

} catch (EA& a) {

 cout << a.what() << endl;

} catch (EB& b) {

 cout << b.what() << endl;

}

}

109. Catch Derived Exceptions

捕获派生异常

  1. 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<MyException*>(&e); // 转指针失败不会再抛异常

if (p != nullptr)

 cout << p->what() << endl;

else

 cout << e.what() << endl;

}

U dynamic_cast<NewType>(obj) U

  1. 若转型失败且NewType是指针类型,则返回nullptr。

  2. 若转型失败且NewType是引用类型,则抛出std::bad_cast类型的异常

  1. 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) {

...

}

110. C++11:noexcept

C++11的noexcept

  1. Why noexcept

1.1. C++03将throw(ExceptionType)放到函数后面,说明函数会抛出什么类型的异常,也被称为“异常规约”

java用 throws关键字做同样的事情

C++中基本没人用“异常规约”

1.2. C++11使用noexcept指明函数是否抛出异常

若函数不抛异常,则可做编译优化

即便函数抛异常,也不再说明所抛异常类型(简化)

  1. 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

}

  1. Usage of noexcept operator(noexcept运算符的用法)

bool noexcept( expression )

noexcept 运算符进行编译时检查,若表达式声明为不抛出任何异常则返回 true

void may_throw();

void no_throw() noexcept;

int main() {

std::cout << std::boolalpha

         << "Is may_throw() noexcept? "
 ​
         << noexcept (may_throw()) << '\n'
 ​
         << "Is no_throw() noexcept? "
 ​
         << noexcept (no_throw()) << '\n';

}

111. Exception Propagation

异常传播

  1. Exception Propagation (异常传播)

1.1. 什么是异常传播?

1.2. Nested Function Call (嵌套的函数调用)

每个函数中都有 try-catch

内层函数抛出异常

1.3. which "catch" can do?

U 关注2点 U

\1. try块中抛出异常的语句后面的语句

\2. catch的异常类型与所需匹配的异常实例的类型

  1. 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)

112. Rethrowing Exceptions

重抛异常

  1. 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;

}

  1. 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*>(&e); // 转指针失败不会再抛异常

MyException& p = dynamic_cast< MyException&>(e); // 引用转换失败会抛 std::bad_cast异常

cout << p.what() << endl; // 上面抛异常,本语句被跳过

}

113. Rethrowing Exceptions

重抛异常

  1. 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;

}

  1. 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*>(&e); // 转指针失败不会再抛异常

MyException& p = dynamic_cast< MyException&>(e); // 引用转换失败会抛 std::bad_cast异常

cout << p.what() << endl; // 上面抛异常,本语句被跳过

}

114. When Do We Use Exceptions?

何时应该使用异常?

  1. 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

  1. 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; }

115. Meta-Programming and Generic Programming

元编程与泛型编程

  1. 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.(写个程序去处理数据,但是只对数据的结构做最小假设以使该程序能重用于处理广泛的数据类型)

  1. C++: Meta / Generic Programming (C++的元编程和泛型编程)

2.1. C++ implements MetaProgramming with "template" to produce template instance, i.e. programs, in compiling time. (C++用模板实现元编程,由编译器在编译期根据模板生成模板实例,也就是程序)

  1. Generic programming in C++ (i.e. compile-time polymorphism) is accomplished by metaprogramming (i.e. code generation from templated code).(C++的泛型编程,即编译时多态,是藉由元编程实现的,也就是由代码模板生成代码)

116. Template Basics

初识模板

  1. 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,

              double y);

char maxChar(char x, char y);

three overloaded functions: (C++中,用重载函数)

int maxValue(const int &value1, const int &value2) {

if (value1 > value2)

 return value1;

else

 return value2;

}

double maxValue(const double &value1, const double &value2){

if (value1 > value2)

 return value1;

else

 return value2;

}

char maxValue(const char &value1, const char &value2) {

if (value1 > value2)

 return value1;

else

 return value2;

}

  1. Function with generic type (带有泛型的函数)

We Expect:

GenericType maxValue (const GenericType &x,

                   const GenericType &y) {

if (x > y)

 return x;

else

 return y;

}

......

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;

117. Function Template

函数模板

  1. Function template (函数模板)

1.1. C++ introduces function template with generic types. (C++引入了带有泛型的函数模板)

1.2. How to specify a type parameter? (如何声明类型参数)?

<typename T> : 描述性强,较好

<class T> : 易与类声明混淆,不推荐

  1. 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<class T> ...

template<class C, class D> ...

  1. Using Function Template (使用函数模板)

template <typename T, typename S>

auto add (T x1, S x2) { //C++14

return (x1 + x2);

}

int main () {

auto y = add ( 1, 2.0 );

return 0;

}

编译器根据函数调用的实参,生成函数模板的实例:

118. Function Template Instantiation

函数模版实例化

  1. 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 (隐式实例化)

  1. Explicit instantiation (显式实例化)

强制某些函数实例化,可出现于程序中模板定义后的任何位置。

template < typename T >

void f( T s ){

std::cout << s << '\n';

}

template void f<double>(double); // 实例化,编译器生成代码

// void f(double s) { // T: double

                //      std::cout << s << '\n';
 ​
               //  }

template void f<>(char); // 实例化 f<char>(char) ,推导出模板实参

template void f(int); // 实例化 f<int>(int) ,推导出模板实参

  1. Implicit instantiation (隐式实例化)

编译器查看函数调用,推断模版实参,实现隐式实例化。

#include <iostream>

template<typename T>

void f(T s) {

 std::cout << s << '\n';

}

int main(){

 f<double>(1); // 实例化并调用 f<double>(double)
 ​
 f<>('a'); // 实例化并调用 f<char>(char)
 ​
 f(7); // 实例化并调用 f<int>(int)
 ​
 void (*ptr)(std::string) = f; // 实例化 f<string>(string)

}

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]

}

}

119. Make a Function Generic

将一个函数泛型化

  1. 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

  1. Generic Sort Function (泛型排序函数)

2.1. 非泛型函数原型

void selectionSort(double list[] , const int size);

2.2. 泛型函数原型

template <typename T>

void selectionSort(T list[] , const int size);

120. Class Template

类模板

  1. 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<typename T>

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];

}

发布了6 篇原创文章 · 获赞 0 · 访问量 211

猜你喜欢

转载自blog.csdn.net/LYR1994/article/details/105341323
今日推荐