您的位置:首页 > 编程语言 > C语言/C++

C++Primer4笔记

2006-12-17 19:56 288 查看
C++Primer4 笔记

第一章 快速入门
1 std::cin, std::cout, std::endl
using namespace std; // 使用std命名空间
cin >> para; // 从cin流中读取值到para
cout << para; // 将para的值写入到cout流
endl; // 换行并刷新与设备相关连的缓冲区
2 注释不可嵌套
"/*"总是以最近的"*/"作为结束

第一部分 基本语言

第二章 变量和基本类型
1 内置类型
bool - // 我的机器是8位
char 8位
wchar_t 16位
short 16位
int 16位 // 我的机器是32位
long 32位
float 6位有效数字 // 我的机器有7位精度,32位表示
double 10位有效数字 // 我的机器有16位精度,64位表示
long double 10位有效数字 // 我的机器有16位精度,64位表示
· signed和unsigned的整型
用一个符号位表示符号,因此,同样整型的signed和unsigned版本,前者最大值是后者一半-1
· 整型越界赋值取最大界值的模
· 一般来说,int类型运行代价远远低于long类型。double类型计算代价相对于float可以忽略甚至更快。long double类型提供的精度通常没必要。
2 变量
· 左值:可以出现在赋值语句的两侧,右值:只能出现在赋值语句右边。
· 直接初始化和复制初始化
int ival(1024); // 直接初始化
int ival = 1024; // 复制初始化
直接初始化语法更灵活而且效率更高。
复制初始化不同于赋值。_
const对象需要在定义时初始化。
· 变量的初始化
内置类型在函数体外都初始化成0,在函数体内不进行自动初始化。
类类型如果有默认构造函数,则在定义变量时会调用默认构造函数;如果没有默认构造函数,定义变量时必须提供显示初始化式。
· 变量的定义和声明
定义为其分配存储空间,有且仅有一次。
声明可以有多次。需要使用extern说明,该变量的定义在程序其他地方。
· 作用域
全局作用域:定义在所有函数外部;
局部作用域:定义在函数局部;
类作用域:共有,保护,私有
语句作用域:在某个语句中可视(如for, while, 但是我的机器上for里面定义的变量作用域在循环体后也能调用,也许是编译器的原因);
作用域嵌套:局部作用域可以屏蔽全局作用域同样名字的变量。
const对象默认为文件局部作用域。通过指定const变量为extern,可以在整个程序中可视该const对象。
3 引用
引用是它绑定的对象的另一个名字,const引用指向一个右值,也可以指向一个左值,但并不能修改它。
4 typedef
定义类型别名,简化复杂类型的定义。
5 枚举
一个常量集合。(补充)上限:比enum中最大的元素还要大的,最近的2的N次幂的那个数-1. 如最大元素是120, 则上限是127.
下限是比最小的元素还小的,最近的2的N次幂的那个数+1。
6 类
(补充)类通过成员函数(方法)和操作符重载传递消息。
使用struct定义类类型,默认的访问标号是public。
头文件设计:类的定义、extern变量的声明,extern const对象的定义,函数声明。后面还将学到,包括内联函数的定义;加入
#ifndef **_H
#define **_H
// 类声明等
#endif
头文件的引用:
#include <iostream> // 系统标准头文件,编译器会在预定位置查找
#include "mylib.h" // 自定义头文件,编译器从源文件(引用该文件的文件)开始查找

