Essential C++读书笔记

一、C++编程基础

1、对象初始化(两种不同的初试化语法)
  • int num_tries = 0, num_right = 0

  • int num_tries(0)//构造函数语法,主要用来处理“多值初试化”

  • #include<complex>
    complex<double> purei(0,7);
    
2、特殊字符

在Windows操作系统下以常量字符串表示文件路径时,必须以“转义字符”表示反斜线字符

”F:\\essential\\pragrams\\charpter1\\ch1_main.cpp“
3、const 常量

被定义为const的对象在获得初值之后无法进行变动

const int max_tries = 3;
const double_PI  = 3.14
4、条件判断

switch

注意:当某个标签和switch的表达式值吻合时,该case标签之后的所有case标签也都会被执行,除非明确使用break来结束执行动作

switch(num_tries)
{case 1:cout<<"\n";break:case 2:cout<<"  ";break;default:cout<<"    ";break;
}
5、arrays和Vectors

初始化:利用已初始化的array作为vector的初值

int elem_vals[seq_size]={1,2,3,4,5,6,7,8};
vector<int> elem_seq{elem_vals,elem_vals+seq_size};

vector可以知道自身的元素个数

elem_seq.size()
6、指针

防止对NULL指针进行操作可以检验该指针所含有的地址是否为0

if(pi && *pi != 1024)
{*pi = 1024;
}

对于vector的指针的声明以及初始化

vector<int> *pv = 0;

定义vector的指针数组

vector<int> *seq_addrs[seq_cnt]={&fibonacci, &lucas, &pell, &triangular}

rand()和srand()函数

rand()每次回返回一个介于0和int所能表示的最大整数间的一个整数,srand()的参数是所谓的随机数产生器种子(seed),将随机数产生器的种子seed设为5就可以将rand的返回值限制在0-5之间

7、文件的读写

声明outfile后,如果指定的文件不存在,便会产生指定的文件,如果指定的文件存在,则该文件会开启用来输出,并且原文件中存在的数据会被丢弃

#include<fstream>
ofstream outfile("seq_data.txt");//输出模式
ifstream infile("seq_data.txt");//读取模式
fstream iofile("seq_data.txt",ios_base::in|ios_base::app)//读写模式,追加模式

追加模式开启文件

ofstream outfile("seq_data.txt",ios_base::qpp);
if(!outfile)cerr<<"Unable to save!"//cerr代表标准错误输出设备
elseoutfile<<user_name<<endl;

当以附加模式开启文件时,文件位置会位于尾端,如果没有重新定位,就试着读取文件内容,那么立刻就会遇到“读到文件尾”的状况,seekg()可将文件位置重新定位至文件的起始处

iofile.seekg(0)

二、面向过程的编程风格

1、撰写函数

函数必须被申明才能调用,通常函数声明会被置于头文件,如引入cstdlib文件就是为了含入exit()函数声明

int fibon_elem(int pos)//即函数原型,不需要写函数主体

使用**exit()**必须包含头文件

#include<cstdlib>
2、调用函数

当我们调用一个函数时,会在内存中建立起一块特殊区域,称为**“程序栈”**,这个特殊区域提供了每个函数参数的储存空间,同时也提供了函数所定义的每个对象的内存空间,一旦函数完成,这些内存就会被释放掉

3、引用
int ival = 1024;//对象,类别为int
int *pi = &ival;//指针
int &rval = ival;//引用,代表一个int对象
int jval = 4096;
rval = jval;//即把jval赋值给rval所代表的对象,即ival

C++不允许改变引用所代表的对象

4、生存空间及生存范围

除static以外,函数内定义的对象只存活与函数执行之际

对象如果在函数以外声明,则从其声明点至文件尾端都是可视的,也该对象必定被初始化为0

5、动态内存管理

不管是local scope还是file scope,对我们而言都是由系统自动管理,还有第三种为dynamic extent(动态范围),其内存由程序的自由空间配置(也称堆内存)而来,此种内存必须由程序员自行管理,其配置由new表达式达成,释放由delete表达式完成

int *pi;
pi = new int(1024);
int *pia = new int[24];//配置数组,C++不可以从heap配置数组的同时为其元素设定初值
delete pi;//
delete []pia;//删除数组中的所有对象

如果不适用delete,则从heap配置来的对象永远不会释放,即内存泄漏

6、提供默认参数值
void display(const vector<int> &vec,ostream &os =cout)//将cout设置为默认的ostream参数
{for(int ix = 0;ix<vec.size();++ix)os<<vec[ix]<<' ';os<<endl;
}

默认参数值提供的两个规则

  • 如果为某个参数提供了默认值,那么该参数右侧的所有参数都必须也具有默认值
  • 默认值只能制定一次
7、声明inline函数(还不懂)

将函数声明为inline,表示要求编译器在每个函数调用点上将函数的内容展开,面对一个inline函数,编译器可将该函数的调用改以一份函数码副本取代

inline bool_fibon_elem(int pos, int &elem)
{//内联函数必须跟函数定义一起,不然无法构成内联函数
}

定义在类声明之中的成员函数将自动地成为内联函数

class A
{public:void FOO(int x, int y){///自动地成为内联函数}
}

调用函数过程**:调用前要先保存寄存器,并在返回时恢复,复制实参,程序还必须转向一个新位置执行(因此会在消耗一定的时间)

引入内联函数的目的是为了解决程序中函数调用的效率问题,这么说吧,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的i节省。所以内联函数一般都是1-5行的小函数。在使用内联函数时要留神:

  • 在内联函数内不允许使用循环语句和开关语句;
  • 内联函数的定义必须出现在内联函数第一次调用之前;
  • 类结构中所在的类说明内部定义的函数是内联函数。
8、函数重载(略简单,应在后期有补充)

参数表不相同的多个函数可以拥有相同的函数名称

void display_message(char ch);
void display_message(const string&);
void display_message(const string&, int);
void display_message(const string&, int, int);
9、模板函数(后期应有补充)

函数模板可以将单一函数的内容与希望显示的各种vector型别捆绑起来

function template以关键词template开场,其后紧接着以<>包围起来的一个或多个识别名称,这些名称表示我们希望延缓决定的数据类别,每当用户使用这个模板产生函数时,他就必须提供确实的类别信息

template <typename elemType>
void display_message(const string &msg, const vector<elemType> &vec)
{cout<<msg;for(int ix = 0; ix<vec.size();++ix){elemType t = vec[ix];cout<<t<<' ';}
}
vector<int> ivec;
string msg;
//....
display_message(msg, ivec);//将elemType绑定为int
//....
vector<string> ivec;
string msg;
//....
display_message(msg, ivec);//将elemType绑定为string
10、函数指针

函数指针:必须指明起所指向函数的返回值类型及参数表

const vector<int> * (*seq_ptr)(int);//seq_ptr是一个指针,指向具有const vector<int>* (int)形式的函数
bool seq_elem(int pos, int&elem, const vector<int> *(*seq_ptr)(int))
{//调用seq_ptr所指的函数const vector<int> *pseq = seq_ptr(pos);//函数名即是指针if(!pseq){elem = 0;return false;}elem = (*pseq)[pos-1];return true;
}
11、头文件
  • 头文件扩展名习惯上是**.h**,标准程序库例外,他们没有扩展名。

