C++ Primer 10-12

原创 2013年12月02日 15:44:45
再读C++ Primer


第10章:关联容器
关联容器和顺序容器的本质差别在于:关联容器通过键(key)存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。
关联容器是通过键来高效的查找和读取元素。两个基本的关联容器类型是map和set。map的元素以键-值对的形式组织:键用作元素map中的索引,而值则表示所有存储和读取的数据。set仅包含一个键,并且有效的支持关于某个键是否存在的查询。
map 关联数组;元素通过键来存储和读取
set 大小可变的集合,支持通过键实现的快速读取
multimap支持同一个键多次出现的map类型
myltiset支持同一个键多次出现的set类型




pair类型:该类型在utility头文件中定义。与其他标准库类型不同,对于pair类,可以直接访问其数据成员:其成员是公有的,分别命名为first和second。
pair类型提供的操作:
pair<T1,T2> p1; 创建一个空的pair对象,他的两个元素分别是T1和T2类型,采用值初始化
pair<T1,T2> p1(v1,v2); 创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,而second成员初始化为v2
make_pair(v1,v2); 以v1和v2值创建一个新的pair对象,其元素类型分别是v1和v2
p1<p2; 两个pair对象之间的小于运算,其定义遵循字典次序:如果p1.first<p2.first或者!(p2.first < p1.first)&&p1.second<p2.second,则返回true
p1 == p2; 如果两个pair对象的first和second成员依次相等,则这两个对象相等。
p.first; 返回p中名为first的(公有)数据成员
p.second; 返回p中名为second的(公有)数据成员




关联容器共享大部分顺序容器操作,关联容器不提供front、push_front、back、push_back以及pop_back操作。
1. 关联容器三种构造函数如下,不提供通过容器大小来定义,因为这样的话就无法知道键所对应的的值是什么。
C<T> c; //创建一个名为c的空容器。C是容器类型名,T是元素类型,适用于所有容器。
C c(c2); //创建容器c2的副本c;要求容器类型和元素类型都相同。适用于所有容器.
vector<int> ivec;
vector<int> ivec2(ivec);//OK
list<int> ilist(ivec);//error
vector<double> dvec(ivec);//error
C c(b,e); //创建c,其元素是迭代器b和e标示的范围内元素的副本。适用于所有容器。第二个元素是新建容器的超出末端元素。不要求容器类型相同。元素类型也可以不同,只要他们相互兼容。能够将要复制的元素转换为所构建的新容器的元素类型,即可实现复制。采用这种初始化方式可复制不能直接复制的容器,也可复制其他容器的一个子序列。
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());
2.关系操作符
3.begin和end成员
c.begin();
c.end();
c.rbegin(); //返回一个逆序迭代器,指向最后一个元素
c.rend(); //返回一个逆序迭代器,指向第一个元素前面的位置
上诉操作都有两个不同的版本:一个const成员,另一个是非const成员。这些操作返回什么类型取决于容器是否为const。如果容器不是const,则这些操作返回iterator或reverse_iterator;如果容器是const,则要加const_前缀。
4.关联容器定义的类型别名:
1、size_type 无符号整形,足以存储次容器类型的最大可能容器长度
2、iterator 此容器类型的迭代器类型
3、const_iterator元素的只读迭代器类型
4、reverse_iterator按逆序寻址元素的迭代器
5、const_reverse_iterator元素的只读逆序迭代器
6、difference_type足够存储两个迭代器差值的有符号整形,可为负数
7、value_type 和顺序容器不一样,关联容器的value_type并非元素类型,而是描述键以及其关联制类型的pair类型。
8、reference 元素的左值类型,是value_type&的同义词
9、const_reference元素的常量左值类型,等效于const value_type&
5.swap和赋值操作,但关联容器不提供assign函数。
c1=c2; 删除容器c1的所有元素,然后将c2的元素赋值给c1。c1和c2的类型(把偶偶容器类型和元素类型)必须相同
c1.swap(c2); 交换内容:调用完该函数 后,c1中存放的是c2原来的元素,c2中存放的则是c1原来的元素。c1和c2的类型必须相同。该函数的执行速度通常要比c2的元素复制到c1的操作快。
6.clear和erase操作,但关联容器的erase返回void类型。
c.erase(p)
c.erase(b,e)
c.clear() 删除容器c内的所有元素。返回void
7.关于容器大小的操作。但resize函数不能用于关联容器。
c.size(); //返回容器c中的元素个数,返回类型为c::size_type
c.empty(); //判断容器代销是否为0,返回bool值
c.max_size(); //返回容器c可容纳的最多元素个数;返回类型为c::size_type