第二章 标准库类型
1 using声明
using namespace::name;
这样可以直接使用命名空间内的名字。(类似java中的import java.util.Hashtable;)。
2 string类型
· 初始化
string s1; // 空的string
string s2(s1); // s2初始化为s1的副本
string s3("value"); // 直接初始化为字面值的副本
string s4(n, 'C'); // 初始化为字符'C'的n个副本构成的字符串
· string::size_type类型
是一种unsigned int/ unsigned long 型,能保证存储任意string对象的长度。
· 关系操作
<, <=, >, >=比较:按照字典排序
· 相加
string可以与字符串字面值连接混合得到一个新的string
· 下标
按下标取出string中某个元素,可以用于左值。
3 vector类型
· 初始化
vector<T> v1; // 类型为T的对象,默认构造函数v1为空
vector<T> v2(v1); // v2初始化为同样类型v1的副本
vector<T> v3(n, i); // n个类型为T的i元素
vector<T> v4(n); // n个T类型元素副本,其值都由默认构造函数初始化而来/如果没有默认构造函数需要提供元素初始值/
如果没有构造函数,产生一个每个成员都进行了值初始化的对像
· 关系操作
按照元素进行比较
· push_back
插入元素到元素集最后面
· 下标
用作左值
4 容器的迭代器类型
· 根据迭代器访问容器内元素
vector<int> ivec(10, 1);
ivec.begin(); // 指向第一个元素
ivec.end(); // 指向最后一个元素的后面的哨兵
ivec::iterator itr; // 根据begin(), end()通过自加或者自减遍历整个容器所有元素, 得到一个左值
ivec::const_iterator c_itr // 遍历但只读的访问所有元素,得到右值
ivec.rbegin(); // 指向最后一个元素
ivec.rend(); // 指向第一个元素的前面的哨兵
ivec::reverse_iterator ritr; // 根据rbegin(), rend()通过自加或者自减遍历整个容器所有元素, 得到一个左值
ivec::const_reverse_iterator c_ritr; // 遍历但制度的访问所有元素,得到右值
· 迭代器的算术操作
类似算术操作,返回结果是vector<T>::size_type或difference_type类型
· 上述概念也适用string容器
5 bitset类型
· 初始化
bitset<n> b; // b有n位,都是0
bitset<n> b(u); // b是unsigned long型u的一个副本,二进制表示后,从低阶位到高阶位依次读入。超过部分将被丢弃
bitset<n> b(s); // b是string对象s中含有的位串的副本,从右到左读入
bitset<n> b(s, pos, n); // b是string对象s从pos开始的n个位的副本
· 操作
控制整个容器的所有元素和某个元素的开关状态

第四章 数组和指针
1 数组
· 数组的定义
数组的维数需用大于等于1的整型字面值,枚举值,用常量表达式初始化的const对象定义。
运行时才知道值的const变量不能用于定义维数。
· 初始化
初始化列表提供的元素个数不能超过维数.
如果维数大于列表中初值个数,剩下的元素,如果是内置类型则初始化为0,如果是类类型则调用默认的构造函数进行初始化。
· 数组名是指向第一个元素的指针
2 指针
指针保存的是某个对象的地址(我的机器指针的size是4byte,一个字长)。
利用"&"可以获得对象的地址,而利用"*p"就可解除指针的引用,获得该指针指向的对象。
· 指针的初始化
指针可初始化为0值常量表达式,指向0(NULL),或类型匹配的对象的地址,或同类型的另一个指针,或另一个对象之后的下一地址(通常表示结束)。
void*指针可以保存任何类型的对象的地址。可通过强制类型转换重新获得实际对应类型地址。
· 指针的操作
自加自减操作可获取连续存放空间下一个元素的地址。每做一次自加,实际移动内存的字节数等于指针指向的数据类型的size。
指针之间的算术操作得到的标准库类型ptrdiff_t的数据。
指针是数组的迭代器。
· 指向const对象的指针
这种指针可以理解为"自以为指向const的指针",它并不能修改所指向的对象。
· const指针
该指针本身不能修改,需在定义时初始化。
typedef string* pstring;
const pstring cstr; // 实际上是 string *const cstr; 定义了一个const指针
3 动态数组
int *p=new int[10]; 申请空间,并用p指向这片空间。与delete[] 配套使用。

