/********************************
C++笔记
自己记录的觉得关键 容易忘得东西
内容杂乱没有次序不适合学习
参考内容来自《C++primer》第4版
《C++编程思想》第二版 第一卷第二卷
仅供参考
!!!!!!!!!!有错误的地方见谅可能抄错了!!!!!!!!!!!!!
QQ710055071 十二年/DZF
********************************/
0001. int func2() 对于带空参数表的函数:
C语言中表示“一个可带任意参数(任意数目,任意类型)的函数”
C++不带参数的函数
0002. <>和"" 用尖括号来指定文件时,预处理器是以特定的方式来寻找文件,一般是环境中或编译器命令行指定的某种寻找路径。
这种设置寻找路径的机制随机器、操作系统、C++实现的不同而不同,要视情况而定。
用双引号时,预处理器以“由实现定义的方式”来寻找文件,它通常是从当前目录开始寻找,如果没找到,那么include命令就按与尖括号同样的方式重新开始寻找。
0003. break和continue
break 退出循环,不再执行循环中剩余的语句
continue 停止执行当前的循环,返回到循环开始处开始新的一轮循环;
0004.强制转换
1.static_cast包含的转换类型包括典型的非强制变换、窄化变换,使用void*的强制变换、隐式类型变换和类层次的静态定位
int i=0x7fff
long l;
float f;
l=static_cast<int>(i);
f=static_cast<int>(i);
const_cast 常量转换非常量 volatile 转换成非volatile
reinterpret_cast 重解释转换 使用之前必须转换回去
dynamic_cast 用于类型安全的向下转换
type(data);类型名(变量)//被称为伪构造函数
(type)date;//C语言强制类型转换
0005. typedef 名命别名
0006. struct enum union
0007.复杂的声明和定义
1).void *(*(*fp1)(int))[10];
fp1是一个指向函数的指针,该函数接受一个整型参数并返回一个指向含有10void指针数组的指针
2).float (*(*fp2)(int,int,float))(int);
fp2是一个指向函数的指针,该函数接受3个参数(int,int ,float),且返回一个指向函数的指针,该函数接受一个整型参数并返回一个float
3).typedef double (*(*(*fp3)())[10])();
相当于typedef double (*(*(*)())[10])() fp3;
fp3是一个指向函数的指针,该函数无参数,且返回一个指向含有10个指向函数指针数组的指针,这些函数不接受参数且返回double值
4).int (*(*fp4())[10])();
fp4是一个返回指针的函数,该指针指向含有10个函数指针的数组,这些函数返回整型值
5)int (*ff(int))(int*,int)
int board[8][8];//int 数组的数组
int **ptr;//指向int的指针的指针
int *risks[10]//具有10个元素的数组,每个元素是指向int的指针
int (*risks)[10]//一个指针,指向具有10个元素的int数组
int *oof[3][4]//一个 3X4的数组,每个元素是一个指向int的指针
int (*uuf)[3][4]//一个指针,指向3X4的int数组
int (*uof[3])[4]//一个具有3个元素的数组,每一个元素指向具有4个元素的int数组的指针
0008. delete []a;删除指针数组
0009. #define和#undef
#define 定义宏
#undef 取消宏定义
0010.在使用默认参数时必须记住两条规则:
第一,只有参数列表的最后部参数才是可默认的,也就是说,不可以在一个默认参数后面又跟一个非默认的参数。
第二,一旦在一个函数调用中开始使用默认参数,那么这个参数后面的所有参数都必须是默认的。
0011.ctrl+z 输入文件结束符 Unix,Mac,OS-X通常用ctrl+d;
0012.左值和右值
左值:左值可以出现在赋值语句的左边和右边;
右值:右值只能出现在赋值的右边,不能出现在赋值语句的左边;
0013.复制初始化和直接初始化
复制初始化:复制初始化语法用等于号;
直接初始化:直接初始化语法用括号;
0014.namespace a=b;定义一个名字空间的别名
0015.未名命的名字空间
这个实际上就是用来取代全局范围的静态变量和函数;
0016.数组
在函数体外定义的内置数组,初始化为0
在函数体内定义的内置数组,无初始化;
一个数组不能用另外一个数组初始化
也不能将一个数组赋值给另一个数组
size_t数组下标类型
0017.void*指针支持的操作
向函数传递void*指针或从函数返回void*指针
给另一个void*指针赋值
不允许使用void*指针操纵它所指向的对象
0018.指针和常量
const 限定符放在类型前面 也可以放在类型后面
*const 要将指针本身而不是被指的对象声明为常量
*const 也是一个类型 即常量指针类型
char *const cp;到char 的const指针
char const *pc;到const char 的指针
const char *pc1;到const char 的指针
typedef char* A;
const A b;A const b;char *const b;
int const * nx等价于const int * nx;
const int * nx; //左指针 const 在 * 号左边, nx运算时可以放在=号左边
int * const nx //右指针 const在 * 号右边,nx在运算时放在=号右边(不能放在左边)
const int * const ccnx; // 独立指针,禁止参与运算
int const * const ccnx;
int * px; 自由指针,允许指针的所有操作
const double pi=3.14;
double *ptr=π//error
const double *cptr=π//ok
把一个const 对象的地址赋值给一个普通的、非const对象的指针会导致编译时错误
不能使用void*指针保存const对象的地址,而必须使用const void*类型保存const对象的地址;
允许把非const对象的地址赋给指向const对象的指针
0019可变的:按位const 和按逻辑const
如果想要建立一个const 成员函数,但仍然想在对象里改变某些某些数据
这关系到按位const和按逻辑const的区别
按位const 意思是对象中的每个字节都是固定的,所以对象的每个位映像从不改变
按逻辑const意思是 虽然整个对象从概念上讲是不变的,但是可以以成员单位改变
当编译器被告知一个对象是const对象时,它将绝对包含对象按位的常量性
要实现按逻辑const属性 ,有两种由内部const 成员函数改变成员数据的方法
方法一 const成员函数改变成员变量
这种方法已经成为过去,称为强制转换常量性
const 成员函数 this 是const的
class Y{
int i;
public:
Y(){i=0};
void f()const;
};
void Y::f()const
{
//i++;error
((Y*)this)->i++;//ok
const_cast<Y*>(this)->i++;//ok
}
int main()
{
const Y yy;
yy.f();
return 0;
}
方法二
使用 mutable关键字 可以指定一个特定的数据成员
可以在一个const成员函数里改变数据成员的值
0020 const对象、指向const对象的指针或者引用,只能调用const成员函数 非const对象可以调用任意成员
类的const 数据成员必须在构造函数的初始化列表里初始化
static const 必须在定义它的地方初始化;
0021对于一个类型T的前缀++和后缀++,一般是这种原型:
T& operator++()
{
*this += 1;
return (*this);
}
T operator++(int)
{
T tmp = *this;
*this += 1;
return tmp;
}
0022
一:C语言中的内存机制
在C语言中,内存主要分为如下5个存储区:
(1)栈(Stack):位于函数内的局部变量(包括函数实参),由编译器负责分配释放,函数结束,栈变量失效。
(2)堆(Heap):由程序员用malloc/calloc/realloc分配,free释放。如果程序员忘记free了,则会造成内存泄露,程序结束时该片内存会由OS回收。
(3)全局区/静态区(Global Static Area): 全局变量和静态变量存放区,程序一经编译好,该区域便存在。并且在C语言中初始化的全局变量和静态变量和未初始化的放在相邻的两个区域(在C++中,由于全局变量和静态变量编译器会给这些变量自动初始化赋值,所以没有区分了)。由于全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。
(4)C风格字符串常量存储区: 专门存放字符串常量的地方,程序结束时释放。
(5)程序代码区:存放程序二进制代码的区域。
二:C++中的内存机制
在C++语言中,与C类似,不过也有所不同,内存主要分为如下5个存储区:
(1)栈(Stack):位于函数内的局部变量(包括函数实参),由编译器负责分配释放,函数结束,栈变量失效。
(2)堆(Heap):这里与C不同的是,该堆是由new申请的内存,由delete或delete[]负责释放
(3)自由存储区(Free Storage):由程序员用malloc/calloc/realloc分配,free释放。如果程序员忘记free了,则会造成内存泄露,程序结束时该片内存会由OS回收。
(4)全局区/静态区(Global Static Area): 全局变量和静态变量存放区,程序一经编译好,该区域便存在。在C++中,由于全局变量和静态变量编译器会给这些变量自动初始化赋值,所以没有区分了初始化变量和未初始化变量了。由于全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。
(5)常量存储区: 这是一块比较特殊的存储区,专门存储不能修改的常量(如果采用非正常手段更改当然也是可以的了)。
三:堆和栈的区别
3.1 栈
具体的讲,现代计算机(冯诺依曼串行执行机制),都直接在代码低层支持栈的数据结构。这体现在,有专门的寄存器指向栈所在的地址(SS,堆栈段寄存器,存放堆栈段地址);有专门的机器指令完成数据入栈出栈的操作(汇编中有PUSH和POP指令)。
这种机制的特点是效率高,但支持数据的数据有限,一般是整数、指针、浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构(可以自定义栈结构支持多种数据类型)。因为栈的这种特点,对栈的使用在程序中是非常频繁的 。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把返回地址入栈,然后跳转至子程序地址的操作,而子程序的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。
C/C++中的函数自动变量就是直接使用栈的例子,这也就是为什么当函数返回时,该函数的自动变量自动失效的原因,因而要避免返回栈内存和栈引用,以免内存泄露。
3.2 堆
和栈不同的是,堆得数据结构并不是由系统(无论是机器硬件系统还是操作系统)支持的,而是由函数库提供的。基本的malloc/calloc/realloc/free函数维护了一套内部的堆数据结构(在C++中则增加了new/delete维护)。
当程序用这些函数去获得新的内存空间时,这套函数首先试图从内部堆中寻找可用的内存空间(常见内存分配算法有:首次适应算法、循环首次适应算法、最佳适应算法和最差适应算法等)。如果没有可用的内存空间,则试图利用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以适当的形式返回给调用者。当程序释放分配的内存空间时,这片内存空间被返回到内部堆结构中,可能会被适当的处理(比如空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。 这套复杂的分配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下几个原因:
(1)系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于大量的小内存分配来说会造成浪费。
(2)系统调用申请内存可能是代价昂贵的。 系统调用可能涉及到用户态和核心态的转换。
(3)没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。
3.3 栈和堆的对比
从以上介绍中,它们有如下区别:
(1)栈是系统提供的功能,特点是快速高效,缺点是由限制,数据不灵活;而堆是函数库提供的功能,特点是灵活方便,数据适应面广,但是效率有一定降低。
(2)栈是系统数据结构,对于进程/线程是唯一的;堆是函数库内部数据结构,不一定唯一。不同堆分配的内存无法互相操作。
(3)栈空间分静态分配和动态分配,一般由编译器完成静态分配,自动释放,栈的动态分配是不被鼓励的;堆得分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存/释放内存匹配是良好程序的基本要素。
(4)碎片问题:对于堆来讲,频繁的new/delete等操作势必会造成内存空间的不连续,从而造成大量的碎片,使程序的效率降低;对于栈来讲,则不会存在这个问题,因为栈是后进先出(LIFO)的队列。
(5)生长方向:堆的生长方向是向上的,也就是向这内存地址增加的方向;对于栈来讲,生长方向却是向下的,是向着内存地址减少的方向增长。
(6)分配方式:堆都是动态分配的,没有静态分配的堆;栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配则由alloca函数进行分配,但是栈的动态分配和堆不同,它的动态分配是由编译器进行释放,无需我们手工实现。
(7)分配效率:栈是机器系统提供的数据结构,计算机在底层提供支持,分配有专门的堆栈段寄存器,入栈出栈有专门的机器指令,这些都决定了栈的高效率执行。而堆是由C/C++函数库提供的,机制比较复杂,有不同的分配算法,易产生内存碎片,需要对内存进行各种管理,效率比栈要低很多。
0023使用引用是有一定规则
1)当引用被创建时, 它必须初始化(指针则可以在任何时候被初始化)
2)一旦一个引用被初始化为一个对象,它就不能改变位另一个对象的引用
指针则可以在任何时候指向另一个对象
3)不可能有NULL引用,必须确保引用是和一块何方的存储单元关联
const int &a=1;//const 会为1分配内存 这是创建一个临时的整形变量并被A引用
int &A = 10; //Error 肯定不正确,类型不匹配,不能保证常量10不被修改。可以修改为下面
int b = 10;
int &A = b;
const int &A = 10;//OK 把A定义成一个int的常引用,这时A的值不能再被修改,保证常量10不被修改。如下则提示错误
int b = 20;
const int &A = 10;
A = b;
0024 string 对象的定义和初始化
string s1;//默认构造函数,s1为空;
string s2(s1);//将s2初始化为s1的一个副本
string s3("111111");//将s3初始化为一个字符串常量的副本
string s4(n,'c');//将s4初始化为n个‘c’
string s;
cin>>s;
//读取并忽略开头的所有空白字符(如空格 换行符 制表符)
//读取字符直至再次遇到空白字符,读取结束;
getline(cin,line)//不忽略行开头的换行符 遇到换行符停止读入并返回 读取不包括换行符
s.empty();如果s为空串则返回真 否则返回假
s.size();返回s中字符的个数
s[n],返回位置为n的字符
string 的size操作返回类型必须为string::size_type类型
下标类型也是string::size_type类型
空白字符— 空格 ,制表符,垂直制表符,回车符,换行符,进纸符
标 点 符— 除了数字、字母、或(可打印的)空白符以外的其他可打印的字符
isalnum(c);//如果c是字母或数字,则返回真
isalpha(c);//如果c是字母,则返回真
iscntrl(c);//如果c是控制字符,则返回真 ???
isdigit(c);//如果c是数字,则返回真
isgraph(c);//如果c不是空格,但可以打印,则返回真???
islower(c);//如果c是小写字母,则返回真
isprint(c);//如果c是可打印字符,则返回真
ispunct(c);//如果c是标点符号,则返回真
isspace(c);//如果c是空白字符,则返回真
isupper(c);//如果c是大写字母,则返回真
isxdigit(c);//如果c是十六进制数,则返回真
tolower(c);//如果c是大写则返回小写 否则返回c
toupper(c);//如果c是小写则返回大写 否则返回c
strlen(s) //返回s的长度,不包括字符串结束符null
strcmp(s1,s2)//比较字符串是否相同 相等返回0,s1>s2返回整数,否则返回负数
strcat(s1,s2)//将字符串s2链接到s1后返回s1
strcpy(s1,s2)//将s2复制给s1,并返回s1
strncat(s1,s2,n)//将s2的前n个字符链接到s1后面,并返回s1
strncpy(s1,s2,n)//将s 2的前n个字符复制给s1,并返回s1
s1.c_str()//转换成C风格的字符串
0025
iterator
const_iterator//不能改变元素
difference_type
size_t数组下标类型
ptrdiff_t两个指针减法操作的结果类型
0026 通过引用传递数组
void abc(int (&a)[10])
严格检查形参和实参大小是否匹配
0027
%c 字符
%d 带符号整数
%i 带符号整数
%e 科学计数法, 使用小写"e"
%E 科学计数法, 使用大写"E"
%f 浮点数
%g 使用%e或%f中较短的一个
%G 使用%E或%f中较短的一个
%o 八进制
%s 一串字符
%u 无符号整数
%x 无符号十六进制数, 用小写字母
%X 无符号十六进制数, 用大写字母
%p 一个指针
%n 参数应该是一个指向一个整数的指针
指向的是字符数放置的位置
%% 一个'%'符号
这个是scanf
%c 一个单一的字符
%d 一个十进制整数
%i 一个整数
%e, %f, %g 一个浮点数
%o 一个八进制数
%s 一个字符串
%x 一个十六进制数
%p 一个指针
%n 一个等于读取字符数量的整数
%u 一个无符号整数
%[] 一个字符集
%% 一个精度符号
0028顺序容器
顺序容器
vector 支持快速访问
list 支持快速插入/删除
deque 双端队列
顺序容器适配器
stack 后进先出
queue 先进先出
priority_queue 有优先级管理的队列
C<T> c;//定义一个C类型的容器,容器的元素类型为T
C<T> c(c2);//创建容器c2的副本c,c和c2必须具有相同的容器类型,并存放相同类型的元素
C<T> c(b,e);//创建c, 其元素是迭代器b和e标示范围内元素的副本//应该不包括e
C<T> c(n,t);//用n个值为t的元素创建容器c,只适用于顺序容器
C<T> c(n);//创建有n个值初始化元素的容器c;
容器内元素
元素类型必须支持赋值运算
元素类型的对象必须可以复制
不支持引用元素
//表9-5
size_type 无符号整型,足以存储此容器类型的最大可能容器的长度
iterator 此容器类型的迭代器类型
const_iterator 元素的只读迭代器类型
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 元素的只读逆序迭代器
difference_type 足够存储两个迭代器差值的有符号整型,可为负数
//??????下面不理解 ?????????????????????????
vaule_type 元素类型
reference 元素的左值类型,是vaule_type &的同义词
const_reference 元素的常量左值类型,等效于const value_type&
//表9-6
c.begin() 返回一个迭代器 指向第一个元素
c.end() 指向最后一个元素下一个位置
c.rbegin() 指向最后一个元素
c.rend() 指向第一个元素的前一个位置
//表9-7
c.push_back(t) 尾部添加元素
c.push_front(t)头部添加元素 list deque
c.insert(p,t) 在p前面添加t元素
c.insert(p,n,t)在p前面添加n个t元素
c.insert(p,b,e)在p前面添加 b到e的元素
//表9-8
c.size() 返回C::size_type类型 返回元素的个数
c.max_size();返回类型同上 ,返回可容纳的最多元素个数
c.empty()返回容器大小是否为0的bool值
c.resize(n)调整容器大小 多余删除 否则才用初始化新元素
c.resize(n,t)调整大小 所有新元素为t
//表9-9
c.back()返回容器最后一个元素的引用
c.front()返回容器第一个元素的引用
c[n]适用于vector deque
c.at[n]返回下标为n的引用适用于vector deque
//表9-10
c.erase(p)删除p指向的元素返回p后面的元素的迭代器
c.erase(b,e)删除b到e的元素,返回e后面的迭代器
c.clear()删除c的所有元素返回void
c.pop_back()删除最后元素 返回void
c.pop_front()删除第一个元素,返回void
//表9-11
c1=c2;删除c1的所有元素,然后将c2的元素复制给c1
c1.swap(c2)交换内容 调用该函数之后c1存放的是c2原来的元素c2中存放的是c1原来的元素 比执行复制操作快
c.assign(b,e) 重新设置c的元素 将迭代器b到e的范围所有元素复制到c中
//????????
capacity
reserve
0029 string
string s;定义一个新的空string的对象
string s(sp);定一个新的string对象,用sp所指向的C风格字符串初始化//以空字符null结束 \0
string s1(s2);初始化为s2的副本
getline(is,s);读取一行字符
s1+s2;把s1、s2串起来产生一个新的string
s1+=s2;把s2拼接在s1后面
string 支持的操作
表9-5列出的typedef 包括迭代器类型
表9-2列出的容器构造函数,但包括只需要一个长度参数的构造函数
表9-7列出的vector容器所提供的添加元素的操作。注意无论vector和string都不支持push_front
表9-8列出的长度操作
表9-9列出的下标和at操作;但string类型不提供该表列出的back或front操作
表9-6列出的begin和end操作
表9-10列出的erase和clear操作;但string类型不提供pop_back或pop_front操作
表9-11列出的赋值操作
string的字符也是连续存储的
string s1;
string s2(5,'a');//s2="aaaaa"
string s3(s2);
string s4(s3.begin(),s3.begin()+s3.size()/2);//s4=="aa";
在c++中string类的构造函数有六种方式
分别是:
1.string(const char * s)
说明:将string对象初始化为s指向NBTS。NBTS为null-byte-temnated string的缩写,表示以空字符结束的字符串------传统的C字符串。
2.string(size_type n,char c)
说明:创建一个包含n个元素的string对象,其中每个元素都被初始化为字符c
3.string(const string & str,string size_type n = npos)
说明:将string对象初始化为对象str中从位置pos开始到结尾的字符,或从位置pos开始的n个字符
4.string()
说明:创建一个的string对象,长度为0
5.string(const char * s, size_type n)
说明:将string对象初始化为s指向的NBTS中的前n字符,即使超过了NBTS端
6.template<clas Iter> string(Iter begin,Iter end)
说明:将string对象初始化为区间[begin,end]内的字符,其中begin和end的行为就像指针,用于指定位置,范围包括begin在内,但不包括end
//和容器共有的操作
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)
//string 类型特有的版本
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.assign(pos,len)
//容器不支持的操作
s.substr(pos,n);
s.substr(pos);
s.substr();
s.append(args);
s.replace(pos,len,args);
s.replace(b,e,args);
append和replace支持
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)
find支持的操作
c,pos
s2,pos
cp,pos
cp,pos,n
find_last()
find_first()
s.compare(s2)
以上具体用法查找C++primer第9章
0030 类
在普通非const成员函数中,this的类型是一个指向类类型的const指针
可以改变this所指向的值,但不能改变this所保存的地址
在const成员函数中,this的类型是一个指向const类类型对象的const指针,
既不能改变this所指向的对象也不能改变this所保存的地址
/?????????????????????????
不能从const成员函数返回指向类对象的普通引用。const成员函数只能返回*this作为一个const引用
/
隐式类类型转换
抑制由构造函数定义的隐式转换
explicit
只能用于构造函数
static类成员
每个static数据成员是与类关联的对象,并不是与该类的对象相关联
static成员函数没有this形参,它可以直接访问所属类的static成员,但不能直接使用非static成员
使用类的static成员的优点
1)static成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突
2)可以实施封装。static成员可以是私有的,而全局对象不可以
3)通过阅读程序容易看出static成员是与特定类关联的,这种可见性可清晰地显示程序员的意图
static成员函数 不能被声明为const 也不能被声明为虚函数
const static数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外定义
因为类的静态数据成员有着单一的存储空间而不管产生多少个对象,所以存储空间必须在一个单独的地方定义
编译器不会分配存储空间。如果一个静态数据成员被声明但没有定义时,链接器会报告一个错误
定义必须出现在类的外部(不允许内联),而且只能定义一次,因此它通常放在一个类的实现文件中
class A{
static int i;//这是声明
public:
//...
};
int A::i=1;//这才是定义
在这里,类和作用域运算符用于指定了A::i.
有些人对A::i是私有的这点感到疑惑不解,可是在这里似乎在公开地直接对它处理
这不是破坏了类结构的保护性吗? 有两个原因可以保证它绝对的安全
第一、这些变量的初始化唯一合法的地方是在定义时。事实上,如果静态数据成员
是一个带构造函数的对象时,可以调用构造函数来代替"="操作符;
第二、一旦这些数据被定义了,最终的用户就不能再定义它,否则连接器会报告错误
而且这个类的创建者被迫产生这个定义,否则这些代码在测试时无法连接
这就保证了定义只才出现一次并且它是由类的制造者来控制
其它 数组等见编程思想P239页
0031标准库bitset类型
#include<bitset>
using std::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是s中从位置pos开始的n个位的副本
其他具体用法见C++primer第3章
0032操作符优先级别见C++primer147页
0033刷新缓冲区P248页
0034 定义智能指针类//没懂 421页
0035标准IO库//未看
0036泛型算法
find(s.begin(),s.end(),sum)使用迭代器 或者指针标记范围查找元素
#include<algorithm>泛型算法头文件
#include<numeric>算术算法 头文件
accumulate(s.begin(),s.end(),sum)累加算法 sum是初始值
find_first_of()
fill() 写入 操作已经存在的元素
fill_n()
back_inserter插入迭代器
copy()
replace()
replace_copy()
unique()
stable_sort()
sort()
count_if()
0037插入迭代器 iostream 迭代器 反向迭代器 const迭代器
back_inserter
front_inserter
inserter
0038操作符重载
操作符重载[]
C++编程思想 第12章 一元 二元操作符例子
0039 函数对象 P449///?????????????????????????
0040 指针和引用
C++中的引用与指针的区别
指向不同类型的指针的区别在于指针类型可以知道编译器解释某个特定地址(指针指向的地址)中的内存内容及大小,而void*指针则只表示一个内存地址,编译器不能通过该指针所指向对象的类型和大小,因此想要通过void*指针操作对象必须进行类型转化。
★ 相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;
引用是某块内存的别名。
★ 区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
引用“从一而终” ^_^
4. 引用没有 const,指针有 const,const 的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,但是当引用作为类成员名称时,其占用空间与指针相同4个字节(没找到标准的规定)。
7. 指针和引用的自增(++)运算意义不一样;
1. 引用在语言内部用指针实现(如何实现?)。
2. 对一般应用而言,把引用理解为指针,不会犯严重语义错误。引用是操作受限了的指针(仅容许取内容操作)。
引用是C++中的概念,初学者容易把引用和指针混淆一起。一下程序中,n 是m 的一个引用(reference),m 是被引用物(referent)。
int m;
int &n = m;
n 相当于m 的别名(绰号),对n 的任何操作就是对m 的操作。例如有人名叫王小毛,他的绰号是“三毛”。说“三毛”怎么怎么的,其实就是对王小毛说三道四。所以n 既不是m 的拷贝,也不是指向m 的指针,其实n 就是m 它自己。
引用的一些规则如下:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有NULL 引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
以下示例程序中,k 被初始化为i 的引用。语句k = j 并不能将k 修改成为j 的引用,只是把k 的值改变成为6.由于k 是i 的引用,所以i 的值也变成了6.
int i = 5;
int j = 6;
int &k = i;
k = j; // k 和i 的值都变成了6;
上面的程序看起来象在玩文字游戏,没有体现出引用的价值。引用的主要功能是传递函数的参数和返回值。C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。
1)以下是“值传递”的示例程序。由于Func1 函数体内的x 是外部变量n 的一份拷贝,改变x 的值不会影响n, 所以n 的值仍然是0.
void Func1(int x)
{
x = x + 10;
}
int n = 0;
Func1(n);
cout << “n = ” << n << endl;// n = 0
2)以下是“指针传递”的示例程序。由于Func2 函数体内的x 是指向外部变量n 的指针,改变该指针的内容将导致n 的值改变,所以n 的值成为10.
void Func2(int *x)
{
(* x) = (* x) + 10;
}
⋯
int n = 0;
Func2(&n);
cout << “n = ” << n << endl; // n = 10
3)以下是“引用传递”的示例程序。由于Func3 函数体内的x 是外部变量n 的引用,x和n 是同一个东西,改变x 等于改变n,所以n 的值成为10.
void Func3(int &x)
{
x = x + 10;
}
⋯
int n = 0;
Func3(n);
cout << “n = ” << n << endl; // n = 10
对比上述三个示例程序,会发现“引用传递”的性质象“指针传递”,而书写方式象“值传递”。实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用”这东西?
答案是“用适当的工具做恰如其分的工作”。
指针能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。
就象一把刀,它可以用来砍树、裁纸、修指甲、理发等等,谁敢这样用?
如果的确只需要借用一下某个对象的“别名”,那么就用“引用”,而不要用“指针”,以免发生意外。比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如果把取公章的钥匙交给他,那么他就获得了不该有的权利。
——————————
摘自「高质量c++编程」
指针与引用,在More Effective C++ 的条款一有详细讲述,我给你转过来
条款一:指针与引用的区别
指针与引用看上去完全不同(指针用操作符‘*’和‘->’,引用使用操作符‘。’),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢?
首先,要认识到在任何情况下都不能用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。
“但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?”
char *pc = 0; // 设置指针为空值
char& rc = *pc;// 让引用指向空值
这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生),应该躲开写出这样代码的人除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。
因为引用肯定会指向一个对象,在C++里,引用应被初始化。
string& rs; // 错误,引用必须被初始化
string s("xyzzy");
string& rs = s; // 正确,rs指向s
指针没有这样的限制。
string *ps; // 未初始化的指针
// 合法但危险
不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。
void printDouble(const double& rd)
{
cout << rd; // 不需要测试rd,它
} // 肯定指向一个double值
相反,指针则应该总是被测试,防止其为空:
void printDouble(const double *pd)
{
if (pd)
{ // 检查是否为NULL
cout << *pd;
}
}
指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。
string s1("Nancy");
string s2("Clancy");
string& rs = s1; // rs 引用 s1
string *ps = &s1; // ps 指向 s1
rs = s2; // rs 仍旧引用s1,
// 但是 s1的值现在是
// "Clancy"
ps = &s2; // ps 现在指向 s2;
// s1 没有改变
总的来说,在以下情况下你应该使用指针,
一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),
二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。
还有一种情况,就是当你重载某个操作符时,你应该使用引用。
最普通的例子是操作符[].这个操作符典型的用法是返回一个目标对象,其能被赋值。
vector<int> v(10); // 建立整形向量(vector),大小为10;
// 向量是一个在标准C库中的一个模板(见条款35)
v[5] = 10; // 这个被赋值的目标对象就是操作符[]返回的值
如果操作符[]返回一个指针,那么后一个语句就得这样写:
*v[5] = 10;
但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这有一个有趣的例外,参见条款30)
当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针假设你有
void func(int* p, int&r);
int a = 1;
int b = 1;
func(&a,b);
指针本身的值(地址值)是以pass by value进行的,你能改变地址值,但这并不会改变指针所指向的变量的值,
p = someotherpointer; //a is still 1
但能用指针来改变指针所指向的变量的值,
*p = 123131; // a now is 123131
但引用本身是以pass by reference进行的,改变其值即改变引用所对应的变量的值
r = 1231; // b now is 1231
尽可能使用引用,不得已时使用指针。
当你不需要“重新指向”时,引用一般优先于指针被选用。这通常意味着引用用于类的公有接口时更有用。引用出现的典型场合是对象的表面,而指针用于对象内部。
上述的例外情况是函数的参数或返回值需要一个“临界”的引用时。这时通常最好返回/获取一个指针,并使用 NULL 指针来完成这个特殊的使命。(引用应该总是对象的别名,而不是被解除引用的 NULL 指针)。
注意:由于在调用者的代码处,无法提供清晰的的引用语义,所以传统的 C 程序员有时并不喜欢引用。然而,当有了一些 C++ 经验后,你会很快认识到这是信息隐藏的一种形式,它是有益的而不是有害的。就如同,程序员应该针对要解决的问题写代码,而不是机器本身。
1、引用在定义时就必须初始化(和另一个量相关联),而指针定义时可以不初始化;
2、引用就是一个别名,本身不占用内存地址,而指针是一个独立的量,本身要占用内存地址,在32位系统中占四个字节;
3、引用的实质仍然是一个指针,它和指针常量的行为相似——本身不可修改,只可修改指向内容。
0041运算符重载//编程思想第12章
使用全局重载运算符而不用成员运算符的最便利的原因之一是在全局版本中的自动类型转换可以针对左右任一操作数
而成员函数版本必须保证左侧操作数已处于正确的形式
一元运算符
class Integer{
long i;
Integer* This(){
return this;
}
public:
Integer(long ll=0):i(ll){} //构造函数后面 有分号也通过???DEV编译通过
friend const Integer& operator+(const Integer& a ){
return a;
}//这个为啥要用友元 ?
friend const Integer operator-(const Integer& a ){
return Integer(-a.i);
}
friend const Integer operator~(const Integer& a ){
return Integer(~a.i);
}
friend Integer* operator&(Integer& a ){
return a.This();
}
friend int operator!(const Integer& a ){
return !a.i;
}//这个返回值怎么不是bool型?
friend const Integer& operator++(Integer& a ){
a.i++;//++a.i;
return a;
} //++前缀
friend const Integer operator++(Integer& a,int ){
Integer before(a.i);
a.i++;//++a.i;
return before;
}//++后缀
friend const Integer& operator--(Integer& a ){
a.i--;//--a.i;
return a;
} //--前缀
friend const Integer operator--(Integer& a ,int){
Integer before(a.i);
a.i--;//--a.i;
return before;
}//--后缀
}; //没有使用this指针 需要参数
class Byte{
unsigned char b;
public:
Byte(unsigned char bb=0):b(bb){}
const Byte& operator+()const{
return *this;
} //怎么是const成员函数????
const Byte operator-()const{
return Byte(-b);
} //??
const Byte operator~()const{
return Byte(~b);
}//??
Byte operator!()const{
return Byte(!b);
} //??
Byte* operator&(){
return this;
}
const Byte& operator++(){
b++;
return *this;
}
const Byte operator++(int){
Byte before(b);
b++;
return before;
}
const Byte operator--(){
--b;
return *this;
}
const Byte operator--(int){
Byte before(b);
--b;
return before;
}
}; //使用this指针 所以没参数 不需要友元
二元运算符
class Integer{
long i;
public:
Integer(long ll=0):i(ll){}
friend const Integer& operator+(const Integer& left,const Integer& right)
{
return Integer(left.i+right.i);
}
friend const Integer& operator-(const Integer& left,const Integer& right)
{
return Integer(left.i-right.i);
}
friend const Integer& operator*(const Integer& left,const Integer& right)
{
return Integer(left.i*right.i);
}
//#include<./require.h>
friend const Integer& operator/(const Integer& left,const Integer& right)
{
require(right.i!=0,"divide by zero");
return Integer(left.i/right.i);
}
friend const Integer& operator%(const Integer& left,const Integer& right)
{
require(right.i!=0,"modulo by zero");
return Integer(left.i%right.i);
}
friend const Integer& operator^(const Integer& left,const Integer& right)
{
return Integer(left.i^right.i);
}
friend const Integer& operator&(const Integer& left,const Integer& right)
{
return Integer(left.i&right.i);
}
friend const Integer& operator|(const Integer& left,const Integer& right)
{
return Integer(left.i|right.i);
}
friend const Integer& operator<<(const Integer& left,const Integer& right)
{
return Integer(left.i<<right.i);
}
friend const Integer& operator>>(const Integer& left,const Integer& right)
{
return Integer(left.i>>right.i);
}
/
friend Integer& operator+=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身赋值*/
}
left.i+=right.i;
return left;
}
friend Integer& operator-=(Integer& left,const Integer& rihgt)
{
if(&left==&right){
/*self-assignment身赋值*/
}
left.i-=right.i;
return left;
}
friend Integer& operator*=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身赋值*/
}
left.i*=right.i;
return left;
}
friend Integer& operator/=(Integer& left,const Integer& right)
{
require(right.i!=0,"divide by zero");
if(&left==&right){
/*self-assignment身赋值*/
}
left.i/=right.i;
return left;
}
friend Integer& operator%=(Integer& left,const Integer& right)
{
require(right.i!=0,"modulo by zero");
if(&left==&right){
/*self-assignment身赋值*/
}
left.i%=right.i;
return left;
}
friend Integer& operator^=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身赋值*/
}
left.i^=right.i;
return left;
}
friend Integer& operator&=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身赋值*/
}
left.i&=right.i;
return left;
}
friend Integer& operator|=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身赋值*/
}
left.i|=right.i;
return left;
}
friend Integer& operator>>=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身赋值*/
}
left.i>>=right.i;
return left;
}
friend Integer& operator<<=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身赋值*/
}
left.i<<=right.i;
return left;
}
friend int operator==(const Integer& left,const Integer& right)
{
}
friend int operator!=(const Integer& left,const Integer& right)
{
return left.i==right.i;
}
friend int operator<(const Integer& left,const Integer& right)
{
return left.i<right.i;
}
friend int operator>(const Integer& left,const Integer& right)
{
return left.i>right.i;
}
friend int operator<=(const Integer& left,const Integer& right)
{
return left.i<=right.i;
}
friend int operator>=(const Integer& left,const Integer& right)
{
return left.i>=right.i;
}
friend int operator&&(const Integer& left,const Integer& right)
{
return left.i&&right.i;
}
friend int operator||(const Integer& left,const Integer& right)
{
return left.i||right.i;
}
};
class Byte{
unsigned char b;
public:
Byte(unsigned char bb=0):b(bb){}
const Byte operator+(const Byte& right) const
{
return Byte(b+right.b);
}
const Byte operator-(const Byte& right) const
{
return Byte(b+right.b);
}
const Byte operator*(const Byte& right) const
{
return Byte(b*right.b);
}
const Byte operator/(const Byte& right) const
{
require(right.b!=0,"divide by zero");
return Byte(b/right.b);
}
const Byte operator%(const Byte& right) const
{
require(right.b!=0,"modulo by zero");
return Byte(b%right.b);
}
const Byte operator^(const Byte& right) const
{
return Byte(b^right.b);
}
const Byte operator&(const Byte& right) const
{
return Byte(b&right.b);
}
const Byte operator|(const Byte& right) const
{
return Byte(b|right.b);
}
const Byte operator<<(const Byte& right) const
{
return Byte(b<<right.b);
}
const Byte operator>>(const Byte& right) const
{
return Byte(b*>>right.b);
}
Byte& operator=(const Byte& right)
{
if(this==&right) return *this;
b=right.b;
return *this;
}
Byte& operator+=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b+=right.b;
return *this;
}
Byte& operator-=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b-=right.b;
return *this;
}
Byte& operator*=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b*=right.b;
return *this;
}
Byte& operator/=(const Byte& right)
{
require(right.b!=0,"divide by zero");
if(this==&right){/*self-assignment*/}
b/=right.b;
return *this;
}
Byte& operator%=(const Byte& right)
{
require(right.b!=0,"modulo by zero");
if(this==&right){/*self-assignment*/}
b%=right.b;
return *this;
}
Byte& operator^=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b^=right.b;
return *this;
}
Byte& operator&=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b&=right.b;
return *this;
}
Byte& operator|=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b|=right.b;
return *this;
}
Byte& operator>>=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b>>=right.b;
return *this;
}
Byte& operator<<=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b<<=right.b;
return *this;
}
int operator==(const Byte& right) const
{
return b==right.b;
}
int operator!=(const Byte& right) const
{
return b!=right.b;
}
int operator<(const Byte& right) const
{
return b<right.b;
}
int operator>(const Byte& right) const
{
return b>right.b;
}
int operator<=(const Byte& right) const
{
return b<=right.b;
}
int operator>=(const Byte& right) const
{
return b>=right.b;
}
int operator&&(const Byte& right) const
{
return b&&right.b;
}
int operator||(const Byte& right) const
{
return b||right.b;
}
};
#ifndef REQUIRE_H
#define REQUIRE_H
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
inline void require(bool requirement,
const std::string& msg = "Requirement failed"){
using namespace std;
if (!requirement) {
fputs(msg.c_str(), stderr);
fputs("\n", stderr);
exit(1);
}
}
inline void requireArgs(int argc, int args,
const std::string& msg =
"Must use %d arguments") {
using namespace std;
if (argc != args + 1) {
fprintf(stderr, msg.c_str(), args);
fputs("\n", stderr);
exit(1);
}
}
inline void requireMinArgs(int argc, int minArgs,
const std::string& msg =
"Must use at least %d arguments") {
using namespace std;
if(argc < minArgs + 1) {
fprintf(stderr, msg.c_str(), minArgs);
fputs("\n", stderr);
exit(1);
}
}
inline void assure(std::ifstream& in,
const std::string& filename = "") {
using namespace std;
if(!in) {
fprintf(stderr, "Could not open file %s\n",
filename.c_str());
exit(1);
}
}
inline void assure(std::ofstream& out,
const std::string& filename = "") {
using namespace std;
if(!out) {
fprintf(stderr, "Could not open file %s\n",
filename.c_str());
exit(1);
}
}
#endif
class A{
float a;
public:
A(float aa=0.0f):a(aa){ }
//下面是重载一个float()转换类型 把A类型转换为float类型;
operator float()const{
return a;
}
};
0042 一个类重载new delete
见编程思想P328页
0043 一个数组类重载new delete
见编程思想P320页
0044定位new delete
见编程思想P333页
0045
int(47)
int *p=new int(44)
// /
0047未看
《C++编程思想》 异常处理、防御性编程、输入输出流、通用算法、通用容器 模板
高级篇8 9 10 11章 模板
0048引用计数
0049智能指针
0050模板
懒惰初始化
#include "../require.h"
#include <iostream>
using namespace std;
template<class T,int size=100>
class Array{
T array[size];
public:
T& operator[](int index){
require(index>=0&&index<size,"Index out of range");
return array[index];
}
int length()const { return size;}
};
class Number{
float f;
public:
Number(float ff=0.0f):f(ff){ }
Number& operator=(const Number& n){
f=n.f;
return *this;
}
operator float()const{ return f;}
friend ostream& operator<<(ostream& os,const Number& x){
return os<<x.f;
}
};
template<class T,int size=20>
class Holder{
Array<T,size>* np;
public:
Holder():np(0){ }
T& operator[](int i){
require(0<=i&&i<size);
if(!np) np=new Array<T ,size>;
return np->operator[](i);
}
int length() const{ return size; }
~Holder(){ delete np; }
};
Array是被检查的对象数组,并且防止下标越界。类Holder 很像Array ,只是它有一个指向Array的指针,
而不是指向类型Array的嵌入对象。该指针在构造函数中不被初始化,而是推迟到第一次访问时。这
称为:懒惰初始化。如果创造大量的对象,但不访问每一个对象,为了节省存储,可以用懒惰初始化技术。
0051 编程思想 14章
。派生类成员函数重载基类成员函数 基类成员函数将会被隐藏
。构造函数 析构函数 operator= 不能被派生类所继承
。继承和静态成员函数
静态成员函数与非静态成员函数的共同点:
1 它们均可被继承到派生类中
2 如果我们重新定义了一个静态成员,所有在基类中的其他重载函数会被隐藏
3 如果我们改变了基类中一个函数的特征,所有使用该函数名字的基类版本都将会被隐藏。
。确定应当用组合还是用继承,最清楚的方法之一是询问是否需要从新类型向上类型转换。
0052编程思想 15章
C++如何实现晚捆绑 P369页
293页 301页
虚函数 三种访问 对象 指针 引用
#include<iostream>
using namespace std;
class father{
public:
virtual void run() const {
cout<<"父亲可以跑万米"<<endl;
}
};
class son :public father{
public:
void run() const{
cout<<"儿子可以跑十万米"<<endl;
}
};
class daughter:public father{
public:
void run()const{
cout<<"女儿可以跑五万米"<<endl;
}
};
void one(father);
void two(father*);
void three(father&);
int main()
{
father *p1=0;
father *p2=0;
father *p3=0;
p1=new son;
one(*p1);
p2=new daughter;
two(p2);
p3=new father;
three(*p3);
delete p1,p2,p3;
p1=0,p2=0,p3=0;
return 0;
}
void one(father one){
one.run();
}
void two(father* two){
two->run();
}
void three(father& three){
three.run();
}
//输出
//父亲
//女儿
//父亲
#include<iostream>
using namespace std;
class A{
public:
virtual void print(){
cout<<"A"<<endl;
}
};
class B:public A{
public:
void print(){
cout<<"B"<<endl;
}
};
class c:public A{
public:
void print(){
cout<<"C"<<endl;
}
};
int mai()
{
A a;
B b;
C c;
A* p=&a;
B* P1=&b;
C* p2=&c;
p->print();
p1->print();
p2->print();
return 0;
}
纯虚函数
virtual void f()=0;
当继承一个抽象类时,必须实现所有的纯虚函数,否则继承出的类也将是一个抽象的类。
纯虚函数禁止对抽象类的函数以传值方式调用 即函数参数使用指针或者引用
在基类中,对纯虚函数提供定义是可能的 但是不允许创建对象 如果要创建对象必须在派生类里定义
#include<iostream>
using namespace std;
class Pet{
public:
virtual void speak() const=0;
virtual void eat() const=0;
};
void Pet::eat() const{
cout<<"Pet::eat()"<<endl;
}
void Pet::speak() const{
cout<<"Pet::speak()"<<endl;
}
class Dog:public Pet{
void speak() const { Pet::speak();}
void eat() const { Pet::eat();}
};
int main()
{
Dog simba;
simba.eat();
simba.speak();
}
运行时类型辨认(Run-Time Type Identification,RTTI)
不能在重新定义过程中修改虚函数的返回类型。但是返回一个
指向基类的指针或引用 则该函数的重新定义版本将会从
基类返回的内容中返回一个指向派生类的指针或者引用
编程思想P383页
虚机制在构造函数里不发生作
构造函数
构造函数
先看看构造函数的调用顺序规则,只要我们在平时编程的时候遵守这种约定,
任何关于构造函数的调用问题都能解决;构造函数的调用顺序总是如下:
1.基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,
而不是它们在成员初始化表中的顺序。
2.成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,
而不是它们出现在成员初始化表中的顺序。
3.派生类构造函数。
析构函数
析构函数的调用顺序与构造函数的调用顺序正好相反,将上面3个点反过来用就可以了,首先调用派生类的析构函数;其次再调用成员类对象的析构函数;最后调用基类的析构函数。
析构函数在下边3种情况时被调用:
1.对象生命周期结束,被销毁时(一般类成员的指针变量与引用都i不自动调用析构函数);
2.delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
3.对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。
下面用例子来说说构造函数的的调用顺序:
#include "stdafx.h"
#include "iostream"
using namespace std;
class Base
{
public:
Base(){ std::cout<<"Base::Base()"<<std::endl; }
~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
};
class Base1:public Base
{
public:
Base1(){ std::cout<<"Base1::Base1()"<<std::endl; }
~Base1(){ std::cout<<"Base1::~Base1()"<<std::endl; }
};
class Derive
{
public:
Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
};
class Derive1:public Base1
{
private:
Derive m_derive;
public:
Derive1(){ std::cout<<"Derive1::Derive1()"<<std::endl; }
~Derive1(){ std::cout<<"Derive1::~Derive1()"<<std::endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Derive1 derive;
return 0;
}
运行结果是:
Base::Base()
Base1::Base1()
Derive::Derive()
Derive1::Derive1()
Derive1::~Derive1()
Derive::~Derive()
Base1::~Base1()
Base::~Base()
那么根据上面的输出结果,笔者稍微进行一下讲解,构造函数的调用顺序是;
首先,如果存在基类,那么先调用基类的构造函数,如果基类的构造函数中仍然存在基类,
那么程序会继续进行向上查找,直到找到它最早的基类进行初始化;如上例中类Derive1,
继承于类Base与Base1;其次,如果所调用的类中定义的时候存在着对象被声明,
那么在基类的构造函数调用完成以后,再调用对象的构造函数,
如上例中在类Derive1中声明的对象Derive m_derive;
最后,将调用派生类的构造函数,如上例最后调用的是Derive1类的构造函数。
virtual析构函数
下面来说一说为多态基类声明virtual析构函数:
在C++中,构造函数不能声时为虚函数,这是因为编译器在构造对象时,
必须知道确切类型,才能正确的生成对象,因此,不允许使用动态束定;
其次,在构造函数执行之前,对象并不存在,无法使用指向此此对象的指针来调用构造函数,
然而,析构函数是可以声明为虚函数;C++明白指出,当derived class对象经由一个base class指针被删除,
而该base class带着一个non-virtual析构函数,
其结果未有定义---实际执行时通常发生的是对象的derived成分没被销毁掉。
看下面的例子:
class Base
{
public:
Base(){ std::cout<<"Base::Base()"<<std::endl; }
~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
};
class Derive:public Base
{
public:
Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Base* pBase = new Derive();
//这种base classed的设计目的是为了用来"通过base class接口处理derived class对象"
delete pBase;
return 0;
}
输出的结果是:
Base::Base()
Derive::Derive()
Base::~Base()
从上面的输出结果可以看出,析构函数的调用结果是存在问题的,
也就是说析构函数只作了局部销毁工作,这可能形成资源泄漏败坏数据结构等问题;
那么解决此问题的方法很简单,给base class一个virtual析构函数;
class Base
{
public:
Base(){ std::cout<<"Base::Base()"<<std::endl; }
virtual ~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
};
class Derive:public Base
{
public:
Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Base* pBase = new Derive();
delete pBase;
return 0;
}
输出结果是:
Base::Base()
Derive::Derive()
Derive::~Derive()
Base::~Base()
可能上面的输出结果正是我们所希望的吧,呵呵!由此还可以看出虚函数还是多态的基础,
在C++中没有虚函数就无法实现多态特性;因为不声明为虚函数就不能实现“动态联编”,
所以也就不能实现多态啦!
0053关联容器
map 关联数组,元素通过键来存储和读取
set 大小可变的集合,支持通过键实现的快速读取
multimap 支持同一个键多次出现的map
multiset 支持同一个键多次出现的set
pair 类型 #include<utility>
一种模板类型
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的(公有)数据成员
pair 类型的使用相等繁琐,因此,如果需要定义多个相同的pair类型对象,可以用typedef简化声明
typedef pair<string,string> Autor;
Autor proust("Marcel","Proust");
pair类型支持的操作见 《C++Primer》308页
map 类型 #include<map>
map 是键-值对的集合,map类型通常可理解为关联数组,可使用键作为下标来获取一个值,正如内置数组类型一样。
而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取
map<k,v> m; 创建一个名为m的空map对象,其键和值的类型分别为k和v。
map<k,v> m(m2); 创建m2的副本m,m与m2必须有相同的键类型和值类型。
map<k,v> m(b,e); 创建map类型的对象m,存储迭代器b和e标记的范围内所有元素的副本。
元素的类型必须能转换为pair<const k,v>.
在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。默认情况下,标准库使用键类型定义
的<操作符来实现键的比较。
所用的比较函数必须在键类型上定义严格的弱排序见 《C++Primer》309页
map容器额外定义的类型别名
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类型//???
value_type是pair类型 它的值成员可以修改,但键成员不能修改。
map迭代器进行解引用将产生pair类型的对象
给map添加元素
使用insert成员实现或者先用下标操作符获取元素,然后给获取的元素赋值
map<string,int> word_count;
word_count["Anna"]=1;
1)在word_count中查找键为Anna的元素,没有找到
2)将一个新的键-值对插入到word_count中。它的键是const string类型对象,保存Anna。
而它的值则将
3)将这个新的键-值对插入到word_count中
4)读取新的插入元素,并将它的值赋为1.
注:使用下标访问不存在的元素将导致map容器中添加一个新的元素,它的键即为该下标值。
如同其他下标操作符一样,map的下标也使用索引(其实就是键)来获取该键所关联的值
如果该键已在容器中,则map的下标运算与vector的下标运算行为相同:返回该键所关联的值
只有在所查找的键不存在时,map容器才为该键创建一个新元素,并将它插入到此map对象中。
此时,所关联的值采用值初始化:类类型的元素用默认构造函数初始化,而内置裂隙的元素则
初始化为0
map迭代器返回value_type类型的值-包含const key_type和mapped_type类型成员的pair
下标操作符则返回一个mapped_type类型的值
map<string ,int > word_count;
string word;
while(cin>>word)
++word_count[word];
创建一个map对象用于记录每个单词出现的次数
下标操作符是先初始化后赋值
m.insert(e) e是一个用在m上的value_type类型的值,如果(e.first)不在m中,则插入一个值为
e.second的新元素;如果该键在m中已存在,则保持m不变。该函数返回一个pair类型
包含指向键为e.first的元素的map迭代器 ,以及一个bool类型的对象表示是否插入了该元素
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_cout.insert(map<string,int>::value_type("Anna",1));
word_cout.insert(make_pair("Anna",1));
typedef map<string,int>::value_type ValType;
word_cout.insert(ValType("Anna",1));
带一个键-值pair形参的insert版本将返回一个值:包含一个迭代器和一个bool值的pair对象,其中迭代器指向map中具有
相应键的元素,而bool值则表示是否插入了该元素,如果该键已在容器中则其关联的值保持不变,返回的bool值为false
如果该键不在容器中,则插入新元素,且bool值为true
m.count(k) 返回m中K的出现次数 返回值只能是0或者1 键是唯一的
m.find(k) 如果m容器中存在按k索引的元素,则返回指向该元素的迭代器,如果不存在,则返回超出抹点的迭代器
m.erase(k) 删除m中键为k的元素,返回size_type类型的值,表示删除元素的个数
m.erase(p) 从m中删除迭代器p所指向的元素,p必须指向m中确实存在的元素,而且不能等于m.end()。返回void类型
m.erase(b,e) 从m中删除段范围内的元素,该范围由迭代器b和e标记,b和e必须标记m中的一个段有效范围:
即b和e都必须指向m中的元素或者最后一个元素的下一位置。而且b和e要么相等(此时删除范围为空)
要么b所指向的元素必须出现在e所指向元素之前 返回void类型
0054异常处理
关键字throw 将导致一系列事情发生
首先,它将创建程序所抛出的对象的一个拷贝
然后,实际上,包含throw表达式的函数返回了这个对象,即使该函数原先并未设计为返回这种类型的对象类型。
一种简单的考虑异常处理的方式是将其看做是一种交错返回机制
当然可以通过抛出的一个异常而离开正常的作用域。在任何一宗情况下都会返回一个值,并且退出函数或作用域
异常发生前创建的局部对象被销毁。这种对局部对象的自动清理通常被称为“栈反解(stack unwinding)”
class Rainbow{
public:
Rainbow(){
cout<<"调用构造函数"<<endl;
}
~Rainbow(){
cout<<"调用析构函数"<<endl;
}
};
void oz(){
Rainbow rb;
for(int i=0;i<3;++i)
cout<<"这是函数调用"<<endl;
throw 47;
}
int main()
{
try{
cout<<"发生异常"<<endl;
oz();
}catch(int){
cout<<"异常处理"<<endl;
}
return 0;
}
//结果
//调用构造函数
//这是函数调用
//这是函数调用
//这是函数调用
//调用析构函数
//异常处理
enum class Colors { Black, Blue, White, END_OF_LIST };
// Special behavior for ++Colors
Colors& operator++( Colors &c ) {
c = static_cast<Colors>( static_cast<int>(c) + 1 );
if ( c == Colors::END_OF_LIST )
c = Colors::Black;
return c;
}
/*
const float a=3.f;
int n=8;
(float&)a=6.f;
float ff=(float&)n;
*/
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
template <typename T> class print
{
public:
void operator()(const T& elem)//没有被调用
{
cout<<elem<<" ";
}
};
int main()
{
int a[6]={0,1,2,3,4,5};
vector<int> i(a,a+6);
for_each(i.begin(),i.end(),print<int>());
return 0;
}
/*
死循环用for(;;)比while(1)好 这两者在编译器优化后可能是一样的 ,但没优化的优化的DEBUG模式下 前
者比后者性能高
*/
#include<stdio.h>
#include<windows.h>
void main()
{
float *fp[3];
float a=0.1,c=0.3;
fp[0]=&a;
fp[1]=(float*)malloc(sizeof(float));
*fp[1]=0.2;
*(fp+2)=&c;
printf("%f\n",*fp[0]);
printf("%f\n",*fp[1]);
printf("%f\n",*fp[2]);
free(fp[1]);
system("pause");
}
/*
* 主要测试该头文件<stdlib.h>中的如下函数(参数中的const表示保护源指针指向的字符串不能修改):
* double atof(const char *nptr)
* atof()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换
* 而再遇到非数字或字符串结束时('\0')才结束转换,并将结果返回。参数nptr字符串可包含正负号
* 小数点或E(e)来表示指数部分,如123.456或123e-2。
* 与strtod(nptr,(char **)NULL)结果相同。
* int atoi(const char *nptr)
* atoi()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,
* 而再遇到非数字或字符串结束时('\0')才结束转换,并将结果返回。
* 与使用strtol(nptr,(char**)NULL,10)结果相同。
* long atol(const char *nptr)
* atol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,
* 而再遇到非数字或字符串结束时('\0')才结束转换,并将结果返回。
* atol()与使用strtol(nptr,(char**)NULL,10)结果相同。
* char *gcvt(double number,size_t ndigits,char *buf)
* gcvt()用来将参数number转换成ASCII码字符串,参数ndigits表示显示的位数。
* gcvt()与ecvt()和fcvt()不同的地方在于,gcvt()所转换后的字符串包含小数点或正负符号。
* 若转换成功,转换后的字符串会放在参数buf指针所指的空间。返回一字符串指针,此地址即为buf指针。
* double strtod(const char *nptr ,char **endptr,int base)
* strtod()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,
* 到出现非数字或字符串结束时('\0')才结束转换,并将结果返回。若endptr不为NULL,
* 则会将遇到不合条件而终止的nptr中的字符指针由endptr传回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分。如123.456或123e-2。
* long int strtol(const char *nptr,char **endptr,int base)
* strtol()会将参数nptr字符串根据参数base来转换成长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,
* 若base值为16则采用16进制等。当base值为0时则是采用10进制做转换,但遇到如'0x'前置字符则会使用16进制做转换。一开始strtol()会扫描参数nptr字符串,
* 跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时('\0')结束转换,并将结果返回。若参数endptr不为NULL,
* 则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。
*
* unsigned long int strtoul(const char *nptr,char **endptr,int base)
* strtoul()会将参数nptr字符串根据参数base来转换成无符号的长整型数。参数base范围从2至36,或0。
* 参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制数等。
* 当base值为0时则是采用10进制做转换,但遇到如'0x'前置字符则会使用16进制做转换。一开始strtoul()会扫描参数nptr字符串,
* 跳过前面的空格字符串,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时('\0')结束转换,并将结果返回。
* 若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。
* 返回转换后的长整型数,否则返回ERANGE并将错误代码存入errno中。ERANGE指定的转换字符串超出合法范围。
*
* int toascii(int c)
* toascii()会将参数c转换成7位的unsigned char值,第八位则会被清除,此字符即会被转成ASCII码字符。
*
* int tolower(int c)
* 若参数c为大写字母则将该对应的小写字母返回。
*
* int toupper(int c)
* 若参数c为小写字母则将该对映的大写字母返回。
*
*/
055 运算符转换//???不理解
C++编程思想 307页到311页
class Three{
int i;
public:
Three(int ii=0,int =0):i(ii){
}
};
class Four{
int x;
public:
Four(int xx):x(xx){
}
operator Three()const { return Three(x);}
};
void g(Three) {
}
int main()
{
Four FOUR(1);
g(four);
g(1);
}
可以创建一个成员函数,这个函数通过在关键字operator 后面跟随想要转换到的类型方法,当前类型转换
为希望的类型,这种形式重载很是独特,因为没有指定一个返回类型-返回类型就是正在重载的运算符的名字
056
位运算
位与运算 a&b 同时为1 则 1 其他全是0
位或运算 a|b 同时为0 则 0 其他全是1
位异或运算a^b 相同为0 不同为1
57模板
P583页
模板类型的模板参数
template<class T,template<class> class Seq> class A{};
template<class T,template<class U> class Seq> class A{};
typename 标示是类型而不是变量
template<class T> class Outer{
public:
template<class R> class Inner{
public:
void f();
};
};
template<class T> template<class R>
void Outer<T>::Inner<R>::f(){
}
P598
template<class Seq,class T,class R>
void apply(Seq& sq,R (T::*f)()const){
typename Seq::iterator it = sq.begin();
while(it!=sq.end())
((*it++)->*f)();
}
template<class Seq,class T,class R,class A>
void apply(Seq& sq,R(T::*f)(A)const, A a){
typename Seq::iterator it = sq.begin();
while(it != sq.end())
((*it++)->*f)(a);
}
template<class Seq,class T,class R,class A1,class A2>
void apply(Seq& sq, R(T::*f)(A1,A2)const,A1 a,A2 a2)
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
((*it++)->*f)(a1,a2);
}
template<class Seq,class T,class R>
void apply(Seq& sq,R(T::*f)())
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
((*it++)->*f)();
}
template<class Seq,class T,class R,class A>
void apply(Seq& sq,R(T::*f)(A),A a)
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
((*it++)->*f)(a);
}
template<class Seq,class T,class R,class A1,class A2>
void apply(Seq& sq,R(T::*f)(A1,A2),A1 a1,A2 a2)
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
((*it++)->*f)(a1,a2);
}
P601
模板特化
一个模板定义就是一个实体一般化的过程,以为它在一般条件下描述了某个范围内的一族函数或类
给定模板参数时,这些模板参数决定了这一族函数或类的许多可能的实例中的一个独一无二的实例
因此这样的结果就被称做模板的一个特化。
template<class T> const T& min(const T& a,const T& b){
return (a>b)?a:b;
}
template<>
const char* const& min<const char*>(const char* const& a,
const char* const& b){
return (strcmp(a,b)<0)?a:b;
}
template<> 告诉编译器接下来的是一个模板的特化。
模板特化的类型必须出现在函数名后紧跟的尖括号中,就像通常在一个明确指定参数类型的函数调用中一样。
用const char* 代替T
如果一旦在最初模板定义时使用const T 特化时必须用const char* const 代替const T
没看懂