根据键排列元素:容器元素根据键的次序排列,这一事实就是一个重要的结论:在迭代遍历关联容器时,我们科确保按键的顺序访问元素,而与元素在容器中的存放位置完全无关。




map类型:
#include<map>


定义:
map<k,v> m;
map<k,v> m(m2);
map<k,v> m(b,e); //创建map类型对象m,存储迭代器b,e标记的范围内所有元素的副本。元素的类型必须能转换为pair<const k,v>,也就是它的值可以修改,但键不能修改


对于键的类型,其唯一的约束就是必须支持<操作符,至于是否支持其他的关系或相等运算则不作要求。




map定义的类型:
map的value_type是存储元素的键以及值的pair类型,而且键为const。例如,word_count数组的value_type为pair<const string,int>类型。
map<K,V>::key_type 在map容器中,用做索引的键的类型
map<K,V>::mapped_type 在map容器中,键所关联的值的类型
map<K,V>::value_type 一个pair类型,它的first元素具有const map<K,V>::key_type类型,而second元素则为map<K,V>::mapped_type类型


对map的迭代器做解引得到的是pair对象。它的first成员存放键,为const;而second成员则存放值。




给map添加元素:可使用insert成员实现;或者,先用下标操作符获取元素,然后给获取的元素赋值。
1.如同其他下标操作符一样,map的下标也使用索引(其实就是键)来获取所关联的值。如果该键已存在容器中,则map的下标运算与vector的下标运算行为相同:返回该键所关联的值。只有在所查找的键不存在时,map容器才为该键创建一个新的元素,并将它插入到次map对象中。此时,所关联的值采用值初始化:类类型的元素用默认构造函数初始化,而内置类型的元素则初始化为0;
map迭代器返回value_type类型的pair对象,而下标操作付则返回一个mapped_type类型的对象。
2.map::instert的使用
m.insert(e) e为m上value_type类型的值;如果键(e.first)不在m中,则插入一个值为e.second的新元素;如果该键在m中已存在,则保持m不变,不作任何操作。该函数返回一个pair类型对象,包含指向键为e.first的元素的map迭代器,以及一个bool类型的对象,表示是否插入该元素。具体地,返回值包含一个迭代器和一个bool值的pair对象,其中迭代器指向map中具有相应键的元素,而bool值则表示是否插入该元素。如果该键已在容器中,则其关联的值保持不变,返回的bool值为false;如果该键不存在容器中,则插入新元素,且bool值为true。在这两种情况下,迭代器都将指向具有给定键的元素。
m.insert(beg,end) beg和end是标记元素范围的迭代器,其中的元素必须为m.value_type类型的键值对。对于该范围内的所有元素,如果它的键在m中不存在,则将该键及其关联的值插入到m。返回void类型。
m.insert(iter,e) e是一个用在m上的value_type类型的值。如果(e.first)不在m中,则创建新元素,并以迭代器iter为起点搜索新元素存储的位置。返回一个迭代器,指向m中具有给定键的值。
如: word_count.insert(map<string,int>::value_type("Anna",1));
word_count.insert(make_pair("Anna",));//简化1
typedef map<string,int>::value_type valType;
word_count.insert(valType ("Anna",1));//简化2
检测insert的返回值:
map<string,int> word_count;
string word;
while(cin>>word){
pair<map<string,int>::iterator,bool> ret =
word_count.insert(make_pair(word,1));
if(!ret.second)
++ret.first -> second;
}
每个输入的单词所关联的值都赋为1。if语句检测insert函数返回值中的bool值。如果该值为false,则表示没有插入操作,按word索引的元素已在word_count中存在,此时将元素加1。




