第八天
1. 泛型算法
find函数如果不支持相等(==)操作符,或打算用不同的测试方法来比较元素,则可以使用第二个版本的find函数。这个版本需要一个额外的参数:实现元素比较的函数的名字。
**泛型算法本身从不执行容器操作,只是单独依赖迭代器和迭代器操作实现。**算法从不直接添加或删除元素。比如操纵出入器(inserter)。
#include<algorithm>
#include<numeric>
accumulate()
int sum = accumulate(vec.begin(),vec.end(),42);
int sum = accumulate(vec.begin(),vec.end(),string(""));
将sum设置为vec元素之和加上42.头两个形参指定要累加的元素范围。第三个形参是累加的初值。
第三个实参时必要的,可以创建合适的起始值或关联的类型。
find_first_of()
两对迭代器参数标记元素范围。在第一段范围内查找与第二段范围中任意元素匹配的元素。然后返回指向第一个匹配的元素的迭代器。
size_t = cnt = 0;
list<string>::iterator it = roster1.begin();
while((it=find_first_of(it,roster1.end(),roster2.begin(),roster2.end()))!=roster1.end())
{
++cnt;
++it;
}
cout<<cnt<<' '<<endl;
第一次循环中,遍历整个roster1范围。第二次以及后续的循环迭代器值考虑roster1中尚未匹配的部分。
每对迭代器两个实参的类型必须精确匹配,但不要求两对之间的类型匹配。元素可以存储在不同类型徐磊中,只要这两序列的元素可以比较即可。
**fill()**写入输入序列的元素
fill(vec.begin(),vec.end(),0);
fill(vec.begin(),vec.begin()+vec.size()/2,10);
fill带有一对迭代器形参,用于指定要写入的范围,写入的值是第三个形参的副本。
fill_n不检查写入操作的算法
一对迭代器,一个计数器以及一个值。
该函数从迭代器指向的元素开始,将指定数量的元素设置为给定的值。
对于指定数目的元素做写入运算,或者写到目标迭代器的算法,都不检查目标的大小是否足以存储要写入的元素。
确保算法有足够的元素存储输出数据的一种方法是使用插入迭代器。可以给基础容器添加元素的迭代器。
back_inserter() 迭代器适配器
#include<iterator>
vector<int> vec;
fill_n(bcak_inserter(vec),10,0);
fill_n函数每次写入一个值,都会通过back_inserter生成的插入迭代器实现。效果相当于在vec上调用push_back,在vec末尾添加10个位0的元素。
**copy()**写入到目标迭代器的算法
头两个指定输入范围,第三个指向目标序列的第一个元素。
list<int> ilist;
vector<int> ivec;
copy(ilst.begin(),ilst.end(),back_inserter(ivec));
vector<int> ivec(ilst.begin(),ilst.end());
replace()
对输入序列做读写操作,将序列中特定的值替换为新的值。
一对指定输入范围的迭代器和两个值。每一个等于第一个值的元素都替换成第二个值。
replace(ilst,begin(),ilst.end(),0,42);
**replace_copy()**如果不想改变原来的序列
接受第三个迭代器实参,指定保存调整后序列的目标位置。
vector<int> ivec;
replace_copy(ilst.begin(),ilst.end(),back_inserter(ivec),0,42);
ilst没有改变,ivec存储ilst一份副本,而ilst内所有的ivec中都变成42。
想知道使用了多少个由六个或以上的字母组成的单词。每个单词统计一次,长度,字典顺序输出。
-
去重
-
按长度排序
-
统计长度大于等于六个字符的单词个数
-
去重
-
按长度排序
vector<string> words;
sort(words.begin(),words.end());
vector<string>::iterator end_unique = unique(wprds.begin(),words.end()):
words.erase(end_unique,words.end());
unique()
带有两个指定元素范围的迭代器参数。删除相邻的重复元素,重新排列输入范围内的元素,并且返回一个迭代器。
实际上将无重复的元素赋值到序列前端
算法不直接修改容器的大小。如果要添加或删除元素,则必须使用容器操作。
- 统计长度大于等于六个字符的单词个数
谓词,做某些检测的函数,返回用于条件判断的类型,支出条件是否成立。
定义一个谓词函数来实现两个string对象的比较,并返回一个bool值,支出第一个字符串是否比第二个短。
bool isShorter(const string &s1,const string &s2)
{
return s1.size()<s2.size();
}
bool GT6(const &s)
{
return s.size()>=6;
}
**stable_sort()**保留相等元素的原始相对位置。对于长度相同的元素,保留字典顺序。
stable_sort(words.begin(),words.end(),isShort);
count_if() 统计长度不小于6的单词
vector<string>::size_type wc= count_if(words.begin(),words.end(),GT6);
读取两个实参所标记的范围内的元素。每次读出一个元素,传递给第三个实参表示的谓词函数。将每个单词传递给GT6,返回一个bool值。
组合:
bool isShorter(const string &s1,const string &s2)
{
return s1.size()<s2.size();
}
bool GT6(const &s)
{
return s.size()>=6;
}
int main()
{
vector<string> words;
string next_word;
while(cin>>next_word)
{
words.push_back(next_word);
}
sort(words.begin(),words.end());
vector<string>::iterator end_unique = unique(words.begin(),words.end());
stable_sort(words.begin(),words.end(),isShorter);
vector<string>::size_type wc = count_if(words.begin(),words.end(),GT6);
cout<<wc<<' ' <<make_plural(wc,"Word","s")<<endl;
return 0;
}
2.迭代器
- 插入迭代器:与容器绑定在一起,实现在容器中插入元素的功能。
- iostream迭代器:可与输入输出流绑定在一起,用于迭代遍历所关联的IO流。
- 反向迭代器:实现向后遍历,而不是向前遍历。
插入迭代器
- back_inserter,创建使用push_back实现插入的迭代器。
- front_inserter,创建使用push_front实现插入。
- inserter,使用insert实现插入。第二实参,指向插入起始位置的迭代器。
list<int>::iterator it = find(ilst.begin(),ilst.end(),42);
replace_copy(ivec.begin(),ivec.end(),inserter(ilst,it),100,0);
iostream迭代器
- istream_iterator:用于读取输入流
- ostream_iterator:用于写输出流
创建流迭代器时,必须指定迭代器所读写的对象类型。
istream_iterator<int> cin_it(cin);
istream_iterator<int> end_of_stream;
ifstream outfile;
ostream_iterator<T> output(outfile," ");
ostream_iterator对象必须与特定的流绑定在一起。
istream_iterator可直接把它绑定在一个流上。
istream_iterator<int> in_iter(cin);//front
istream_iterator<int> eof;//end
while(in_iter!=eof)
{
vec.push_back(*in_iter++);//先自增向前移动,再解引用
}
使迭代器的流中移动到下一个值,但是返回指向前一个值的迭代器。就该迭代器进行解引用获取该值。
osteam_iterator<string> out_iter(cout,"\n");
istream_iterator<string> int_iter(cin),eof;
while(in_iter!=eof)
{
*out_iter++ = *in_iter++;
}
定义一个istream_iterator,用于将string类型的数据写到cout中,后面跟一个换行符。
从cin中读取对象。
将读取的值赋给out,然后再输出到cout上。
在类类型上使用istream_iterator
使用istream_iterator对象读取一系列的Sales_iter对象,并求和。
istream_iterator<Sales_item> item_iter(cin),eof;
Sales_item sum;
sum=*item_iter++;
while(item_iterQ!=eof)
{
if(item_iter->same_isbn(sum)) sum = sum+*item_iter;
else cout<<sum<<endl;sum=*item_iter;
++item_iter;
}
cout<<sum<<endl;
流迭代器的限制:
- 不可能从ostream_iterator对象读入,也不可能写到istream_iterator对象中。
- 一旦给istream_iterator对象赋值,写入就提交了。没办法再改变这个值。并且只能输入一次。
- ostream_iterator没有->操作。
反向迭代器从最后一个元素到第一个元素遍历容器。
输出列表中最后一个单词,可使用反向迭代器。
string::reverse_iterator rcomma = find(line.rbegin(),line.rend(),',');
const迭代器
不希望使用这个迭代器修改容器中的元素。
- 输入迭代器:可用于读取容器中的元素,但是不保证能支持容器的写入操作。
- 相等或不等操作
- 前十和后置的自增运算
- 解引用操作符
- 箭头操作符->
输入迭代器只能顺序使用,不能检查之前的元素。
find()和accumulate()
- 输出迭代器 用于先IG容器写入元素,但是不保证你能支持读取容器内存。
- 前置和后置的自增运算
- 解引用操作符
要求每个迭代器的值必须正好写入一次。对于指定的迭代器值只能使用一次*运算。
copy()
-
前向迭代器
用于读写指定的容器。指挥以一个方向遍历序列。除了输入和输出迭代器的操作,还支持对同一个元素的多次读写。
replace(); -
双向迭代器:除了前向迭代器的所有操作,还提供前置和后置的自减运算。
reverse(); map set list -
随机访问迭代器:提供在常量时间内访问容器任意位置的功能。
除了支持双向迭代器的所有功能外。
- 关系操作符
- 迭代器和证书之间的加法减法操作。返回迭代器在容器中前n个元素。
- 下标操作。
sort() vector deque string
3.容器特有的算法
list容器上的迭代器是双向的。不支持随机访问。
list容器特有的算法与其泛型版本之间有两个至关重要的差别。
- remove和unique的list版本修改了其关联的基础容器。真正删除了除了指定的元素。
- list容器提供的merge和splice运算会破坏他们的实参。
第九天
1. 类
vector/istream/string 都是类类型
构造函数初始化
Sales_item():units_sold(0),revenue(0,0){}
类内部定义的函数默认为inline.
const成员不能改变其操作的对象的数据成员。const必须同时出现在声明和定义中。
在创建对象之前,必须完整定义该类。编译器才会给类的对象预订相应的存储空间。
类的定义后可以接一个对象定义列表。定义必须以分号结束。
myScrean.move(4,0).set('#');
==
myScreen.move(4,0);
myScreen.move('#');
2.返回*this
如果在调用操作时,必须返回一个引用,该引用指向指向操作的那个对象。
class Screen{
public:
Screen& move(index r,index c);
Screen& set(char);
Screen& set(index,index,char);
};
Screen& 指明该成员函数返回对自身类类型的对象的引用。每个函数都返回调用自己的那个对象。
使用this指针来访问该对象。
Screen& Screen::set(char c)
{
contents[currsor] = c;
return *this;
}
Screen& Screen::move(index r,index c)
{
index row = r*width;
cursor = row+c;
return *this;
}
this是一个指向非常量Screen的指针。可以通过对this指针解引用来访问this指向的对象。
可以改变this所指向的值,到哪不能改变this所保存的地址。
在const成员函数中,this的类型是一个指向const类类型对象的const指针。
既不能改变this所指向的对象,也不能改变this所保存的地址。
3. 基于const的重载
基于成员函数是否为const,可以重载一个成员函数。
基于一个指针形参是否指向const,也可重载一个函数。
class Screen
{
public:
Screen& display(std::ostream &os)
{
do_display(os);
return *this;
}
const Screen& display(std::ostream &os)
{
do_display(os);
return *this;
}
private:
void do_display(std::ostream &os) const
{
os<<contens;
}
};
Screen myScreen(5,3);
const Screen blank(5,3);
myScreen.set('#').display(cout);
blank.display(cout);
将display嵌入到一个长表达式中时,调用非const版本。
当display一个const对象是,调用const版本。
4.可变数据成员
可变数据成员永远不能是const。甚至当它是const对象的成员也是如此。
const成员函数可以改变mutalbe成员。
class Screen
{
public:
mutable size_t access_ctr;
};
void Screen::do_display(std::ostream& os) const
{
++access_ctr;
os<<contents;
}
尽管do_display是const,它也可以增加acces_ctr。该成员是可变成员。
如果函数在类定义体之外定义,则用于返回类型的名字在类作用于之外。
如果返回类型使用由类定义的类型,则必须使用完全限定名。
class Screen
{
public:
typedef std::string::size_type index;
index get_cursor() const;
};
inline screen::index Screen::get_cursir() const
{
return cursor;
}
5.名字查找
- 首先检查成员函数局部作用域中的声明。
- 如果在成员函数中找不到该名字的声明,则检查对所有类成员的声明。
- 如果在类中找不到该名字的声明,则检查在此成员函数定义之前的作用于中出现的声明。
int height;
class Screen
{
public:
void dummy_fcn(index height)
{
cursor = width * height;
}
void dummy_fcn(index height)
{
cursor = width * this ->height;
cursor = width * Screen::height;
}
private:
index cursor;
index height,width;
};
6. 构造函数
构造函数分两个阶段执行:
- 初始化阶段
- 普通的计算阶段
必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。
成员被初始化的次序就是定义成员的次序。
如果一个成员是根据其他成员而初始化,则成员初始化的次序是至关重要的。
Sales_item(const std::string &book,int cnt,double price):
isbn(bool),units_sold(cnt),revenue(cnt * price){}
合成的默认构造函数:使用与变量初始化相同的规则来初始化成员。
如果类包含内置或符合类型的成员,则类不应该依赖于合成的默认构造函数。
如果定义了其他构造函数,则提供一个默认构造函数几乎总是对的。
使用默认构造函数
Sale_item myobj();
myobj的定义被编译器解释为一个函数的声明,该函数不接受参数并返回一个Sale_item类型的对象。
正确使用默认构造函数:去掉括号
Sale_item myobj;
Sale_item myobj = Sales_item();
隐式类类型转换
class Sale_item
{
public:
Sale_item(const std::string &book=" "):
isbn(book),units_sold(0),revenue(0,0);
Sales_item(std::istream &is);
}
string null_book="qqq";
item.same_isbn(null_book);
item.same_isbn(cin);
在期待一个Sales_item类型对象的地方,可以使用一个string或一个istream。
抑制由构造函数定义的隐式转换
class Sale_item
{
public:
explicit Sale_item(const std::string &book=" "):
isbn(book),units_sold(0),revenue(0,0);
explicit Sales_item(std::istream &is);
}
explicit 只能作用于类内部的构造函数声明上。编译器将不使用它作为转换操作符。
显示的构造函数来生成转换:
string null_bool = "qqq";
item.same_isbn(Sales_item(null_book));
显式初始化
struct DatA
{
int ival;
char *ptr;
};
Data val1 ={0,0);
有三个缺点:
- 要求类的全体数据成员那都是public
- 初始化容易出错。
- 增删成员时,必须找到所有的初始化并正确更新。
7.友元
友元机制允许一个类将对其非公有成员的访问授权于指定的函数或类。
class Screen
{
friend class Window_Mgr;
};
Window_Mgr& Window_Mgr::reloocate(Screen::index r,Screen::index c,Screen&s)
{
s.height+=r;
s.width+=c;
return *this;
}
8. static类成员
每个static数据成员是与类关联的对象,并不与该类的对象相关联。
static成员函数没有this形参,它可以直接访问所属类的static成员,但不能直接使用非static成员。
static 成员函数不能被声明为const。不能被声明为虚函数。
- static成员的名字是在类的作用域中,因此可以避免与其他类成员或全局对象名字冲突。
- 可以实施封装。static成员可以是私有成员,而全局对象不可以。
- static成员是与特定类关联的。
class Account
{
public:
void applyint()
{
amount+=amount * interestRate;
}
static double rate()
{
return interestRate;
}
static void rate(double);
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
可以通过作用于操作符从类直接调用static成员,或者通过对象、引用或指向该类类型对象的指针间接调用。
Account ac1;
Account *ac2 = &ac1;
double rate;
rate = ac1.rate();
rate = ac2->rate();
rate=Account::rate();
static数据成员必须在类定义的外部定义。
static成员不是通过类构造函数进行初始化,而是应该在定义时进行初始化。
double Account::interestRate initRate();
const static 数据成员可以在类的定义中进行初始化。
class Account
{
public :
static double rate()
{
return interestRate();
}
static void rate(double);
private:
static const int period = 30;
double daily_tbl[period];
};
const static数据成员在类的定义体重初始化时,该数据成员仍必须在类的定义体之外进行定义。
只是不必再指定初始值。
const int Account::period;
static数据成员的类型可以使该成员所属的类类型。
非static成员被限定声明为其自身类对象的指针或引用。
class Bar
{
public:
//...
private:
static Bar mem1;
Bar *mem2;
};
static数据成员可用作默认实参
非static数据成员不能用作默认参数,因为无法提供对象以获取该成员的值。
class Screen
{
public:
Screen& clear(char = bkground);
private:
static const char bkgrond='#';
};