From 1c6c437f7a2f705b7a8cc8c4852e4a30269c70f9 Mon Sep 17 00:00:00 2001 From: Shine wOng <1551885@tongji.edu.cn> Date: Wed, 1 Jan 2020 21:20:59 +0800 Subject: [PATCH] add a conclusion on c++ constructor and copy control, not finished yet. --- c++ note/chp13/chp13.cpp | 53 ++++++++++ c++ note/chp13/chp13.md | 165 ++++++++++++++++++++++++++++++ c++ note/chp13/chp13_exercises.md | 80 +++++++++++++++ ml_scipts.md | 22 ++++ words.md | 45 ++++++++ 5 files changed, 365 insertions(+) create mode 100644 c++ note/chp13/chp13.cpp create mode 100644 c++ note/chp13/chp13.md create mode 100644 c++ note/chp13/chp13_exercises.md diff --git a/c++ note/chp13/chp13.cpp b/c++ note/chp13/chp13.cpp new file mode 100644 index 0000000..e66274c --- /dev/null +++ b/c++ note/chp13/chp13.cpp @@ -0,0 +1,53 @@ +#include +#include + +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; +} diff --git a/c++ note/chp13/chp13.md b/c++ note/chp13/chp13.md new file mode 100644 index 0000000..65e0c8c --- /dev/null +++ b/c++ note/chp13/chp13.md @@ -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 iVec = 2020; //illegal, this constructor is explicit +vector 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){ + ... +} +``` diff --git a/c++ note/chp13/chp13_exercises.md b/c++ note/chp13/chp13_exercises.md new file mode 100644 index 0000000..7ab826b --- /dev/null +++ b/c++ note/chp13/chp13_exercises.md @@ -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; +} +``` diff --git a/ml_scipts.md b/ml_scipts.md index 36276ba..d33cd3b 100644 --- a/ml_scipts.md +++ b/ml_scipts.md @@ -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是什么含义。 diff --git a/words.md b/words.md index 8c2c92a..0ebbed0 100644 --- a/words.md +++ b/words.md @@ -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.
+> (v)to give a particular job, duty, right, etc. to someone else so that they do it for you.
+> (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
+> (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
+> (v)substitute for sth: to perform the same job as another thing or to take its place.
+> (n)a thing or person that is used instead of another thing or person.
+> (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. + ++