查找并读取map中的元素:
map<string,int> word_count;
int occurs = word_count["foobar"];
用下标操作符:如果foobar不存在,则会在map中插入具有该键的新元素,其关联的值为0.在这种情况下,occurs获得0值。然后我们可能不希望对不存在的键进行操作。map提供了count和find操作用于检查某个键是否存在而不会插入该键。
m.count(k); 返回m中k出现的次数。对于map对象,count成员只能返回0或1,因为不能出现重复的键。不存在,返回0值。
m.find(k); 如果m容器中存在按k索引的元素,则返回指向该元素的迭代器。如果不存在,则返回超出末端迭代器。
int occurs = 0;
if(word_count.count("foobar")) //count的使用
occurs = word_count["foobar"];
map<string,int>::iterator it=word_count.find("foobar");//find的使用
if(it != word_count.end())
occurs = it -> second;






从map中删除对象:
m.erase(k); //删除m中键为k的元素,返回size_type类型的值,表示删除元素的个数。对于map只能为0或1。
m.erase(p); //删除迭代器p所指向的元素,返回void。
m.erase(b,e); //删除迭代器b和e之间的元素,返回void。b必须在e前面或者相等(此时删除的范围为空)。




set类型:单纯的键的集合。
#include<set>
set不提供下标操作,也不提供mapped_type操作。value_type不是pair类型,而是和key_type相同的类型,键唯一。
与map容器一样,set容器的每个键都只能对应一个元素。
vector<int> ivec;
for(vector<int>::size_type i=0;i!=10;++i){
ivec.push_back(i);
ivec.push_back(i);
}
set<int> iset(ivec.begin(),ivec.end());
cout << ivec.size() << endl; //prints 20
cout << iset.size() << endl;//prints 10




在set中添加元素:
sset1.insert("the"); //使用键参数,返回pair类型对象,包含一个迭代器和一个bool值。
iset2.insert(ivec.begin(),ivec.end()); //是用迭代器返回void类型


从set中获取元素:
不提供下标操作符,可使用find获取元素。用count判断元素是否存在,返回个数0或1。set中的键值也为const类型,不能修改。
iset.count(1); //OK
set<int>::iterator set_it = iset.find(1);
*set_it = 11; //error
count << *set_it << endl; //OK,可读


multimap和multiset类型:允许一个键对应多个实例
#include<map>
#include<set>


multimap,multiset不支持下标操作。
支持的操作:
insert(e);
erase(k); //返回删除元素的个数
erase(b,e); //返回void
find(k) //返回e第一次出现的迭代器
count(k) //返回个数




lower_bound(k),upper_bound(k),equal_range(k);适用于所有关联容器,比较常用在multimap和multiset容器中。
m.lower_bound(k):返回一个迭代器,指向键不小于k的第一个元素
m.upper_bound(k):返回一个迭代器,指向键不大于k的第一个元素
m.equal_range(k):返回一个迭代器的pair对象。它的first成员等价于m.lower_bound(k)。而second成员则等价于m.upper_bound(k)。
在同一个键上调用lower_bound(k)和upper_bound(k)将产生一个迭代器范围,指向该键所关联的所有元素。如果该键在容器中才能在,则会获得两个不同的迭代器:lower_bound返回的迭代器指向该键关联的第一个实例,upper_bound(k)返回的迭代器指向最后一个实例的下一个位置。如果不存在,他们返回同一个迭代器指向依据元素排列顺序该键应该插入的位置。




第11章:泛型算法
必须包括#include<algorithm>,此外标准库还定义了一组泛化的算术算法,其命名习惯与泛型算法相同,使用这些算法必须包括#include<numeric>
每个泛型算法都独立于单个的容器。
大多数情况下,每个算法都需要 使用(至少)两个迭代器来指出该算法操作的元素范围。第一个迭代器指向第一个元素,而第二个迭代器指向最后一个元素的下一位置。第二个迭代器所指向的元素本身不是要操作的对象,而被用作终止遍历的哨兵。


算法本身从不执行容器操作,只是单独依赖迭代器和迭代器操作实现。本质上暗示了:使用普通的迭代器时,算法从不修改基础容器的大小,不会插入或者删除元素。但有一种特殊的迭代器:插入器,除了用于遍历其所绑定的序列之外,在给这类迭代器赋值时,将在基础容器上执行插入运算。如果运算操纵这类迭代器,迭代器将可能导致在容器中添加元素。
理解算法最基本的方法是了解该算法是否读元素,写元素或者对元素进行排序。


