add a conclusion on c++ constructor and copy control, not finished yet.

This commit is contained in:
Shine wOng
2020-01-01 21:20:59 +08:00
parent 9bc971279d
commit 1c6c437f7a
5 changed files with 365 additions and 0 deletions

53
c++ note/chp13/chp13.cpp Normal file
View 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
View 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){
...
}
```

View 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;
}
```

View File

@@ -23,4 +23,26 @@ Handscripts when studing Machine Learning
首先是学习率(learning rate)的选择。如果$\alpha$太小,则需要多次迭代才能找到局部最优解,需要较长的学习时间;而如果$\alpha$太大,则可能直越过最低点,导致无法收敛,甚至发散。
因此$\alpha$的选择,最好是选择可以使代价函数收敛的最大的$\alpha$,为了找到这样一个$\alpha$,可以作出代价函数-迭代次数的关系图。从理论上来将,如果$\alpha$取得足够小,则每一次迭代代价函数都会减小。因此,如果代价函数呈现出其他的趋势,则往往说明是$\alpha$取得太大了。
需要指出的是,$\alpha$过大时,也可能会出现收敛速度慢的现象。
此外显而易见的是梯度下降法只能找到局部最优解而非全局最优解。实际上梯度下降法找到的解取决于初始位置的选择。然而对于线性回归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是什么含义

View File

@@ -2105,3 +2105,48 @@ Some Words
+ 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.
## 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.
+