4 多维数组
int ia[rowSize][colSize]; //ia是第1行第1个元素的地址;ia[i]是第i行第1个元素的地址
int (*ip)[4] = ia; // ip指向一个多维数组的第1行(就是一个一维数组),该数组有4个元素(4列)
ip = &ia[3]; // ip指向第4行了

第五章 表达式
1 求模
如果:23 % -5 = 3 (这是我的机器),那么 -23 % 5 = -3,因为除得的值是-4 // 求模符号跟分子一致
如果:23 % -5 = -2 (这是我的机器),那么 -23 % 5 = 2,因为除得的值是-5 // 求模符号跟分母一致
2 动态创建对象
new和delete配套使用。
当执行delete p之后, p变成没有定义,但仍然存放了它之前所指向对象的地址,它指向的内存已经释放。变成一个悬垂指针,应该立即置为0。
3 类型转换
· 整型提升:对于所有比int小的整型,都会提为int或unsigned int。bool的true和false将转成1和0;
· long和unsigned int的转换:如果long足够表示unsigned int,则转换成long。否则都转成unsigned long;
· 数组名转换为第一个元素的指针;
· 0转换为bool的false,其他算术值或指针转换为true;
· enum对象或枚举成员至少提升为int;
· const_cast:转换掉表达式的const性质;
· static_cast:编译器隐式提供的任何类型转换都可以由static_cast显示完成。
· 旧式强制转换:在合法使用static_cast或const_cast的地方,旧式强制转换提供了上述一致的功能,如果不能,则执行reinterpret_cast。
type(expr);
(type)expr;

第六章 语句(略)
1 控制流向,各种语言都差不多。
2 预处理变量有条件的调试代码
#ifndef NDEBUG
// debug sentences goes here
#endif
3 assert宏帮助调试
assert(expr);
只要NDEBUG未定义(在程序调试中),assert宏就求解表达式的值。

第七章 函数
1 函数的定义
返回值类型:可以是内置类型,类类型或复合类型和void型(不返回数组,但可以返回指针);
函数名:字母或下划线开头的非标识符;
参数列表:用园括号括起来,指定类型和参数名,并用逗号分隔;此上是函数原型。
函数体:一对花括号的块语句。
2 参数传递
· 非引用形参:通过复制对应的实参实现形参初始化。形参只是一个副本,任何改变该副本的操作都不会影响到传入来的实参。
void fcn(const int i);
void fcn(int i);
可以看成是同样的2个函数。
· 引用形参
返回更多的信息(在return之外);
const引用避免修改实参;
const引用使得函数更灵活:非const引用形参只能与完全同类型的非const对象关联(不能给它传const,否则会编译出错)。
· 数组形参
非引用类型传递数组指针。形参会复制这个实参(指针)的值(是一个地址)。
通过引用传递数组本身,编译器检查实参和形参的大小是否匹配。如
void printValue(int (&arr)[10]); // 圆括号不可少
多维数组(数组的数组)传入0号元素的指针,编译器忽略第一维的长度。如
void printValue(int (matrix*)[10]); // 或
void printValue(int matrix[][10]);
3 return 语句
非引用类型返回值复制给调用函数的对象;
引用类型的返回是对象本身。
不要返回指向局部对象的指针和局部对象的引用。
4 函数声明
· 在头文件中声明;
· 默认实参的排列:使用最少的排在最前;
· 默认实参只能指定一次(一般在函数声明的头文件中);
· 内联函数定义在头文件中。
5 重载函数的确定
· 确定候选重载函数集合
· 选择可行函数
·寻找最佳匹配(如果有的话)
6 实参类型转换
· 精确匹配
· 类型提升
· 标准转换
· 类类型转换
· 这两个函数是一样的
f(int *);
f(int *const);
7 函数指针
· 指向函数类型的指针。函数类型由返回类型和形参表确定。
· 通过指针的解引用(*pf),或直接将指针当函数名用传入参数即可调用该函数。
· 返回指向函数的指针
int (*ff(int))(int*, int); // 相当于:
typedef int (*PF)(int*, int);
PF ff(int);