只读算法:
find(begin,end,value); //在指定范围内找value的值,返回迭代器。找不到,返回超出末端迭代器。
accumulate(begin,end,value) //第三个参数为累加初值,然后累加指定范围内的数。元素必须支持加法操作,如int,string。
如:string sum=accumulate(v.begin(),v.end(),string(" "));//ok,返回以空格开头连起来的字符串
    string sum=accumulate(v.begin(),v.end()," "); //error!第三个参数为字符串字面值,不能做加法操作。
    find_first_of(v1.begin(),v1.end(),v2.begin(),v2.end());//在第一个范围内查找与第二段范围中任意元素匹配的元素,然后返回一个迭代器,指向第一个匹配的元素。




写容器元素算法:
fill(vec.begin(),vec.end(),0); //reset each element to 0;这个算法只对输入范围内已存在的元素进行写操作。
fill_n(vec.begin(),10,0); //vec必须已存在10个空间,才能安全写入,否则出错。
back_inserter; //插入迭代器是可以给基础容器添加元素的迭代器。通常,用迭代器给容器赋值时,被赋值的是迭代你所指向的元素。而是用插入迭代器赋值,则会在容器中添加一个新元素,其值等于赋值运算的右操作数的值。使用back_inserter必须包括iterator头文件。back_inserter是一个迭代器适配器,使用一个对象作为实参。
如: fill_n(back_insert(vec),10,0);//在本例中传递给back_inserter的实参是一个容器的引用,back_inserter生成一个绑定在该容器上的插入迭代器。现在fill_n函数每次写入一个值,都会通过back_inserter生成的插入迭代器实现。效果相当于在vec上调用push_back,在vec末尾添加10个元素,每个元素的值都是0;
copy(ilist.begin(),ilist.end(),back_insert(ivec));//传递给copy的目标必须至少要与输入范围一样大。copy从输入范围中读取元素,然后将他们复制给目标ivec。效率比较差。通常应该vector<int> ivec(ilst.begin(),ilst.end());
replace(ilst.begin(),ilst.end(),0,42);//将指定范围为内所有值为0的实例替换为42;
replace_copy(ilst.begin(),ilst.end(),back_inserterz(ivec),0,42);//ilst没有改变,ivec存储ilst的副本,而ilst内所有值为0的元素在ivec中都变成了42。


排序算法:
sort(v.beg(),v.end());
unqiue(v.beg(),v.end()); //返回指向超出无重复的元素范围末端的下一位置。
unique_copy(v.beg(),v.end(),v1);


再谈迭代器:标准库所定义的迭代器不依赖于特定的容器。
插入迭代器:这类迭代器与容器绑定在一起,实现在容器中插入元素的功能。
iostream迭代器:这类迭代器与可输入或输出流绑定在一起,用于迭代遍历所关联的IO流。
反向迭代器:这类迭代器实现向后遍历,而不是向前遍历。所有容器类型都定义了自己的reverse_iterator类型,由rbegin和rend成员函数返回。
上述迭代器类型都在iterator头文件中定义。


插入迭代器:一种迭代器适配器,带有一个容器参数,并生成一个迭代器,用于容器中的插入元素。通过插入迭代器赋值时,迭代器将会插入一个新的元素。
back_inserter,创建使用push_back实现插入迭代器
front_inserter,创建使用push_front实现插入,故vector或其他没有push_front运算的容器中不能使用
inserter,使用insert实现插入操作。除了所关联的容器外,inserter还带有第二个实参:指向插入其实位置的迭代器
如:
list<int>::iterator it = find(ilist.begin(),ilist.end(),42);
replace_copy (ivec.begin(),ivec.end(),inserter(ilst,it),100,0);
首先用find定位ilist中的某个元素,使用inserter作为实参调用replace_copy,inserter将会在ilst中由find返回的迭代器所指向的元素插入新元素。而调用replace_copy的效果是从ivec中复制元素,并将其中值为100的元素替换为0值。ilst的新元素在it所表明的元素前面插入。在创建inserter时,应指明新元素在何处插入。inserter函数总是在它的迭代器实参所标明的位置前面插入新元素。


