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 *); 访问