第八章 标准IO库
1 标准输入输出库

· 对应的宽类型
· IO对象不可赋值和复制
2 条件状态
· 标记给定的IO对象是否可用
· 多种状态的处理:传递二进制位或操作连接的条件状态符:
is.setstate(is.badbit | is.failbit);
3 输出缓冲区的管理
·flush:刷新,不加入数据
·ends:刷新,加入一个null
·endl:刷新并换行
·unitbuf刷新所有输出
·nonitbuf:恢复系统管理缓冲区刷新的方式
·使用tie函数可用使得输入输出流绑在一起。
4 文件模式
· 各种文件流支持的文件模式
ofstream: out, trunc, app, ate, binary
fstream: in, out, trunc, app, ate, binary
ifstream: in, ate, binary
· 打开模式的有效组合
out
out | app
out | trunk
in
in | out
int | out | trunk
5 字符串流
可以通过stringstream进行转换和格式化:
istreamstream is(“abc def 1 ghi”);
string word1, word2, word3;
int i;
is >> word1 >> word2 >> i >> word3;

第二部分 容器和算法

第九章 顺序容器
1 顺序容器的定义
· 初始化
C<T> c; 创建空的容器
C c(c2); 创建容器c2的副本c
C c(b, e); 利用一对迭代器内的元素创建c
C c(n, t); 用n个值为t的元素创建容器c,只适合于顺序容器
C c(n); 创建n个值初始化的元素的容器c。如果是类类型要求有默认构造函数。
· 元素约束:可赋值,可复制
2 迭代器
· vector,deque支持算术操作;而list只支持自增自减和相等不等的运算。
· 迭代器范围是一个左闭合区间[first, last)
3 在顺序容器的操作
· 添加元素:都是添加原元素的副本
c.push_back(t);
c.push_front(t); // 只适用list,deque
c.insert(p, t);
c.insert(p, n, t);
c.insert(p, b, e);
· 容器的比较
如果容器存放的元素可以进行比较:
如果2个容器具有相同的长度和元素,则他们相等; 如果长度不相同,但某容器中的所有元素都包含在令一容器之中的对应的一部分,则后者大;否则比较第一个不相等的元素。
· 调整大小
c.resize(n);
c.resize(n, t);
· 访问元素
c.front();
c.back();
c
; // 只适合vector,deque
c.at
; // 只适合vector,deque
· 删除元素
c.erase(p);
c.erase(b,e);
c.clear();
c.pop_back();
c.pop_front(); // 不适合vector
· 赋值
c.assign(b, e); // 重新设置c的元素,将迭代器范围内的元素复制到c。b,e必须是别的容器一对迭代器
c.assign(n, t); // 重新赋值为存储n个t
·swap
c1.swap(c2); // 交换内容。c1,c2类型必须相同。
4 vector的自增长
capacity
size
reserve
5 容器选择
·如果要求随机访问:vector或deque
·如果要在中间位置插入或删除元素:list
·如果只是在首尾插入或删除元素:deque
· 如果需要随机访问元素又必须在中间位置插入元素:先使用list,排序后复制到vector
6 string
·初始化
string s(cp, n); //
string s(s2, pos2); //
string s(s2, pso2, len2); //
·操作
s.insert(p, t); //
s.insert(p, n, t); //
s.insert(p, b, e); //
s.assign(b, e); //
s.assign(n, t); //
s.erase(p); //
s.erase(b, e); //
特有的:
s.insert(pos, n, c); //
s.insert(pos, s2); //
s.insert(pos, s2, pos2, len); //
s.insert(pos, cp, len); //
s.insert(pos, cp); //
s.assign(s2); //
s.assign(s2, pos2, len); //
s.assign(cp, len); //
s.assign(cp); //
s.erase(pos, len); //
s.substr(pos, n); //
s.substr(pos); //
s.substr(); //
s.append(args)//
s.replace(pos, len, args); //
s.replace(b, e, args ); //
args 包括:
s2,
s2, pos2, len2
cp
cp, len2
n, c
b2, e2
s.find(args); //
s.rfind(args); //
s.find_first_of(args); //
s.find_last_of(args); //
s.find_first_not_of(args); //
s.find_last_not_of(args); //
args 包括:
c, pos
s2, pos
cp, pos
cp, pos, n
s.compare(s2); //
s.compare(pos1, n1, s2); //
s.compare(pos1, n1, s2, pos2, n2); //
s.compare(cp); //
s.compare(pos1, n1, cp); //
s.compare(pos1, n1, cp, n2); //
7 容器适配器
· 初始化
A<T> a(c); // 基础容器初始化
A<T, Tc> a(c); //其中c的模版类型是Tc,覆盖关联的基础容器
·限制
stack:任意顺序容器
queue:list
priority_queue: vector,deque
·运算
同类型适配器可做比较,依次按元素进行比较。
·stack
s.empty(); //
s.size(); //
s.pop(); //
s.top(); //
s.push(item); //
·queue和priority_queue
q.empty(); //
q.size(); //
q.pop(); //
q.front(); // 只适合队列
q.back(); // 只适合队列
q.top(); // 只适合优先级队列
q.push(); //