iostream迭代器:虽然不是容器,但提供了在iostream对象上使用的迭代器。
istream_iterator迭代器用于读取输入流和ostream_iterator迭代器用于写输出流。
这两个迭代器将他们所对应的的流视为特定类型的元素序列。
istream_iterator<T> in(strm); //创建从输入流strm中读取T类型对象的istream_iterator对象。
istream_iterator<T> in; //istream_iterator对象的超出末端迭代器
ostream_iterator<T> in(strm); //创建将T类型的对象写到输出流strm的ostream_iterator对象。
ostream_iterator<T> in(strm,delim); //创建T类型的对象写到输出流strm的ostream_iterator对象,在写入过程中使用delim作为元素的分隔符。delim是以空字符结束的字符数组。
流迭代器之定义最基本的操作:自增,解引和赋值。此外istream迭代器提供是否相等。而ostream迭代器不提供比较运算。
istream_iterator使用:
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof;
while(in_iter != eof)
vec.push_back(*in_iter++);

istream_iterator<int> in_iter(cin),eof;
vector<int> vec(in_iter,eof);


ostream_iterator的使用:
ostream_iterator<string> out_iter(cout,"\n");
istream_iterator<string> in_iter(cin),eof;
while(in_iter != eof)
*out_iter++ = *in_iter ++;


反向迭代器:从最后一个元素到第一个元素的遍历,反向将自增和自减的含义反过来了。++表示访问前一个元素,--表示访问下一个元素。
降序排序:sort(vec.rbegin(),vec.rend());


string::iterator comma = find(line.begin(),line.end,",");//如果输入为FIRST,MIDDLE,LAST
cout << string(line.begin,comma)<< endl; //输出FIRST
string::reverse_iterator rcomma = find(line.rbegin(),line.rend(),",");
cout << string(line.rbegin(),rcomma) << endl;//输出为TSAL
cout << string(rcomma.base(),line.end()) << endl; //输出LAST




const迭代器:
例1:
int search_value = 42;
vetor<int>::const_iterator result =find(vec.begin(),vec.end(),search_value);//用const_iterator不希望改变元素
cout << "The value "<<search_value<<(result == vec.end ? "is not present" : "is present") << endl;
例2:
size_t cnt = 0;
list<string>::iterator it = roster1.begin(); //将迭代器用作find_first_of的实参,该函数调用的输入范围由it和调用roster1.end()返回的迭代器指定。算法要求 用于指定范围内的两个迭代器必须具有完全一样的类型。roster1.end()返回的迭代器依赖于roster1的类型。如果该容器是const对象,则返回的迭代器时const_iterator类型;否则,就是普通的iterator类型。在这个程序中,roster1不是const对象,因而end返回的只是一个普通的迭代器。
while((it = find_firsr_of(it,roster1.end(),roster2.begin(),
roster2.end())) !=roster1.end()){
++cnt;
++it;
}
cout << "Found" << cnt <<"names on both rosters" << endl;


迭代器种类:
输入的迭代器 读,不能写;只支持自增运算
输出迭代器 写,不能读;只支持自增运算
前向迭代器 读和写;只支持自增预算
双向迭代器 读和写,支持自增和自减
随机访问迭代器 读和写;支持玩这个的迭代器算术运算


泛型算法的数据结构:
算法基本性质是需要是用迭代器的种类。所有算法都制定了它每个迭代器形参可使用的迭代器类型。如果形参为随机访问迭代器,则可提供vector或deque类型的迭代器,或者指向数组的指针。
另外算法可分为以下几种:
1.只读算法,不修改元素的值和顺序
2.给指定元素赋新值的算法
3.将一个元素的值移给另外一个元素的算法


C++还提供了两种算法模式:
1.由算法所带的形参定义
sort(beg,end)
find(beg,end,val)
2.通过两种函数命名和重载的规范定义
sort(beg,end,cmp)
find(beg,end,pred) //返回第一个使pred为true的迭代器




第12章:类
double ave_price() const; //const成员不能改变其所操作的对象的数据成员。const必须同时出现在声明和定义中,若只出现在其中一处,就会出现一个编译错误。


显式指定inline成员函数。
可以在类定义体内部指定一个成员为inline,作为其声明的一部分。或者,也可以在类定义体外部的函数定义上指定inline。在声明和定义处指定inline都是合法的。


隐含的this指针:
1.何时使用
当我们需要讲一个对象作为整体而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用this指针:该函数返回对调用该函数的对象的引用。返回*this。
2.从const成员函数返回*this
在普通的非const成员函数中,this的类型是一个指向类类型的const指针。可以改变this所指向的值,但不能改变this所保存的地址。在const成员函数中,this的类型是一个指向const类类型对象的const指针。既不能改变this所指向的对象,也不能改变this所保存的地址。
不能从const成员函数返回指向类对象的普通引用。const成员函数只能返回*this作为一个const引用。


