c++ Primer(4th)学习笔记,按章节,抓重点.
第一部分:c++基础
一些重点;
形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值;如果形参位引用类型,则它只是实参的别名.
默认实参(string screenInit(string::size_type height=24,string::size_type width=80,char background = ''))
普通形参(int): 在函数体里建立局部副本.
指针形参(int*):指针值不变,所指对象在函数体里改变.如需保护(只读),则const int*
引用形参(int&):将实参传入函数体,可以修改其值,还可以返回额外的值.const int&可以避免复制副本,直接只读.
数组形参(int array[]):数组对象自动转换为指向数组首地址的指针
函数形参():函数形参也自动转换为指向函数的指针
容器形参(vector<int>::const_iterator beg)
命令行选项: 应用:main -d -o ofile data0
定义:int main(int argc,char *argv[]) {...}
const fun(): 是返回一个const类型的值,它要求接受类型也是const的,否则会编译错误.
fun() const: 多是用于类里面的函数,是保证在该函数中各个数据只是传值,并不发生值的变化,比如说a++就不可以.
static fun():使得函数只能在本文件或类中可见,在其他地方是不可见的.
static_cast: 一般的类型转换,no run-time check.通常,如果你不知道该用哪个,就用这个.
dynamic_cast: 通常在基类和派生类之间转换时使用,run-time cast
const_cast: 主要针对const和volatile的转换.
reinterpret_cast: 用于进行没有任何关联之间的转换,比如一个字符指针转换为一个整形数.
标准库类型
1,namespace, using声明
using std::cin; #include <iostream> 声明std标准库中的cin, ::作用域操作符
2,string类型
using std::string; #include <string> string标准库负责管理字符相关的内存和提供各种可变长度字符串的操作.
string标准库自带的几个构造函数:
string s1;默认 string s2(s1); string s3("value"); string s4(n, 'c');
string的读写:
1,读 cin >> s1 >> s2; 输入第1个到s1,第2个到s2,去掉空格的.
2,写 cout << s1 << s2 << end1; helloworld
3,读入无限 while( cin >> word ) cout << word << end1;
4,读入一行 while( getline( cin , word )) cout << word << end1;
string的操作:
s.empty()判断 s.size()大小 s[n]下标 s1+s2相加 s1 = s2赋值 s1 == s2(!= <= >=,大小按字典排列)比较
对于string的size,它的类型必须是string::size_type(这种类型是配套类型,也叫库类型使与机器无关,unsigned型)
string s3 = s1 + "," + s2 + "/n";
s[n] = '*'; 下标操作可以是左值
string中的字符处理函数:#include <cctype.h>
isalnum(c)字母或数字 isalpha(c)字母 ......
3,vector类型
#include <vactor>
using std::vector; vector容器是同一类元素的集合,也是一个类模板(class template),包含系列类定义和函数定义,
而使用不同的元素(string,int或sales_itme类类型对象).
定义和初始化:
vactor<int> ivec1;
vactor<int> ivec2(ivec1);
vector<string> svec(10,"hi!"); 一般先定义再添加,容器的优点就是动态增长.
vector<int> fvec(10); 10个元素,标准库调用默认构造函数默认值为0
vector<string> svec(10); 10个元素,默认为空string
操作:
v.empty(); v.size();安全的泛型编程概念 v.push_back(t);插入
v[n];下标只能读,不能插入元素,string可以 v1=v2,空的不能读,缓冲区溢出 v1==v2;(!=;<=或>=)
4,迭代器
除了用下标访问容器,标准库提供了迭代器(iterator)检查容器元素并遍历元素.只有少数容器支持下标.
每种容器定义了自己的iterator类型,都支持iterator操作.
vector<int>::iterator iter;
每种容器都定义了一对begin,end函数.用于返回迭代器. vector<int>::iterator iter = ivec.begin();
迭代器的解引用操作符*和自增运算符++, *iter=0; ++iter;
==;!=操作
for ( vector<int>::iterator iter = ivec.begin(); iter = ivec.end(); ++iter )
*iter = 0;
const_iterator用来做只读迭代器, for ( vector<int>::const_iterator iter = ivec.begin(); iter = ivec.end(); ++iter )
cout << *iter << endl;
vector和deque的iterator也支持算数操作(iterator arichmetic),其它不太支持.
iter + n ,iter - n , iter1 - iter2(difference_type,signed)
vector<int>::iterator mid = ivec.begin() + ivec.end()/2 在vector的push_back操作后,当前iter的值失效.
5,bitset类型
是位操作的类模板. #include <bitset> using std::bitset;
初始化: bitset<n> b; bitset<n> b(u); ulong的位副本
bitset<n> b(s);string中位串的副本 bitset<n> b(s,pos,n);string中pos后n个位的副本
bitset<32> bitset(0xffff);
string strval("1100"); bitset<32> bitset(strval);
操作: b.any(); b.none(); b.count() b.size() b[pos]
b.test(pos); b.set(); b.set(pos) b.reset() b.rest(pos)
b.flip();取反 b.flip(pos); b.to_ulong();返回ulong值 os << b;
输出二进制: cout << "bitvec" << bitvec << endl;
STL的输入输出:c++的输入输出由标准库提供.1,文件和控制窗口的IO库 2,定义了一些类(istream,ostream),使string能像文件一样操作.
istream ostream cin cout cerr >> << getline
1,标准库
ostream -> ofstream
-> ostringstream
----------------->/ iostream -> stringstream (字符串操作流) char型组成的流
istream ----------------->/ -> fstream (文件操作流) char型组成的流
-> ifstream
-> istringstream
wostream,wistream,wiostream,wistringstream,wostringstream,wstringstream,wcin,wcout,wcerr.
加w支持wchar_t型(国际宽字符)组成的流.
iostream流不支持复制和赋值操作.只能用指针或引用.
ofstream &print(ofstream&);
while (print(out2)) { /* ... */ }
2,条件状态成员
strm::iostate strm::badbit strm::failbit strm::eofbit
s.eof() s.fail() s.bad() s.good() s.clear() s.clear(flag)
s.setstate(flag) s.rdstate()
所有流对象包含一个int型iostate数据成员,其定义了3个常量值,分别定义特定的位模式.badbit,failbit,eofbit/failbit.
int ival;
while ( cin >> ival, !cin.eof() ) { 判断非空
if ( cin.bad() ) 流坏了
throw runtime_error("IO stream corrupted");
if ( cin.fail() ) { 流出错
cerr << "bad data, try again";
cin.clear( istream::failbit ); 清除出错标志
continue;
}
}
istream::iostate old_state = cin.rdstate(); 读取条件状态
cin.clear();
process_input();
cin.clear(old_state);
is.setstate( ifstream::badbit | ifstream::failbit ); 多状态处理
3,输出缓冲区的管理
4,文件的输入输出
5,字符串流
第二部分:标准库的容器与算法
顺序容器
1,定义
按位置存取单一元素.容器的接口应该统一,标准库只提供少量接口,大部分由算法库提供,分3种:
1,适用于所有容器的统一接口.
2,只适用于顺序或关联容器的接口
3,只适用于顺序或关联容器的子集(部分容器)的接口
标准库提供了3种顺序容器
vector: 支持快速随机访问
list: 支持快速插入/删除
deque(double end queue): 双端队列
和3种adapter(在原始接口上定义出的新接口)
stack 后进先出LIFO的栈
queue 见进先出FIFO的队列
priority_queue 有优先级管理的队列
初始化: #include <vector> #include <list> #include <deque>
1,定义 vector<string> svec; list<int> ilist; deque<Sales_item> items
2,定义为另一个容器的副本 vector<int> ivec2(ivec);
3,定义并初始化为一段元素的副本 list<string> slist( svec.begin(), svec.end() ); 利用迭代器
vector<string>::iterator mid = svec.begin() + svec.size()/2;
deque<string> front(svec.begin(), mid);
deque<string> back( mid, svec.end() );
list<sting> word2( words, words+word_size ); 指针就是迭代器
4,初始化指定数目的元素 list<int> ilist(list_size);
vector<string> svec( get_word_count("Chimera") );
元素的要求:
1,可复制和赋值,内置和复合类型;容器;标准库类型可以做元素.
引用和IO库类型不能做元素.
2,有些容器操作要求有指定容器大小和单个初始化的构造函数,大部分的元素类型有这个默认的构造函数.
例如foo没默认的构造函数,但定义了一个int形参的构造函数,则可以 vector<foo> ok(10,1);
3,可以用容器作为元素. vector< vector<string> > lines; 注意> >
2,迭代器
vector和deque迭代器多了支持+,-,>=,<=.list只支持++,--,==,!=
容器的insert,earse操作会使iterator无效.
3,容器中的操作
类型别名: size_type;iterator;const iterator(只读);const_reverse_iterator;difference_type(存储2个it差值,带符号int);
value_type(元素类型);reference(元素的左值类型value_type&);const_reference
begin和end成员: c.begin();c.end();c.rbegin();c.rend();(返回逆序it)
添加元素: c.push_back(T); 在尾部插入一个元素,void,会导致迭代器失效,所以不能存储v.end()到变量.
c.push_front(T); 在前部插入一个元素.
c.insert(pos,T); 指定位置添加
c.insert(pos,n,T);
c.insert(pos,pos1,pos2);
string sarray[4] = {"quasi","simba","frollo","scar"};
slist.insert(slist_iter, sarray+2,sarray+4);
删除元素: c.earse(it); c.earese(b,e);(返回下个it) c.clear(); c.pop_bak(); c.pop_front(); (void)
关系操作符: ==; !=; ++; >=; <=
大小操作: c.size(); size_type
c.max_size(); size_type
c.empty(); bool是否空
c.resize(n); 改变容器大小
c.resize(n,t);改变大小并初始化为t.
访问元素: c.front();返回引用 c.back(); c(n);下标访问 c.at(n);也是返回下标n的元素引用
iterator解引用 *it
赋值与swap: c1=c2; 赋值后容器长度会相等
c1.swap(c2); c1和c2互换
c1.assign(b,e) 将其他容器的b到e元素复制到c1
c1,assign(n,t) 将c1设置为n个t
4,vector自增长时内存分配: vector容器是连续存放在内存里的,超界时要申请新空间和copy.影响效率,list和deque好些,但在空间类vector增长效率最好.
ivec.capacity(); 返回现有空间
ivec.reserve(50); 将空间设为50,STL以加倍策略为vector分配新的存储空间.
5,容器的选用
list不连续,不支持随机访问,高效insert和erase,单个访问慢点.
vector连续,可随机访问,insert和erase要移动右边元素慢,单个访问快.
deque两端insert和erase快,中间insert和erase代价更高,可随机访问.
原则: 1,随机访问用vector和deque.
2,随机插入用list.
3,头尾插入用deque.
4,list输入,复制到vector并排序,随机读出.
5,综合考虑
6,string类型
可以看做vector,其大部分操作和顺序容器一样.不支持栈方式:fron,back,pop_back等,好多,先列纲;
1,构造和初始化
2,操作
插入删除;
vector的
特有的
substr
append
replace
find
compare
7,容器适配器
adaptor是一种事物的行为类拟另一种事物的行为的一种机制.容器适配器是容器的扩展.
标准库提供三种顺序容器适配器;
queue(FIFO先入后出),priority_queue(按优先级存入,按级取出),stack(栈). 例如:stack适配器可以使顺序容器以栈方式工作.
#include <stack> stack,queue基于deque(双端队列)实现,priority_queue基于vector
#include <queue> 预定义: size_type;value_type;container_type
stack<int> stk(deq); 定义并初始化
stack<string, vector<string>> str_stk(svec); 指定第二实参vector,可以覆盖原来的deque基本容器类型
stack可以基于vector,list,deque;queue要求push_front()只能基于list;priority_queue要求随机访问只能基于vector和deque.
容器适配器可以: == != < <= > >=
stack操作: s.empty() s.size() s.pop()弹出不返回 s.top()返回不弹出 s.push(item)压栈
queue/p_queue操作: q.empty() q.size() q.pop() q.top q.push(item) q.front() q.back()
关联容器(associative container),关联容器按"键"值存取和查找,顺序容器按位置.操作差不多.
1,pair(一对) 类型:包含2个数据值.first .secnod的集合
pair<T1, T2> 定义pair,其内有T1,T2类型
pair<T1, T2> p1(v1, v2); 创建pair,定义其包含T1,T2类型,并初始化为v1,v2
make_pair(v1, v2) 用v1,v2创建pair对象
p1 < p2 按字典顺序顺序比较
p1 == p2 顺序比较pair里每个元素相等
p.first / p.second 返回第一,第二个成员
2,操作:
关联容器的大部分操作和顺序容器一样,但也有不同.
不同点:
3,map类型:key-value成队出现的集合,key是索引.字典里单词是键,解释就是注释.key必须有一个比较函数<定义在容器里,
此比较函数必须strick weak ordering.
map<k, v> m; 定义一个map类型
map<k, v> m(m2); 创建m2的副本
map<k, v> m(b, e); 创建迭代器b到e的副本到m,迭代器指向的元素必须能转换为pair<k, v>
map<k, v>::value_type 是一个pair类型, pair<const map<k,v>::key_type, map<k,v>::mapped_type>
typedef map<k, v>::key_type key_type 定义类型别名
typedef map<k, v>::mapped_type mapped_type
map的迭代器解引用是指向一个pair型的值.
map<string, int>::iterator map_it=m.begin();
map_it->first = "new key";
++map_it->second;
操作:
1,添加: map::insert
2,下标访问:
map<string,int> word_count;
word_count["Anna"] = 1;
3,查找: map.count(key);map.find(key)
4,删除: map.erase(key);返回删除的size_type个数
map.erase(p); iterator p void
map.erase(b,e); iterator b,e void
5,迭代遍历: for() cout<<map_it->first<<map_it->second<<endl; 分别输出
6,实例: 单词转换
4,set类型:key键的集合,key是const的.通过键快速读取.
将某段元素或插入一组元素到set,实际只是添加了一个key.
set< string > set1;
set1.insert("the");
set1.insert(ivec.begin(),ivec.end());
iset.find(5); 返回iterator或iset.end()
iset.count(5); 返回1,没找到返回0
5,multimap和multiset:一个键对应多个实例.
插入: authors.insert(make_pair(sting("Barth,John"),string("Sot-Weed Factor")));
authors.insert(make_pair(sting("Barth,John"),string("Lost in the Funhouse"))); 1对多插入
删除: multimap<string,string>::size_type cnt = authors.erase(("Barth,John"); 删除key对应所有元素,返回个数.删除迭代器只删除指定元素
查找: 以key打头,元素相邻存放.
1,使用 find(key)和count(key): iter=authors.find(search_item);
cout<<iter->second<<endl;
2,使用 lower_bound(key),upper_bound(key) 返回iter
3,使用 equal_range(key); 返回iter型pair
6,容器的综合应用,文本查询程序.
泛型算法:generic algorithm
1,概述:标准库为容器提供了基本操作的功能函数,还定义了泛行算法来进行排序,查找等工作.标准库有超过100种算法.
算法一般使用迭代器遍历来实现,其元素可以比较.
#include <algorithm> 泛型算法
#include <numeric> 一组泛化的算术算法
2,初窥算法
1,只读算法
find(vec.beging(), vec.end(), search_value);
string sum=accumulate(vec.begin(), vec.end(), string(" ")); vec里的元素强制转换或匹配" "类型,既const char*型并相加
it = find_first_of( roster1.begin(),roster1.end(), rester2.begin(),rester2.end() );
在第二段中找出和第一段里匹配的任一元素,返回其iter
2,写
fill(vec.begin(), vec.begin()+vec.size()/2, 10); 会先检查空间再写入
fill_n(vec.begin(), 10, 0); 不检查强制写入
fill_n(back_insert(vec), 10, 0); back_insert(vec)插入迭代器是迭代器适配器,要带一个实参指定类型
这里实参是一个容器的引用.
copy( ilst.begin(), ilst.end(), back_inserter(ivec) ); 复制一段元素到目标,这个例子效率差
vector<int> ivec(ilst.begin(),ilst.end()); 直接初始化会快点
replace(list.begin(), list.end(), 0, 42); 将一段元素里的0替换为42.
replace_copy(list.begin(), list.end(), back_insert(ivec), 0, 42); 先copy一个ilst副本到ivec,且所有0变成42.
3,排序实例:统计文章里的长度大于6的单词的个数
bool isShorter( const string &s1, const string &s2 )
{
return s1.size() < s2.size();
}
bool GT6( const string &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()); 将重复的放后面
words.erase( end_unique, words_end() );
stable_sort( words.begin(), words.end(), isShorter );长短排序,isShorter谓词函数,2个元素类型实参,返回可做检测条件的值.
vector<string>::size_type wc = count_if( words.begin(), word.end(), GT6 );统计GT6个数
cout << wc << make_plural(wc, "word", "s") << "6 charactor or longer" << endl;
return 0;
}
3,再谈迭代器:#include <iterator.h>还定义了其他迭代器,insert iterator,iostream iterator,reverse iterator.
1,插入迭代器
带有3种迭代器适配器:back_inserter;front_inserter;inserter.
replace_copy( ivec.begin(), ivec.end(), insert(ilst, it), 100, 0 ); 复制ivec到ilst的it位置,100换为0
2,iostream_iterator流迭代器:是类模版,所有带<<,>>操作符的类都可以使用.
定义了的构造函数:
istream_iterator<T> in(strm); strm是关联的输入流
istream_iterator<T> in;
ostream_iterator<T> out(strm);
ostream_iterator<T> out(strm, delim); 写入时用delim做元素的分隔符,delim是以空字符结束的字符数组,也就是字符串.
定义了的操作:自增,解引用,赋值和比较
++it;it++;*it;it->mem;it1==it2;it1!=it2
1,输入 istream_iterator<int> cin_it(cin);
istream_iterator<int> eof 定义结束符
while( in_iter != eof ) vec.push_back(*in_iter++);
2,输出 vector<int> ivec(in_iter, eof);
ostream_iterator<string> out_iter(cout, "/n");
istream_iterator<string> in_iter(cin), eof;
while( in_iter != eof )
*out_iter++ = *in_iter++;
3,类做流
istream_iterator<Sales_item> item_iter(cin), eof;
Sales_item sum = *item_iter++;
while ( item_iter != eof ) {
if ( item_iter->same_isbn(sum) ) sum = sum + *item_iter; 比较ISBN,相加
else {
cout << sum << endl;
sum = *item_iter; }
++item_iter;
}
cout << sum << endl;
4,和算法一起使用
istream_iterator<int> cin_it(cin), eof;
vector<int> vec(cint_it, eof);
sort( vec.begin(), vec.end() );
ostream_iterator<int> output( cout, " " );
unique_copy( vec.begin(), vec.end(), output ); 不重复拷贝
3,反向迭代器reverse_iterator
vector<int>::reverse_iterator r_iter;
begin()是最后一个,end()是第一个的前一个.sort(ivec.rbegin(), ivec.rend())将由大到小排序.
string::reverse_iterator rcomma = find( line.rbegin(), line.rend(), ',' ); rcomma指向','号
cout << string(rcomma.base(), line.end()) << endl; rcomma的成员函数base()指向LAST
4,const迭代器
const后不能用这个迭代器来修改容器里的元素.
find_first_of( it, roster1.end(), roster2.begin(), roster2.end() ) 这里不用const是因为it必须roster1.end()同类型
roster1.end()返回的迭代器依赖于roster1类型.如果roster1是const对象,则迭代器是const_iterator.
5,按算法要求的功能分五种迭代器
输入迭代器:input 只读,自增 ,== * -> ,find accumulate ,istream_iterator
输出迭代器:output 只写,自增 ,* ,copy ,ostream_iterator
前向迭代器:forward 读写,自增 , ,replace
双向迭代器:bidirectrional 读写,自增自减 ,-- ,reverse ,map,set,list自带迭代器
随机迭代器:random-access 读写,完整的算术操作 ,< <= += iter[n] ,sort ,vector,deque,string自带迭代器.
4,泛型算法的结构,100多种算法分类
按形参模式
alg ( beg, end, other, parms );
alg ( beg, end, dest, other parms );
alg ( beg, end, beg2, other parms );
alg ( beg, end, beg2, end2, other parms );
命名规范
sort( beg, end );
sort( beg, end, comp );
find( beg, end, val );
find_if( beg, end, pred );
reverse( beg, end );
reverse_copy( beg, end, dest );
5,容器特有的算法
因为list是双向连续而不随机的,不能sort,merge,remove,reverse,unique等标准库算法也效率不高.所以标准库定义其他算法给list.
它们用在其他容器上有相同效果.
lst.merge(lst2); lst.merge(lst2, comp);
lst.remove(val); lst.remove_if(unaryPred);
lst.reverse()
lst.sort()
lst.splice(iter, lst2) lst.splice(iter, lst2, iter2) lst.splice(iter, beg, end)
lst.unique() lst.unique(binaryPred)
第三部分:类Class
类Class
1,Class的声明,定义和类对象
class Screen; 声明
class Screen{ 定义类类型,定义了才能固定成员,预定存储空间.
public: 类成员类型:public private protected
typedef std::string::size_type index; 定义类型别名来简化类,并允许客户使用
char get() const { return contents[cursor]; }; 成员函数.默认为inline
inline char get(index ht, index wd) const; 可以重载,可以显式的定义为inline(类定义体内部),const是将成员函数定义为常量
index get_cursor() const;
Screen *next; 有了类的声明,类成员就可以是自身类型的指针或引用.
Screen *prev;
screen(): units_sold(0),revenue(0.0) { } 构造函数是与类同名的成员函数,用于对每个成员设置初始值.
private:
std::string contents; 多个数据成员的私有化体现类的抽象和封装
index cursor;
index height,width;
protected:
}; 右花括号,类定义结束,编译器才算定义了类.
char Screen::get(index r, index c) const 这是成员函数的定义,Scrren::后形参表和函数体是在类作用域中,
可以直接引用类定义体中的其它成员.而返回在Screen::之前,要Screen::index使用.
{
index row = r * width;
return contents[row + c];
}
inline Screen::index Screen::get_cursor() const 或在类定义体外部指定inline,阅读方便
return corsor;
Screen screen; 定义一个类类型的对象时才分配存储空间
class Screen screen;
2,this指针:对象到成员函数,包含一个隐含形参this,编译器会定义,成员函数可以显式的引用this指针.
class Screen
{
public:
Screen& move( index r, index c );
Screen& set( char );
Screen& set( index, index, char );
Screen& display( std::ostream &os )
{ do_display(os); return *this }
const screen& display( std::ostream &os ) const
在const成员函数中,this指针是指向const类对象的const指针.内容地址都不能变
{ do_display(os); return *this }
...
private:
void do_display( std::ostream &os ) const 私有成员函数,放在这简捷,好调试,好改.
{ os << contents; }
mutable size_t access_ctr; mutable声明可变数据成员,在const对象和函数里,数据都可改变
protected:
};
Screen& Screen::move( index r, index c ) 本对象的引用
{
index row = r * width;
cursor = row + c;
return *this; 返回this指针的内容
}
Screen& Screen::set( char c )
{
contents[cursor] = c;
return *this; 普通成员函数里,this指针是const指针,能改内容但不能改地址.
}
Screen myscreen;
myscreen.move(4,0).set('#').display(cout); 操作序列的使用.call non-const ver
myscreen.move(4,0).display(cout).set('#'); 对于const的display成员函数,这个操作违法.所以使用重载.
const Screen blank.display(cout); call const ver
3,作用域
每个类都有自己的作用域,成员一样的也是不同的域.
obj.member obj.memfun()
ptr->member ptr->memfun()
类作用域中名字的查找:块内->类定义体内->全局,(都是查找在名字使用前的定义(块内没找到会到类定义全局里找),不能重复定义.在名字后重复定义也不行)
4,构造函数
包括名字,形参表,初始化列表和函数体.
重载: class Sales_item
{
public:
Sales_item();
Sales_item( const std::string& ); 参数不同,不能定义为const
Sales_item( std::istream& );
...
}
Sales_item empty;
Sales_item Primer_3rd_Ed( "0-201-138-178" ); 实参决定使用哪个构造函数
Sales_item Primer_4th_Ed( cin );
初始化:
1,screen(): units_sold(0),revenue(0.0) { } 构造函数是与类同名的成员函数,用于对每个成员设置初始值.
2,Sales_item::Sales_item( const string &book )
{
isbn = book; 在函数体内初始化.
units_sold = 0; isbn是隐形的初始化,使用string默认的构造函数初始化.
revenue = 0.0;
}
3,对于const数据对象和引用,只能而且必须初始化,不能在函数体里赋值.
4,初始化可以是任意表达式.
5,类类型的初始化可以是类的任一构造函数. Sales_item():isbn(10,'#'),.. { }
6,Sales_item( const std::string &book = " " ):isbn(book),units_sold(0),revenue(0.0) { }
Sales_item empty; empty中包括一个默认实参.
Sales_item Primer_3rd_Ed( "0-201-123-189" ); 显式实参.
7,默认的构造函数
当类没定义构造函数时,编译器使用默认的构造函数.即类成员用各自的默认构造函数来初始化.
内置和复合类型成员(指针和数组),编译器只对定义在全局作用域的对象初始化.定义在局部的,不进行初始化.必须使用构造函数.
8.隐式类类型转换
string null_book = " 9-999-999-9 ";
item.same_isbn( null_book ); same_isbn()期待一个Sales_item对象做实参. string->构造函数->临时的Sales_item对象
如果把构造函数声明为explicit Sales_item( const std::string &book = " " ): 则关闭隐式转换.
9.显式类类型转换
item.same_isbn( Sales_item(null_book) ); 关闭explicit功能
10.显式初始化没有定义构造函数且全部public的类.但程序员工作量大且删除成员要改程序.
vall.ival=0; vall.ptr=0;
5,友元
class Screen{
friend class window_Mgr; 定义友元类,友元(成员函数,非成员函数或类)可以访问Screen类中的所有成员,包括私有成员.
...
}
class Screen{
friend window_Mgr& window_Mgr::relocate( Screen::index r, Screen::index c, Screen& s ); 其它类的成员函数成为友元
...
}
window_Mgr& window_Mgr::relocate( Screen::index r, Screen::index c, Screen& s )
{
s.height += r;
s.width += c;
return *this;
}
成员函数做友元要先定义。
非成员函数和类不必先声明,用友元引入的类名和函数,可以象预先声明样使用.
重载的函数要单独声明为友元.
6,static 成员
1, 声明类的静态成员与类关联,但不与类的对象关联,没有this,不能做虚函数.便于封装.
2, 静态成员的提出是为了解决数据共享的问题.实现共享有许多方法,如:设置全局性的变量或对象是一种方法。但是,全局变量或对象是有局限性的。
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中
共享的成员,而不是某个对象的成员。
3, 使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。
静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,
这样可以提高时间效率。
const int account::period; 必须做 const static 的类外定义
double account::interestRate = initRate(); 类外定义并初始化,可以直接引用私有成员.
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 const int period = 30; const static 的类内初始化.
};
void account::rate( double newRate ) 这里不用加static,类外定义了.
{
interestRate = newRate;
}
2.static 成员不是类的组成部分,他的特殊用法和成员不一样
class bar {
public:
Bar& clear( char = bkground );
private:
static const char bkground = '#'; static数据成员做默认实参
static bar mem1; 定义为该类的类型
}
其他
7,复制构造函数
类定义体里的为默认构造函数
ifstream file1( "filename" ); 直接初始化
Sales_item item = string( "9-999-99999-9" ); =复制操作符,Sales_item构造函数不是显式的(explicit)
string make_plural( size_t, const string&, const string& ); const类的引用做形参,隐式的使用string复制构造函数做实参.返回复数形式
vector<string> svec(5); 先用string默认构造函数初始化svec,再使用复制构造函数复制到svec每个元素.
Sales_item primer_eds[] = {
string("0-201-16487-6"), 调用单实参构造函数
string("0-201-54848-8"),
string("0-201-82470-1"),
Sales_item() 调用完整的构造函数语法
};
如果没有定义复制构造函数,编译器自动合成一个,内容是对逐个成员初始化.
定义复制构造函数
class foo {
public:
foo(); 默认的构造函数
foo(const foo&); 声明复制构造函数,主要用于指针成员处理和消息处理.
};
把复制构造函数定义在private区域,可以禁止复制,如果再只声明不定义,则友元和类成员中的复制也禁止.
8,赋值操作符 =
=操作符的重载
class Sales_item {
public:
Sales_item& operator=(const Sales_item &); 在类里要声明,左参数带this,右参数用const,返回右参数的引用.
};
用户没定义=操作符重载,则编译器会自动合成赋值操作符.复制构造函数和赋值操作符重载通常一起定义.
Sales_item& Sales_item::operator=( const Sales_item &rhs )
{
isbn = rhs.isbn;
units_sold = rhs.units_sold;
revenue = rhs.revenue;
return *this;
}
9,析构函数
构造函数申请资源(buffer或open file),析构函数就要释放资源(buffer或close file).
Sales_item *p = new Sales_item; p指向一个新Sales_item对象,调用默认构造函数
{
Sales_item item(*p); *p复制到item
delete p; 删除指针p,调用析构函数释放p资源
} 超出作用域,调用析构函数释放item
{
Sales_item *p = new Sales_item[10]; 动态分配
vector<Sales_item> vec( p, p+10 ); 本地对象
...
delete [] p; 删除p
...
} vector默认析构函数,逐个逆序撤销元素(size()-1,-2,..0)
复制构造函数和赋值操作符重载,析构函数 通常一起使用.
编译器合成的析构函数按对象创建的逆序撤销每个非static成员.
class Sales_item{
public :
~Sales_item(); 用户编写的析构函数,没形参,没返回.撤销本对象时会调用,再调用合成的析构函数.
{ }
};
10,例子消息处理,将信息字符串message加入关联容器set:folder里
class message{
public:
message( const std::string &str = "" ):contents(str) { } 默认构造函数,单个实参(空串)并将folder初始化为空集
message( const message& ); 复制构造函数声明
message& operator=( const message& ); 赋值=操作符重载
~message(); 析构
void save( folders& );
void remove ( folder& );
private:
std::string contents;
std::set<folder*> folders;
void put_msg_in_folders( const std::set<folder*>& ); 各个folder添加指向msg的指针,复制和赋值将调用这个函数
void remove_msg_from_folders(); 各个folder删除指向msg的指针,析构和赋值将调用这个函数
};
message::message( const message &m ):content(m.contents),folders(m.folders) 用副本初始化
{
put_msg_in_folders( folders ); 复制构造函数要把msg添加到folders
}
void message::put_msg_in_folders( const set<folder*> &rhs )
{
for( std::set<folder*>::const_iterator beg=rhs.begin(); beg!=rhs.end(); ++beg ) 利用迭代器遍历
(*beg)->addmsg(this); *beg是folder指针,利用addmsg()成员函数实现添加
}
message& message::operator=( const message &rhs ) 赋值定义
{
if ( &rhs != this ) 左右操作数不同
{
remove_msg_from_folders(); 从folders删除msg
contents = ths.contents;
folders = rhs.folders;
put_mag_in_folders(rhs.folders); 将新msg加入folders
}
return *this;
}
void message::remove_msg_from_folders()
{
for ( std::set<folder*>::const_iterator beg=folders.begin(); beg!=folders.end; ++beg )
(*beg)->remmsg(this);
}
message::~message() 析构函数
{
remove_msg_from_folders();
}
11,指针成员管理,3种方法
1,常规指针成员
class hasptr{
public:
hasptr(int *p, int i):ptr(p), val(i) { } 构造函数,2个形参
int *get_ptr() const { return ptr; }
int get_int const { retrun val; }
void set_ptr( int *p ) { ptr = p; }
void set_int( int i ) { val = i; }
int get_ptr_val() const { retrun *ptr; }
void set_ptr_val( int val ) const { *ptr = val; }
private:
int *ptr; 指针成员
int val;
};
int obj = 0;
hasptr ptr1( &obj, 42 );
hasptr ptr2( ptr1 ); 2个对象中的2个指针成员,共享同一对象obj
这时可能出现空指针,悬垂指针.
int *p = new int(42);
hasptr ptr( p, 10 );
delete p;
ptr.set_ptr_val(0); 空指针,*p空间已经删除了
2,智能指针成员,smart point
在析构函数中加计数器,当计数为0时,删除共享对象.避免空指针(悬垂指针).但这样在有3个引用时计数器无法同步.
可以使用计数类(一个指针指到共享空间,一个计数)来解决这个问题:
class U_Ptr {
private: 所有成员都是private
friend class HasPtr; 友元HasPtr
int *ip; 指针
size_t use; 计数
U_Ptr(int *p):ip(p),use(1) { } 构造
~U_Ptr() { delete ip; } 析构
};
class hasptr{
public;
hasptr( int *p, int i ):ptr(new U_Ptr(p)),val(i) { } 构造
hasptr( const hasptr &orig ):ptr(orig.ptr),val(orig.val) { ++ptr->use; } 复制
hasptr& operator=( const hasptr& ); 赋值
~hasptr() { if (--ptr->use == 0 ) delete ptr; } 析构
int *get_ptr() const { retrun ptr->ip; } 操作成员函数
int get_int() const { return val; }
void set_ptr( int *p ) { ptr->ip = p; }
void set_int( int i ) { val = i; }
int get_ptr_val() const { return *ptr->ip; }
void set_ptr_val( int i ) const { *ptr->ip = i; }
private:
U_Ptr *ptr;
int val;
};
hasptr& hasptr::operator=( const hasptr &rhs )
{
++rhs.ptr->use; 右操作数use++
if ( --ptr->use == 0 ) 左操作数use--为0,则左操作数被替换,先删除ptr.
delete ptr;
ptr = rhs.ptr;
val = rhs.val;
return *this;
}
3,值形指针成员
不再使用共享数据,复制时把原指针的值copy到新的对象.
class hasptr{
public;
hasptr( const int &p, int i ):ptr(new int(p)),val(i) { } 构造
hasptr( const hasptr &orig ):ptr(new int(*orig.ptr)),val(orig.val) { } 复制
hasptr& operator=( const hasptr& ); 赋值
~hasptr() { delete ptr; } 析构
int *get_ptr() const { retrun ptr; } 操作成员函数
int get_int() const { return val; }
void set_ptr( int *p ) { ptr = p; }
void set_int( int i ) { val = i; }
int get_ptr_val() const { return *ptr; }
void set_ptr_val( int i ) const { *ptr = i; }
private:
int *ptr;
int val;
};
hasptr& hasptr::operator=( const hasptr &rhs )
{
*ptr = *rhs.ptr; 直接把值copy到新对象,2个指针2个空间
val = rhs.val;
return *this;
}
重载 操作符
1,定义
2,输入和输出操作符
3,算数和关系操作符
4,赋值操作符
5,下标操作符
6,成员访问操作符
7,++,--
8,调用 操作符和 函数对象
9,转换 与 类 类型
第四部分:OOB编程与模板
OOB
1,概述
继承,动态绑定和数据抽象的概念是OOB的基础.
模板(泛型类,泛型函数)定义了系列操作,用来定义具体类,他们都使用类似操作.
1,c++的派生类(derived class)继承基类(base class),base class中成员函数分 非虚函数std::string book();
虚函数virtual double net_price(size_t).虚函数(virtual)需要在派生类里重定义.
2,动态绑定(dynamic banding)是指应用程序会根据实参调用的继承层次中相应类 的虚函数 来执行,形参是基类的引用(或指针)const Item_base &item.
2,基类和派生类
派生类和用户程序一样只能访问基类的public,不能访问private.友元可以访问private.
为了虚函数在派生类里的重定义,虚函数用到的数据成员定义到protected,它们可以通过派生类的继承关系访问.
但不能在引用里直接访问基类对象的protected成员.
class Bulk_item; 前向声明
class Item_base;
class Bulk_item : public Item_base{ Item_base必须先定义
public:
double net_price(std::size_t) const; 虚函数的声明必须和基类里一样,但返回可以是基类或派生类的引用(指针).
private:
std::size_t min_qty;
double discount;
};
派生类包括的数据成员:min_qty,discount/isbn,price.
double Bulk_item::net_price( size_t cnt ) const
{
if ( cnt >= min_qty )
return cnt*(1-discount)*price; 派生类里对基类protected数据成员的直接调用.
else
return cnt*price; 卖了几本书,计算总价(包括折扣)
}
1,可以将基类的引用绑定到继承类,也可以用基类的指针指向派生类.
double print_total( const item_base&, size_t );
print_total( bulk, 10 ); Item_base *p=&bulk;
2,根据实参的不同,调用不同的虚函数版本.
void print_total( ostream &os, const item_base &item, size_t n ) 引用和指针的静态类型和动态类型的多态性是c++的基石
{
os << "ISBN" << item.book() << item.net_price(n) << endl; 不过实参是什么item.book非虚函数都是item_base里的.
item.net_price(n)虚函数是可变的.
}
print_total( cout, base, 10 );
print_total( cout, derived, 10);
3,强制某个虚函数.
Item_base *baseP = &derived;
double d = baseP->Item_base::net_price(42); 强制使用基类的虚函数,因为继承层次的基类中一般包含公共信息.
4,虚函数也有默认实参,在基类和派生类里会有不同的默认实参,如果通过基类的引用和指针做参数,实参是派生类,这时默认参数是基类的,不是派生类的.
5,继承类型:
struct Public_derived : public Base{} 派生类继承基类public和protected
struct Protected_derived : protected Base {} 到派生类里都为protected
struct Private_derived : private Base {} 到派生类里都为private
6,恢复个别成员的访问级别
class Derived : private Base { 基类里所有成员继承为私有级别
public :
using Base::size; 使用using恢复访问级别,但改变不了原级别
protected :
using Base::n;
//...
};
7,struct D2 : Base {}; 默认继承级别是public
class D3 : Base {}; 默认继承级别是private
8,友元(friend class Frnd;)可以访问类的private和protected,但不能访问继承类的.
9,类的静态成员访问 static void statmem();继承层次里只能有一个静态函数.它根据访问级别被访问.可以用 :: -> .
Base::statmem();
Derived::statmem();
derived_obj.statmem();
statmem();
3,转换及继承(比较难理解)
派生类->基类
1,派生类对象引用 -> 基类对象的引用:只是引用,派生类没变化.
2,派生类对象 -> 基类对象 :会复制派生类的基类部分到实参.
Item_base item;
Bulk_item bulk;
Item_base item(bulk); 复制,bulk(item_base部分)的引用做实参->item_base的复制构造函数(初始化)
item = bulk; 赋值,bulk(item_base部分)的引用做实参->item_base的赋值函数(赋值)
3,派生类到基类的可访问性,参照基类的public部分能否访问.
public继承, 用户和后代类 可以用此派生类访问基类.
private继承,用户和后代类都不可以.
protect继承,后代类可以,用户不可以.
派生类里的成员都可以使用 派生类到基类的装换.
基类->派生类是不合理和不存在的.但指针可以绑定.
Item_base *itemP = &bulk; 指针可以绑定,编译器会报错.
static_cast Item_base *itemP = &bulk; 强制转换
dynamic_cast Item_base *itemP = &bulk; 申请运行是检查
4,构造函数和复制控制
构造函数
派生类也有构造,复制,赋值,撤销.在继承层次中基类要考虑其构造函数是protected或private以保证派生类使用其特殊构造函数.
默认的派生类构造函数先默认初始化基类,再默认初始化其数据成员.
用户定义的构造函数可以传递实参. Bulk_item bulk("0-201-82470-1", 50, 5, .19);
class Bulk_item : public Item_base {
public:
Bulk_item( const std::string book="", double sales_price=0.0, std::size_t qty=0, double disc_rate=0.0 ):默认参数
Item_base(book, salse_price),min_qty(qty),discount(disc_rate) { } 初始化
};
这里的实参只能初始化到直接的基类数据.
重构refactoring的概念,例如在2层间加入一层以便扩展或处理其他改变时,使用上下二层类的程序不用改变,但要重新编译.
首先要定义新层的类:
class Disc_item : public Item_base {
public:
Disc_item(const std::string book=" ",double price=0.0, std::size_t qty=0, double dis_rate=0.0):
Item_base(book,price),quantity(qty),discount(dis_rate){ }
protected:
std::size_t quantity;
double discount;
}
class Bulk_item : public Disc_item {
public:
Bulk_item(const std::string book=" ",double price=0.0, std::size_t qty=0, double dis_rate=0.0):
Disc_item(book,price,qty,dis_rate) {}
double net_price(std::size_t) const;
protected:
}
复制控制
1,复制
class Derived : public Base{
public:
Derived( const Derived& d ):Base(d), , {} 派生类的复制构造函数,和构造函数不同.显式的定义了复制操作.
必须包括Base(d)来调用Base的复制函数,如果不包括,
派生类的基类复制部分将调用默认的构造函数.
...
}
2,赋值
Derived &Derived::operator=(const Derived &rhs)
{
if ( this != &rhs ) {
Base::operator=(rhs); 基类赋值
...
}
return *this;
}
3,析构
class Derived : public Base {
public:
~Derived() { ... } 只析构派生类自己的成员,编译器会自动调用.
};
基类的虚析构函数,派生类将继承虚析构函数特性,不管是显式的还是合成的.空的虚析构函数不遵循三法则(可以不需要构造,复制,赋值等复制函数).
构造和赋值操作符不能是虚函数.
class Item_base {
public:
virtual ~Item_base() {} 空虚析构函数
}
在构造和析构函数中最好是不要包含虚函数,因为这个过程对象不固定.
5,继承情况下的类作用域
派生类的作用域嵌套在基类作用域中. cout << bulk.book();
在派生类里找不到名字的定义,编译器就到下层类去找.重名的会使用上层的定义,可以用作用域操作符指定下层数据成员.
struct Base {
Base():mem(0){ }
protected:
int mem;
};
struct Derived : Base {
Derived(int i):mem(i) { }
int get_mem() { return mem } 返回先找到的mem
int get_base_mem { return Base::mem; } 指定下层mem
protected:
int mem;
};
重名的成员函数也一样,编译器使用先找到的那个,如果参数不匹配,就报错. d.Base::memfcn();
派生类成员函数重载,一般using基类中的所有重载成员函数(因为覆盖),再重定义本类里要定义的函数.
虚函数定义在基类,派生类应该使用同一原型(包括形参),这样才能通过基类的引用或指针访问到派生类.编译器将先在基类查找匹配的函数.
1,确定函数调用的对象,引用或指针的静态类型.
2,查找函数名,有上到下.
3,找到名字,进行常规检查,参数是不是合法.
4,合法就生成代码,虚函数且是引用和指针调用就根据实参类型调用动态特性的函数.
6,纯虚函数pure virtual
一些类里的继承的虚函数不需要重定义(因为不使用),可以将这个类里的虚函数定义为纯虚函数,这个类(叫做abstract base class,抽象基类)
将不能创建对象,只能用于派生层次.
class Disc_item : public Item_base {
public:
double net_price( std::size_t ) const = 0; 纯虚函数
}
7,容器和句柄类用于继承
容器是单一对象的集合,基类和派生类可以一起放入容器,当派生类会被切掉只剩下基类部分,也可以把基类强制装换为派生类放入容器,但派生部分没初始化.
唯一办法是将对象的指针放入容器,用指针要保证容器存在,对象就存在,容器消失,动态分配的对象要适当的释放.
muitlset<Item_base> basket; 容器是base_item的集合
Item_base base; Bulk_item bulk;
basket.insert(base);
basket.insert(bult); bult剪切到base部分加入集合
为了继承和OOB,C++必须使用引用(reference)和指针(pointer),以便访问动态绑定的函数.C++使用句柄类(handle或叫cover包装类)来存储管理基类指针.
指针型句柄类
用户使用:
Sales_item item( Bulk_item("0-201-82470-1",35,3,.20) ); 用户绑定句柄类到Item_base继承层次的对象.
item->net_price(); 用户使用 * -> 操作符操作Item_base,
用户不用存储和管理对象,由句柄类处理,操作是多态行为.
定义:
class Sales_item {
public:
Sales_item():p(0),use(new std::size_t(1) ) { } 1,默认构造函数
Sales_item( const Sale_item &i ):p(i.p),use(i.use) { ++*use } 2,复制构造函数
Sales_item( const Item_base& ); 3,接受Item_base的复制构造函数
~Sales_item() { decr_use(); } 析构
Sales_item& operation=( const Sales_item& ); 赋值函数声明
const Item_base *operation->() const { ->符号 重定义,Sales_item返回一个item_base*.
if (p) return p;
else throw std::logic_error("unbound Sales_item"); } 无效的Sales_item,向内核抛出错误信息
const Item_base &operation*() { *符号 重定义,Sales_item&返回一个引用 item_base&
if (p) return *p
else throw std::logic_errir("unbound Sales_item"); }
private:
Item_base *p; 1,指向item_base的指针
std::size_t *use; 2,指向计数use的指针
void decr_use()
{ if(--*use == 0) delete p; delete use }
};
Sales_item& Sales_item::operation=( const sales_item &rhs ) {
++*rhs.use; use是指针,*use是内容
decr_use();
p = rhs.p;
use = rhs.use;
return *this;
}
为了支持未知类型的复制,要在基类开始定义虚函数clone,再在派生里重定义.
class Item_base{
public:
virtual Item_base* clone() const
{ return new Item_base(*this); }
};
class Bulk_item{
public:
Bulk_item* clone() const
{ return new Bulk_item(*this); } 虚函数的返回可以是派生类的指针或引用
};
Sale_itme::Sales_item( const Item_base &item ): p(item.clone()),use(new std::size_t(1)) { }
这个复制构造函数初始化,指针指到实参对象并新建use=1.
使用句柄类Sales_item和关联容器muiltset来实现继承层次中虚函数的统一操作:购物篮,
包括添加卖出的书,获得某书的卖出数量和总的卖价(折扣前和折扣后的),首先定义muitlset的比较Sales_item的方法:
inline bool
compare( const Sales_item &lhs, const Sales_item &rhs ) multiset容器可以指定比较函数
{
return lhs->book() < rhs->book(); Sales_item=继承层次中的某个对象的引用
}
class Basket {
typedef bool (*Comp)(const Sales_item&, const Sales_item&); 将Comp定义为函数类型指针,该函数类型和compare函数相匹配
public:
typedef std::multiset<Sales_item,Comp> set_type; 类型别名set_type
typedef set_type::size_type size_type; 类型别名size_type
typedef set_type::const_iterator const_iter; 类型别名const_iter
Basket(): items(compare) { } 构造是容器items初始化,指定使用compare函数做容器对比函数
void add_item( const Sales_item &item ) 接受Sales_item对象引用将其加入multiset
{ items.insert(item); }
size_type size( const Sales_item &i ) const 接受Sales_item对象引用返回该类ISBN的记录数
{ retrun items.count(i); }
double total() const; 计算总的卖价
private:
std::muitlset<Sales_item,Comp> items;
};
double total() const{
double sum = 0.0;
for ( const_iter iter=items.begin(); 迭代器iter,是容器里定义的数据类型(指针),和下标用处差不多.
iter!=items.end(); 但用下标的容器不多
iter=items.upper_bound(*iter) ) { 容器的upper_bound,形参是iter解引用(Sales_item),返回下一类isbn的iter
sum += (*iter)->net_price( items.count(*iter) ); *iter 获得Sales_item对象,*iter-> 获得句柄关联的Item_base对象指针.
net_price形参size_t,实参items.count(*iter)
}
return sum;
}
8,文本文件查询的进阶设计:
实现功能:
查询Daddy 查询非~(Alice) 查询或(hair|Alice) 查询与(hair&Alice) 复合查询((fiery&bird)|wind)
类定义:
1,定义出四个查询类: WordQuery,NotQuery,OrQuery,AndQuery,它们是兄弟类,应该共享相同的抽象接口.所以定义出一个抽象基类Query_base,
作为查询操作继承层次的根.Query_base->WordQuery 单操作数
->NotQuery
->BinaryQuery->AndQuery 双操作数,所以加个抽象类BinaryQuery表示两个操作数的查询
->OrQuery
2,定义TextQuery类: 读指定文件,建立查询,查找每个单词.
3,定义Query_base类: 用来表示查询操作的类型,需要两个纯虚函数,每个派生类重定义自己的操作.
1,eval: 返回匹配的行编号集合,TextQuery对象做形参
2,display: 打印给定对象的查询结果,ostream引用做形参
4,定义句柄类Query: Query对象将隐藏继承层次,用户代码根据此句柄执行,间接操作Query_base对象.
Query必须: 定义&操作符
定义|操作符
定义~操作符
定义参数为string对象的构造函数,生成新的WordQuery
5,应用代码: Query q = Query("fiery") & Query("bird") | Query("wind");
6,过程: 生成10个对象,3个Wordquery,1个OrQuery,1个AndQuery,5个对应的句柄.
/->Wordquey(fiery)
/-->Andquery
Query对象-->OrQuery /->Wordquey(bird)
/-->Wordquey(wind)
知道了这个对象树,操作就会沿着这些链接,比如调用q,则q.eval->OrQuery.eval->AndQuery.eval ...以此类推
->WordQuery.eval
模板:OOB依赖于继承的多态性(指针多指),模板依赖于编译器对实参的实例化.
模板定义:
1,函数模板
template <class T> inline int compare(const T&, const T&); 声明为内联函数模板的定义.后接返回类型.
template <typename T,class T1, size_t T2> 定义
模板形参表(template parameter list)包括类型形参(class/template T)和非类型形参(int T),逗号分开
int compare( const T &v1, const T &V2 )
{
if ( v1 < v2 ) return -1;
return 0;
}
compare(s1, s2);
2,类模版
template <class Type> class queue { 定义
public:
Queue ();
Type &front ();
const Type &front() const;
void push(const Type &);
void pop();
bool empty() const;
private:
//...
}
queue< vector<double> > qvec; 使用
类里包括数据成员,函数成员和类型成员. typename parm::size_type *p; 加typename来显式指定p的类型,
parm::size_type *p; 而不是size_type数据成员.
3,非类型的模板形参
template <class T, size_t N> void array_init(T (&parm)[N]) 包含N个T类型元素的数组的引用做形参
{
for ( size_t i = 0; i!= N; ++i ) {
parm[i] = 0;
}
}
int x[42]; array_init(x);
实例化:实参类型的推断
1,多个实参类型必须和形参相匹配.
2,实参可以受限转换: const自动忽视和转换;数组和函数自动装换为指针,int* *func
3,非模板的实参还会做常规转换. template <class Type> Type sum(const Type &op1, int op2)
4,函数指针推断.
template <typename T> int compare(const T&, const T&);
int (*pf1)(const int&, const int&) = compare; 函数模板对函数指针的初始化和赋值
5,不推断,显式实参:
int i, short s; sum(static_cast<int>(s), i); int sum(int, int)
template <class T1, class T2, class T3> T1 sum(T2, T3);
long val = sum<long>(i, lng) 指定返回为long型,T1,T2,T3对应于返回,第1实参,第2实参.
template <typename T> int compare(const T&, const T&);
void func(int(*) (const string&, const string&));
void func(int(*) (const int&, const int&));
func(compare<int>); 显式指明用compare int实例做初始化
编译模型
1,包含编译模型:在.h文件中包含声明和定义.
2,分别编译模型;在.h文件中声明,在.c文件中定义.并用export template<class T> class queue扩展.
类模板的定义
1,完整的queue定义
template <class T> class Queue; 类模板声明
template <class T> std::ostream& operator<<(std::ostream&, const Queue<T>&);
std::ostream&表示形参是一个ostream型的引用,形参名在声明中省略.
template <class T> class QueueItem {
friend class Queue<T>; 特定同类型模板友
friend std::ostream& operator<< <T>(std::ostream&, const Queue<T>&); 特定同类型普通友元
// private class: no public section
QueueItem(const T &t):item(t),next(0) { } 构造函数带参数和初始化,默认构造函数体
T item;
QueueItem *next;
}
template <class T> class Queue {
friend std::ostream& operator<< <T>(std::ostream&,const Queue<T>& );
public:
Queue(): head(0),tail(0) { } 默认构造 Queue是Queue<T>的缩写,是类模板的非限定名
template <class It> Queue(It beg, It end):head(0), tail(0) { copy_elems(beg, end); } 模板成员
Queue( const Queue &Q ): head(0),tail(0) { copy_elems(Q); } 复制构造带形参Q Queue<T>( const Queue<T> &Q):head(0) ...
Queue& operator=( const Queue& ); 赋值构造
~Queue() { destroy(); } 析构
template <class Iter> void assign( Iter, Iter ); 模板成员声明
T& front() { return head->item; } 成员函数front声明
const T &front() const { return head->item; } 重载函数front
void push( const T & ); 压入
void pop(); 弹出
bool empty() const { return head==0; } 是否空
private:
QueueItem<T> *head; 数据成员
QueueItem<T> *tail;
void destory(); 私有函数
void copy_elems( const Queue& );
template <class Iter> void copy_elems(Iter, Iter); 模板成员声明
};
template <class T> void Queue<T>::destory() 成员函数定义格式,模板形参表,作用域操作符,类后跟模板类型
{
while( !empty() )
pop();
}
template <class T> void Queue<T>::pop() {
QueueItem<T>* p = head;
head = head->next;
delete p; 删除p指定内存
}
template <class T> void Queue<T>::push(const T &val) {
QueueItem<Type> *pt = new QueueItem<T>(val); 申请QueueItem大小内存.
if ( empty() ) head = tail = pt;
else{ tail->next = pt;
tail = pt; }
}
template <class T> void Queue(T)::copy_perm(const Queue &orig){
for ( QueueItem<T> *pt=orig.head, pt, pt=pt->next ) pt在编译时其值为orig.head
push(pt->item);
}
用户调用类模版的成员函数,不会进行编译器实参推断,而是由类模板的实参决定. Queue<int> qi; qi.push(s); s编译时转化为int
其实例化发生在为程序所用时,在定义的时候,实例化其默认构造函数,成员函数和模板类型.例如:顺序容器定义时,可以带没定义默认构造函数类型,但必须2个参数.
2,
template <int hi, int wid> class Screen { 非类型形参
public:
Screen():screen(hi*wid, '#'), cursor(0), height(hi), width(wid) { }
...
private:
std::string screen;
std::string::size_type cursor;
std::string::size_type height, width;
};
Screen<24, 80> hp2621; 用户定义
类模板中的友元:
1,普通友元
template <class T> class Bar {
friend class FooBar; Foobar的成员和fcn函数可以访问Bar类的
friend void fcn(); 任意实例的private和protected成员,public都能访问
...
}
2,普通的模板友元
template <class T> class Bar {
template <class T1> friend class Foo1; Fool的任意实例都可以访问Bar实例的私有元素
template <class T2> friend void templ_fcn1(const T2&);
}
3,特定的模板友元
template <class T1> class Foo2;
template <nametype T2> void templ_fcn2(const T2&);
template <class T> class Bar {
friend class Foo2<char*>;
friend void templ_fcn2<char*>(char* const *); 指定类型char*的实例化是Bar实例的友元
}
template <class T> class Bar {
friend class Foo3<T>;
friend void templ_fcn3<T> (const T&); 和Bar同类型实参的Foo3和templ_fcn3的实例是Bar的友元
}
4,声明的依赖性
在设定模板友元之前或同时先进行友元类模板或函数模板的声明,没声明的是普通友元或编译出错.
5,新增输出成员函数并定义友元:
template <class T> ostream& operator<<(std::ostream &os, const Queue<T> &q) 书里没std::??
{ Queue的输出操作符重载
QueueItem<T> *p;
for (p=q.head, p, p=p->next)
os << p->item << " "; 3 5 8 13
return os;
}
template <class T> class QueueItem {
friend class Queue<T>; 同类型友元Queue
friend std::ostream& operator<< (T)(std::ostream&, const Queue<T>&); 同类型友元operator<<
}
template <class T> class Queue {
friend std::ostream& operator<< (T)(std::ostream&, const Queue<T>&); 同类型友元operator<<
}
输出<<操作依赖于具体类型,类型本身要有<<操作,如没有,虽可以定义对象,但输出操作时会编译出错.
类模板中的模板成员:类类型(模板或非模板)可以包括类模板和函数模板成员,叫做成员模板(member remplate)
成员模板使其他类型的数据可能导入类模板里,当然要先删除原来的类型.
定义:
template <class T> class Queue {
public:
template <class It> Queue(It beg, It end):head(0), tail(0) { copy_elems(beg, end); }
template <class Iter> void assign(Iter, Iter); 声明
private:
template <class Iter> void copy_elems(Iter, Iter); 声明
}
在类外部定义
tempalte <class T> tempalte <class Iter> void Queue<T>::assign(Iter beg, Iter end)
{
destroy();
copy_elems(beg, end);
}
成员模板的实例化
templater <class T> template<class Iter> void Queue<T>::copy_elems(Iter beg, Iter end )
{
while ( beg != end ) {
push(*beg);
++beg; }
}
static数据成员和成员函数
template <class T> size_t Foo<T>::ctr = 0; 类外声明和初始化static数据成员
tempalte <class T> class Foo{
public:
static std::size_t count() { return ctr;} static 函数
private:
static std::size_t crt; static 数据成员
}
Foo<int> f1,f2,f3; 实例化,f1,f2,f3共享一个static成员
Foo<string> fs; fs实例化另外一个str数据
泛型句柄类:句柄能够动态申请和释放相关的继承类的对象.并将实际的工作转发到继承层次的底层类.
泛型句柄类提供管理操作(计数和基础对象).
定义
template <class T> class Handle
{
public:
Handle( T *p = 0 ):ptr(0),use(new size_t(1)) { } 构造函数1
Handle( const Handle& h):ptr(h.ptr),use(h.use) {++*use;}; 构造函数2
T& operation*(); * 解引用操作符
T* operation->(); -> 成员访问操作符
const T& operation*() const; const *
const T* operation->() const; const ->
Handle& operation=( const Handle& ); = 赋值操作符
~Handle() { rem_ref(); }; 析构
private:
T *ptr; 继承对象指针
size_t *use; 计数
void rem_ref() { if(--*use == 0) {delete ptr; delete use;} } 无计数则释放空间
}
template <class T> inline Handle<T>& Handle<T>::operation=( const Handle &rhs )
{
++*rhs.use;
rem_ref();
ptr = rhs.ptr;
use = rhs.use;
return *this;
}
template <class T> inline T& Handle<T>::operator*()
{
if (ptr) return *ptr; 判断一下是否为空
throw std::runtime_error( "dereference of unbound Handle" );
}
template <class T> inline T* Handle<T>::operation->()
{
if (ptr) return ptr;
throw std::runtime_error( "access through unbound Handle" );
}
template <class T> inline const ....
使用
1,Handle<int> hp( new int(42) );
{
handle<int> hp2 = hp;
cout << *hp << " " << *hp2 << end1; 42 42
*hp2 = 10;
cout << *hp << end1; 10
}
2,class Sales_item {
public:
Sales_item (): h() { }
Sales_item( const Item_base &item ):h(item.clone()) { }
const Item_base& operator*() const { return *h; }
const Item_base* operator->() const { return h.operator->(); }
private:
Handle<Item_base> h;
}
3,double Basket::total() const
{
...
sum += (*iter)->net_price(items.count(*iter)); (*iter)返回h,h->是继承层次对象的指针.
}
模板特化
1,函数模板特化:模板的一个或多个形参其实际类型或实际值是指定的.对于默认模板定义不适用的类型,特化非常有用.
template<> int compare<const char*>( const char* const &v1, const char* const &v2 )
{
return strcmp( v1, v2 );
}
2,类模板特化
3,特化成员而不特化类
4,类模板的部分特化
重载函数模板:匹配(带转换和隐式转换)原则,先普通函数再模板实例.
template <typename T> int compare( const T&, const T& );
template <class U, class V> int compare( U, U, V );
int compare( const char*, const char* );
compare( 1, 0 ); 和int实例化匹配
compare( ivec1.begin(), ivec1.end(), ivec2.begin() );
compare( ia1, ia1+10, ivec1.begin() );
compare( const_arr1, const_arr2 ); 选择普通函数优先模板版本
compare( ch_arr1, ch_arr2 ); 选择普通函数优先模板版本
如果有重载模板函数定义为:
template <typename T> int compare2(T, T);
则不同,compare2( ch_arr1, ch_arr2 ); 这时使用模板函数
compare2( const_arr1, con_arr2 ); 使用普通函数,还是定义函数模板的特化好些.
第五部分:进阶
异常处理:大型程序出错情况下的处理,程序的问题检测部分抛出一个对象,通过对象的类型和数据到处理部分处理.
throw runtime_error("Data");创建一个异常对象(exception object,由编译器管理,保留在任意catch能访问的空间,处理完毕后释放),
他是被抛出表达式的副本(局部存储在抛出块的空间,在处理异常时,一般抛出块不存在了),其结果必须是可复制的副本.
catch(const runtime_error &e);可以匹配的第一个catch.
抛出类类型的异常
异常对象及继承:一般抛出的异常对象在静态编译时就决定了,其属于一个异常对象层次及其继承.
异常指针:抛出的指针,其解引用的异常对象就是静态编译的类型,不会动态解引用到继承层次类.如果静态类型是基类,实参是派生类.
则对象被分割,只抛出基类部分.单单抛出一个指针是错误的.
栈展开(stack unwinding):在throw时,当前函数暂停,开始匹配catch语句,首先try块,再沿嵌套函数链继续向上.
catch结束后,在紧接与try相关的最后一个catch之句之后的点继续执行.
为局部对象自动调用析构函数:块的局部对象在栈展开时会调用析构函数自动释放,块直接分配单元new不会释放.
析构函数从不抛出异常:析构函数不能throw异常,因为在stack unwinding时,在析构抛出未经过处理的异常,会导致调用标准库terminate函数,
terminate调用abort函数,强制整个程序非正常退出.
构造时抛出异常:构造时异常要保证部分已初始化的对象撤销.
未捕获的异常终止程序:没匹配到得异常,编译器自动调用库函数terminate.
捕获异常catch(exception specifier)
匹配原则:比匹配实参与形参类型的规则严格.除以下区别,异常对象类型必须与catch说明符类型完全匹配.不能算数转换,不能类类型转换.
1,允许非const到const的转换.
2,允许派生类到基类的转换.
3,将数组转换为指向数组类型的指针,将函数转换为函数指针.
异常说明符:可以是异常对象的引用,或者是异常对象的副本.
异常说明符与继承:派生类的异常对象可以通过引用和指针和catch形参匹配,如果异常派生对象传递到基类说明符类型,则副本是分割的基类子对象.
catch子句的次序必须和派生层次对应,派生类的catch要在基类的catch之前.
重新抛出:在校正了一些行动后,catch一般rethrow原来的传进来的异常对象,异常对象的引用的改变可以rethrow, throw;
1,捕获的所有异常的处理代码: throw(...) { } 捕获任何类型的异常,在其他catch之后.
2,函数测试块与构造函数: 在构造函数体之前的初始化时,也可能发生异常,这时使用函数测试块(func try block).
template <class T> Handle<T>::Handle(T *p)
try : ptr(p), use(new size_t(1)) func try block
{
//empty function body
} catch( const std::bad_alloc &e )
{ handle_put_of_memory(e); }
3,异常类层次: exception -> bad_cast
-> runtime_error -> overflow_error 运行错误
-> underflow_error
-> range_error
-> logic_error -> domain_error 逻辑错误
-> invalid_argment
-> out_of_range
-> length_error
-> bad_alloc
异常类的派生:
class isbn_mismatch : public std::logic_error {
public:
explicit isbn_mismatch( const std::string &s ) : std::logic_error(s) { }
isbn_mismatch( const std::string &s, const std::string &lhs, const std::string &rhs ) :
std::logic_error(s), left(lhs), right(rhs) { }
const std::string left, right; 共有的数据成员
virtul ~isbn_mismatch() throw() { }
};
派生类的使用:
Sales_item operator+( const Sales_item& lhs, const Sales_item& rhs )
{
if ( !lhs.same_isbn(rhs) )
throw isbm_mismatch( "isbn mismathc", lhs.book(), rhs.book() );
Sales_item ret(lhs);
ret += rhs;
return ret;
}
Sales_item item1, item2, sum;
While ( cin >> item1 >> item2 ) {
try {
sum = item1 + item2;
}
catch ( const isbn_mismatch &e ) { cerr << e.what() << e.left << e.right << endl; }
}
4,自动资源释放: 局部对象会自动撤销,但通过new申请的数组不会自动撤销.
通过异常安全技术(exception safe),可以定义一个类来封装资源的分配和释放.既"资源分配既初始化"RAII.
class Resource {
public:
Resource(parms p) : r(allocate(p)) { }
~Resource() { release(r); }
private:
resource_type *r; 私有数据
resource_type *allocate(parms p); 私有成员函数allocate
void release(resource_type*); 私有成员函数resource_type
};
void fcn()
{
Resource res(args);
...
}
auto_ptr类: 是个RAII的模板,#include <memory>
auto_ptr<T> ap; auto_ptr<T> ap(p);ap拥有指针p指向的对象,explicit关闭隐形转换的. auto_ptr<T> ap(ap2);
apq = ap2; ~ap; *ap; ap->成员访问操作符 ap.reset(p); ap.release(); ap.get();
auto_ptr类型只能管理new返回的一个对象,不能管理动态分配的数组.不能将auto_ptr作为标准库容器的类型,
(其在复制和赋值时有不同于普通指针的行为).
auto_ptr类只能保存指向一个对象的指针,不能指向动态分配的数组.但其撤销时,就自动回收auto_ptr指向的动态分配对象.
1,为new申请的内存使用auto_ptr.
2,auto_ptr可以保存任意类型的指针,
3,auto_ptr<string> apl(new string("Brontosaurus"));
4,*ap1 = "TRex"; sting s = *ap1; if (ap1->empty())
5,复制和赋值后原来的右auto_ptr对象指向空.
6,左操作数指向的对象会删除.
7,auto_otr的默认构造函数初始化为0.
8,测试auto_ptr对象,if( p_auto.get() )
9,p_auto = new int(1024); //error不能直接传指针
if ( p_auot.get() ) *p_auto = 1024;
else p_auto.reset(new int(1024));
警告:1,不能用auot_ptr指向const数据 2,2个auto_ptr不能指向同一个对象3,不能指向数组4,不能存于容器.
异常说明:
定义: void recoup(int) throw(runtime_error) 异常类型列表
void recoup(int) throw(); 不抛出任何异常
违反异常说明: 抛出没在异常类型表的异常,编译器没提示,会自动调用unexpected,unexpected调用terminate终止程序.
确定函数不抛出异常: throw();
成员函数异常说明: virual const char* what() const throw();
析构函数异常说明: vitual ~isbn_mismatch() thrwo() { } 析构函数不能抛出异常
异常说明与虚函数: 基类虚函数的抛出异常类型表必须大于派生类虚函数的异常类型表.这样基类指针访问派生类时不会unexpected.
函数指针的异常说明:
void recoup(int) throw(runtime_error);
void (*pf1) (int) throw(runtime_error) = recoup; 匹配,ok
void (*pf2) (int) throw(tuntime_error, logic_error) = recoup; 超出,error
void (*pf3) (int) throw() = recoup; 不抛出,ok
void (*pf4) (int) = recoup; 没异常说明,ok
命名空间:解决第三方库的多重名问题(命名空间污染 namespace pollution),库一般会在全局里定义类,函数,类别.
定义: 在全局或其他namespace定义,不能在类和函数里定义.
namespace cplus_primer{
class Sales_item { /* ... */ }; 可以包括类,函数,变量及其初始化,模板,其他namespace
Sales_item operator+( const Sales_item&, const Sales_item&);
class Query {
public:
Query( const std::string& );
std::ostream &display( std::ostream& ) const;
//...
};
class Query_base { /* ... */};
}
cplusplus_primer::Query q = cplusplus_primer::Query("hello"); 在其它域引用
q.display(cout);
using cplusplus_primer::Query; 声明
namespace namespace_name { } 定义和添加定义
namespace可以在.h里声明,其成员定义在.c里( namespace cplusplus_primer{ } ).保证接口和实现的分离.
定义可以在命名空间里,在全局里,但是不能在其它namespace里.
嵌套命名空间: 嵌套里同名的声明屏蔽其外围命名空间中的声明. 调用cplusplus_primer::QueryLib::Query
未命名的命名空间: namespace { 声明块 }; 适用于本文件,可以在同文件里不连续,其成员名不能和全局重名.
如果是.h里的未命名空间,则在不同的.c里定义局部实体.
c++不赞成c中的静态声明,即static func();改用namespace代替.
命名空间成员的使用: using std::map; 一次定义一个,一般定义在函数体或其他作用域的内部.在作用域结束时结束.
namespce Qlib = cplusplus_primer::QueryLib; 命名空间别名.
namespace blip{ int bi = 16, bj = 15, bk = 23; }
void main()
{
using namspace blip; using指示,在函数里定义,表明namespace成员局部可见.
++bi; --bj; ...
}
类,命名空间和作用域: 命名空间是作用域.其成员从声明时可见,透过嵌套和类,直至块的末尾.
std::string s; getline( std::cin, s ); 编译器会关联形参作用域到函数名.
接受类类型实参的友元和类在同一个命名空间,引用时无须使用显式命名空间限定符.
void f2() { A::C cobj; f(cobj); }
重载与命名空间: 确定候选函数集合,再匹配,再二义性.
using NS::print 引入所有的重载到候选集合,单引入某个重载函数会导致程序奇怪的行为.
using namespace 也一样.
命名空间与模板: 主要是特化的命名空间,模板特化的声明,定义和引用和其它命名空间成员一样.
复杂的继承关系:多重继承(multiple inheritance),虚继承
多重继承:多余一个基类的派生类的能力,用于建模.
class Panda : public Bear, public Endangered { };
ZoomAnimal -> Bear -> panda
Endangered ->
panda::panda(std::string name, bool onExhibit) : 派生类构造函数初始化所有基类.
Bear(name, onExhibit, "Panda"), 如省略,则调用默认构造函数.
Endangered(Endangered::critical) { }
构造的次序按基类构造函数在类派生列表中出现的次序调用,与构造函数中基类出现的次序无关.
ZoomAnimal -> Bear -> Endangered -> Panda
析构的次序则反过来.~Panda,~Endangered,~Bear,~ZooAnimal.
多个基类的转换:对派生类的指针或引用可以转换为任何基类的指针和引用,二义性的报错.
和单继承一样,对基类的指针和引用只能访问基类的(或继承的)成员.不能访问到派生类.
无论调用继承层次中的那个虚析构函数,处理都是按构造的逆序,全部析构.
多重继承派生类的复制控制:合成的复制和赋值是全体的,按次序由低到高.ZoomAnimal,Bear,Endangered,Panda.
派生类自己定义的则只负责基类子部分.
多重继承下的类作用域:成员函数中使用的名字首先在函数体内部查找,再在类里查找,再按继承层次由上往下查找.
相同的要指定名字,否则具有二义性.
多基类导致的二义性,即使是多声明,形参不同也会.使用 ying_yang.Endangered::print(cout) 可避免二义性.
为避免二义性,可以定义一个新的函数.
std::ostream& panda::print(std::ostream &os) const { 类里数据只是传值而不发生变化.
Bear::print(os);
Endangered::print(os);
return os;
}
虚继承:在定义继承层次时,ios基类管理流的状态和读写缓冲区,istream/ostream实现流操作,iostream多重继承istream/ostream.按常规继承其有2个ios.
这时需要共享虚类子对象ios,称为虚基类(virtual base class).
声明: class istream : public virtual ios { ... }; 指定虚继承ios派生istream
class ostream : virtual public ios { ... };
虚继承不是直观的,必须在提出虚继承之前虚派生.虚继承一般不会引起问题,他通常由一个人开发.
Zooanimal (虚)-> Bear ->
(虚)-> Raccon -> Panda(前一级虚)
Endangered ->
定义完的虚基类支持派生类对象到虚基类的转换.
虚继承的特殊的初始化语义:
Panda::Pamda( dtd::string name, bool onExhibit ) :
ZooAnimal(name, onExhibit, "Panda"), 直接初始化虚基类ZooAnimal,第1构造
Bear(name, Onexhibit), 第2构造,忽略Bear的ZooAnimal构造函数初始化列表.
Raccoon(name, onExhibit), 第3,忽略Raccoon的ZooAnimal构造函数初始化列表.
Endangered(Endangered::critical), 第4
sleeping_flag(false) { } 第5
最后,构造Panda本身
带虚基类的继承层次,首先按先后构造虚基类(不管显式的还是合成的初始化),再按次序正常构造其他类.析构则相反.
优化内存分配: 一般是分配内存,并构造初始化.(new),但效率不高,
且vector不能预知类型;这时一般先申请一段内存,使用时在构造.优化一般实现后再做.
对未构造的对象进行操作而不是初始化,其结果是运行崩溃.
allocator类: 模板,提供可知类型的内存分配操作,但不初始化. 其成员construct和destory分别在未构造内存中初始化对象和析构.
allocate<T> a; a.allocate(n); a.deallocate(p, n);
a.construct(p, t); a.destroy(p);
uninitialized_copy(b, e, b2); uninitialized_fill(b, e, t); uninitialized_fill_n(b, e, t, n);未初始化构造
template <class T> class vector {
public:
Vector() : elements(0), first_free(0), end(0) { }
void push_back(const T&);
// ...
private:
static std::allocator<T> alloc; 定义alloc
void reallocate(); 声明成员函数reallocate
T* elements; 指向数组的第一元素
T* first_free; 指向第一个空的元素
T* end; 指向数组本身之后的元素
// ...
}
template<class T> vector<T>::push_back( const T& t )
{
if ( first_free == end ) reallocate(); 申请一段新的空间,并进行复制,first_free指向新空间.
alloc.construct( first_free, t );
++first_free;
}
template <class T> vector<T>::reallocator()
{
std::ptrdiff_t size = first_free - elements;
std::ptrdiff_t newcapcity = 2 * max(size, 1);
T* newelements = alloc.allocate(newcapacity); 申请新空间
uninitialized_copy( elements, first_free, newelements ); 未初始化复制
for (T *p = first_free; p != elements; /* empty */) alloc.destory( --p );
if ( elements ) alloc.deallocate( elements, end - elements );逆序释放原空间
elements = newelements; 重定位置
first_free = elements + size;
end = elements + newcapacity;
}
operator new/operator delete:标准库机制,new/delete调用,会分配或释放可知大小,未类型化(既未初始化)的内存,
uninitialized_fill/uninitialized_copy构造对象,析构函数析构对象.
void *operator new(size_t); void *operator new[](size_t);
void *operator delete(void*); void *operator delete[](void*);
T* newelements = alloc.allocate( newcapacity ); 和operator new功能一样,参数带类型,安全
T* newelements = static_cast<T*>( operator new[](newcapacity*sizeof(T)) ); 静态类型转换.
定位new表达式: 接受未构造的内存的指针,并在该空间初始化一个对象或一个数组.
new (place_address) type(initializer-list)
new (first_free) T(t);
allocator<string> alloc;
string *sp = alloc.allocate(2); 构造临时对象
new (sp) string(b, e); 一对迭代器做实参
alloc.construct(sp+1, string(b, e)); 构造后的复制,有些类的复制构造函数是私有的,不能复制,必须用定位new表达式.
显式析构函数的调用: 要清除对象时
1, alloc.destroy( p );
2, p->~T; 直接析构成员对象,但内存还在,可以再用.
类自定义的new和delete:
类定义或继承了自定义的operator new/operator delete,就忽略标准库的new/delete.或operator new[]/operator delete[].
void* operator new(size_t) void operator delete(void*, size_t) 只访问静态成员,默认是静态的,不用static
void* operator new[](size_t) void oeprator delete[](void*, size_t)
在类自定义了operator new/delete后,可以使用全局作用域操作符::强制使用标准库的new/delete
Type *p = ::new Type; ::delete p;
CacheObj内存分配器基类:分配和管理已分配但未构造的自由列表(相当于在一段内存上添加和释放成员对象).希望自定义的new/delete的类可以继承CacheObj.
1,定义 template <class T> class CacheObj {
public:
void *operator new(std::size_t); 自定义new
void operator delete( void*, std::size_t ); 自定义delete
virtual ~CacheObj(); 虚析构函数,因为是虚基类.
protected:
T *next;
private:
static void add_to_freelist(T*);
static std::allocator<T> alloc_mem;
static T *freeStore;
static const std::size_t chunk;
};
2,成员 template <class T> void *CachedObj<T>::operator new(size_t sz)
{
if ( sz != sizeof(T) )
throw srd::runtime_error( "CachedObj: wrong size object in operator new" );
if ( !freestore ) {
T* array = alloc_mem.allocate(chunk); 申请chunk个T的空间
for ( size_t i=0; i!=chunk; ++i )
add_to_freelist(&array[i]); 用基类的next将其连起
}
T *p = freeStore;
freeStore = freeStore->CachedObj<T>::next; 下一个剩余空间
return p; 返回现在的地址指针
}
3, template <class T> void CachedObj<T>::operator delete(void *p, size_t)
{
if ( p != 0 ) add_to_freelist(static_cast<T*>(p)); 将现在的指针指向的空间放回.
}
4, template <class T> void add_for_freelist(T *p)
{
p->CachedObj<T>::next = freeStore;
freeStore = p;
}
5, template <class T> alloctor<T> CachedObj<T>::alloc_mem; 定义静态成员
template <class T> T *CachedObj<T>::freeStore = 0;
template <class T> const size_t CachedObj<T>::chunk = 24;
6,应用 class Screen : public CacheObj<Screen> { }; 类继承模板CacheObj
template <class T> class QueueItem : public CacheObj< QueueItem<T> > { }; 模板继承模板CacheObj
运行时的类类型识别RTTI: 通过基类的指针和引用来检索其运行时对象的实际派生类型.
带虚函数的类,在运行时执行RTTI操作符,其它的在编译时计算RTTI操作符.程序员必须知道对象强制转换为哪种类型.
要想通过基类来操作派生类,最好的办法还是虚函数,使用虚函数时,编译器会自动根据对象的实际类型选择正确的函数.
dynamic_cast操作符: 将基类类型的指针和引用安全转换为派生类型的指针和引用.
if ( Derived *derivedPtr = dynamic_cast<Derived*>(basePtr) ) 强制基类指针转换使用if/else
{
//use Derived object
}else{
//use Base object
}
void f(const Base &b) 强制基类引用转换
{
try {
const Derived &d = dynamic_cast<const Derived&>(b);
}catch ( bad_cast ) {
}
}
typeid操作符: 返回指针或引用所指对象的实际类型.返回type_info
if ( typeid(*bp) == typeid(Derived) ) { 必须是*bp,bp所指的对象.
} 如果bp是带虚函数的类型,typeid(*bp)抛出bad_typeid异常.
RTTI的使用:
举例:在比较继承层次的对象equal时,先比较运行类型相等,再逐层调用虚函数比较.
class Base {
friend bool operator==(const Base&, const Base&);
public:
protected:
virtual bool equal(const Base&) const;
};
class Derived : public base {
friend bool operator==(const Base&, const Base&);
public:
private:
bool equal(const Base&) const;
}
bool operator==(const Base &lhs, const Base &rhs)
{
return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}
bool Derived::equal( const Base &rhs ) const
{
if ( const derived *dp = dynamic_cast<const Derived*>(&rhs) ) { 必须强制转换
}else return false;
}
type_info类:提供操作
t1 == t2; t1 != t2; t.name(); t1.before(t2);
类成员指针: 对象指针通过->成员访问符访问成员,也可以先直接定义类成员的指针,再使用这个指针绑定到函数或数据成员.
static成员不是对象的组成部分,不能定义类成员的指针.
class Screen {
public:
typedef std::string::size_type index;
char get() const;
char get( index ht, index wd ) const;
private:
std::string contents;
index cursor;
index height, width;
};
数据成员的指针:
string Screen::*ps_screen = &Screen::contents; Screen里的std::string类型成员contents的指针.
Screen::index Screen::*pindex = &Screen::width; 指针指向screen里的index类型成员,cursor,height,width.
函数成员的指针:
char (Screen::*)() const 这个类型指定Screen类的const成员函数指针,无形参,返回char类型的值.
char (Screen::*pmf2)() const = &Screen::get; 定义类成员函数指针
char (Screen::*pmf2)(Screen::index, Screen::index) const = &Screen::get;
char Screen::*p() const; //error:mon-member function p connot have const qualifier
调用操作符的优先级高于成员指针操作符,所以括号是必须的.
成员指针的类型别名:
typedef char (Screen::*Action)(Screen::index, Screen::index) const;
Action是类型"Screen类的接受两个index类型,返回char的成员函数的指针"的名字.
Action get = &Screen::get; 定义get,Action类型.复制为&Screen:get
Screen& action(Screen&, Action = &Screen::get); 声明函数action带形参Screen&和Action,返回Screen&
Screen myscreen;
action(myscreen); use the default argument
action(myscreen, get); use the variable get taht we priviously defined
action(myscreen, &Screen::get); pass address explicitly(明确的)
使用类成员指针:
成员指针解引用操作符(.*) :从对象或引用获取成员.
成员指针箭头操作符(->*) :通过对象的指针获取成员.
1,使用成员函数的指针.
char c2 = (myscreen.*pmf)(); char c2 = (pScreen->*pmf)(0, 0);
2,使用数据成员的指针.
Screen::index ind2 = myScreen.*pindex; Screen::index ind2 = pScreen->*pindex;
3,应用
class Screen {
public:
Screen& home(); 成员函数指针表.
Screen& forward();
Screen& back();
Screen& up();
Screen& down();
typedef screen& (Scren::*Action)(); 定义成员函数指针类型
static Action Menu[]; 定义指针数组
enum Directions { HOME, FORWARD, BACK, UP, DOWN }; 枚举
Screen& move(Directions); 操作成员函数move
};
Screen::Action Screen::Menu[] = { &Screen::home, 定义成员函数指针表.
&Screen::forward,
&Screen::back,
&Screen::up,
&Screen::down,
};
Screen& Screen::move(Directions cm)
{
(this->*Menu[cm])(); 使用函数指针表.
return *this;
};
Screen myScreen;
myScreen.move(Screen::HOME);
myScreen.move(Screen::DOWN);
嵌套类:nested class在一个类里定义一个新类,这个新类叫嵌套类.一般用于定义执行类,2个类的对象的成员互相独立,互相独立.
嵌套类的名字只在外围类的作用域可见,在其它类作用域或定义外围类的作用域不可见.
template<class T> class Queue{ 模板Queue
private:
struct QueueItem; 私有嵌套类声明
QueueItem *head;
QueeuItem *tail;
};
tempalte <class T> struct Queue<T>::QueueItem { 在外围类的外部定义嵌套类,也是一个模板(T)
QueueItem(const T &t) : item(t),next(0) { }
T item;
QueueItem *next;
};
template <class T> int Queue<T>::QueueItem::static_mem = 1024;嵌套类静态成员定义
嵌套类可以直接引用外围类的静态成员,类型名和枚举成员.引用外围作用域以外的要加作用域确定操作符.
template <class T> void Queue<T>::pop() { 外围类使用嵌套类.
QueueItem *p = head;
head = p->next;
delete p;
}
实例化: Queue<int> qi; 实例化Queue,但不实例化QueueItem.当head/tail解引用时才实例化QueueItem.
嵌套类作用域中的名字查找???
联合类:节省空间的类,具有某些类特征:可以包括构造,析构,赋值.不能有static或引用成员.
class Token {
enum Tokenkind { INT, CHAR, DBL };
TokenKind tok;
union {
char cval;
int ival;
double dval;
} val;
};
switch ( token.tok ) {
case Token::INT:
token.val.ival = 42; break; //.val.
case Token::CHAR:
token.cval = 'a'; break; //匿名联合可以直接访问其成员
};
局部类:在函数体内可以定义局部类,再进一步封装数据.局部类不能声明static数据,可以访问外围作用域的static变量,枚举,类型名.
int a, val; 全局变量
void foo( int val ) 函数体定义
{
static int si; 外围作用域变量
enum Loc { a = 1024, b }; 外围作用域enum
class Bar {
public: 一般都是public,不需再封装
Loc locVal; ok,use local type name
int barVal;
void fooBar( Loc l = a ) ok
{
barVal = val; err,val is local to foo
barVal = ::val; ok
barVal = si; ok,use static
locVal = b; ok
}
};
}
类成员声明所用名字必须出现在之前的作用域中,
成员定义中所用名字可以出现在局部类作用域任何地方,
没找到在外围作用域找,再到包含函数的作用域找.
不可移植的一些特征:支持低级编程,从gcc语言继承来的.
位域: typedef unsigned int Bit; 位域必须是int型
class File {
Bit mode: 2; Bit是位域,2位.
Bit modified: 1;
Bit prot_owner: 3;
Bit prot_group: 3;
Bit prot_world: 3;
...
};
void File::write() 成员函数可以直接使用位域.
{
modified = 1;
}
enum { READ = 01, WRITE = 02 };
int main() {
File myFile;
myFile.mode |= READ;
if ( myFile.mode & READ )
cout << " myFile.mode READ is set/n";
}
&不能应用于位域,位域也不能是类的静态成员(多对象共享,省内存).
volatile限定符:一些数据的值由程序之外的硬件所控制, 如时钟.其对象声明为volatile:
(不优化,,编译器就不会把那个变量缓存到寄存器中,对变量的每次访问都直接通过实际内存的位置).
volatile int display_register,v;
int *volatile vip; vip is a volitile pointer to int
volatile int *ivp; ivp is a pointer to valatile int
ivp = &v; 只能用volitile的引用初始化
类合成的复制控制不适用于volatile对象.必须重定义带volatile成员的类的复制控制.
extern "c":链接指示(linkage directive),不能出现在类定义或函数定义内部,它必须出现在函数的第一次声明上.
1,extern "C" size_t strlen(const char *);
2,extern "C" {
int strcmp( const char *, const char* );
char *strcat( char*, const char* );
}
3,extern "C" { #incldue <string.h> } string.h必须是c或c++函数.
4,extern "C" double calc(double dparm) { /* ... */} 链接指示也可以将c++导出
5,extern "Ada" extern "FORTRAN"
6,#ifdef __cplusplus 如果编译c++,就extern "c"
extern "C"
#endif
int strcmp( const char*, const char* );
7,c++的重载函数只能链接指示一个到c.
extern "C" double calc(double); c/c++调用
extern SmallInt calc(const SmallInt&); c++调用
extern BigNum calc(const BigNum&); c++调用
8,void (*pf1)(int); c++
extern "C" void (*pf2)(int); c
pf1 = pf2; //error;c和c++指针具有不同类型.
9,extern "C" void f1(void(*)(int)); 应用于整个声明的链接指示
fi是个c函数,返回void,形参void(*)(int):c函数指针.
extern "C" typedef void FC(int); 类型别名
void f1(FC *); 访问