第十一章 关联容器
1 pair
pair<T1, T2> p1; //
pair<T1, T2> p1(val1, val2>; //
make_pair(val1, val2);
p1 < p2; // 先比较first,如果相同,比较second
p1 == p2; // 如果first,second都相同,则成立
p.first; //
p.second; //
2 map
·初始化
map<k, v> m; //
map<k, v> m(m2); //
map<k, v> m(b, e); //
·键约束
键不但有一个类型,而且还有一个相关的比较函数(在键类型上定义严格弱排序:当键与自身比较时,导致false;不能出现键k1<k2,同时k2<k1;如果k1<k2,k2<k1都为false,则视为相等)。
·map定义的类型
map<k,v>::key_type; // 键类型
map<k,v>::mapped_type; // 键关联的值类型
map<k,v>::value_type; // 一个pair类型, first元素类型为键类型,second元素类型为值类型
·相关操作
m[k]; // 如果k键不存在,将添加新元素
m.insert(e); // e是value_type
m.insert(beg, end); // 这对迭代器指向的元素都必须是pair,且符合map的键-值结构
m.insert(iter, e);
如果insert的元素对应的键已在容器中,则不做任何操作,返回一个pair <iterator, false>;如果成功返回一个pair <iterator, ture>。
m.count(k);
m.find(k); // 返回一个指向pair的迭代器
m.erase(k);
m.erase(p);
m.erase(b, e);
3 set
·初始化
set<k > s; //
set<k> s(s2); //
set<k> s(b, e); //
·操作
s.insert(); //
s.insert(b, e); //
s.find(k); // 返回指向元素的迭代器
s.count(k); // 返回次数
map,set获得元素后, 键都是const,不能修改。
3 multimap和multiset
一个键对应多个值,这些值都是相邻存放的(根据键上定义的严格弱排序的比较函数)。
·初始化
·操作
mt.find(k); // 返回第一个找到的迭代器位置
mt.count(k); // 返回总数
mt.lower_bound(k); // 返回一个迭代器,指向键不小于k的第一个元素
mt.upper_bound(k); // 返回一个迭代器,指向键大于k的第一个元素
mt.equal_range(k); // 返回一个迭代器的pair,其first, second分别等价于mt.lower_bound(k),mt.equal_range(k)

第十一章泛型算法
1 输入范围
每对迭代器都是表示的左闭右开区间。每对迭代器必须指向同一个容器中的元素,并且第一个迭代器可通过不断自增到达第二个迭代器(它并不参与运算,只是一个终止条件的哨兵)。迭代器可以来自不同的容器,包括标准容器,内置数组容器,自定义的兼容容器等。
2 算法示例
accmulate(beg, end, init_val); // 一对迭代器和一个起始值。起始值指出了结果类型
find_first_of(beg1, end1, beg2, end2); // 两对迭代器,在第二对中找第一个与第一对迭代器中第一个相同的元素。返回找到的迭代器或者end1
fill(beg, end, val); // 对输入范围内存在的元素写入val的副本值
fill_n(itr, n, val); // 从itr开始,对n个元素的值写成val的副本值
fill_n(back_inserter(c), n , val); // 对支持push_back操作的容器c进行n个val副本值的写入。back_insert自动适配生成一个插入迭代器。
copy(beg, end, back_inserter(c)); // 同上
replace(beg, end, found_val, replace_val); //]
replace_copy(beg, end, back_inserter(c), found_val, replace_val);
sort(beg, end); // 对原容器的元素进行了移动
sort(beg, end, predicate); // 根据谓词函数(而不是默认的”<”)进行比较
unique(beg, end); // 返回第一个非unique的itrator。对容器元素进行了移动
count_if(beg, end, predicate); // 根据谓词函数计数
3 迭代器
·插入迭代器
back_inserter(c):使用push_back
front_inserter(c):使用push_front
inserter(c, it):使用insert。inserter(c, 0)与front_inserter的区别
·iostream迭代器
任何定义了”>>”和”<<”的类型都可以使用。
istream_iterator<T> in(strm); // 从输入流中读取T类型对象的输入流迭代器
istream_iterator<T> in; // 超出末端的输入流迭代器
istream_iterator<T> out(strm); // T类型对象写入到输出流strem的输出流迭代器
istream_iterator<T> out(strm, delim); // 同上,不过写的时候用delim作为分隔符,delim是一个C风格的字符数组
限制:不能读ostream_iterator,不能写istream_iterator;在给ostream_iterator赋值后无法修改,而且ostream_iterator的对象只能输出一次;ostream_iterator不能进行比较和取成员(->)。
·反向迭代器
·const_iterator
·五种迭代器
输入迭代器
输出迭代器
前向迭代器
双向迭代器
随机访问迭代器
除输出迭代器,其他类别的迭代器形成了一个层次结构:需要低级类别迭代器的地方,可以使用任何一种高级的迭代器。
4 泛型算法的结构
· 形参模式
alg(beg, end, other_params);
alg(beg, end, dest, other_params);
alg(beg, end, beg2, other_params);
alg(beg, end, beg2, end2, other_params);
· 命名
_if
_copy
重载(如sort(b, e)和sort(b, e, comp))
5 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);
lst特有算法对容器直接进行操作。泛型算法不直接添加或删除元素