基于const的重载:const对象只能使用成员,非const对象可以使用任一成员,但非const版本是一个更好的匹配。实例如下:
class Screen{
public:
Screen& display(std::ostream &os)
{do_display(os);return *this;}
const Screen& display(std::ostream &os) const
{do_display(os);return *this;}
private:
void do_display(std::ostream &os) const
{os << contents;}
}
Screen myScerrn(5,3);
const Screen blank(5,3);
myScreen.display(cout); //call nonconst version
blank.display(cout); //call const version




可变数据成员:
有时我们希望类的数据成员(甚至是const成员函数内)可以修改。可以通过mutable来实现。




构造函数:
构造函数分为两个阶段执行:初始化阶段;普通的计算阶段(计算阶段由构造函数体中的所有语句组成)。


构造函数初始化式:
没有默认构造函数的类类型的成员,以及const或引用类型的成员,不管是以上哪种,都必须在构造函数初始化列表中进行初始化。
构造函数初始化的次序就是定义成员的次序。
class X{
int i;
int j;
public:
X(int val):j(val),i(j){}//error //按成员定义顺序,先初始化i,在初始化j。本例用未出画的j来初始化i导致错误。
}


默认构造函数:
一个类只要定义了一个构造函数,编译器就不会生成合成的默认构造函数。只有当一个类没有构造函数时,编译器才会自动生成一个默认构造函数。


隐式类类型转换:可以用单个实参来调用的构造函数定义了从形参得到该类类型的一个隐式转换。
如: class Sales_item{
public:
Sales_item(const std:string &book = " "):isbn(book),units_sold(0),revenue(0.0){}
Sales_item(std::istream &is);
}


string null_book = "9-999-99999-9";
item.same_isbn(null_book); //隐式转换


item.same_isbn(cin); //隐式转换
隐私转换创建的对象是临时对象,一旦same_isbn结束就不能再访问它。实际上我们构造了一个在测试完成后被丢弃的对象。这几乎肯定是一个错误。
1.抑制构造函数定义的隐式转化
class Sales_item{
public:
explicit Sales_item(const std:string &book = " "):isbn(book),units_sold(0),revenue(0.0){}
explicit Sales_item(std::item &is);
}
可通过将构造函数声明为explicit来防止隐式转换。explicit关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不能重复它。
现在两个函数都不能用于隐式转换。
item.same_isbn(null_book); //error
item.same_isbn(cin); //error
2.为转化而显式地使用构造函数
string null_book = "9-999-99999-9";
item.same_isbn(Sales_item(null_book)); //隐式转换




类成员的显式初始化:对于没有定义构造函数并且其全体成员均是public的类,可以采用与初始化数组元素相同的方式初始化其成员。
struct Data{
int ival;
char *ptr;
}
Data vall = {0,0};
Data val2 = {222,"dewf dwd"};




友元:允许一个类将对其非公有成员的访问授予指定的函数或类。友元的声明以关键字friend开始。它只能出现在类定义的内部。友元可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以他们不受其声明部分的访问控制影响。
友元可以是普通的非成员函数,或前面定义的其他类的成员函数,或整个类。将一个类设为友元,友元类的所有成员函数都可以访问授予友元关系的那个类的非共有成员。


static成员:独立于该类的任意对象而存在。每个static数据成员是与类关联的对象,并不与该类的对象相关联。static成员函数没有this形参,它可以直接访问所属类的static成员,但不能直接使用非static成员。


使用static成员的优点:
1.static成员的名字在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。
2.可以实施封装。static成员可以是私有成员,而全局对象不可以
3.通过阅读程序容易看出static成员于特定的类相关联。




定义static成员:
当我们在类外部定义static成员时,无需指定static保留字,该保留字只出现在定义体内部的声明处。




static函数没有this指针
static成员是类的组成部分但不是任何对象的组成部分,因此,static成员函数没有this指针。通过使用非static成员显式或隐式地引用this是一个编译错误。
因为static不是任何对象的组成部分,所以static成员函数不能被声明为const。毕竟,将成员函数声明为const就是承诺不会修改该函数所属的对象。最后,static成员函数也不能被声明为虚函数。


