2 const 指针
int *const cur;
1 const指针的值不可改变
int err=4,num=8;
int *const cur=&err; (O)
cur=# (X)
2 const指针在定义时必须初始化
3 可以使用const指针修改它所指对象的值
*cur=0; (O)
3 指向const对象的const指针
const double pi=3.14;
const double *const pi_ptr=π
既不能改变指针所指向对象的值,也不可修改指针本身。
4 typedef 和指针
typedef string *pstring;
const pstring cstr;
问:cstr是什么类型
答:const 修饰的是pstring 的类型,而pstring 是一个指向string类型对象的指针
因此,cstr是一个指向string类型对象的const指针,即
string *const cstr;
而不是 const string * cstr;
5 const string str;与 string const str 都是一个意思。
6 使用数组初始化vector对象
必须指出第一个元素以及数组最后一个元素的下一个位置的地址
const size_t arr_size=6;
int int_arr[arr_size]={0,1,2,3,4,5};
vector<int> ivec(int_arr,int_arr+arr_size);
还可以并不完全都将原来数组内容全部包含。
7
7 指针与多维数组
int ia[3][4];
int (*ip)[4]=ia;//ip是一个指向含有4个元素的数组的指针
ip=&ia[2];//ip指向最后含有4个元素的一行了。
8 数组指针与指针数组
int *ip[4]; //指针数组,含有4个指针的数组,与普通数组的区别就是元素是地址(指针),而非数值
int (*ip)[4]; //数组指针,指向含有4个元素的数组的指针
9 typedef与多维数组指针
typedef int int_arr[4];
int_arr *ip=ia;
for(int_arr *p=ia;p!=ia+3;++p)
{
for(int *q=*p;q!=*p+4;++q)
{
cout<<*q<<endl;
}
}
10 创建 删除 动态数组
int *ptr=new int[10];
对于动态数组,只能初始化为元素类型的默认值,无法赋予其他值。
但是,字符串类型的就特殊一些
string *pstr=new string(10,'9');
推而广之到其他类型,都可以。但是必须是各个类型的指针做左值。
int i=new int(1024); (X)
删除很简单
delete[] ptr;
delete ptr;
而且,删除0值的指针是可以的
一旦删除指针指向的对象后,立即将指针赋值为0。 p=null是好习惯。
11 const对象的分配与回首
const int *pci_err=new const int[10]; (X)
const int *pci_ok=new const int[10](); (O)
因为对于const变量是需要进行初始化的
但是,
const string *cstr=new const string[100]; (O)
这是因为使用了string类的默认构造函数
删除时:
delete [] pci_ok;
12 空数组
char ma[0]; (X)
char *mama=new char[0]; (O)
13 异常处理
1 throw 错误检查部分使用它来说明遇到了不可处理的错误,引发了异常条件。停止余下程序的执行,并将控制转移给处理这种错误的处理代码。
2 try-catch 后面跟多个catch,
try{
xxxxxxxxxxx
}catch(异常说明符)
{
xxxxxxxxxxx
}
int i=0;
while(i!=10)
{
try{
int a,b;
cin>>a>>b;
if(a!=b)
throw runtime_error("Data must refer to same ISBN");
cout<<"a!=b";
}catch(runtime_error err){
cout<<err.what()<<"/nTry again? Enter y or n"<<endl;
char c;
cin>>c;
if(cin&&c=='n')
break;
}
i++;
}
在try中,当遇到throw后开始转入相应的catch语句.
14 非引用形参
1 普通形参:复制实参初始化。函数不会访问调用所传递的实参本身,不会改变实参的值。
2 指针形参:被复制的指针只是影响对指针的赋值。若函数形参是非const类型的指针,则函数可通过指针实现赋值,修改指针所指向对象的值。
void reset(int *ip)
{
//改变了实参指针所指向的值
*ip=0;
//没有改变实参
ip=0;
}
如果连实参指针所指的值都不能变,就用 const int *ip
对于
void fun1(int *ip)
void fun2(const int *ip)
可以用int *,也可以用const int *来调用 fun2;
但是只能int *调用fun1.
3 const形参
1 如果憾事使用非引用非const形参,己可以传递const实参,也可以传递非const实参。
2 如果形参定义为非引用的const类型。则在函数中,不可以改变实参的局部副本。而实参可以是const也可以是非const.
15 引用形参
1 引用形参直接绑定到实参,而非形参的副本。
2 利用引用返回额外的值。因为引用实参在函数中会变化,因此在函数外可以获取其至。
3 利用const避免复制 const string &str
const 引用就是避免复制实参。而且实参在函数中不会被改变。这样就避免了引用会改变实参的特性。
4 函数具有普通的非const引用形参,则不能通过const对象进行调用。这样会造成形参可变,而实参不可变。非const引用参数只能与完全同类型的非const对象关联。
5 应该将不需要修改的引用形参定义为const引用。对于非const引用,既不能用const对象初始化,不能用字面值活右值来初始化。
16 传递指向指针的引用
int *&val; //val是一个引用,与指向int型对象的指针相关联。是指针的别名。
17 数组形参的定义
void fun1(int *);
void fun2(int []);
void fun3(int [10]);
1 不修改实参指针的值,但是,函数却可以通过该指针来改变它所指向的数组元素的值。
2 不想修改数组形参的元素,可以定以为 void fun(const int*){};
18通过引用传递数组
1 形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。会检查数组实参与形参的大小。
void print(int (&arr)[10]){xxxx}
int k[10]={xxx};
print(k);//这是唯一的正确初始化方法
int &arr[10]; //arr是一个引用数组
int (&arr)[10] //arr是数组的引用
19 多维数组的传递
1 void print(int (*matrix)[10]);//matrix为指向含有10个int类型元素的数组的指针
2 编译器忽略第一维的长度。不要将起包含进来。
void print(int matrix[][10]); //等价
20 确保函数的操作不超出数组实参的边界
1 在数组本身防止一个标记检测数组的结束。 字符串以'/'结尾
2 传递给数组第一个和最后一个元素的下一个位置的指针。如同用数组初始化vector一样
3 将第二个形参定义为数组的大小。 void print(const int a[],size_t size);
21 返回引用
1 const string &func(const string &str)
{xxxxxxxxxx}
返回的是对象本身。
2 不要返回局部对象的引用
当函数执行完毕时,将释放分配给局部对象的存储空间。对局部对象的引用就会指向不确定的内存。
const string &map(const string &s)
{
string res=s;
return res; //disaster
3 给函数返回值赋值
char &get_val(string &str,size_t ix);
{
return str[ix];
}
string s("a value");
get_val(s,0)='A';//将A付给第一个字符。
//get_val(s,0)是一个引用,引用string类型s的第一个字符a
如果不想引用返回值被修改,可声明为 const char &get_val(xxx)
4 不要返回指向局部对象的指针。
22 默认实参
1 默认实参只能用来替换函数调用缺少的尾部实参。不可逾越。
2 形参可以用函数表达式来初始化。
3 如果在函数定义的形参中提供默认实参,则只有在包含该函数定义的源文件中调用该函数时,默认实参才有效。
23 局部对象
1 每个名字都有作用域,指的是知道该名字的程序文本区;每个对象都有生命周期,指的是程序执行过程中对象存在的时间。
2 一个变量若位于函数的作用域内,但生命期却跨越了这个函数的多次调用,则被定义为static
24 静态变量
1 在程序第一次经过该对象时初始化
2 一旦被创建,在结束前不会撤销。不会因为定义静态局部对象的函数的结束而结束。
3 在该函数的多次调用的过程中,static会持续存在并保存它的值。
int count_calls()
{
static int ctr=0; //初始化
return ++ctr;
}
25 类的成员函数
1 this指针 除static外的都有一个额外的,隐含的形参this。在调用成员函数时,this初始化为调用函数的对象的地址。是一个类指针(Sales_item*)
total.same_isbn(trans);
编译器会重写为:
Sales_item::same_isbn(&total,trans);
2 const成员函数
bool same_isbn(const Sales_item &rhs) const{}
1 const的作用是改变了隐含的this形参的类型。在调用total.same_isbn(trans)时,this将是一个指向total对象的const Sales_item*类型的指针。
作用是不能修改调用该函数的对象的成员函数。
2 const对象,指向cosnt对象的指针或引用只能用于调用其const成员函数。不能调用非const成员函数。
3 构造函数的初始化列表
Sales_item():units_sold(0),revenue(0.0){}
units_sold和revenue都是Sales_item的成员变量。
25 重载函数
1 只规定是形参个数不同或形参类型不同。不论返回值
2 非引用形参有const与无const都是一样的,不是重载。
3 对于const引用形参和const指针形参则是重载。
4 重载与作用域
局部声明的函数将屏蔽而不是重载在外层作用域中声明的同名函数。但是怎么定义这个局部函数啊?
原来是这样:
void print(double dou)
{
cout<<dou<<endl;
}
void print(int i)
{
cout<<i<<endl;
}
void foobar()
{
void print(int i);
print(3.14);
}
你猜怎么着,输出的是3 而不是3.14。函数调用的是形参为int的函数,它覆盖了double函数
就是因为重写写了 void print(int i);这是foobar作用域的问题,因为它的里面只能找到int函数
如果不再一次写int函数,则就能输出3.14了。
5 const形参
1 仅仅当形参是应用活指针时,形参是否是const才有影响。
2 可基于函数的引用形参是指向const对象还是非const对象实现重载。
3 不能基于指针本身是否为const来实现函数的重载。
fun(int *);与
fun(int *const)相同的了。当形参以副本传递时,不能实现重载。
26 指向函数的指针
1 指向函数而非指向对象的指针。
bool (*pf) (const string &){}
bool *pf(const string &){} //这里声明的是一个函数pf,它的返回值是指向bool类型对象的指针
2 使用typedef简化函数指针的定义
typedef bool (*pf)(const string &)();
pf是指向函数的指针名。该指针类型为“指向返回值为bool类型并带有一个const string 引用形参的函数的指针”。以后可以直接使用pf。
pf pf1=lengthCompare; //其中lengthCompare为
bool lengthCompare(const string &)();//因为在引用函数名的时候,函数名就被自动解释为指向函数的指针。
此时 lengthCompare 等效于 &lengthCompare。
3 通过指针调用函数
lengthCompare("hi")就等效于pf1("hi")或者是 (*pf1)("hi").
4 函数指针形参
1 void useBigger(const string &,const string &,bool(const string &,const string &));
最后一个参数,直接被当作是一个函数指针。连个名字都不用写。
2 void useBigger(const string &,const string &,bool (*)(const string &,const string &));
5 返回指向函数的指针
1 int (*ff(int ))(int *,int);
ff是一个函数,其返回值是 int (*)(int *,int).这是一个函数指针。所指向的函数返回int类型。
2 typedef int(*PF)(int *,int);
PF ff(int);其实核心还是ff(int).外面那么多东西都只是返回值而已。
3 函数不能当作返回类型。
typedef int func();
func f(); //(X)
func *f(); //是合法的f返回一个指向函数的指针
4 指向重载函数的指针
extern void ff(vector<double>);
extern void ff(unsigned int);
void (*pf1)(unsigned int)=&ff; //right
void (*pf2)(int)=&ff;//error 形参
//error 返回值
double (*pf3)(vector<double>);
pf3=&ff;
27 顺序容器
1 vector 支持快速随即访问
list 支持快速插入/删除
deque 双端队列
2 适配器
stack ,queue,priority_queue
28 容器类型的初始化
1 副本:C<T> c(c1) c与c1的类型必须匹配,包括容器类型和元素类型
2 当是复制不同类型的容器可以使用迭代器
list<int> ilist(ivec.begin(),ivec.end());
vector<int>::iterator mid=ivec.begin()+ivec.end()/2 ;
3 const list<int>::size_type size=64;
list<int> silist(size,"abc");
大小还可以是非常量表达式
extern unsigned getword(const string &str);
vector<string> svec(getword("helloworld"));
29 容器约束
1 元素不能是引用类型。
2 类Foo有一个int型形参的构造函数。
vector<Foo> empty;
vector<Foo> bad(10);此时没有默认构造函数了。
vector<Foo> ok(10,1);
只有在同时给定了每个元素的初始化后,才能使用容器大小的默认构造函数创建同类型的容器对象
3 容器的容器
vector<vector<string> > lines;//必须使用空格在两个>之间
30 迭代器
1 list不提供vector和deque的算术运算和关系运算
2 迭代器是[first,last)
while(first!=last)
{
//do something
++first;
}是安全的。
30 在顺序容器中添加元素
1 push_back(t),push_front(t).后者只用于list和deque
2 insert()
3 容器元素都是副本
4 避免存储end操作返回的迭代器
vector<int> v;
vector<int>::iterator first=v.begin(),last=v.end();
while(first!=last) //last不会更新 应该为(first!=v.end())
{
//do something
first=v.insert(first,42);
++first;
}
//这样的话first与last的距离会越来越远。是一个死循环。
31 关系操作符
1 比较的容器必须具有相同的容器类型,而且元素类型也必须相同。
2 若两个容器中具有相同长度而且所有元素相等,则容器相等。
3 子容器小于父容器
4 若两个容器都不是对方的初始子序列,则其比较结果取决于所比较的第一个不相等的元素。
5 类可以存放在容器中,但是如果类没有定义关系运算,则不可以使用容器的关系运算。
32 容器大小
c.resize(n)改变容器所包含的元素个数。若当前容器长度大于新的长度,则该容器后面的元素会被删除;若小于,则在尾部会添加。
33 访问元素
*ivec.begin()与ivec.front()同
*--ivec.end()与ivec.end()同
还有c[n]和c.at(n)。但是只适用于vector和deque
34 删除元素
1 删除第一个c.pop_front().删除最后一个c.pop_back()
2 删除指定erase
3 删除全部clear
35 赋值与swap
1 赋值和assign会使左操作数容器的所有迭代器失效。swap擦作则不会使迭代器失效。尽管被交换的元素已经存放在另一个容器中,但是迭代器仍然指向相同的元素
2 在不同类型的容器间赋值。使用assign函数。且迭代器不能是指向调用容器的迭代器。
36 vector容器的自增长
1 vector元素在内存中是连续存放。且实际分配空间比所需空间多一些。list则是不连续存放
2 capacity与reserve成员
capacity获取在容器需要分配更多的存储空间前能够存储的元素总数。reserve告诉容器应该预留多少个元素的存储空间。
你可能往vector中添加了10个元素,但是capacity可能是18。如果设置reserve为30后,则一知道这30个为此用满为止,vector都不会重新分配空间。
而且,capacity会以加倍当前长度的策略来分配。
37 容器的选用
1 vector和deque相顺序表一样,随即访问。但是不好插入和删除
2 list则如同是单连表
3 deque与vector的不同在于:deque可以高校的在其首部是实现insert和erase操作。
4 一般采用vector。当有插入删除中间元素时,才有list
38 容器适配器
1 适配器有 容器适配器,迭代适配器,函数适配器。
2 适配器实是一事物的行为类似于另一事物的行为的机制。
3 适配器的初始化:一个是A a 一个空对象。一个是A a(c)c为容器的副本
deque<int> deq;
stack<int> stk(deq);
4 基础容器类型
1 默认的stack和queue都基于deque容器实现。而priority_queue则在vector容器上实现。
而通过指定适配器的第二个类型实参,可覆盖其关联的基础容器类型
stack<string, vector<string> > stk(svec); //这样stack的容器类型就改为了vector
2 约束
stack可以建立在三种容器之上。
queue可建立在list和deque纸上
priority_queue可以建立在vector和deque上
依我看阿,无非是将顺序表和单连表中的数据赋值到栈,队列中,然后再有栈,队列的方法操纵这些数据而已。
39 关联容器
1 关联容器通过键值存储和读取元素。而顺序容器是以元素在容器中的位置顺序存储和访问元素。
2 map 采用key-value的形式。key是元素在map中的索引。
2 set仅包含一个键,支持键是否存在的检查
4 multimap和multiset允许为同一个键添加多个元素
40 pair类型:pair包含两个数值
1 初始化
pair的两个类型名称不必相同
pair<string,vector<int> > line;
2 使用typedef
typedef pair<string,string> author;
author prost("jim","kate");
author mahdo("lily","lucy");
3 构造新的pair对象
pair<string,string> next;
string first,last;
while(cin>>first>>last)
next=make_pair(first,last);
或者是
while(cin>>next.first>>next.second)
4 pair对象操作
author.first 和 author.second;
41 关联容器
1 关联容器不能通过容器大小来定义,因为这样的话就无法知道键所对应的值是什么
2 容器元素根据键的次序排列。在迭代遍历关联容器时,确保按照键的顺序访问元素,耳语存放位置无关。
42 map
1 可以使用键作为下标来获取一个值。
2 map<键类型,值类型> map对象
3 map<k,v> m(b,e) 创建map类型的对象m,存储迭代器b,e标记的范围内所有元素的副本。元素的类型必须转换为pair<const k,v>
4 键类型必须定义<操作符。
5 键不但有类型,还有一个相关的比较函数。该比较函数必须在键类型上定义 严格弱排序。即为键类型数据上的小于关系。键可以做小于运算,使得可以比较值。
map<ISBN,Sales_item> bookstore;
以ISBN类型的对象为索引,其所有元素都存储了一个关联的sales_item类类型势力。
43 map 定义的类型
1 map<K,V>::value_type 是一个pair类型,first是const map<K,V>::key_type类型,不可改变;second是map<K,V>::mapped_type类型
2 value_type是pair类型,它的值成员可以修改,键成员不能修改。
3 map迭代器进行解引用将产生pair类型的对象
map<string,int> word_count;
map<string,int>::iterator map_it=word_count.begin();
*map_it则是一个pair<string,int>对象的引用。
例如:
map<string,int> word_count;
word_count.insert(map<string,int>::value_type("Anna",1));
map<string,int>::iterator map_it=word_count.begin();
cout<<map_it->first<<" "<<map_it->second<<endl;
++map_it->second;
cout<<map_it->second<<endl;
输出的结果就是:
Anna 1
2
这里一方面说明map的迭代器是一个引用,指向容器中一个value_type类型。而value_type是pair类型。
另一方面说明可以对值成员进行修改,但是不能对键成员修改。
43 给map添加元素
1 使用下标访问map对象
map<string,int> word_count;
word_count["Anna"]=1;
此为map中没有Anna这个键的情况,如果有,则word_count["Anna"]表示与之关联的值1.
而word_count["Anna"]与++map_iterator->second作用相同
2 下标编程
map<string,int> wordCount;
string word;
while(cin>>word)
++wordCount[word];
map<string,int>::const_iterator mapIter=wordCount.begin();
while(mapIter!=wordCount.end()){
cout<<mapIter->first<<" occurs "<<mapIter->second<<" times "<<endl;
++mapIter;
}
这个程序可以很容易的统计出字符串出现的次数。
3 map::insert的作用
1 插入单个元素的insert版本使用k-v pair类型的参数。如果K已经存在,则v不变,map就不变。否则插入K-V
2 对于参数是一对迭代器的,迭代器必须指向K-V pair类型的元素。
3 word_count.insert(map<string,int>::value_type("Anna",1));
可以简化为:
1 word_count.insert(make_pair("Anna",1));
2 typedef map<string,int>::value_type valType;
word_count.insert(valType("Anna",1));
其实,单单这样插入也没有什么,关系变态的是这个insert的返回类型很复杂:pair<map<K,V>::iterator,bool>
3 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;
}
44 查找并读取map中的元素
1 int occurs=word_count["foobar"]; //如果foobar在map中出现,则occurs为foobar对应的值;如果foobar不存在,则插入foobar,并默认值为0。occurs为0
2 使用count用于检测键是否存在,而不用插入新键。返回map中key出现的次数。取值为0/1
int occurs=0;
if(word_count.count("foobar"))
occurs=word_count["foobar"];
3 若希望当元素存在就使用时,应该用find。返回指向元素的迭代器。若不存在,则返回end
int occurs=0;
map<string,int>::iterator mapIt=word_count.find("foobar");
if(mapIt!=word_count.end())
occurs=mapIt->second; //对迭代器进行解引用,获得一个value_type类型的对象。即*mapIt是一个pair对象的引用
45 从map中删除元素
1 map的erase操作返回void类型,而顺序容器,返回一个指向被删除元素后面的元素的迭代器
2 erase(K) 返回被删除元素个数
46 map遍历
在使用迭代器遍历map时,迭代器指向的元素按照键的升序排列
****在做练习的过程中,不可避免地要使用标准IO,文件IO,字符串IO
47 getline(istream,string) 从istream中读取一个单词,然后写入string对象中
48 条件状态
1 流处于无错状态 while(cin>>word)
2 流状态由bad(无法恢复的读写错误),fail(可恢复的读写,如数据类型不匹配),eof(文件结束符),good(前面三个没有一个为true才true)操作揭示
3 setstate可以打开某个指定的条件,clear将条件重新设置为有效状态
4 int ival;
while(cin>>ival,,!cin.eof()){
if(cin.bad())
throw runtime_error("IO wrong!“);
if(cin.fail()){
cerr<<"bad data,try again!";
cin.clear(istream::failbit);
continue;
}
// operate ival;
}
5 每个IO对象管理一个缓冲区, 输出缓冲区的刷新
endl:末尾插入一个换行符,然后刷新
ends:末尾插入一个null,然后刷新
flush:不添加任何字符,刷新
6 刷新所有输出,使用unitbuf,在执行完操作后都刷新流。
51 set类型
1 set容器只是定义了键值。没有关联的数值。而且其中的元素没有重复的。
2 添加元素 insert(T)或者 insert(begin,end)
3 获取元素find(K),count(K)运算
4 set中的键是const类型,不能给其赋值
52 multimap和multiset
1 元素添加和删除
添加也是insert,而且是多次插入
multimap<string,string>::size_type cnt=authors.erase(search_item);
删除所有键为search_item的值,并返回个数
而带有一个或一对迭代器参数只是删除指定的元素。返回void
2 查找元素
equal_range函数返回存储一对迭代器的pair对象,pair对象中的第一个迭代器指向该键关联的第一个实例,第二个指向该键关联的最后一个实例的下一个位置。若找不到,则两个迭代器都指向该键插入的位置
typedef multimap<string,string>::iterator authorIt;
pair<authorIt,authorIt> pos=author.equal_range(search_item);
while(pos.first!=pos.second){
cout<<pos.first->second<<endl;
++pos.first;
}
53 抽象意味着知考虑它的接口,即它能执行的操作。封装意味着无须了解内部细节
54 显式指定inline成员函数,当在类外实现的时候,在inline前面要加上类名。
55 类的前向声明用来编写相互依赖的类。只要类名一出现就可以认为该类已经声明。因此,该类的数据成员就可以是指向自身的指针或者引用。而其他类则可以直接使用已经声明的类
class LinkScrean{
LinkScrean *next;
LinkScrean &ref;
};
56 类对象
定义类的时候不分配空间,定义对象时才分配空间。每一个类对象具有自己的类数据成亚的副本。
57 隐含的this指针
1 必须使用this指针:当我们需要将一个对象作为一个整体引用而不是引用对象的一个成员时。即该函数返回对调用该函数的对象的引用
class Screan{
Screan& move();
Screan& set(char c);
const Screan& display();
};
指明该成员函数返回对其自省类类型的对象的引用。返回调用自己的那个对象
Screan& Screan::set(char c)
{
xxx
return *this; //通过对this的解引用来访问this指向的对象
}
而可以直接这样使用上面两个成员函数:myScrean.move().set('#');
2 从const成员函数返回*this
在普通的非const成员函数中,this的类型是一个指向类类型的const指针。可以改变指针所指向的指,不可改变this所保存的地址;在const成员函数中,this的类型是一个指向const类类型对象的const指针。不能改变指针指向的值也不能改变指针
3 不能从const成员函数返回指向类对象的普通引用。。const成员函数只能返回*this作为一个const引用.如果将函数定义为const成员,就可以在非const对象上调用该函数。
而myScrean.display().set('*');如果display返回一个const对象。那么const对象不能调用非const成员函数。
4 基于const的重载
class Screan{
public:
Screan& display(ostream& os)
{
do_display(ostream& os);
return *this;
}
const Screan& display(ostream& os) const
{
do_display(ostream& os);
return *this;
}
private:
void do_display(ostream& os) const
{
os<<contents;
}
};
这样:
Screan myScrean;
const Screan cScrean;
myScrean.display();
cScrean.display();就实现了重载。
5 可变数据成员。
希望类的数据成员,甚至是在const成员函数内可以修改。可声明为mutable
可变数据成员永远不能成为const,甚至当它是const对象的成员时也是如此。const成员函数可以改变mutable成员。
class Screan{
xxxxx
private:
mutable size_t access_ctr; //可以在const成员函数中变化
};
void Screan::do_display(ostream& os) const
{
++access_ctr; //计算程序被调用的次数
}
58类作用域
1 即便两个类的成员列表相同,他们也是不同的类型。因此不能相互赋值
2 形参表和函数体处于类作用域中,但是函数返回类型不一定在类作用域中
inline Screan::index Screan::get_cursor()const//这个时候就必须使用Screan来限定index了
3 使用this->或者Class::可以显示掉要类的成员数据。
4 在函数作用域之后,在类作用域中查找
void fcn(index ht){cursor=width*height;}
这样是有的就是类的成员数据
5 在类作用域之后,在外围作用域这哦姑娘查找
void fcn(index height){curosr=width* ::height} //这时候使用的就是类外的全局变量了。
59 构造函数
1 初始化式 Sales_item::Sales_item(const string& book),isbn(book),units_sold(0),revenue(0.0){} //看到没有
2 必须使用初始化列表的时候:没有默认构造函数的类类型的成员,const或引用类型的成员。
3 成员初始化的次序依据的是数据成员定义时的顺序,而不是初始化列表中的数据
4 有时顺序很重要
class x{
int i;
int j;
public:
x(int val):j(val),i(j){}
};
但是这样会报错,因为,你用一个尚未初始化的j去初始化i了
5 初始化表达式可以是任意表达式
Sales_item(const string& book,int cnt,double price):isbn(book),units_sole(cnt),revenue(cnt*price){}
6默认实参
Sales_item(const stirng& book=""):isbn(book){} //这就是默认实参
而 Sales_item empty就是采用了采用 默认实参
7 默认构造函数
只有当一个类没有定义构造函数,编译器会自动生成一个默认构造函数
为所有形参提供默认实参的构造函数也定义了默认构造函数
8 即便定义了其他构造函数,也可以提供一个默认构造函数。
9 使用默认构造函数
Sales_item myobj;或者
Sales_item myobj=Sales_item();
60 友元
1 非成员函数也可以访问私有成员。友元允许将非公有成员的访问权限授予指定的函数或者类
2 class Screan{
friend class Window_Mgr;
};
Window_Mgr& Window_Mgr::relocate(Screan::index r,Screan::index c,Screan& s)
{
s.height+=r;
s.width+=c;
return *this;
}
有元类的所有成员函数都可以访问授予有元关系的那个类的非共有成员。
或者单单限定一个成员函数
friend Window_Mgr& Window_Mgr::relocate(Window_Mgr::index,Window_Mgr::index,Screan&);
必须指定函数名所属的类
3 用有元引入的类名和函数,可以像预先声明的那样使用
class X{
friend class Y;
friend void f(){xxx}
};
class Z{
Y *ymem;
void g() {return ::f();}
};
4 对于重载的函数,如果想有元的话,必须都要写上,不能单单只写一个
61 static成员
1 对于特定类类型的全体对象而言,访问一个全局对象有时是必要的。在程序的任意点需要统计已经创建的特定类类型对象的数量;或则,全局对象可能是只想类的错误处理历程的一个指针;或者全局对象是只想类类型对象的内存自由存储区的一个指针。
2 如果对象是全局的,一般的用户代码就可以修改这个值。
3 static数据成员独立于该类的任意对象而存在;么一个static数据成员食欲类关联的对象。
4 static成员函数没有this形参,可以直接访问该类的static成员,但不能直接使用非static成员。而非static函数则可以使用static成员。
5 定义static成员
class Account{
public:
static double rate(){ return interestRate; }
private:
double amount;
static double interestRate;
static double initRate();
};
//表示出interestRate是 该类的对象们共同用于的属性。不变的属性。
6 static成员函数不是任何对象的组成部分,所以不能被声明为const.因为声明为const就承诺不会修改该函数所属的对象。也不能被声明为虚函数。
7 static数据成员必须在类内声明,但是要在类外定义。不是通过构造函数初始化的,而是定义时进行初始化
double Account::interestRate=initRate();
必须指定成员是哪个类定义的。而且在定义时不能使用static
8 const static 数据成员可以在类的定义体中初始化。但是必须还要在类外定义
class Account{
static const int period=30;
double dialy[period];
};
const int Account::period; //可以不指定初始值
9 static数据成员类型可以是所属的类的类型
class Bar{
static Bar m1; //ok
Bar *m2;//ok
Bar &m3;//ok
Bar m4;//error
};
62 管理指针成员
复制指针只是复制指针中的地址,不会复制指针只想的对象。
63 常规指针型行为
class HasPtr{
private:
int val;
int *ptr;
public:
HasPtr(int *p,int i):ptr(p),val(i){}
int *get_ptr() const {return ptr;} //get不改变值,那就可以用const
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 val) {*ptr=val;}
};
int obj=0;
Hasptr ptr1(&obj,42);
Hasptr ptr2(ptr1);
//int值是独立的,但是指针都指向同一个对象obj
ptr1.set_ptr_val(85);
ptr2.get_ptr_val(); //就会变成85。改变了已经
64 智能指针类
使用计数器来跟踪该类有多少个对象共享同一指针。当计数为0时,删除对象。
使用计数类
class U_Ptr{
friend class HasPtr; //这样HasPtr中的成员函数就可以使用U_Ptr中的成员了
int *ip;
size_t use;
U_Ptr(int *p):ip(p),use(1){}
~U_Ptr(){ delete ip;}
};