第三部分 类和数据抽象

第十二章 类
1 类成员
·数据成员,成员函数,类型别名
· 在类内部定义的成员函数,将自动作为inline处理. 也可以显示地将成员函数声明为inline(类定义体内,外)
· 我们希望类的数据成员(甚至在const成员函数内)可以修改,可以通过声明其为multable数据成员
2 this指针是一个指向类类型的const指针
3 类定义实际上是在两个阶段中处理
·编译成员声明:检查出现在名字使用之前的类成员的声明,如不成功,检查包含类定义的作用域中出现在类定义之前的声明。
·所有成员出现之后才编译它们定义的本身:检查成员函数局部作用域中的声明,如不成功则检查对所有类成员的声明,如不成功则检查此成员函数定义之前的外围作用域中出现的声明。
4 构造函数的工作是保证每个对象的数据成员具有合适的初始值。
·构造函数分两个阶段执行:初始化阶段,普通计算阶段。
·不提供初始化列表的构造函数执行时也进行了初始化,只是在后续工作中进行了赋值。这相对低效。建议使用初始化列表,而且必须对任何const火引用类型成员以及没有默认构造函数的类类型提供初始化式。
·成员初始化的顺序是定义成员的次序,不是初始化列表中的次序。
·单个实参来调用的构造函数定义了从形参类型到类类型的一个隐式转换。用explicit修饰该构造函数将抑制隐式转换。建议使用explicit。
·一旦自己定义了构造函数,对应的合成的默认构造函数就不存在。
5 友元机制允许一个类将其非公有成员的访问权授予指定的函数或类。建议将友元声明成组的放在类定义的开始或结尾。
6 static成员
· static关键字只能用于类定义体内部的声明,定义不能标示为static。
· static数据成员独立于类的任意对象而存在,只于类相关联。它必须在类定义体的外部定义,并进行初始化。但整型const static成员可以在类定义体中初始化(定义仍在类定义体之外),但我的编译器上通不过。
· static成员函数没有this形参,也不能被声明为虚函数。它可以直接访问类的static成员,不能访问非static成员。