  • 函数的定义只能有一份,不过可以有许多份声明。我们不把函数的定义纳入头文件,因为同一个程序的多个代码文件可能都会含入这个头文件

  • 如果文件被认定为标准的、或项目专属的头文件,便用尖括号将文件名括住;编译器搜寻此档时,会现在某些默认的驱动器目录中找寻。如果文件名由成对的双括号括住,此文件便被认为是一个用户自行提供的头文件;搜寻此文件时,会由含入此文件之文件所在的驱动器目录开始找起。

一次定义规则的例外

  • inline函数的定义,为了能够扩展inline函数的内容,在每个调用点上,编译器都能取得其定义,这意味着我们必须将inline函数的定义置于头文件,而不是把它放在各个不同的程序代码文件
  • const object的定义只要一出文件之外便不可见,这意味着我们可以在多个程序代码文件中加以定义,不会导致任何错误
//NumSeq.h
bool seq_elem(int pos,int &elem);
const vector<int> *fibon_seq(int size);
const vector<int> *lucas_seq(int size);
const vector<int> *pell_seq(int size);
//....
const int seq_cnt = 6;//之所以可以定义是因为是const object
extern const vector<int>* (seq_array)([seq_cnt])(int);

三、泛型编程风格

1、Iterators(泛型指针)

每个标准容器都提供一个名为**begin()**的函数,返回一个iterator指向第一个元素,end()返回的iterator指向最有一个元素

vector<string> svec;
vector<string>::iterator iter=svec.begin();
//::表示此iterator乃是位于string vector 定义式内的嵌套型别

设计一个find()函数,让它同时支持两种形式:一对指针,或是一对指向某种容器的iterators

template<typename IteratorType, typename elemType>
IteratorType find(IteratorType first,IteratorType last,const elemType &value)
{for(;first!=last;++first)if(value==*first)return first;return last;
}
2.所有容器的共通操作(包括string类)
  • ==!=,返回true或者false
  • =,将某个容器复制给另一个容器
  • empty(),容器无任何元素时返回true,否则返回false
  • size(),返回容器内的元素数目
  • clear()删除所有元素
  • begin()
  • end()
  • insert()将单一或某个范围内的元素安插到容器内
  • erase()将单一或某个范围内的元素删除,返回的iterator指向被删除元素的下一个元素

insert()erase()视容器本身为循序式或关联式而有所不同

3.序列式容器
  • vector:动态数组,需要一块连续的内存
  • list:双向链表
  • deque:队列,queue便是以deque实现的

产生容器的方法

//1.产生空的容器
list<string> slist;
vector<int> ivec;
//2.产生特定大小的容器,以默认值为初值,int,double等内建型别的算术型别默认值为0
list<int> ilist(1024);
vector<string svec> svec(32);
//3.产生特定大小的容器,并为每个元素指定初值
vector<int> ivec(10, -1);
list<string> slist(16,"unassigned");
//4.以一对iterators产生容器
int a[8] = {1,2,3,4,5,6,7,8};
vector<int> fib(ia, ia+8);
//5.根据某个容器产生新容器
list<string> slist;//空容器
list<string> slist2(slist);

一些特殊的安插方法

push_back();
pop_back();
//下面的支持deque和list
push_front();
pop_front();
front();//读取最前端的值
4.使用泛型算法

必须包含头文件

#include<algorithm>

一些泛型算法

  • find()用于搜寻无序集合是否存在某值,搜寻范围又iterator(first,last)标出,找到则返回一个iterator指向该值,否则指向last
  • binary_search()用于有序,找到则返回true,否则false
  • count()返回数值相符的元素数目
  • search()比较容器内是否存在某个子序列,找到则返回iterator指向子序列起始处,否则指向容器末尾
  • max_element(vc.begin(),vc.end())返回区间内的最大值
  • copy(vec.begin(), vec.end(), temp.begin())将vec容器的内容复制到temp开始处
  • sort(temp.begin(),temp.end())
5.Function Objects

function objects是某种class的实体对象,这类classes对function call运算符进行了重载操作,function objects实现出原本可能以独立函数加以定义的事物(比如greater),从而提高效率,可以令call运算符成为inline

  • 6个算术运算:

    plus<type>,minus<type>,negate<type>,multiplies<type>,divides<type>,modules<type>
    
  • 六个关系

    less<type>,less_equal<type>,greater<type>,greater_equal<type>,equal_to<type>,not_equal_to<type>
    
  • 三个逻辑运算

    logical_and<type>,logical_or<type>,logical_not<type>
    

function object adapters会使function object的参数绑定至特定值上,使二元function object变成一元function object