static数据成员
static数据成员可以被声明为任意类型,可以是常量,引用,数组,类类型等等。
static数据成员必须在包含类的非内联成员函数定义的文件中,不应该在类定义体内。不像普通数据成员,static成员不是通过构造函数进行初始化,而是应该在定义时进行初始化。
在类定义体外部引用类的static成员时,必须指定成员是在哪个类定义的。然而,static关键字只能用于类定义体内部的声明中,定义不能标为static。


1.特殊的整形const static成员
一般而言,类的static成员不能在类的定义体中初始化。static数据成员通常在定义是才初始化。这个规则的一个例外是,只要初始化式是一个常量表达式,整形const static数据成员就可在类的定义体中进行初始化。const static数据成员是一个常量表达式。
class Account{
public:
static double rate(){ return interestRate;}
static void rate(double);
private:
static const int period = 30;
doble daily_tbl[period];
}
const static数据成员在类定义体中初始化时,该数据成员仍须在类的定义体之外定义,但成员的定义不必在指定初始值。
如const int Account::period;


2.static成员不是类对象的组成部分
因为static数据成员不是任何对象的组成部分,所以他们的使用方式对于非static数据成员而言是不合法的。
例如:static数据成员的类型可以是该成员所属的类类型。非static成员被限定声明为其自身对象的指针或引用;
class Bar{
public:
private:
static Bar mem1;//ok
static *mem2; //ok
Bar mem3; //error
}
类似地,static数据成员可以用作默认实参:
class Screen{
public:
Screen& clear(char = bkground);
private:
static const char bkground = '#';
}
非static数据成员不能用作默认实参,因为它的值不能独立于所属的对象而使用。使用非static数据成员作默认实参,将无法提供对象以获取该成员的值,因而是错误的。

相关文章推荐

《C++ Primer Plus(第六版)》(12)(第八章 函数探幽 编程题答案)

内联函数、左值和右值、模板函数、显式实例、隐式实例、显式具体、后置返回类型、编程题答案... 8.8编程练习 1. void printInfo(char* str, int check = 0) {...

《C++ Primer第五版》读书笔记(12)-Overloaded Operations and Conversions

运算符重载,C++11没有太多的新东西.

C++primer pe13_15(还有13——4、9、12中NoName类的指针疑惑)

new Sales_item;     new Sales_item;     new Sales_item;     new Sales_item;//这些必须主动delete//计算析构函数...

c++primer 3/12----复制构造函数

C++ 支持两种初始化形式):直接初始化和复制初始化。复制初始化使用 = 符号,而直接初始化将初始化式放在圆括号中。 当形参或返回值为类类型时,由复制构造函数进行复制 string make_...
  • G_rrrr
  • G_rrrr
  • 2012年03月12日 16:02
  • 462

c++primer第十二章动态内存小结-12

第十二章---动态内存 1.动态内存 C++中,动态内存管理是通过一对运算符完成的:new和delete。C语言中通过malloc与free函数来实现先动态内存的分配与释放 C++中new与dele...

2017年1/12初读C++primer笔记

一个使用IO库的程序#include int main(void) { std::cout v2; std::cout

C++ Primer学习笔记——$12 类

题记:本系列学习笔记(C++ Primer学习笔记)主要目的是讨论一些容易被大家忽略或者容易形成错误认识的内容。只适合于有了一定的C++基础的读者(至少学完一本C++教程)。  作者: tyc611,...
  • whycold
  • whycold
  • 2011年01月19日 15:07
  • 488

从零单排c++ primer(12)

(1)函数退出有两种可能,正常处理结束或者发生了异常,无论哪种情况,局部对象都会被销毁。 (2)当发生异常时,直接管理的内存是不会自动释放的。 (3) 大多数应用应该使用标准库容器而不是动态分配的...

C++ Primer笔记12_运算符重载_递增递减运算符_成员访问运算符

1.递增递减运算符 C++语言并不要求递增递减运算符必须是类的成员。但是因为他们改变的正好是所操作对象的状态,所以建议设定为成员函数。 对于递增与递减运算符来说,有前置与后置两个版本,因此,我们应该为...

C++ Primer学习之(12)——类

P545: 通过类能够将实现和接口分离,
  • song_mu
  • song_mu
  • 2014年06月13日 16:54
  • 365
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++ Primer 10-12
举报原因:
原因补充:

(最多只允许输入30个字)