第十三章 复制控制
1 复制构造函数
·用于:初始化对象;按对象传实参;函数返回值。
·一般不设为explicit。
·为了防止复制,类必须声明其复制构造函数为private。
·最好显示或隐式定义默认构造函数和复制构造函数。如果定义了复制构造函数,也必须定义默认构造函数。
2 赋值操作符
·应该返回对左操作数的引用。
·赋值和复制常常一起使用。
3. 析构函数
·完成资源回收以及类对象使用完毕之后执行的操作。
·编译器总是提供合成的析构函数不管是否定义了自己的析构函数。
·如果类需要析构函数,则它也需要赋值操作符和复制构造函数。这称为“三法则”。
4. 显式复制函数一旦定义也就不存在合成的复制函数了,需要自己控制成员的复制。
5. 管理指针成员
·常规共享
·智能指针(示例:智能计数类封装一个int指针)

// private class for use by HasPtr only
class U_Ptr {
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p): ip(p), use(1) { }
~U_Ptr() { delete ip; }
};

/* smart pointer class: takes ownership of the dynamically allocated
* object to which it is bound
* User code must dynamically allocate an object to initialize a HasPtr
* and must not delete that object; the HasPtr class will delete it
*/
class HasPtr {
public:
// HasPtr owns the pointer; pmust have been dynamically allocated
HasPtr(int *p, int i): ptr(new U_Ptr(p)), val(i) { }

// copy members and increment the use count
HasPtr(const HasPtr &orig):
ptr(orig.ptr), val(orig.val) { ++ptr->use; }
HasPtr& operator=(const HasPtr&);

// if use count goes to zero, delete the U_Ptr object
~HasPtr() { if (--ptr->use == 0) delete ptr; }
private:
U_Ptr *ptr; // points to use-counted U_Ptr class
int val;
};

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++rhs.ptr->use; // increment use count on rhs first
if (--ptr->use == 0)
delete ptr; // if use count goes to 0 on this object, delete it
ptr = rhs.ptr; // copy the U_Ptr object
val = rhs.val; // copy the int member
return *this;
}

·采取值类型,即对指针成员独立对应唯一的对象。