  • bind1st绑定指定值至第一操作数
  • bind2nd绑定指定值至第二操作数
  • not1可逆转unary function object的真伪值
  • not2可逆转binary function object的真伪值
vector<int> filter(const vector<int> &vec, int val, less<int> &Lt)
{vector<int> nvec;vector<int>::const_iterator iter = vec.begin();while((iter = find_if(iter, vec.end(), bind2nd(lt, val)))!=vec.end()){//find_if函数寻找比*iter值小的数//bind2nd把val这个值绑定到lt的第二参数nvec.push_back(*iter);iter++;}return nvec;
}

消除filter()与vector元素型别以及vector容器类型的相依关联

template<typename InputIterator, typename OutIterator, typename ElemType, typename COmp>
filter(InputIterator first, Inputerator last, OutputIterator at, const ElemType &val, COmp pred)
{while((first = find_if(first, last, bind2nd(pred, val)))!=last){//观察进行情况cout<<"found value:"<<*first<<endl;*at++=*first++;}return at;
}
6、使用map

map对象有一个名为first的member,对应于key,领有一个名为second的member,对应value

查询map是否存在某个key,三种方法

  • int count = 0;
    if(!(count = words["vermeer"]))
    //如果索引的key不在map中,则key会被自动加入map中并且其value会被设为默认值
    
  • //利用map的find()函数
    words.find("vermeer")
    
  • //利用map的count(),count()会返回某特定项目在map内的个数
    int count = 0;
    string search_word("vermeer");
    if(words.count(serch_word))count = words[search_word];
    
7、使用Set

对于任何key值,set只能存储一份,如果要存储多份相同的key值,必须使用multiset,默认情况下,set元素会依据所属型别默认的less-than运算符进行排列。

int ia[10] = {1,3,5,8,5,3,1,5,8,1};
vector<int> vec{ia,ia+10};
set<int> iset(vec.begin(),vec.end());
//iset的元素为{1,3,5,8}。
8、Iterator Inserters

原本的对元素进行复制行为的算法都是通过将值赋值给需要赋值的容器,因此目标容器大小需要确定,为了能够使得目标容器大小可变,可采用以下方法(需要头文件#include<iterator>):

  • back_inserter()会以容器的push_back()函数取代assignment运算符,传给back_inserter的引数就是容器本身。

    vector<int> result_vec;
    unique_copy()(ivec.begin(),ivec.end(),back_inserter(result_vec));
    
  • inserter()会以容器的insert()函数取代assignment运算符,inserter()有两个参数,一个是容器,一个是iterator,指向容器内的安插操作起始点

    vector<int> svec_res;
    unique_copy()(svec.begin(),svec.end(),inserter(svec_res,svec_res.end()));
    
  • front_inserter()会以容器的push_front()函数取代assignment运算符,只适用于list和deque

    list<int> ilist_clone;
    copy(ilist.begin(),ilist.end(),front_inserter(ilist_clone));
    
9、iostream iterators
#include<iostream>
#include<fstream>
#include<iterator>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
int main()
{ifstream in_file("input_file.txt");ofstream out_file("output_file.txt");if(!in_file||!out_file){cerr<<"!!unable to open!\n";return -1;}istream_iterator<string> is(in_file);//从in_file读取istream_iterator<string> eof;//表示读取最后元素的下一位置,对于标准输入装置end-of-file即代表lastvector<string> text;copy(is,eof,back_insert(text));sort(text.begin(),text.end());ostream_iterator<string> os(out_file, " ");copy(text.begin(),text.end(),os);}

四、基于对象的编程风格

1、如何实现一个class

一般而言,class由两部分组成:一组公开的操作函数和运算符,以及一组私有的实现细节。这些操作函数和运算符被称为class’s member function(成员函数),并代表这个class的公开接口。

class Stack{
public:bool push(const string&);bool pop(string &elem);bool peek(string &elem);bool empty();bool full();int size(){return _stack.size();}//size()定义于class本身内,其他member则仅仅是声明
private:vector<string> _stack;
};
inline bool Stack::empty()
{return _stack.empty();
}//empty为Stack的一个member,并且是inline函数
bool Stack::pop(string &elem)
{if(empty())reutrn false;elem = _stack.back();_stack.pop_back();return true;
}
inline bool Stack::full()
{return _stack.size() == _stack.max_size();
}
bool Stack::peek(string &elem)
{if(empty())return false;elem = _stack.back();return true;
}
bool Stack::push(const string &elem)
{if(full())return false;_stack.push_back(elem);return true;
}
2、Constructors和Destructors(构造函数和析构函数)

constructors的函数名必须和class名称相同,语法规定,constructors不应指定返回类型,亦不需要返回任何值,它可以被重载。

class Triangular{
public:Triangular();//default constructorsTriangular(int len);Triangular(int len, int beg_pos);
private:int _length;//元素数目int _beg_pos;//起始位置int _next;//下一个迭代目标
}
Triangular::Triangular()
{//默认的构造函数,第一种情况_length=1;_beg_pos = 1;_next=0;
}
//第二种情况
class Triangular{public:Triangular(int len=1, int bp=1);//提供了默认参数
}
Triangular::Triangular(int len, int bp)
{_length = len>0?len:1;_beg_pos = bp>0?bp:1;_next = _beg_pos-1;
}
//调用方法
Triangular t;//1,1
Triangular t2(12);//12,1
Triangular t3(8,3);//8,3

成员初始表

成员初始表紧接在参数表最后的冒号后面,是个以逗号为分隔的列表,其中,欲赋值给member的数值被置于member名称后面的小括号中

Triangular::Triangular(const Triangular &rhs):_length(rhs._length),_beg_pos(rhs._beg_pos),_next(rhs._beg_pos-1){}//空的

析构函数

destructors乃是用户自行定义的一个class member,一旦某个class提供有destructors,当其object结束生命时,便会自动调用destructor处理善后,destructor主要用来释放在constructor中或对象生命周期中配置的资源

析构函数执行时间

  • 对象生命周期结束,被销毁时
  • delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类析构函数是虚函数时;
  • 对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。

规定

  • class名称前加~
  • 无返回值
  • 无参数
  • 不可被重载
class Matrix{
public:Matrix(int row, int col){_pmat = new double[row*col];}~Matrix(){delete {}_pmat;//执行析构函数时释放heap内存}
private:int _row,_col;double* _pmat;
}

成员逐一初始化

Triangular tril(8);
Triangular tri2=tri1;//class data members会被依次赋值

二阶构造法

当类的成员比较简单,如只有赋值等简单操作时,普通的构造函数就可以。
然而实际中,以面向对象的思维开发程序时,类往往十分复杂,设计到动态内存申请、文件打开等操作。然而在调用构造函数后,无法得知这些复杂的操作是否顺利完成。假若动态内存未成功申请,然而对象实例在主程序中依然成功创建,这样的对象称为半成品对象。在后续对该对象的操作,往往会引起程序的奔溃。

为了防止出现这样的情况,我们使用二阶构造函数,中心思路为把类的初始化分为两个阶段,第一阶段为构造不会出错的类成员,第二阶段为构造动态内存申请等可能出错的部分,并以bool形式返回构造成功与否。这两个阶段的连接依靠类的静态成员函数
第一步:
形式上就是传统的构造函数,不同的是这里需要将该构造函数的访问属性设置为private,在该构造函数中只进行类中普通成员变量的初始化操作,一般的,纯粹的赋值操作不会发生意外(不成功的情况)
第二步:
一般的,类中额外定义一个名为bool construct()的私有成员方法,该方法中,进行该类的实例化操作时涉及到的系统资源的申请工作,返回值表示申请的成功或失败

#include <iostream>
using namespace std;class A
{
private:int m;int n;int* p;A()       //第一段构造函数,一般的,纯粹的赋值操作不会发生错误{m = 1;n = 2;}bool construct() //第二段构造函数,防止文件打开、系统资源的申请失败导致半成品对象{bool ret = true;p = new int(3);if (!p)ret = false;return ret;}
public:static A* NewInstance()  //必须写为静态函数,以方便用类名调用该函数, 而不用创建对象实例,返回的是指针类型{A* ret = new A(); //完成第一段对象实体if (!(ret && ret->construct()))//ret->construct()完成第二段对象实体,并利用返回值来判断对象是否创建成功{delete ret;ret = NULL;}return ret;}void showdata(){cout << "m = " << m << endl;cout << "n = " << n << endl;cout << "*p = " << *p << endl;}
};void main()
{A* p_A = A::NewInstance();  //静态调用,不同动态申请     p_A->showdata();delete p_A;
3、mutable(可变)和const(不变)

const

当传入函数的参数为const的类对象时,则必须保证在该函数中调用的任何成员函数都无法更改传入的类对象的值,因此必须保证该类的成员函数都不会更改。

int sum(const Triangular &train)
{int beg_pos = train.beg_pos();int length = train,length();int sum = 0;for (int ix=0;ix<length;++ix)sum+=train.elem(beg_pos+ix);return sum;//此函数调用的成员函数有可能会更改train的值
}
class Triangular
{
public:int length() const{return _length;}//声明该成员函数不会更改类的值int beg_pos() const{return _beg_pos;}//const修饰词紧接函数参数表后,在class外定义者,如果是const,则定义中也需要constint elem(int pos)const;//非constbool next(int &val);void next_reset(){_next = _beg_pos-1;}//....
private:int _length;int _beg_pos;int _next;static vector<int> _elems;
}
bool Triangular::next(int &value) const
{if(_next<_beg_pos+_length-1){value = _elem[_next++];return true;}return false;
}
int Triangular::elem(int pos)const{return _elems[pos-1];}

即使某些成员函数没有直接更改类对象的值,但如果存在被更改的可能性,则该成员函数不可被声明为const

class val_class
{
public:val_class(const BigClass &v):_val(v){}BigClass& val() const{return _val;}//此处返回了一个BigClass的非const引用,因此有可能更改成员值,所以声明为const类型会报错
private:BigClass _val;
}

mutable

当某个成员函数被声明为const时,但这个成员函数又修改了类成员的值,如果被修改的值我们认为是可变的,就可以声明为mutable

int sum(const Triangular &train)
{if(!train.length())return 0;int val,sum=0;train.next_reset();while(train.next(val))sum+=val;return sum;
}
class Triangular
{
public:bool next(int &val)const;void next_reset() const{_next = _beg_pos -1;}//...
private:mutable int_next;int _beg_pos;int _length;
}
4、this指针

this指针在member functions内用来寻址其调用者(一个对象)

Triangular& Triangular::copy(Triangular *this, const Triangular &rhs)
{if(this!=&rhs){this->_length = rhs._length;this->_beg_pos = rhs._beg_pos;this->_next = rhs._beg_pos-1;}return *this;//this指向这个类对象,*this则代表这个类对象本身
}
Triangular tr1(8);
Triangular tr2(8,9);
copy(&tr1,tr2);
5、Static Class Member

static data member

static data member用来表示唯一一份可共享的members,它可以在同型的所有对象中被存取。static data member只有唯一一份实体,因此必须在程序代码文件中提供其清楚的定义。

class Triangular
{
public://...
private:static vector<int> _elems;static const int _size = 1024;//这种数据成员变量可以直接赋初值
};
vector<int> Triangular::_elems;
Triangular::Triangular(int len, int beg_pos):_length(len>0?len:1),_beg_pos(beg_pos>0?beg_pos:1)//定义一个构造函数,使用成员初始表赋值{_next = _beg_pos-1;int elem_cnt = _beg_pos + _length -1;if(_elem.size()<elem_cnt)//获取方法可以直接获取gen_elements(elem_cnt);//扩充elem?}

static member function

一般情况下,member function必须通过其类的对象来调用,这个对象会被绑定在该member function的this指针上,通过该指针才能够存取存储于每个对象中的non-static data members。

bool Triangular::is_elem(int value)
{if(!_elems.size()||_elems[_elems.size()-1]<value)gen_elems_to_value(value);vector<int>::iterator found_it;vector<int>::iterator end_it = _elems.end();found_it = find(_elems.begin(),end_it,value);return found_it!=end_it;
}//该成员函数没有调用任何non-static data members,于是想是否可以以非成员函数的形式调用?
if(Triangular::is_elme(8))...//这样调用,需要加class scope,在跟无任何对象有瓜葛的情况下调用
//member function只有在"不存取任何non-static data members"的情况下才能被声明为static
class Triangular
{
public:static bool is_elem(int);static void gen_elements(int length);//此笔记未写定义static void gen_elems_to_value(int value);static void display(int length, int beg_pos,ostream *os =cout);
}
6、Iterator Class

对class进行运算符重载,先定义一个class的Iterator

Triangular train(1,8);
Triangular::iterator it = train.begin(), end_it = train.end();
while(it!=end_it)
{cout<<*it<<' ';//两个class的object怎么比较++it;
}
//重载运算符
class Triangular_iterator
{
public:Triangular_iterator(int index):_index(index-1){}//成员初始表bool operator==(const Triangular_iterator&) const;//重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。bool operator!=(const Triangular_iterator&) const;int operator*() const;Triangular_iterator& operator++();//前置版Triangular_iterator& operator++(int);//后置版
private:void check_integrity() const;int _index;
};
inline bool Triangular_iterator::operator==(const Triangular_iterator &rhs) const
{return _index==rhs._index;//如果两个对象的_index相等则说这两对象相等
}

运算符重载规则

  • 不可以引入新的运算符,除.,*::,?:外都可以被重载
  • 操作数数目不可改变
  • 运算符优先级不可变
  • 运算符函数的参数列必须至少有一个参数为class

运算符的定义

//类似成员函数
inline int Triangular_iterator::operator*() const//此处的const声明该成员函数不会更改类对象的值
{check_integrity();return Triangular::_elems[_index];//静态成员变量调用时需要加class限定域
}
//类似非成员函数
inline int operator*(const Triangular_iterator &rhs)//此处传进一个const引用,表示无法更改这个引用所代表的类对象
{rhs.check_integrity();return Triangular::_elems[_index];
}//非成员运算符的参数列中一定比对应的成员运算符多一个参数,即this指针,这个this指针隐代表左侧操作数

嵌套型别

#include "Triangular_iterator.h"
class Triangular
{
public://将Triangular_iterator 内嵌至Triangular中并typedef为 iteratortypedef Triangular_iterator iterator;Triangular_iterator begin() const{return Triangular_iterator(_beg_pos);//返回一个类的对象,上面有构造函数}Triangular_iterator end() const{return Triangular_iterator(_beg_pos + _length);}
private:int _beg_pos;int _length;//....
}
7、友元函数

任何class都可以将其他functions或classes指定为友元,而所谓的friend,具备了与class member function相同的存取权限,可以存取class的private member。

inline int operator*(const Triangular_iterator &rhs)
{rhs.check_integrity();//直接调用私有成员return Triangular::_elems[rhs.index()];
}//想要通过编译需要将operator*()声明为友元
class Triangular
{friend int operator*(const Triangular_iterator &rhs);//....
}
class Triangular_iterator
{friend int operator*(const Triangular_iterator &rhs);//....
}
//也可以将其他类中的函数声明为友元函数
class Triangular
{//在此定义之前必须先定义Triangular_iterator让Triangular知道,否则无法知道下面的函数的确是Triangular_iterator的成员函数friend int Triangular_iterator::operator*();friend void Triangular_iterator::check_integrity();
}

定义类与类之间的friend关系

class Triangular
{//以下可以使Triangular_iterator得所有成员函数都成为Triangular的friendfriend class Triangular_iterator;
}
8、copy assignment operator
Triangular tri1(8),tri2(8,9);
tri1 = tri2;//默认的成员逐一复制操作,class data members会被依次复制过去。
//但有些时候无法进行默认的复制,因此需要重载该运算符
Matrix& Matrix::operator=(const Matrix &rhs)//Matrix在4.2有,如果直接用默认复制,tri2de _pamt会在析构函数被回收,但tri1的_pmat同样还指向这块空间,因此会出错
{if(this!=&rhs){_row = rhs._row;_col = rhs._cos;int elem_cnt = _row*_col;delete{} _pmat;//没懂_pmat = new double[elem_cnt];//解决办法:重新申请一块内存for(int ix=0;ix<elem_cnt;++ix)_pmat[ix]=rhs._pmat[ix];}return *this;
}
9、function object

所谓function object乃是一种"提供有function call运算符"的class

function object无处不在,比如我们用map的时候,默认我们会使用std::map<int,Anyclass>,但这个map会默认以int类型来进行排序,这是因为stl默认会使用std::less,即map的第三个默认参数,这就是一个function object,我们自己也可以定义一个这样的function object

//当编译器在编译过程中遇到函数调用,例如:It(ival),It可能是函数名称,也可能是函数指针,也可能是一个提供了function call运算符的function object,但如果It是class object,编译器会转化为:It.operator(ival)
class LessThan
{
public:LessThan(int val):_val(val){}int comp_val()const {return _val;}//基值的读取void comp_val(int nval){_val=nval;}//基值的写入bool operator()(int _value)const;//重载了运算符()?()是function call运算符
private:int _val;
};
//function call运算符实现如下:
inline bool LessThan::operator()(int value)const {return value<_val;}
//将function call运算符施加于对象身上
int count_less_than(const vector<int> &vec,int comp)
{LessThan It(comp);int count = 0;for(int ix=0;ix<vec.size();++ix)if(It(vec[ix]))++count;return count;
}
//通常将function object当做参数传给泛型算法
void print_less_than(const vector<int>&vec, int comp, ostream &os=cout)
{LessThan It(comp);//此处即声明了一个function objectvector<int>::const_iterator iter=vec.begin();vector<int>::const_iterator ite_end = vec.end();os<<"elements less than"<<It.comp_val()<<endl;while((iter=find_if(iter,it.end(),It))!=it_end)//即在iter和it.end()之间找比comp小的值{os<<*iter<<' ';++iter;}
}
10、iostream运算符重载
ostream& operator<<(ostream &os, const Triangular &rhs)
{os<<"("<<rhs.beg_pos()<<","<<rhs.length()<<')';rhs.display(rhs.length(),rhs.beg_pos(),os);//此处display为上面之前定义过的一个函数,笔记没写return os;//返回的os起什么作用?
}//
Triangular tir(6,3);
cout<<tri<<'\n';//此处的第一个<<是重载过的,第二个没有
//(3,6)6 10 15 21 28 36
11.指针:指向Class Member Functions
class num_sequence
{
public:typedef void(num_sequence::*PtrType)(int);//PtrType代表了一个函数指针,指向有void (int)类型的函数,但被限制在num_sequence这个类中,此处的typedef定义了一个名为PtrType的指针,指向void(num_sequence::)(int)void fibonacci(int);void pell(int);void lucas(int);void triangular(int);void sequare(int);void pentagonal(int);//...
private:PtrType _pmf;
};
//定义一个指针
PtrType pm=&num_sequeence::fibonacci;

五、面向对象编程风格

1、面向对象编程概念

面向对象编程概念最主要的特性是:

  • 继承

    • 继承机制定义了父子关系,父类定义了所有子类共通的对外公开接口和私有实现内容,每个子类可以增加或改写继承而来的东西
  • 多态

    • 多态是在不同继承关系的类对象,去调同一函数,产生了不同的行为,就是说,有一对继承关系的两个类,这两个类里面都有一个函数且名字、参数、返回值均相同,然后我们通过调用函数来实现不同类对象完成不同的事件。
  • 动态绑定

    • 在非面向对象的编程风格中,当使用mat.check_in();时,编译器在编译时期就依据mat所属的类决定究竟执行哪一个check_in()函数,在执行之前就决定了,这种方式成为静态绑定,在面向对象编程方法中,仅能在执行过程中依据mat所寻址的实际对象来决定调用哪个check_in(),此成为动态绑定

      • 关于动态绑定和动态对象:https://www.cnblogs.com/raichen/p/5622197.html
void loan_check_in(LibMat& mat)
{//mat实际代表LibMat这个抽象类所派生出的类的对象mat.check_in();if(mat.is_late())mat.assess_fine();if(mat.waiting_list())mat.notify_availiable();
}
2、漫游:面向对象编程思维

默认情况下,member function的决议程序皆在编译时期静态地进行,若要令其在执行期动态进行,就要在他的声明式前加关键字virtual

class LibMat
{
public:LibMat(){cout<<"LibMat::LibMat() default constructor!\n";}//1virtual ~LibMat(){cout<<"LibMat::~LibMat() destructor!\n";}//2virtual void print() const{cout<<"LibMat::print()--I am a LibMat object!\n";//3}
}
void print(const LibMat &mat)
{cout<<"in global print():about to print mat.print()\n";//4mat.print();//5
}//挡在main函数中传入LibMat的派生类对象时,语句执行顺序1, 派生类构造函数,4,5,派生类析构函数,2

实现派生类book,为了清楚标示这个新类乃是继承自一个已存在的类,其名称之后必须接着一个冒号:,然后紧接着关键词public和基类的名称

class Book:public LibMat
{
public:Book(const string &title,const string &author):_title(title),_author(author){cout<<"Book::Book("<<_title<<","<<_author<<")constructor\n";}virtual ~Book(){cout<<"Book::~Book() destructor~\n";}virtual void print() const{cout<<"Book::print()--I am a Book object!\n"<<"My title is:"<<_title<<'\n'<<"My author is:"<<_author<<endl;}//重载了LibMat的print函数const string& title() const{return _title;}const string& author() const{return _author;}
protected:string _title;string _author;//protected的所有成员除派生类外都不得直接取用
}

使用派生类时,不需要刻意区分是"继承而来的成员"还是“自身定义的成员”,两者使用完全透明

3、不带继承的多态

这节没笔记,就是讲了一下当不用继承来实现多态有多麻烦,用面向对象方法中的继承实现多态真香。

4、定义一个抽象基类

定义抽象类

  1. 找出所有子类共通的操作行为
  2. 设法找出哪些操作行为与型别相依——也就是说,有哪些操作行为必须根据不同的派生类而拥有不同的实现方式,这些操作行为应该成为整个类继承体系中的虚拟函数,static member function无法被声明为虚拟函数
  3. 找出每个操作行为的存取层级,如果某个操作行为应该让一般程序皆能取用,就应该声明为public,如果某个操作行为在基类之外不需要被用到,则声明为privateprotected这个层级的操作薪给可让派生类取用,却不让一般程序调用
class num_sequence
{
public:virtual ~num_sequence(){};virtual int elem(int pos) const=0;//这个函数之于这个抽象基类无实际意义,便定义为纯虚函数,派生类可以重载该函数以实现多态,将纯虚函数赋值为0,意思便是令它为纯虚函数static int max_elems(){return _max_elems;}virtual ostream& print(ostream &os =cout)const=0;
protected:virtual void gen_elems(int pos)const=0;bool check_integrity(int pos) const;const static int _max_elems=1024;
};
bool num_sequence::check_integrity(int pos) const
{if(pos<=0||pos>_max_elems){cerr<<"!!invalid position:"<<pos<<"Cannot honor request\n";return false;}return true;
}
ostrean& operator<<(ostream&os, const num_sequence &ns)
{return ns.print(os);
}

任何类如果声明有一个(或多个)纯虚拟函数,那么由于其接口的不完整性(纯虚拟函数没有函数定义,是谓不完整),程序无法为它产生任何对象,这种类只能作为派生类的子对象使用,而且前提是这些派生类必须为所有虚拟函数提供确切的定义。(不懂)

5、定义一个派生类

派生类由两部分组成:一是基类所构成的子对象,由基类的non-static data members——如果有的话——组成,二是派生类的部分(由派生类的non-static data members组成)

Fibonacci class必须为基类继承而来的每个纯虚函数提供对应的实现内容。除此之外,它还必须声明Fibonacci class专属的members

class Fibonacci:public num_sequence{
public:Fibonacci(int len=1,int beg_pos=1):_length(len),_beg_pos(beg_pos){}virtual int elem(int pos) const;virtual const char* what_am_i() const{return "Fibonacci";}virtual ostream& print(ostream &os=cout)const;int length() const{return _length;}//非virtual,因为基类未提供实体可供改写,因此基类指针无法调用int beg_pos() const{return _beg_pos;}
protected:virtual void gen_elems(int pos)const;//定义未写bool check_integrity(int pos) const;int _length;int _beg_pos;static vector<int> _elems;
}
//在类本身之外对虚拟函数进行定义时,不需指明关键词virtual
int Fibonacci::elem(int pos) const
{if(!check_integrity(pos))//继承来的,但如果派生类声明了相同的函数,便会遮蔽朱基类的那份member,如果在派生类内使用继承而来的那份member,必须使用class scope运算符加以修饰return 0;if(pos>_elems.size())Fibonacci::gen_elems(pos);//通过class scope运算符可以清楚的让编译器知道想调用哪一份函数实体,于是执行期发生的虚拟机制便被遮掩了。return _elems[pos-1];
}
inline bool Fibonacci::check_integrity(int pos) const
{if(!num_sequence::check_integrity(pos))return false;if(pos>_elems.size())Fibonacci::gen_elems(pos);return true;
}//但由于check_integrity为被声明为virtual,因此实际调用时不会根据实际对象来进行决议
num_sequence *ps=new Fibonacci(12,8);//num_sequence的指针,但实际指向Fibonacci
ps->check_integrity(pos);//根据ps的型别,会被静态决议为num_sequence::check_integrity()
//改进,在check_integrity中使用virtual
bool num_sequence::check_integrity(int pos,int size)
{if(pos<=0||pos>_max_elems){//....}if(pos>size)gen_elems(pos);return true;
}
6、运用继承体系

?讲了下派生类定义好后咋用,多么神奇

7、基类抽象程度修改

之前的基类只提供了接口,并未提供任何实现内容,现修改基类的抽象程度

class num_sequence{
public:virtual ~num_sequence(){}virtual const char* what_am_i() const=0;int elem(int pos) const;ostream& print(ostream&os=cout) const;int length()const{return _length;}int beg_pos const{return _beg_pos;}//在派生类中未定义,当在派生类中调用时调用的是基类的,但由于基类的构造函数中赋予了_beg_pos的值,由派生类的构造函数所调用static int max_elems(){return 64;}
protected:virtual void gen_elems(int pos)const=0;bool check_integrity(int pos, int size)const;num_sequence(int len,int bp,vector<int>&re):_length(len),_beg_pos(bp),_relems(re){}int _length;int _beg_pos;vector<int> &_relems;
};
class Fibonacci:public num_sequence{
public:Fibonacci(int len=1,int beg_pos=1);virtual const char*what_am_i() const{return "Fibonacci";}
protected:virtual void gen_elmes(int pos) const;static vector<int> _elems;
};
8、初始化、析构、复制

num_sequence是一个抽象基类,我们无法为它定义任何对象,num_sequence扮演的角色是每个派生类的子对象,基于这个理由,将基类的constructors声明为protected而非public

派生类的初始化行为,包含调用其基类的constructor,然后再调用派生类自己的constructor,派生类之constructor不仅必须为派生类之data member进行初始化操作,还需为其基类之data members提供适当的值

inline Fibonacci::
Fibonacci(int len, int beg_pos):num_sequence(len, beg_pos, _elems)//因为基类有数据成员变量需要初始化

基类的destructor会在派生类的destructor结束之后被自动调用

9、在派生类中定义一个虚拟函数

当我们定义派生类时,我们必须决定究竟要将基类的虚拟函数改写掉,还是原封不动地加以继承,如果我们继承了纯虚拟函数,那么这个派生类也会被视为抽象类,也就无法为它定义任何对象。

如果决定改写基类所提供的的虚拟函数,那么派生类所提供的新定义,其函数型别必须完全符合基类所声明的函数原型,包括:参数列,返回型别,常量性

虚拟函数的静态决议:

  • 在基类的constructor和destructor内定义虚拟函数

    • 当构造派生类对象时,基类的constructor会先被调用,如果在基类的constructor中调用某个虚拟函数,由于派生类的data member尚未初始化,因此肯定无法调用派生类的虚拟函数,因此会被默认静态决议至基类函数
  • 当使用的是基类的对象,而非基类对象的pointer或reference时
void print(LibMat object, const LibMat* pointrt, const LibMat &reference)
{object.print();//必定调用LibMat::print(),因为是对象,此处print是个虚拟函数pointer->print();reference.print();//均会动态决议
}
10、执行期的型别鉴定机制

typeid运算符:

执行期型别识别机制,它让我们得以查询多态化的class pointer或class reference,获得其所指对象的实际型别

#include<typeinfo>
inline const char* num_sequence::what_am_i() const
{return typeid(*this).name();
}//typeinfo.typeid运算符会返回一个type_info对象,其中存储着与型别相关的种种信息,每一个多态类如FIbonacci都会对应一个type_info对象,该对象的name()函数会返回const char*,y用以表示类名称
//type_info class也支持相等和不相等两个比较操作
num_sequence *ps=&fib;
if(typeid(*ps)==typeid(Fibonacci))ps->Fibonacci::gen_elems(64);//ps的确指向Fibonacci对象
//但ps仍然是num_sequence的指针,它不知道自己指向了fibonacci对象,因此ps->gen_elems(64)不可食用

六、以template进行编程

1、Class Template的定义
template <typename elemType>
class BinaryTree
{
public:BinaryTree();BinaryTree(const BinaryTree&);~BinaryTree();BinaryTree& operater=(const BinaryTree&);bool empty(){return _root==0;}void clear();
private:BTnode<elemType> *_root;//将src所指之子树复制到tar所指之子树void copy(BTnode<elemType>* tar, BTnode<elemType>* src);//此处的BTnode必须以template parameter list加以修饰,一般规则是,在class template 及其members的定义式中,不须如此,除此之外的其他场合都需要以parameter list加以修饰
};
//在类主体之内定义inline函数与non-template class一样,在类主体之外不一样
template<typename elemType>
inline BinaryTree<elemType>::
BinaryTree()_root(0)
{}//第二次出现的BinaryTree不用template parameter list加以修饰,因为实在class定义域内
//以下是BinaryTree的copt constructor、copy assignment operator及destructor的定义:
template<typename elemType>
inline BinaryTree<elemType>::
BinaryTree(const BinaryTree &rhs)
{copy(_root,rhs._root);
}
template<typename elemType>
inline BinaryTree<elemType>::
~BinaryTree()
{clear();
}
template<typename elemType>
inline BinaryTree<elemType>&BinaryTree<elemType>::
operator=(const BinaryTree &rhs)
{if(this!=&rhs){clear();copy(_root,rhs._root);}return *this;
}
2、实现一个Class Template
template<typename elemType>
inline void  BinaryTree<elemType>::insert(const elemType &elem)
{if(!_root)_root=new BTnode<elemType>(elem);//构造一个BTnodeelse_root->insert_value(elem);
}
//insert_value()会通过左侧子节点或右侧子节点递归调用自己,直到以下任何一种情形发生才停止:(1)合乎条件的子树并不存在(2)欲安插的数值已在树中,由于每个数值只能在树中出现一次,所以用_cnt记录这个节点的安插次数
template<typename valType>
void BTnode<valType>::insert_value(const valType &val)
{if(val==_val){_cnt++;return;}if(val<_val){if(!_lchild)_lchild=new BTnode(val);else _lchild->insert_value(val);}else{if(!_rchild)_rchild=new BTnode(val);else _rchild->insert_value(val);}
}
3、一个以Function Template完成的Output运算符
template<typename elemType>
inline ostream& operator<<(ostream& os, const BinaryTree<elemType>& bt)
{os<<"Tree:"<<endl;bt.print(os);//print是BinaryTree<elemType> 的私有成员函数,因此<<运算符必须是friendreturn os;//此处返回os是为了接收后面的输入流
}
BinaryTree<string> bts;
cout<<bts<<endl;
4、常量表达式与默认参数

template参数并不是非得某种型别不可,也可以以常量表达式作为template参数

template <int len>
class num_sequence
{
public:num_sequence(int beg_pos=1);//...
};
template <int len>
class Fibonacci:public num_sequence<len>
{
public:Fibonacci(int beg_pos=1):num_sequence<len>(beg_pos){}//...
};
Fibonacci<16> fib1;//Fibonacci 对象,但是基类num_sequence因为参数len(16)导致元素数目为16
Fibonacci<16> fib2(17);//Fibonacci 对象,beg_pos为17

即len不需要在类中单独定义了

5.Member Template Functions

可以讲member functions定义成template形式

class PrintIt{
public:PrintIt( ostream &os):_os( os ){}//下面是一个member template functionvoid print( const elemType &elem, char delimiter ='\n'){_os<<elem<<delimiter;}
private:ostream& _os;
};
//用法
PrintIt to_standard_out( cout );
to_standard_out.print("hello");

Class template内也可以定义member template function

template<typename OutStream>
class PrintIt
{
public:PrintIt( OutStream &os):_os(os){}template<typename elemType>void print(const elemType &elem, char delimiter ='\n'){_os<<elem<<delimiter;}
private:ostream& _os;
};
PrintIt to_standard_out( cout );
to_standard_out.print("hello");

七、异常处理

1.抛出异常
inline void Triangular_iterator::check_integrity()
{if( _index >= Triangular::_max_elems)throw iterator_overflow( _index, Triangular::_max_elems);//抛出异常,会直接调用拥有两个参数的constructorif( _index >= Triangular::_elems.size())Triangular::gen_elements( _index + 1 );
}
//异常类定义
class iterator_overflow
{
public:iterator_overflow( int index, int max):_index( index ),_max( max ){}int index() {return _index};int max() {return _max};void what_happened( ostream &os = cerr ){os<<"Internal error: current index"<<_index<<" exceeds maximum bound: "<<_max;}
private:int _index;int _max;
};
2.捕捉异常

我们可以利用单一或连串的catch子句来捕捉被抛出的异常对象。catch子句由3部分组成:关键词catch、小括号内的一个型别或对象,大括号内的一组语句(用以处理异常)

extern void log_message( const char *);
extern string err_messages[];
extern ostream log_file;
bool some_function()
{bool status = true;//....catch(int errno){log_message( err_messages[errno]);status = false;}//对应throw 42catch( const char* str){log_message(str);status = false;}//对应throw “panic:no buffer!\n”;catch( iterator_overflow &iof){iof.what_happend( log_file );status = false;}//对应throw iterator_overflow( _index, Triangular::_max_elems)return status;
}

以上呈现了完整的异常处理过程。流程通过所有catch子句之后,由正常程序接受,本例之中,正常程序会重新接手return status这一行。

有时候可能无法完成异常的完整处理,在记录消息之外,我们需要重抛异常,以寻求其他catch子句的协助,做进一步的处理

 catch( iterator_overflow &iof){iof.what_happend( log_file );throw;}
//捕捉任何异常
catch(...)
{log_messag("exception of unknown type");//清理,然后离开
}
3.提炼异常

catch子句和try一起使用,try块以关键词try作为开始,然后是大括号内的一连串程序语句,catch子句置于try块的尾端,这表示如果try块内有任何异常发生,便由接下来的catch子句加以处理。

bool has_elem( Triangular_iterator first, Triangular_iterator last, int elem)
{bool status = true;try{while( first != last ){if( *first == elem )return status;++first;}}//try中如果有异常抛出(iterator_overflow类型),会被捕捉catch( iterator_overflow &iof){log_message( iof.what_happend() );log_message( *check if iterator address same container*);}status = false;//异常捕捉后程序恢复从这开始,又或者没有找到elemreturn status;
}
//其中*first会调用被重载的 * 运算符
inline int Triangular_iterator::operator *()
{check_integrity();return Triangular::elems[_index];
}
//又调用check_integrity
inline void Triangular_iterator::check_integrity()
{if( _index >= Triangular::_max_elems )throw iterator_overflow( _index, Triangular::_max_elems); //....后面语句在异常被处理之前无法执行
}

当查找的索引大于_max_elems于是就会从check_integrity()中抛出异常,此处异常处理机制会沿着函数调用链去寻找能处理异常的catch子句

4.局部资源管理
extern Mutex m;
void f()
{//申请资源int *p = new int;m.acquire();process( p );//释放资源m.release();delete p;
}//此函数内看似申请了资源并且在最后释放了资源,但如果process这个函数抛出异常呢?就不会释放申请的资源

C++的发明者因日了资源管理的手法,对对象而言,初始化操作发生于constructor内,资源的申请也在constructor内完成。资源的释放则应该在destructor内完成。

#include <memory>
void f()
{auto_ptr<int> p( new int);MutexLock ml( m );process( p );//p和m1的destructor会在此处被悄悄调用
}

如果process( p );执行过程中有任何异常被抛出,C++保证在异常处理机制终结某个函数之前,函数中的所有局部对象的destructor都会被调用

5.标准异常

如果new表达式无法从程序的自由空间配置到足够的内存,它会抛出bad_alloc异常

vector<string>* init_text_vector( ifstream &infile )
{vector<string> *ptext = 0;try{ptext = new vector<string>;//打开file和file vector}catch( bad_alloc ){cerr << "ouch.heap memory exhausted!\n ";//..清理并离开}return ptext;
}

完结撒花

Essential C++读书笔记相关推荐

  1. ESSENTIAL C++ 读书笔记

    title: ESSENTIAL C++ 读书笔记 date: 2016-05-27 12:46:36 categories: C++ tags: - C++ 1.条件语句 if条件语句,如果括号内的 ...

  2. 《Essential C++》读书笔记 之 泛型编程风格

    <Essential C++>读书笔记 之 泛型编程风格 2014-07-07 3.1 指针的算术运算(The Arithmetic of Pointer) 新需求1 新需求2 新需求3 ...

  3. 《Essential C++》读书笔记 之 基于对象编程风格

    <Essential C++>读书笔记 之 基于对象编程风格 2014-07-13 4.1 如何实现一个class 4.2 什么是Constructors(构造函数)和Destructor ...

  4. 《Essential C++ 中文版》 读书笔记及习题解答

    目录 前言 preface 第一章 C++编程基础 Basic C++ Programming 简介 读书笔记 1.1 如何撰写C++程序 1.2 对象的定义与初始化 1.3 撰写表达式 1.4 条件 ...

  5. 《Deep Learning With Python second edition》英文版读书笔记:第十一章DL for text: NLP、Transformer、Seq2Seq

    文章目录 第十一章:Deep learning for text 11.1 Natural language processing: The bird's eye view 11.2 Preparin ...

  6. 【读书笔记】知易行难,多实践

    前言: 其实,我不喜欢看书,只是喜欢找答案,想通过专业的解答来解决我生活的困惑.所以,我听了很多书,也看了很多书,但看完书,没有很多的实践,导致我并不很深入在很多时候. 分享读书笔记: <高效1 ...

  7. 读书笔记:编写高质量代码--web前端开发修炼之道(二:5章)

    读书笔记:编写高质量代码--web前端开发修炼之道 这本书看得断断续续,不连贯,笔记也是有些马虎了,想了解这本书内容的童鞋可以借鉴我的这篇笔记,希望对大家有帮助. 笔记有点长,所以分为一,二两个部分: ...

  8. 《编程匠艺》读书笔记

    <编程匠艺>读书笔记之一 <编程匠艺>读书笔记之二 <编程匠艺>读书笔记之三 <编程匠艺>读书笔记之四 <编程匠艺>读书笔记之五 <编 ...

  9. 《Java: The Complete Reference》等书读书笔记

    春节期间读了下<Java: The Complete Reference>发现这本书写的深入浅出,我想一个问题,书中很多内容我们也知道,但是为什么我们就写不出这样一本书,这么全面,这么系统 ...

最新文章

  1. 腐烂国度2巨霸版计算机学知识,《腐烂国度2巨霸版》新手攻略 新手入门玩法技巧大全...
  2. 云原生ASP.NET Core程序的可监测性和可观察性
  3. mysql connector api_mysql8 参考手册-Connector/J使用X DevAPI进行连接压缩
  4. 数据科学和人工智能技术笔记 二、数据准备
  5. (c语言)输入一个数,将该数按原规律插入到有序数组中
  6. 手机尺寸相关的概念 +尺寸单位+关于颜色
  7. linux上的 heartbeat 双机热备服务架设
  8. Markdown编辑器: 语法、Atom、Word、PPT
  9. Prometheus和Grafana监控实践
  10. 数据介绍 | 长序列归一化植被指数NDVI
  11. MySQL(4) 数据库增删改查SQL语句(整理集合大全)
  12. WPF ScrollViewer跟随鼠标滑动设置
  13. SWFUpload批量上传插件
  14. win10计算机系统优化设置,win10加速优化的方法是什么_windows10优化设置的方法
  15. 程序员为什么要学算法?
  16. 浅论信息化环境下的印刷业发展
  17. 电商网站秒杀和抢购的高并发技术实现和优化
  18. 微型计算机技术陈慈发,微型计算机技术
  19. DB2 AS400数据库恢复
  20. CAN设计与应用指南

热门文章

  1. 世界杯 叮当 机器人 树莓派_世界杯营销大战,移动互联网谁捧起了大力神杯
  2. 数学建模_国2000A——DNA序列问题中的数据处理
  3. 安全生产施工单位材料准备清单
  4. windows 任务相关 删除任务
  5. MxN螺旋矩阵(由外向内)
  6. 易基因 | 常用的6种DNA甲基化测序方法,你知道几个?
  7. VS2012 MFC + OpenCV
  8. 怎样将一个Word表格拆分为两个
  9. 七牛云发送短信验证码
  10. Hive Thrift Server