add a conclusion on c++ constructor and copy control, not finished yet.
This commit is contained in:
53
c++ note/chp13/chp13.cpp
Normal file
53
c++ note/chp13/chp13.cpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
class HasPtr {
|
||||||
|
public:
|
||||||
|
explicit HasPtr(const std::string &s = std::string()) :
|
||||||
|
ps(new std::string(s)), i(0) {}
|
||||||
|
|
||||||
|
//copy constructor
|
||||||
|
HasPtr(const HasPtr& ptr);
|
||||||
|
|
||||||
|
//copy assignment operator
|
||||||
|
HasPtr& operator=(const HasPtr& ptr);
|
||||||
|
|
||||||
|
//destructor
|
||||||
|
~HasPtr(){
|
||||||
|
delete ps;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::string *ps;
|
||||||
|
int i;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
HasPtr::HasPtr(const HasPtr& ptr){
|
||||||
|
ps = new string(*ptr.ps);
|
||||||
|
i = ptr.i;
|
||||||
|
}
|
||||||
|
|
||||||
|
HasPtr& HasPtr::operator=(const HasPtr& ptr){
|
||||||
|
delete ps;
|
||||||
|
ps = new string(*ptr.ps);
|
||||||
|
i = ptr.i;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
HasPtr f(HasPtr fp){
|
||||||
|
HasPtr ret = fp;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
HasPtr ptr1("2020");
|
||||||
|
HasPtr ptr2("2019");
|
||||||
|
HasPtr ptr3 = ptr2;
|
||||||
|
ptr2 = ptr1;
|
||||||
|
ptr3 = ptr1;
|
||||||
|
f(ptr1);
|
||||||
|
system("pause");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
165
c++ note/chp13/chp13.md
Normal file
165
c++ note/chp13/chp13.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
Constructor and Copy Control
|
||||||
|
============================
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
+ 构造函数
|
||||||
|
- 初始化列表
|
||||||
|
- 委派构造函数
|
||||||
|
- 转换构造函数
|
||||||
|
+ 拷贝控制
|
||||||
|
- 拷贝构造函数
|
||||||
|
- 拷贝赋值运算符
|
||||||
|
- 析构函数以及三者之间的联系
|
||||||
|
|
||||||
|
## 构造函数
|
||||||
|
|
||||||
|
### 初始化列表
|
||||||
|
|
||||||
|
初始化列表(Constructor Initializer List)其实是我比较常用的,但是我之前的理解有很多误区,这里对它进行一些深入的讨论。
|
||||||
|
|
||||||
|
初始化列表的功能与在构造函数体内直接赋值是没有任何区别的,比如说下面用初始化列表的代码:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Sales_data::Sales_data(const string &s, unsigned int cnt, double price): bookNo(s), units_sold(cnt), revenue(cnt * price){}
|
||||||
|
```
|
||||||
|
|
||||||
|
与
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Sales_data::Sales_data(const string &s, unsigned int cnt, double price){
|
||||||
|
bookNo = s;
|
||||||
|
units_sold = cnt;
|
||||||
|
revenue = price * cnt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在功能上没有任何区别。我之前也是认为两者完全等价,甚至还更加偏向于第二种写法,因为感觉格式要好看一点。实际上,两者在性能上是不相同的。
|
||||||
|
|
||||||
|
构造函数的执行顺序,是首先执行初始化列表,继而执行构造函数体内的赋值语句。需要注意的是,即使初始化列表为空,编译器仍然会首先对各个成员变量进行默认的初始化操作,因此相对于第一种版本,第二种版本不仅没有剩下执行初始化列表的时间,还增加了函数体内的变量赋值的开销。如果成员变量是一个较大的类类型的话,两者的时间开销差别还是蛮大的。
|
||||||
|
|
||||||
|
因此,应该尽量使用初始化列表来对成员变量进行初始化操作,这样可以获得更优的性能。实际上,在一些情况下,必须使用初始化列表。
|
||||||
|
|
||||||
|
对于某一些类型的变量,比如常量变量(const),或者(左值)引用变量,只能进行初始化操作而不能进行赋值操作。如果一个类含有这些成员变量,则必须使用初始化列表对它们进行初始化。此外,如果一个成员变量是没有默认构造函数的类类型,也必须使用初始化列表对它进行初始化。从这里应该可以看出,尽管在多数情况下都可以忽略,但是初始化和赋值其实是两种不同的操作。
|
||||||
|
|
||||||
|
> 初始化列表的执行顺序问题。
|
||||||
|
|
||||||
|
初始化列表的执行顺序并非是按列表中变量的先后次序,而是按照变量在类定义的次序。因此,下面的代码是存在问题的:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class X {
|
||||||
|
private:
|
||||||
|
int i;
|
||||||
|
int j;
|
||||||
|
public:
|
||||||
|
// undefined: i is initialized before j
|
||||||
|
X(int val): j(val), i(j) { }
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
其中,初始化列表中`i`首先被初始化为`j`,然后再对`j`进行初始化,但是对`i`进行初始化时`j`还是未定义的,就有可能导致后续的错误。
|
||||||
|
|
||||||
|
基于上面的讨论,初始化列表的次序最好与成员变量的定义次序相同,并且最好不要用一个成员变量去初始化另一个成员变量,这样就可以避免可能的错误。比如说下面的代码就不存在初始化列表的执行次序问题了:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
X(int val): j(val), i(val){}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 委派构造函数
|
||||||
|
|
||||||
|
在我写代码的时候,也经常存在这样的需要,即一个构造函数是另一个更加详细的构造函数的子集,或者部分操作。这个时候我就不想全部重新写一遍,看起来也不好看,最好的办法就是在后者中调用前者,这就是委派构造函数(delegating constructor)——这个名字应该是指将一部分工作委派给另一个构造函数完成。
|
||||||
|
|
||||||
|
委派构造函数的语法也很简单,只需要在`:`后面,本来初始化列表的位置调用被委派的构造函数即可。被委派的构造函数(delegated constructor)的初始化列表和函数体都会相继被执行,之后再执行委派构造函数(delegating constructor)的剩余部分。下面的代码就是委派构造函数的一个例子:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Sales_data {
|
||||||
|
public:
|
||||||
|
// nondelegating constructor initializes members from corresponding arguments
|
||||||
|
Sales_data(std::string s, unsigned cnt, double price): bookNo(s), units_sold(cnt), revenue(cnt*price) {}
|
||||||
|
|
||||||
|
// remaining constructors all delegate to another constructor
|
||||||
|
Sales_data(): Sales_data("", 0, 0) {}
|
||||||
|
Sales_data(std::string s): Sales_data(s, 0,0) {}
|
||||||
|
Sales_data(std::istream &is): Sales_data(){ read(is, *this); }
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 转化构造函数
|
||||||
|
|
||||||
|
转化构造函数是一类特殊的构造函数,它只含有一个参数,参数的类型可以是类本身(此时就是拷贝构造函数了,将在后面提到),也可以是其他类型。它的特殊性在于,转化构造函数定义了隐式的类型转化方法,即从参数的类型转化到当前的类类型。
|
||||||
|
|
||||||
|
比如说我可以定义一个`Mystring`类型,它基本沿用`std::string`的基本方法,但是增加一个构造函数,将输入的整型转化为对应的字符串,如下:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Mystring{
|
||||||
|
public:
|
||||||
|
string str;
|
||||||
|
|
||||||
|
Mystring() = default;
|
||||||
|
//converting constructor
|
||||||
|
Mystring(int num): str(to_string(num)){}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
这样,在任何期望一个`Mystring`类型变量的位置,都可以传入一个`int`类型,编译器会自动调用上面定义的转换构造函数,从该`int`类型构造出一个`Mystring`类对象。
|
||||||
|
|
||||||
|
例如存在一个外部函数,接收一个`Mystring`类型的对象作为参数,打印出其中的字符串信息:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void print(Mystring mystr){
|
||||||
|
cout << mystr.str << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
print(2020);//this call is perfectly legal
|
||||||
|
```
|
||||||
|
|
||||||
|
完全可以对该函数传入一个整型的变量,此时编译器会自动调用转换构造函数,从该整型变量构造出一个`Mystring`类型变量,作为函数的参数。实际上,在`Mystring`对象初始化的时候,还可以采用下面的方式:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mystring mystr = 2020;//copy initialization
|
||||||
|
```
|
||||||
|
|
||||||
|
这种初始化方式称为拷贝初始化(copy initialization),与直接初始化存在一些区别。在上面的语句中,编译器也会调用转化构造函数,因此该语句与
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mystring mystr(2020);//direct initialization
|
||||||
|
```
|
||||||
|
|
||||||
|
这样的直接初始化完全等效。与上面类似,甚至可以直接使用`int`型变量对`Mystring`对象进行赋值。
|
||||||
|
|
||||||
|
从上面也可以看出,转化构造函数是存在一些隐患的,比如说`Mystring str = 2020;`这样的语句看起来多少让人看起来有一些迷惑。为了避免这种歧义性,可以手动避免转化构造函数的隐式类型转化,只需要在转化构造函数的声明前面添加`explicit`关键字即可。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Mystring{
|
||||||
|
public:
|
||||||
|
string str;
|
||||||
|
explicit Mystring(int num): str(to_string(num)){}
|
||||||
|
//other functions
|
||||||
|
```
|
||||||
|
|
||||||
|
这样,就可以避免隐式类型转化了。如果需要将`int`转化为`Mystring`类型,就必须显式调用(explicit)转化构造函数才行。实际上,之前我已经多次遇到转化构造函数了,只是我当时并不知道而已。
|
||||||
|
|
||||||
|
例如`std::string`存在一个将`const char*`转化为`string`的构造函数,因此存在下面的`string`初始化与函数调用语句:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
string str = "Study hard tomorrow."; //legal
|
||||||
|
int num = stoi("2020"); //legal, implicit convert "2020" to string.
|
||||||
|
```
|
||||||
|
|
||||||
|
同理,`vector`类中存在一个构造函数,可以指定`vector`的初始大小,传入的参数是一个`int`变量。但是并不可以这样初始化`vector`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
vector<int> iVec = 2020; //illegal, this constructor is explicit
|
||||||
|
vector<int> iVec(2020); //legal, explicit initialization
|
||||||
|
```
|
||||||
|
|
||||||
|
用一个整型变量给`vector`赋值,这看起来也太怪了,存在不少的歧义性,因此这个构造函数被标记为`explicit`了。
|
||||||
|
|
||||||
|
需要注意的是,`explicit`关键字只能出现在类构造函数声明的位置,如果该构造函数在类声明体外部被定义,则不能再次添加`explicit`关键字了。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
//error: explicit allowed only on a constructor declaration in a class header
|
||||||
|
explicit Mystring::Mystring(int num){
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
80
c++ note/chp13/chp13_exercises.md
Normal file
80
c++ note/chp13/chp13_exercises.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
CPP Primer Chp13 Exercises
|
||||||
|
==========================
|
||||||
|
|
||||||
|
> Exercise 13.1: What is a copy constuctor? When is it used?
|
||||||
|
|
||||||
|
拷贝构造函数(copy constructor)是在用拷贝的方式初始化对象的时候,被调用的构造函数,它的形参必须是`const T&`类型,其中`T`为当前类的类名。它会在三种情形下被使用:
|
||||||
|
|
||||||
|
+ 拷贝初始化(copy initialization)。比如说
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
myClass object1();//default construction
|
||||||
|
myClass object2 = object1;//copy initialization
|
||||||
|
```
|
||||||
|
|
||||||
|
+ 函数的形参是一个对象而非对象的引用。此时需要调用拷贝构造函数,从实参拷贝构造一个局部的形参传递到函数体的内部。
|
||||||
|
+ 函数的返回值是一个对象而非对象的引用。由于函数内部的变量具有临时性,函数返回后就会被销毁,因此需要拷贝构造一个新的对象传递给函数的调用者。
|
||||||
|
|
||||||
|
> Exercise 13.2: Explain why the following declaration is illegal:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Sales_data::Sales_data(Sales_data rhs);
|
||||||
|
```
|
||||||
|
|
||||||
|
拷贝构造函数的参数必须是对对象的引用。这是因为拷贝构造函数的一个作用,就是在函数的参数为对象的非引用类型时,将实参拷贝构造到形参。如果如上面的声明,对于传入的`rhs`的实参,由于并非是引用类型,需要调用拷贝构造函数将其转化为形参,这个过程将一直持续下去,无法终止。
|
||||||
|
|
||||||
|
> Exercise 13.4: Assuming Point is a class type with a public copy constructor, identify each use of the copy constructor in this program fragment:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Point global;
|
||||||
|
Point foo_bar(Point arg)
|
||||||
|
{
|
||||||
|
Point local = arg, *heap = new Point(global);
|
||||||
|
*heap = local;
|
||||||
|
Point pa[4] = { local, *heap };
|
||||||
|
return *heap;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
0. `arg`形参并非引用类型,参数传递对应了第一次调用。
|
||||||
|
1. `Point local = arg`。拷贝初始化(copy initialization)时调用拷贝构造函数。
|
||||||
|
2. `heap = new Point(global)`。显式调用拷贝构造函数。
|
||||||
|
3. `Point pa[4] = { local, *heap };`这里有两次调用。
|
||||||
|
4. `return *heap;`返回值并非引用类型,需要调用拷贝构造函数。
|
||||||
|
|
||||||
|
> Given the following sketch of a class, write a copy constructor that copies all the members. Your constructor should dynamically allocate a new string and copy the object to which ps points, rather than copying ps itself.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class HasPtr{
|
||||||
|
public:
|
||||||
|
HasPtr(const std::string &s = std::string()):
|
||||||
|
ps(new std::string(s)), i(0){}
|
||||||
|
private:
|
||||||
|
std::string *ps;
|
||||||
|
int i;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
//copy constructor
|
||||||
|
HasPtr::HasPtr(const HasPtr& ptr){
|
||||||
|
ps = new string(*ptr.ps);
|
||||||
|
i = ptr.i;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Exercise 13.6: What is a copy-assignment operator? When is this operator used? What does the synthesized copy-assignment operator do? When is it synthesized?
|
||||||
|
|
||||||
|
拷贝赋值运算符是一个函数,用于将形参对象的值拷贝到当前对象中;当需要对对象进行赋值时,应该使用拷贝复制运算符;默认的拷贝赋值运算符将传入对象的所有非静态(nonstatic)成员拷贝目标对象当中,可以看出这是一个浅拷贝(shallow copy),当成员变量有指针类型时就会出现问题;如果用户没有定义自己的拷贝赋值运算符,编译器就会生成默认的拷贝赋值运算符。
|
||||||
|
|
||||||
|
> Exercise 13.8: Write the assignment operator for the HasPtr class from exercise 13.5 in. As with the copy constructor, your
|
||||||
|
assignment operator should copy the object to which ps points.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
HasPtr& HasPtr::operator=(const HasPtr& ptr){
|
||||||
|
delete ps;
|
||||||
|
ps = new string(*ptr.ps)
|
||||||
|
i = ptr.i;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
```
|
||||||
22
ml_scipts.md
22
ml_scipts.md
@@ -23,4 +23,26 @@ Handscripts when studing Machine Learning
|
|||||||
|
|
||||||
首先是学习率(learning rate)的选择。如果$\alpha$太小,则需要多次迭代才能找到局部最优解,需要较长的学习时间;而如果$\alpha$太大,则可能直越过最低点,导致无法收敛,甚至发散。
|
首先是学习率(learning rate)的选择。如果$\alpha$太小,则需要多次迭代才能找到局部最优解,需要较长的学习时间;而如果$\alpha$太大,则可能直越过最低点,导致无法收敛,甚至发散。
|
||||||
|
|
||||||
|
因此$\alpha$的选择,最好是选择可以使代价函数收敛的最大的$\alpha$,为了找到这样一个$\alpha$,可以作出代价函数-迭代次数的关系图。从理论上来将,如果$\alpha$取得足够小,则每一次迭代代价函数都会减小。因此,如果代价函数呈现出其他的趋势,则往往说明是$\alpha$取得太大了。
|
||||||
|
|
||||||
|
需要指出的是,$\alpha$过大时,也可能会出现收敛速度慢的现象。
|
||||||
|
|
||||||
此外,显而易见的是,梯度下降法只能找到局部最优解,而非全局最优解。实际上,梯度下降法找到的解取决于初始位置的选择。然而,对于线性回归(linear regression)问题,则不存在这个问题,因为线性回归问题的代价函数是一个凸函数(convex function),即它只有一个极值点,该极值点就是它的全局最优解,因此使用梯度下降算法总是可以得到唯一的最优解。
|
此外,显而易见的是,梯度下降法只能找到局部最优解,而非全局最优解。实际上,梯度下降法找到的解取决于初始位置的选择。然而,对于线性回归(linear regression)问题,则不存在这个问题,因为线性回归问题的代价函数是一个凸函数(convex function),即它只有一个极值点,该极值点就是它的全局最优解,因此使用梯度下降算法总是可以得到唯一的最优解。
|
||||||
|
|
||||||
|
> 梯度下降法的深入讨论。
|
||||||
|
|
||||||
|
对于多元的线性回归问题,如果不同的特征取值范围差别很大,比如$0 < x_1 < 1$,$0 < x_2 < 1000$,两者的取值范围查了1000倍。在使用梯度下降法时,参数$\theta_2$的变化量也将是$\theta_1$的1000倍,这将导致损失函数等高线图呈现扁平的椭圆状,梯度下降路径相对取值范围更大的特征对应的参数来回波动,导致收敛速度缓慢。但是关于这个的数学推导我仍然存在问题。
|
||||||
|
|
||||||
|
为了解决上面的问题,需要对取值相差比较大的特征进行特征缩放(feature scaling),使它们的取值都在1的附近。这样得到的等高线图就接近于圆形,收敛速度就要快多了。
|
||||||
|
|
||||||
|
此外还有一个方法是使这些特征的平均值都在0附近,相对于对特征进行了规范化。
|
||||||
|
|
||||||
|
> 在线性回归问题中,是通过平方损失函数(square error function)作为损失函数,来对参数进行优化的。老师说这样得到的是一条可能性最大的直线,是否可以用最大似然估计来解释这一点呢?
|
||||||
|
|
||||||
|
> 多元线性回归(multivariate linear regression)的梯度下降法与规范方程法(normal equation)的一些问题。
|
||||||
|
|
||||||
|
不明白规范方程法正确性的证明,以及通过多元极值求得的结果与规范方程法是否一致?
|
||||||
|
|
||||||
|
规范方程法伪逆(pseudo-inversion)的数学推导;为什么求逆操作的时间复杂度是`O(n^3)`,其中`n`表示特征的数量。
|
||||||
|
|
||||||
|
为什么梯度下降法的时间复杂度是`O(kn^2)`,k是什么含义。
|
||||||
|
|||||||
45
words.md
45
words.md
@@ -2105,3 +2105,48 @@ Some Words
|
|||||||
|
|
||||||
+ tornado
|
+ tornado
|
||||||
> (n)a strong, dangerous wind that forms itself into an upside-down spinning cone and is able to destroy buildings as it moves across the ground.
|
> (n)a strong, dangerous wind that forms itself into an upside-down spinning cone and is able to destroy buildings as it moves across the ground.
|
||||||
|
|
||||||
|
## 31st, December
|
||||||
|
|
||||||
|
+ delegate
|
||||||
|
> (n)a person chosen or elected by a group to speak, vote, etc. for them, especially at a meeting.</br>
|
||||||
|
> (v)to give a particular job, duty, right, etc. to someone else so that they do it for you.</br>
|
||||||
|
> (v)to choose or elect someone to speak, vote, etc. for a group, especially at a meeting.
|
||||||
|
|
||||||
|
- Each union elects several delegates to the annual conference.
|
||||||
|
- He talks of travelling less, and delegating more authority to his deputies.
|
||||||
|
- A group of teachers were delegated to represent their colleagues at the union conference.
|
||||||
|
|
||||||
|
## 1st, January
|
||||||
|
|
||||||
|
+ sloppy
|
||||||
|
> (adj)very wet or liquid, often in a way that is unpleasant</br>
|
||||||
|
> (adj)not taking care or making an effort
|
||||||
|
|
||||||
|
- She covered his face with sloppy kisses.
|
||||||
|
- Spelling mistakes always look sloppy in formal letters.
|
||||||
|
- He has little patience for sloppy work from colleagues.
|
||||||
|
|
||||||
|
+ substitute
|
||||||
|
> (v)to use something or someone instead of another thing or person</br>
|
||||||
|
> (v)substitute for sth: to perform the same job as another thing or to take its place.</br>
|
||||||
|
> (n)a thing or person that is used instead of another thing or person.</br>
|
||||||
|
> (n)in sports, a player who is used for part of a game instead of another player.
|
||||||
|
|
||||||
|
- You can substitute oil for butter in the recipe.
|
||||||
|
- I use that recipe but substitute wheat-free flour for regular flour.
|
||||||
|
- Of course, no book or course of study can substitute for experience.
|
||||||
|
- Gas-fired power station will substitute for less efficient coal-fired equipment.
|
||||||
|
- Vitamins should not be used as a substitute for a healthy diet.
|
||||||
|
- The manager brought on another substitute in the final minutes of the game.
|
||||||
|
|
||||||
|
+ sophomore
|
||||||
|
> (n)a student studying in the second year of a course at a US college or high school.
|
||||||
|
|
||||||
|
+ frontage
|
||||||
|
> (n)the front part of a building that faces a road or river, or land near a load or river.
|
||||||
|
|
||||||
|
- These apartments all have a delightful dockside frontage.
|
||||||
|
- The restaurant has a river frontage.
|
||||||
|
|
||||||
|
+
|
||||||
|
|||||||
Reference in New Issue
Block a user