C++ 操作重载与类型转换 《C++Primer》第14章 读书笔记
本章内容一览:
1、基本概念 和 限制条件
- 只有重载的函数调用运算符
operator()
能有默认实参,其他重载运算符不能有默认实参。 - 一个重载的运算符,至少含有一个类类型的参数。
- 可被重载的运算符:
- 一般不重载
&&
||
,
&
这几个运算符 - 返回值类型一般与内置版本兼容:
- 逻辑和关系运算符返回
bool
- 逻辑和关系运算符返回
- 算术运算符返回类类型的值
- 赋值运算符 和 复合运算符 返回左侧运算对象的引用
- 若一个运算符是成员函数,则他的左侧运算对象绑定到隐式的
this
指针上。这就是为什么后面重载<<
>>
运算符不能是成员函数的原因。
2、定义为 成员 还是 非成员?
什么是对称性呢?举个例子:
计算一个int
和double
的和,他们中的任意一个都可以是左侧运算对象或右侧运算对象,所以加法就是对称的。如果想计算 含有类对象混合的表达式,则运算符必须定义成非成员函数。
当把运算符定义为成员函数时,他的左侧运算对象必须是运算符所属类的对象。
练习题:
说明g:==
具有对称性,所以定义为非成员
3、重载输入运算符
3.1 注意处理输入失败:
X& operator>>(istream& is, X& x)
{is >> 输入巴拉巴拉.....if (!is) 这个检查是一次性检查,没有逐个检查每个读取操作x = X(); 输入失败,赋予对象默认状态else 某些成员需要由刚刚出入的数据计算出来,则在确保输入正常的情况下进行return is;
}
发生输入错误可能是如下原因:
- 输入的数据类型与代码要求的不符
- 读取到达文件末尾,或遇到其他错误
3.2 标示错误
4、算术运算符 和 复合赋值运算符
因为使用复合赋值运算符来实现算数运算符是很方便的。如下所示:
Sale_data operator+(const Sale_data& s1, const Sale_data& s2)
{Sale_data sum = s1;sum += s2; 直接使用复合赋值运算符就行了,而不用逐成员相加return sum;
}
5、相等运算符
- 定义了
==
也应该定义!=
!=
==
其中一个定义好之后,直接用他去实现另一个
bool operator==(const Sale_data& s1, const Sale_data& s2)
{return s1.isbn() == s2.isbn() &&s1.units_sold == s2.units_sold &&s1.reevnue == s2.revenue;
}bool operator!=(const Sale_data& s1, const Sale_data& s2)
{return !(s1 == s2); 就像这样,不要傻不拉几再逐个比一遍了
}
6、关系运算符
也就是说要有严密的逻辑自洽。当两个对象经!=
运算返回true
,那么必有一个是<
另一个的。
7、赋值运算符
之前学的拷贝赋值运算符只是赋值运算符重载中的一种。我们还可以用很多其他类型给目标赋值。
如下面的例子:
class StrVec
{public:StrVec& operator=(initializer_list<string> l){auto data = alloc_n_copy(l.begin(), l.end());free(); 不管什么样的赋值运算符都要先销毁掉原来的内存空间elements = data.first;first_free = cap = data.second;return *this;}
};int main()
{StrVec s;s = { "hello", "world" };return 0;
}
上例实现了自定义类型StrVec
的列表赋值。
无论什么赋值运算符,都要先销毁原来的内存。在创建新空间。
类的所有赋值运算符都必须定义为成员函数。
这种非拷贝赋值运算符就不用检查自赋值了。
8、下标运算符
- 下标运算符必须是成员函数
- 类的下标运算符通常定义一对,一个返回普通引用,一个返回常量引用。
如下例所示:
class StrVec
{public:string& operator[](size_t n) { return elements[n]; }const string& operator[](size_t n) const { return elements[n]; } const的对象使用下标运算符,就用这个版本
};
9、递增和递减运算符
- 通常定义为成员函数。
- 要同时定义前置 和 后置 版本
9.1 前置版本
class X
{public:X& operator++() { x++; return *this; }X& operator--() { x--; return *this; }
private:int x = 0;
};
注意:返回的是对象的引用。内置版本也是这样的。
9.2 后置版本
虽说是后置版本,但是名字完全一样,所以为了重载他们勉强加一个int
类型的参数进去。这个int
的作用只有区分重载版本:
class X
{public:X operator++(int); 这里只是声明X operator--(int); 函数的定义和前置有所区别
private:int x = 0;
};
注意:返回的是对象的原值(递增减之前的值)。内置版本也是这样的。
在递增减之前要首先记录对象的状态。
X operator++(int) 不需要用到int形参,无需为其命名
{X x = *this; 记录原始状态,便于待会返回++*this; 递增,直接用已实现的前置递增更方便,当然,是对于更复杂的类来说的,这个类太简单了return x; 返回对象原始状态的副本(拷贝)
}X operator--(int) 与后置递增同理
{X x = *this;--*this;return x;
}
10、成员访问运算符*
和->
箭头运算符必须是类的成员,解引用运算符通常也是类的成员,但也可以不是。
重载 ->
- 箭头运算符的作用只能是获取成员。
- 对于形如
point->member
的表达式,point
只能是指向类对象的指针,或者是一个重载了operator->
的类的对象,绝无其它。
11、函数调用运算符
必须是成员函数。
11.1 函数对象
函数对象其实是类,这个类定义了函数调用运算符,当使用这个类的对象调用重载的函数调用运算符时,其形式和调用函数一毛一样,所以叫做函数对象。下面我们举个例子:
class X
{public:int operator()(int value){return value > 0 ? value : -value;}
};int main()
{X x;int a = x(-42); a 的值是42
}
你看,a = x(-42)
,这多像函数调用,可以称X
为函数对象。
函数对象通常作为泛型算法的实参。
就类似lambda
表达式那个作用,还是举个例子,让我们把上面的类改一改。
class X
{public:X(ostream& o = cout, char c = ' ') : os(o), sep(c) { } 构造函数void operator()(const int& x){os << (x > 0 ? x : -x) << sep;}
private:ostream& os;char sep;
};int mina()
{X x;vector<int> v{ -1, 3, 5, -9, -54, 9, -1, -3, -2 };for_each( v.begin(), v.end(), X(cout, '\n') );
}
修改后的类X
有两个成员;分别表示 输出流 和 间隔符。
他的函数调用运算符,能够向给定的输出流 并 以给定间隔符 输出传入的int
的绝对值。
在main
函数中我们使用for_each
对每个v
的元素调用X
的函数调用运算符,就实现了像标准输出流输出v
中所有元素的绝对值,并以换行符为间隔。
这就是上面说的:函数对象通常作为泛型算法的实参
11.2 深入剖析lambda
表达式
当我们编写一个lambda
表达式,编译器将该表达式翻译为一个未命名类的未命名对象,这个对象是一个函数调用运算符。
哇,原来如此,惊为天人,原来是这样。我们举个例子:
stable_sort(words.begin(), words.end(), [](const string& a, const string& s2) { return a.size() < b.size(); }
上面实现了根据长度将string
排序。其lambda
表达式的行为类似于下面的函数对象:
class shorter
{bool operator()(const string& s1, const string& s2){return s1.size() < s2.size();}
};
调用这个函数对象:
stable_sort( words.begin(), words.end(), shorter() );
通过这个例子可一目了然地看到,上面的 lambda
表达式和下面的函数对象是等价的。
那么 捕获 又是怎么一回事呢?
分两种情况:
- 当
lambda
通过引用捕获变量时,由程序确保引用的对象确实存在,故,编译器直接使用该引用, 不用在lambda
产生的类中将其存储为数据成员。 - 当
lambda
通过值捕获方式捕获变量时,变量是被拷贝到lambda
中的,故,lambda
产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,构造函数使用捕获到的值初始化数据成员。
焯!连起来的,知识连起来了!太妙了!!!还是举个例子:
auto first_iter = find_if(words.begin(), words.end(), [size](const string& a) { return a.size() >= size; }
上面能够获得指向序列中第一个长度>= size
的迭代器。
该lambda
产生的类形如:
class size_cpomare
{public:size_compare(size_t n) : size(n) { } 这里得构造函数使用捕获到的值初始化 size 成员bool operator(const string& s){return s.size() >= size;}
private:size_t size; 这里有一个名为 size 的成员,准们保存捕获到的 size 的值
};
调用这个函数对象:
auto first_iter = find_if( words.begin(), words.end(), size_compare(size) );
lambda
表达式产生的类不含默认构造函数、赋值运算符 及 默认析构函数;是否含有默认的拷贝 / 移动构造函数 则通常要视捕获的数据成员类型而定。
这里有个好问题:
11.3 标准库定义的函数对象
下面是标准库定义的表示算术、关系 和 逻辑 运算符的类,每个类都定义了调用运算符,可以执行相应的运算。
标准库定义的这些类都非常好,可以进行我们做不到的操作。例如通过比较指针的地址来排序指针序列。
这些指针可以是毫无关系的指针,我们直接比较两个指针会产生未定义的行为,使用标准库的less
则不会。
vector<string*> v;
sort(v.begin(), v.end(), [](string* a, string* b) { return a < b; } 错误,v里面的指针彼此之间没有关系,<会产生未定义行为。
sort( v.begin(), v.end(), less<string*>() ); 正确,标准库定义的 less 是良好的
注意:
关联容器默认使用less<key_type>
对元素排序,因此可以定义一个指针的set
或在map
中使用指针作为关键字而无须声明less
。
精彩练习:
答案:这个办法就很妙!隐式的把取模的结果转换成bool
类型,正好可以用于判断是否能整除。
bool divided_by_all(vector<int>& vec, int dividend)
{return count_if( vec.begin(), vec.end(), bind2nd(modulus<int>(), dividend) ) == 0;
}
11.4 可调用对象 和 function
类型
目前为止见过的可调用对象有:
- 函数
- 函数指针
lambda
表达式bind
创建的对象- 重载了函数调用运算符的类
- 标准库定义的函数对象(其实这个也算上一条里的)
和其它对象一样,可调用对象也有类型。例如,每个lambda
表达式都有自己唯一的(未命名)类类型。函数和函数指针也有类型,他们的类型由其返回值类型和实参类型决定。
但是,不同类型的可调用对象,可能共享同一种调用形式。
调用形式:是可调用对象的 返回值 和 参数列表 的 组合 。
举个例子,现有如下三种可调用对象:
auto add1 = [](int a, int b) { return a + b; } 这是lambda表达式,是 未命名 的类,用编译器打印 typeid(add1).name() ,是下面这串东西class <lambda_1df7cfe0482636e736c1805ed2e94511>int add2(int a, int b) { return a + b; } 这是函数,是 int (int, int) 类,它的类型其实就是调用形式class add3 这是函数对象,是 add 类
{public:int operator()(int a, int b) { return a + b; }
};
嗯,他们都是可调用表达式,类都不相同。但是他们都有 共同的调用方式 。
那就是:
int(int, int) 返回值 和 参数列表的组合,
根据这一特性,C++推出一个 function
类,允许我们 用一个对象 存储 具有相同调用方式的 可调用对象 。
function
类
function
对象允许我们用一个对象,存储一类具有相同调用方式的可调用对象。
就用上面的三个可调用对象举个例子:
f1, f2, f3, 都具有相同的类型,都是 function<int(int, int)> 类
function<int(int, int)> f1 = add1; f1 存储了可调用对象 add1
function<int(int, int)> f2 = add2; f2 存储了可调用对象 add2
function<int(int, int)> f3 = add3(); f3 存储了可调用对象 add3cout << f1(1, 2); 打印3
cout << f2(1, 2); 打印3
cout << f3(1, 3); 打印3
下面使用function
做一个简易计算器,阅读代码,体会一下function
的用处:
class mul 函数对象,实现乘法
{public:int operator()(int a, int b) { return a * b; }
};int divi(int a, int b) 函数,实现除法
{return a / b;
}int main()
{map<string, function<int(int, int)>> cal; 一个 map,运算符号为键,其实现作为值auto mod = [](int a, int b) { return a % b; }; lambda表达式,实现取模插入元素:cal.insert({ "+", plus<int>() }); 标准库的加法函数对象cal.insert({ "-", [](int a, int b) { return a - b; } }); 未命名的 lambda 对象,实现减法cal.insert({ "*", mul() }); 函数指针cal.insert({ "/", divi }); 用户定义的函数对象cal.insert({ "%", mod }); 命名了的 lambda 表达式int a, b;string s;while (cin >> a >> s >> b){cout << a << s << b << " = " << cal[s](a, b) << endl;}
}
上面代码中名为
cal
的map
是一种叫函数表的东西:
重载函数与function
我们不能直接将重载函数的名字存入function
类型的对象中。如下所示:
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }function(int(int, int)> cal = add; 错误,出现二义性,到底要存哪个 add
即便在声明cal
前面加上了调用方式,仍然不行。
这时应该使用函数指针,而非函数的名字。
int (*fun_ptr)(int, int) = add; 指针'fun_ptr'所指的'add'是接受两个 int 的版本
cal = fun_ptr; 这回对了
12、重载、类型转换 与 运算符
我们已经见过很多内置类型之间的类型转换了,下面将学习自定义类的类型转换。
这其中包含两个方向的转换,从其他类型转换到本类型,通过转换构造函数来完成。
以及从本类型转换成其他类型,通过类型转换运算符完成。
转换构造函数其实和普通的构造函数没啥区别,只不过他只有一个其它类型的参数,用这个参数来构造一个本类型的对象。
12.1 类型转换运算符
类型转换函数的一般形式:
operator type() const; type 是要转换成的类型
type
的类型要求是能作为函数的返回类型(void
除外)。因此不允许传换成数组或函数类型,但可以是数组指针、函数指针或者引用类型。
注意:
- 类型转换运算符必须是成员函数
- 不能声明返回类型
- 形参列表必须为空
- 函数应该用
const
修饰
下面举一个简单的例子:
class X
{public:X(int i = 0) : val(i) { } 转换构造函数, int 类型转 X 类型operator int() { return val; } 类型转换运算符,X 类型转 int 类型
private:size_t val;
};X x;
x = 4; 4 先隐式转换成 X 类型,然后调用合成的赋值运算符
x + 3; x 隐式的转换成 int 类型,然后执行整数加法x = 3.14; 3.14 转换成 int ,int 再转换成 X
x + 3.14; x 先转换成 int,int 再转换成 double
哎停停停停停!最后两行转换代码是不是有什么问题?怎么会自动执行两步类型转换呢???
之前学的分明是:编译器只会自动执行一步类型转换。
嗯,确实只会执行一步。不过这里出现了新的特性。
类型转换运算符的特性:
用户定义的隐式的类型转换可以置于一个内置类型转换之前或之后,并与之一起使用。 只有这种情况下,允许执行两步类型转换。
上面的最后两条语句都是有自定义的类型转换参与,所以可以进行。
因此,可以把任何算术类型传递给X
的构造函数,同理,也能使用类型转换运算符把一个 X
对象转换成int
,然后把得到的int
转换成任何其他算术类型。
不能滥用类型转换运算符:
直接上例子,在早期C++版本中,下面的代码是能编译通过的:
int i = 10;
cin << i; 这里 cin 后接左移运算符,显然是错的,但却能通过编译
istream
本身确实没定义<<
,能通过编译是因为,在早期C++版本的这种情况下,istream
能够隐式的转换成bool
类型,由于bool
是算术类型,所以能执行<<
,就是将bool
的值左移10位。这显然不是我们想要的结果,他应该报错才对。
现实的类型转换运算符:
为了避免上述情况,C++11引入显式类型转换运算符。
在转换运算符前用explicit
修饰,就不会自动执行这一类型转换。同时,(一般情况下)也不能用于隐式类型转换。只能显示的使用它。如下所示:
class X
{public:operator int() { return val; }int val;
};X x;
x + 3; 错误,隐式转化
static_cast<int>(x) + 3; 正确,显示强转转换
上面的规定存在 例外 :
如果表达式被用作条件,则编译器会将explicit
修饰的类型转换运算符自动应用于它。在下列位置,显式类型转换将被隐式的执行:
if
、while
及do
语句的条件部分for
语句的条件表达式!
、||
、&&
这些逻辑运算符? :
条件运算符的条件表达式
这已经是明示你了,C++就是想让你在自定义的类型转换运算符的时候,使用explicit
修饰,同时,要把类类型转换成bool
类型,专门用于这些判断是否为真的情况。不过这是我个人的理解,有时候肯定也是有其他应用情况的。
12.2 避免有二义性的类型转换
有四种由类型转换导致的二义性错误。
12.2.1 两个类定义了转换到同一个类型的成员
图示:
class A
{public:A(const B&); 转换构造函数,B 类型转 A 类型A fun(const A&);
};class B
{public:operator A() const; 转换运算符,B 类型转 A 类型
};B b;
A a = fun(b); fun()里需要一个A类型对象,b需要转换,但是会产生二义性是 fun(B::operator()) 呢? 还是 fun(A::A(const B&)) 呢?
你现在有两种由B
转换成A
的方法,那到底用哪个???这就产生了二义性。
想要区分两种方法,就必须显示的调用相关成员:
A a1 = f(b.operator A()); 用B的类型转换运算符
A a2 = f( A(b) ); 用A的构造函数
解决方法:
不要定义出两种转换成同一类型的方法噻。有一种方法不就够了吗。
12.2.2 涉及到内置类型的多重类型转换
图示:
直接上例子:
class A
{public:A(int = 0); int 转换成 AA(double); double 转换成 Aoperator int() const; A 转换成 intoperator double() const; A 转换成 double
};void fun(long double); 函数,需要 long double 做成员A a; a 到底是先转 int 再转 long double 呢?
fun(a); 还是先转 double 再转 long double 呢?lone lg; lg 到底是先转 int 再转 A 呢?
A a2(lg); 还是先转 double 再转 A 呢?
解决方法:
少定义点类类型 和 算术类型之间的转换,很多算术类型间本来就能转换,你还定义这么多,不是添乱??
12.2.3 重载 和 转换构造函数
直接上例子:
class A
{public:A(int); int 转 A
};class B
{public:B(int); int 转 B
};void f1(const A&); 用 A 重载
void f1(const B&); 用 B 重载
f1(10); 显然,又二义性了f1( A(10) ); 显式的用A,避免二义性
虽说可以通过显式的构造,但是存在这种操作,说明程序设计存在缺陷。
12.2.3 重载 和 转换构造函数(比上一个稍微复杂点)
这个和上一个很想,但要复杂一点:
class A
{public:A(int); int 转 A
};class B
{public:B(double); double 转 B
};void f1(const A&); 用 A 重载
void f1(const B&); 用 B 重载
f1(10); 哎!哎哎哎!!10是 int 欸!是不是不会二义性了?不不不!很可惜,还是会二义性。
分析一波:
f1(const A&)
成立,int
可以转A
,而且是精确匹配f1(const B&)
成立,int
可先转double
,然后double
再转B
啊?这不是有一个可精确匹配吗?虽然但是,不可以!
编译器会报错,这是二义性。
就是因为你有两个类,如果只有一个类的话,里面有int
和 double
的构造函数就不会有二义性了。
这小节很绕,看看这个练习题:
14.50:
看好了LongDouble
并不是内置的long double
,不存在精确匹配一说。
14.51:
这一题告诉我们,内置类型转换优先于自定义的转换构造函数。
12.3 函数匹配 与 重载运算符
注意:本节的讨论的核心是重载运算符。
一言以蔽之,就是下面这句话:
说详细点就是:
当我们使用重载运算符作用于类类型的运算对象时,候选函数中包含该运算符的普通非成员版本和内置版本。除此之外,如果左侧运算对象是类类型,则定义在该类中的运算符的重载版本也包含在候选函数内。
也就是说,若a
是一种类类型,则表达式a sym b
的等价可能是:
a.operator(b); a 的成员函数
operator(a, b); 一个普通的非成员函数
产生二义性的例子:
class X
{friend X operator+(const X&, const X&);
public:X(int = 0);operator int() const { return val; }int val;
};X x1, x2;
X x3 = x1 + x2; 使用重载的operator+成员
int i = s3 + 0; 产生二义性
例题:
这个例题算是您能遇见的最复杂的情况了,把他搞明白,就没问题了。
这是 上图缺少的LongDouble
和SmallInt
的定义:
对于第一个式子ld = si + ld;
:
对于第二个式子ld = ld +si
:仍然使用同样的策略
总结:
当两个类类型相加时,优先考虑一方进行类型之间的转换,再相加,如果不行,再考虑双方都转换成内置类型相加。
再来一个,这个题主要想让你知道,出现二义性,要怎么改:
SmallInt
的定义和上题一样
很明显会产生二义性
所以我们只需 显式的 让他走其中一条路即可。
C++ 操作重载与类型转换 《C++Primer》第14章 读书笔记相关推荐
- C primer plus第二章读书笔记3
进一步使用C 以下是在C中如何使用C计算,运算符的引入使得这个过程变得简单 int main(void) {int feet, fathoms;fathoms = 2;feet = 6 * fatho ...
- C++ Primer 第三版 读书笔记
1.如果一个变量是在全局定义的,系统会保证给它提供初始化值0.如果变量是局部定义的,或是通过new表达式动态分配的,则系统不会向它提供初始值0 2.一般定义指针最好写成:" string * ...
- C++ primer 第14章 操作重载与类型转换
文章目录 基本概念 直接调用一个重载的运算符函数 某些运算符不应该被重载 使用与内置类型一致的含义 选择作为成员或者非成员 输入和输出运算符 重载输出运算符<< 输出运算符尽量减少格式化操 ...
- c++ primer 第14章 习题解答
14.1节 14.1答 不同点: 重载操作符必须具有至少一个class或枚举类型的操作数. 重载操作符不保证操作数的求值顺序,例如对&&和| | 的重载版本不再具有"短路求值 ...
- 《C++ Primer Plus 6th》读书笔记 - 第8章 函数探幽
1. 摘录 默认参数指的是当函数调用中省略了实参时自动使用的一个值. 默认参数并非编程方面的重大突破,而只是提供了一种便捷的方式.使用默认参数,可以减少要定义的析构函数.方法以及方法重载的数量. 试图 ...
- python程序操作的核心_python核心编程-第五章-个人笔记
1.用del删除对对象的引用 >>> a = 123 >>>a123 >>> dela>>>a Traceback (most ...
- mysql中用完即删用什么_MySQL使用和操作总结(《MySQL必知必会》读书笔记)
简介 MySQL是一种DBMS,即它是一种数据库软件.DBMS可分为两类:一类是基于共享文件系统的DBMS,另一类是基于客户机--服务器的DBMS.前者用于桌面用途,通常不用于高端或更关键应用. My ...
- C++ Primer第三章 心得笔记
之前的一章我本末倒置了,我看了一个大佬的此书笔记整理得很详细 很得体.我也想按照他的这种方法 在我学习和敲代码的时候进行记录,但是我发现为了记笔记而记笔记 这种方法使我很累.违背了记录分享交流的初衷. ...
- 《C++ Primer》第14章 14.3节习题答案
<C++ Primer>第14章 操作重载与类型转换 14.3节 算术和关系运算符 习题答案 练习14.13:你认为Sales_data类还应该支持哪些其他算术运算符(参见表4.1,第 ...
最新文章
- C++字符串详解(三) 字符串的查找
- 2.变量/字符串/if/while/数据类型
- C++ 3 基本数据类型
- 【数据结构与算法】之连通网络的操作次数的算法
- arch linux 安装 arm,给树莓派安装 Arch Linux ARM
- Python小白的数学建模课-06.固定费用问题
- 开平区教育局资源分布式存储解决方案
- java文件与bean所定义的_Spring定义bean的三种方式和自动注入
- CCP/XCP和T-BOX知识点
- 31岁零基础转行软件测试,现已成功入职月薪14K+
- android finish 判断当前_Android开发,源码分析finish()和onBackPressed()的区别
- 人生每一件事都是为自己而做
- cad工具箱详细讲解_cad学院派工具箱(cad绘图教程配解析)V20160804 最新版
- 期货开户手续费是怎么查询?
- html怎么读取lrc文件,lrc文件怎么打开?lrc是什么文件?
- shell脚本使用教程3
- 技术分享 | 服务端接口自动化测试, Requests 库的这些功能你了解吗?
- 【FPGA实例】基于FPGA的DDS信号发生器设计
- 【Pytorch】带注释的Transformer (各个部件的实现及应用实例)
- Python - 100天到大师学习笔记(2)
热门文章
- React笔记随笔---kalrry
- 8种基本数据类型转换
- CV中的Attention机制总结
- 关于snp-calling中GATK软件的最佳线程(核心)数
- js逆向第5例:猿人学第8题-验证码图文点选
- ASP.NET2.0雷霆之怒盗链者的祝福
- php setcookie 全局,PHP-setcookie(); 不工作
- Vote3Deep: Fast Object Detection in 3D Point Clouds Using Efficient Convolutional Neural Networks
- 欧几里得算法和更相减损术证明
- 设计模式第十二次作业——观察者模式、状态模式