第十四章 操作符重载和转换
1 重载操作符的定义
·重载操作符必须具有一个类类型操作数;操作符的优先级,结合性或操作数数目不能改变;不再具有短路求求值的特点。
·重载&&,|| 操作符将不具备短路求值的特点,不建议;
重载逗号操作符将对求值顺序不做规定,不建议;
重载取地址符等具有内置含义的操作符,也不建议。
·作为关联容器的类应该定义“<”操作符。即使在顺序容器中,类也经常定义相等“==”和小于“<”操作符。
类定义了相等操作符通常也应该定义不等操作符!=;如果定义了<,可能应该定义“>”,“>=”,“<=”。
·赋值(=),下标([]),调用(())和成员访问箭头(->)必须定义为成员函数;
复合赋值操作符通常定义为类的成员,也可定义为非成员;
改变对象状态或与给定类型紧密联系的其他的操作符(自增,自减,解引用等)通常定义为成员;
对称的操作符,如算术操作符,相等操作符,关系操作符和位操作符,最好定义为非成员。
2 输入输出操作符
·一般定义输入(>>)输出操作符(<<)为友元。IO操作符号必须定义为非成员函数,且第一个实参为IO对象的引用
·通常输入操作符仅需要设置failbit。eofbit(文件耗尽),badbit(流被破坏)最好留给IO标准库自己指出。
3 算术操作符和关系操作符
·既定义了算术操作符又定义了相关复合赋值操作符的类,一般应使用复合赋值实现算术操作符;相等和不等操作符一般互相联系,实际工作有某个完成,而另一个只是调用另一个。
·关系操作符设计要一致。
4 赋值操作符必须定义为成员函数,应该返回左操作数的引用。
5 下标操作符一般提供读写两个版本。
6. 成员访问操作符
·重载箭头操作符
假设我们调用point->action():
a. 如果point是一个指针,指向具有action成员的类的对象,则编译器将代码编译为调用该对象的action成员;
b. 如果point是定义了Operator->操作符的一个对象,则point->action与(point.operator->())->action()相同。即,执行point的operator->(),然后使用该结果重复这三步。
c. 否则代码出错
·重载箭头操作符必须飞回指向类类型的指针,或者飞回定义了自己的箭头操作符的类类型对象(或对象的引用)
a. 如果返回类型是指针,则内置箭头操作符可用于该指针,并获取相应成员。
b. 如果返回类型是类类型的其他对象(或引用),则将递归调用该操作符。
7 后缀自增自减操作符函数接受一个额外的int型形参用于与前缀形式
8 函数对象
·定义了调用操作符的类,称为函数对象,必须声明为成员函数。
·标准库函数对象(在function头文件中)
算术函数对象类型
plus<Type> applies +
minus<Type> applies --
multiplies<Type> applies *
divides<Type> applies /
modulus<Type> applies %
negate<Type> applies --

关系函数类型
equal_to<Type> applies ==
not_equal_to<Type> applies !=
greater<Type> applies >
greater_equal<Type> applies >=
less<Type> applies <
less_equal<Type> applies <=

逻辑函数类型
logical_and<Type> applies &&
logical_or<Type> applies ||
logical_not<Type> applies !
·标准库函数适配器
绑定器(将一个操作数绑定到给定值而将二元函数转对象换为一元函数对象)
band1st:绑定到第一个实参
band2nd:绑定到第二个实参
如:bind2nd(less_equal<int>(), 10)将得到一个小于10的函数对象
求反器(对函数对象的真值求反)
not1:对一元函数对象的真值求反
not2:对二元函数对象的真值求反
如:not1(bind2nd(less_equal<int>(), 10))
9 转换与类类型
·转换操作符定义了从类类型到其他类型的转换。转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。一般形式是:
operator type();
·类类型转换之前和之后都可以跟标准转换,但不能跟另一个类类型转换。
·将类类型转换为其他类型时,如果两个转换操作符都可用于一个调用中,而且在转换函数之后存在标准转换,则根据该标准转换的类别选择最佳匹配。如果标准转换序列没有优次,则调用具有二义性。将其他类型转换为类类型(类定义单实参的构造函数),也存在这样的规则;
如果两个类互相提供了隐式转换,则可能存在二义性。应避免。
·如果重载函数集中的两个函数可以用同一个转换函数匹配,则使用在在转换之后或之前的标准转换序列的等级来确定哪个函数具有最佳匹配。
否则,如果可以使用不同的转换操作,则认为这两个转换是一样好的匹配,不管需要或不需要标准转换的等级如何。
·如果定义了到内置算术类型的转换,不要定义接收内置算术类型的操作符重载版本。因为用户可以通过转换调用内置操作符。同时定义二者将引起二义性。一般而言,给出一个类与两个内置类型的转换是不好的做法,应该让标准转换提供到其他算术类型的的转换。
操作符的重载函数集候选函数既包括成员函数和非成员函数,都需要考虑最佳匹配的问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: