自己总结的一些小知识点,希望能够对大家有帮助。
bool:1
char:1wchar_t:2
char16_t:2
char32_t:4
short:2
int:4
long:4
long long:8
float:4
double:8
long double:8
unsigned char:1
signed char:1
unsigned char:2
unsigned long:4
unsigned long long:8
在C++中变量为存储空间
对象是指一块能存储数据并且具有某种类型的内存空间
c++11用{}初始化得到全面的应用
任何函数体之外的变量被初始化0,函数体之内的不被初始化
用户自定义标识符不能连续出现两个_(下划线),也不能以下划线紧连大写字母开头,函数体外的标识符不能够使用下划线开头
引用必须被初始化,由于引用无法重新绑定到另外一个对象 ,引用类型的初始化必须是一个对象 ,引用不是对象,没有实际地址 int &a=m;
#include cstdlib
int *p=nullptr;使用关键字nullptr初始化指针
int *&r = p //p为int类型指针
const int d=512; 默认状态只在文件内有效
如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字
const int i=1024;const int &r=ci对常量的引用,常量的引用不允许修改常量的值
int i = 40; int &a=i; const int &b=i; //本例子中,可以通过a改变i的值,但不能通过b改变i的值
存放常量对象的地址,只能使用指向常量的指针 for example:const int i=10;const int *p=&i;
把*放到const之前说明指针是一个常量,不变的是指针本身而不是指向的那个值
const int *a=m;为指向常量的指针,不能够改变其所指向常量的值,不能够通过指针去改变指向对象的值
comst *int a=m;为常量指针,不能够改变指针本身的值,而不是所指向的值
如果要求变量和指针都为常量const int *a=10;,需要使用const int *const p=&a;,如果为int a=1;则都可以
声明为constexpr的变量一定是一个常量
typedef 和 using 都可以定义别名 for example : typedef char name; using name=char;
auto item = val1+val2;item的值由val1+val2决定,系统会更具后面的值自动分析出item的类型c++11
如果希望推测出的auto类型是一个const需要明确指出:const auto f = a;
decltyde返回操作数的类型 decltyde (f()) sum = x;
string a(n,'c');//使用n个c为a初始化
string a;cin>>a; //当输入hello world时,a的值仅为hello,string会从第一个真正字符开始读取,直到遇到一处空白未知
string a; a.getline(流cin,string对象),a.empty()判断是否为空,a.size()返回的是一个无符号的整数,a[n]读取string中第n个字符,从0开始计算
for(auto c:string)循环遍历字符串
cctype头文件中包含很多对字符char的处理
vector容器,初始化vector对象vector<int> a(10);//a有10个元素,每个值都是0;vector<int> a(10,1);//a有10个元素,每个值都是1;
vector<int> a{10};//a有1个元素,值为0;vector<int> a{10,1};//a有2个元素,每个值都是1
vector的定义中()为对象构造vector,{}为初始化对象
vector 添加数据函数push_back(),读取函数为pop_back()
vector<int> a;for(int i=100;i>0;i--){a.push_back(i)}//为a赋值
for(auto c:a) cout<<c;//依次输出a的每个元素的值
vector<int>a;对象a可以通过下标去访问 for example :a[0],下标的标号从0开始
string a = "qqqq"; for (auto it = a.begin(); (it != a.end()) && (!isspace(*it)); ++it) cout<<*it<<endl;使用迭代器读取string单个元素,类似于使用a[n]访问单个元素
auto it=a.begin 返回第一个元素的指针,如果a.begin()=a.end()说明数组为空
迭代器默认根据参数类型返回普通指针或者常量指针,可是使用a.cbegin(),a.cend()获得常量指针const_iterator
->把解引用和成员访问两个操作结合起来,(*it).men和it->men 功能相同
注意:但凡使用了迭代器的循环体,都不要向迭代器所属容器添加元素
迭代器运算:iter = s.begin(),iter +n表示迭代器向前移动n个位子;iter - n表示迭代器向后移动n个位子;iter +=n表示将iter加n赋给iter;iter - =n表示将iter加n赋给iter;iter1 - iter2表示同一个容器内两个迭代器的距离;迭代器也可以使用<,>=,<,<=判断同一个容器内的两个迭代器的大小
数组可以采用下面方法访问int a[100] = {};for (auto s : a) cout << s;
大多数情况下,使用数组类型的对象其实就是使用一个指向该数组首元素的指针。int a[10] {};auto a2(a);使用auto 获得是一个一个指针,既a2是一个指针;使用decltype(a) a3;,其中a3为一个数组
int a[10]={};int *e= &a[10];//其中e为指向数组尾元素的下一个位子的的指针
在c++11中新增加类十余vector中的begin和end函数,但是函数需要传入的参数为数字,既int a[10] = {};int *pbeg = begin(a);int *pend = end(a);//pbeg为指向a首元素的指针,pend为指向a最后一个元素的下一个位置
int a[2]={};int *p=a;等价于int *p=&a[0];
在c语言中的几个函数strlen(p)//返回p的长度;strcmp(c1,c2)//比较c1,c2的相等性,如果c1==c2返回0,c1>c2返回正数,c1<c2返回负数;strcat(c1,c2)//将c2附加到c1后面;strcpy(c1,c2)//将c2拷贝到c1
int a[]={1,2,3,4};vector<int> v(begin(a),end(a));//用于创建一个vector变量,并且使用数组赋值,使用标准函数begin()和end()函数将数组的起始地址和结束地址指向数组的起始,结束地址,从而达到赋值
int ap[]={1,2,3,4,5,6,7,8,9,0};vector<int>vec(ap +1,ap +5); //使用数组第ap[1]到ap[5]初始化向量
int ia[3][4];int (*p)[4]=ia;//p指向含有4个整数的数组
int aa[5][5] = {0};for(auto &c:aa) for (auto &cc : c) cout <<cc<<endl;和for (auto p = aa; p != aa + 5; ++p) for (auto pp = *p; pp != *p + 5;++pp) cout<<*pp<<endl;和for (auto q = begin(aa); q != end(aa); ++q) for (auto qq = begin(*q); qq != end(*q); ++qq) cout << *qq << " ";功能相同
假定p的类型为int* ,而decltype(*p)的结果是int&;decltype(&p)的结果是int**类型
C++11规定商一律向0取整,既直接切除小数点部分
string text;for(const auto &s:text)//这里s为text的常引用,他不会改变text的值,引用不会拷贝text的值,如果text的值很大的时候,这样很有用
*p++其中++的优先级大于*,相当于*(p++),
解引用*运算符优先级低于点运算符
int a = (m>0)?best:(m<100)?ok:fail;可以使用这种方法
unsigned char bits=0233; bits<<8;//向做移动8位 bits>>8 // 向右移动8位 ~bits //取反 &与 |或 ^异或
decltype,&,sizeof,typeid或者使用引用初始化一个数组,数组将不会转变为指针,其他情况将会转变为指针
函数的返回类型不能是数组类型或者函数类型,但可以使用指向数组或函数的指针
局部静态对象在程序程序执行路径第一次经过该对象时被初始化,知道程序被终止时才被销毁
函数可以多次声明,但是只能一次定义
int main(int argc,char**argv){}或者int main(int argc,char*argv[]){}其中使用argv的时候需要注意argv[0]存放的是程序的名字,应该从argv[1]开始使用
如果一个函数的实参数量不确定,但是实参的类型相同,可以采用initializer_list<int> list模板类型,initializer_list对象中永远是常量,无法改变
在函数返回对象中,不能够返回局部对象的指针,引用
声明返回数组的指针函数type (*function(paramater_list))[dimension] type 表示元素的类型;paramater_list数组参数;dimension 数组的大小;int (*func(int i))[10]
对于返回类型比较复杂的函数使用尾置返回比较方便,比如需要返回指针,引用for example :auto func(int i) ->int(*)[10]//返回一个指针,该指针指向包含10个整数的数组
函数重载不允许两个函数除返回类型外其他所有要素相同
函数重载int look(k);int look(const k);不能够重载 int look(k *);int look(k* const);不能够重载,
函数重载int look(k&);int look(const k&);能够重载 int look(k *);int look(const k*);能够重载主要通过判断其指向的是常量还是非常量
inline 函数声明 //这样就是内敛函数
constexpr 能用于常量表达式的函数,函数体中有且仅有一条return语句,constexpr函数不一定返回常量表达式
inline和constexpr函数定义放在头文件中
int *founction(){}返回一个int型指针;int (*founction)(){}指向函数的指针
void ff(int *);void(*pf)(int*)=ff//指针pf指向函数
在类的定义中如果成员被声明成常量成员函数,在定义时候也必须在参数列表后明确指定const属性
classname = default; //c++11新标准要求编译器生产默认构造函数
构造函数初始化列表 classname(const std::string &a):book(a){}(后面没有分号)
class和struct的区别在于class默认访问权限为私有,struct为共有
其他函数可以访问类的私有成员,则需要把函数设置成类的有缘,只许要在类的原始声明中添加一条以friend关键字开头的函数声明语句friend std:: $read(std::istream &,int &);
友元类通常将声明和类本身放置在同一个头文件中
假如在一个const成员函数内,可以通过在变量内部声明mutable关键字改变变量的值
通过friend class classname将类声明为一个类的友元类,友元类的成员可以访问此类的包括非公有在内的全部成员;友元类不存在传递性
将一个类class1中的函数声明为类外一个类class2的友元类的时候需要指明函数属于哪一个类,首先要在class1中声明函数,但不能够定义,友元函数使用class2的时候需要声明class2,定义完函数以后才能够使用
在造函数声明前加上explicit,将会抑制构造函数的隐士转换,只能对一个实参构造函数有效
static静态成员函数不能够声明为const,也不能在函数体内使用this指针
在类的外部声明静态成员时,不能重复的使用static
进行io操作的函数通常以引用的方式传递或者返回数据,不能使用const
导致缓存区刷新(数据真正被写到输出设备上)的原因:程序正常结束,main函数return操作;缓存区已满;使用操作符endl显示的刷新缓存区;在每一个输出操作后,使用unitbuf设置流的内部状态来清空缓存区;一个输出流可能被关联到另外一个输出流,当读写被关联的流时,关联到的流的缓存区会被刷新
cout << "hello" << endl; cout << "hello" << ends;//插入一个空字符,然后刷新缓存区 cout << "hello" << flush;//flush刷新缓存区但不输入任何额外字符
如果希望每次输出操作后都刷新缓存区,可以使用unitbuf, cout<<unitbuf; nounitbuf重置缓存区,使其恢复正常 cout<<niunitbuf;
文件模式:in读out写app每次写操作前均定位到末尾ate打开文件后立即定位到末尾trunc截断文件,binary以二进制方式进行io
为了保留以out模式打开文件的内容,需要同时使用app模式将内容添加到文件末尾,或者同时指定in模式,即进行同时输入,输出
ifstream关联的文件默认以in的模式打开;ofstream关联的文件默认以out的模式打开;fstream关联的文件默认以in和out的模式打开
string流在sstream头文件中
vector 可变大小数组,支持随机访问,在尾部之外的地方插入删除可能很慢
deque双端队列,支持随机访问,
list 双向链表,支持双向顺序访问,任何位子插入删除速度比较快
forwoard_list 单向链表
array 固定大小数组,支持快速随机访问,不能够添加删除元素
string 随机访问,尾部插入删除比较快
以上元素的头文件名字和类型名字一样
begin和end操作rbegin(),rend()返回反向迭代器;cbegin(),cend()返回const迭代器
当将一个人容器初始化为另外一个容器的拷贝时,连个容器的类型和元素类型都必须相同
只有顺序容器的构造函数才接受大小参数,关联容器并不支持
标准库array声明时除了指定元素类型还要指定容器的大小,array<int,3>
如果对array进行列表初始化,初始值的数目必须等于或者小于array的大小,array<int,3>={1}//出一个元素为1,其余元素默认构造为0
swap(a,b)交换a,b中的元素,ab必须具有相同的元素
assign操作不适合关联容器和array,seq.assingn(a,b)//将seq的元素替换成迭代器迭代器ab所表示范围内的元素,迭代器不能够指向seq中的元素
除了array,forward_list之外,每个顺序容器都支持push_back;list,forward_list,deque还支持push_front
使用对象初始化容器时,或者将一个对象插入倒容器中时,实际上放入到容器中的是对象值得一个拷贝,而不是对象本身。
insert往数据结构的前部插入相当于push_front()
emplace_front,emplace,emplace_back这些操作不是拷贝元素,分别对应push_front,insert,push_back,可以将元素放到容器的头部,一个指定的位置,或者容器末尾
包括array在内的每个顺序容器都有一个front成员函数和back成员函数,这两个操作分别返回元素的首元素和尾元素list<string> c;*c.bengin()等价于c.front()
在容器中访问元素的成员函数(front,back,下标和at)返回的都是引用
pop_front,pop_back分别删除首元素和尾元素,与vector和string不支持push_front一样,这些类型也不支持pop_front,forward_list不支持pop_back
成员函数erase从容器中指定位置删除元素auto it = lst.begin();lst.earse(it);删除两个表示范围内的元素,返回指向最后一个删除元素之后位置的迭代器elem1=lst.earse(elem1,elem2)调用后elem1==elem2
lst.clear()等价于lst.earse(lst.begin(),lst.end())
forward_list中的一些函数,list.before_begin()//返回链表首元素之前不存在的元素的迭代器,次迭代器不能够解引用;list.insert_after(p,t)//在p之后插入t;list.insert_after(p,n,t)//在p之后插入n个t;list.erase_after(p)//删除p;list.erase_after(b,e)//删除be之间的元素,不包括be
resize可以用来改变容器大小( array除外)list<int> list(10,1);//list有10个元素每个值都为1 list.resize(15)//将list大小设置成15,多出5个用默认值初始化 list.resize(25,1)//将list的大小扩大到25,多出的用1初始化
list.resize(n)中n小于list的大小,则将会丢弃多余的元素
在vector,string中list.capacity()返回在不扩充内存空间的情况下,list可以保存多少个元素;list.reserve(n)分配至少容纳n个元素的内存空间
string s;s.substr(pos,n)//从pos开始的n个字符的拷贝
string相关函数insert,assingn,erase,append,replace
string中find("")函数搜索指定字符串,返回第一次出现的下标,find函数区分大小写,若查找不成功,则返回npos(string最大可能大小);rfind("")查找最后一次出现的位置; string const name = { "hello worl1d!" },number = {"0123456789"};name.find_first_of(number);查找number中任一个字符串出现的位置;find_last_of()find_first_not_of()find_last_not_of()
string中rfind("")功能是从后往前查询
to_string(type)//将type转换成字符串;stod("01254")将string类型转换成double;stoi("51425")将string转换成int;sto*("")后面的*代表相应类型的名的首字母;sto*(type)其中字符串中必须为数字
栈stack定义在stack头文件中 stack<int> s;s.pop()//删除栈顶元素,但不返回该元素;s.push(item);//穿件一个新元素压入到栈;s.emplcae(arg)//由arg构造一个栈;s.top();//返回栈顶元素,不将元素弹出栈
queue和priority_queue定义在queue头文件中,queue基于deque实现,priority_queue基于vector实现;q.pop()返回首元素或者priority_queue中最高优先级元素,但不删除元素;q.front() q.back()返回首元素或者尾元素,但不删除元素;q.top()返回priority_queue中最高优先级元素;q.push(item)在queue的末尾或者priority_queue的恰当的位置创建一个元素;q.emplace(arg)使用arg构造一个queue或者priority_queue
bool isShorter(const string &s1,const string &s2){return s1.size()<s3.size()} sort(words.begin(),words.end(),isShorter);//word是一个vector,sort函数通过传入一个函数,来对word集合大小排序
一个lambda表达式表示一个可以调用的代码单元,可以将其理解为一个内联函数,lambda可以定义在函数内部,定义为:[capture list](paramater list) ->return type {function body};capture list是一个所在函数中定义的局部变量列表
如果lambda的函数包含任意一个return语句之外的内容,且未指定返回类型,则返回void
lambda不能有默认参数,调用的实参数目永远要和形参数目相等
一个lambda函数只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用改变量
[&,indentifer_list]indentifer_list是一个逗号分隔的列表,这些变量采用值捕获方式;任何隐式捕获的变量都采用引用方式捕获,indentifer_list中的名字前面不能够使用&
[=,indentifer_list]indentifer_list中的变量都是采用引用方式捕获,任何隐式捕获的变量都是采用值传递,indentifer_list不能包括this,且这些名字之前必须使用&
如果希望能够改变一个被捕获变量的值,就必须在参数列表首加上关键字mutable,int vi = 42;auto f=[vi]()mutable {return vi++;}
指定lambda的返回类型,使用尾置返回类型 auto f = [c, d](int m, int n) ->int {if (c > d) return c; else return d; };
lambda适合一个只有一两个地方使用的简单操作
在头文件functional中bind标准库函数,调用bind的形式为:auto newCallable = bind(callable,arg_list);其中newCallable本身是一个可调用的对象,arg_list是一个用逗号分隔的参数列表,对应给定的callable的参数
可以对任何具有输出运算符(<<)和输入运算符(>>)的类型定义istream_iterator和ostream_iterator
istream_iterator<T> in(cin);//in从输入流is读取类型为T的值 istream_iterator<T> eof;//读取类型为T的值得istream_iterator迭代器,表示尾后位置
ostream_iterator<T> out(os);//out将类型为T的值写到输出流os中 ostream_iterator<T> out(os,d); out将类型为T的值写到输出流os中,每个值后面都输出一个d,d指向一个空字符结尾的字符数组
rbegin(),rend(),crbegin(),crend()反向迭代器++意思是从最后一个元素向第一个元素移动,--相反,forward_list不支持反向迭代器
输入迭代器只能够顺序的访问,只能用于单趟扫描算法istream_iterator;输出迭代器只能单趟扫描ostream_iterator;向前迭代器,可以读写元素,只能在序列中沿着一个方向移动,可以多次读写同一个元素;双向迭代器可以正向反向读写元素,除forward_list外其他标准库都提供了符合双向迭代器要求的迭代器;随机访问迭代器,支持双向迭代器的所有功能
接受谓词的算法名字后面都会带一个_if,例如find_if(beg,end,comp)
list和forward_list应该优先使用成员函数版的算法,而不能够使用通用的算法,链表定义了一个splice算法
关联容器map中的元素是一些关键字-值对,关键字起索引作用,值表示与索引相关联的数据;set中的每一元素只包含一个关键字set支持高效的关键字查询,例如判断一个关键字是否在set集合中
标准库提供了8个相关联容器1.么一个容器或者是set,或者是map;2.或者要求不重复关键字,或者要求重复关键字;3.按顺序保存元素,或者无序保存元素,允许关键字重复关键字的容器的名字中都包含关键词multi,不保持关键字顺序的容器的名字都可以使用单词unordered开头
关键字有序1.map:保存关键字-值对;2.set只保存关键字;3.multimap关键字可以从复出现的map;multiset关键字可以重复出现的set 无序集合:1.unordered_map(unordered_set)用哈希函数组织的map(set);unordered_multimap(unordered_multimset)哈希组织的map(set)关键字可以重复出现
map和multimap在map头文件中;set和multiset在set头文件中;无序容器定义在unordered_map和unordered_set头文件中
pair在utility头文件中,pair保存两个数据成员,pair是一个用于生成特定类型模板,
key_type此容器的关键字的类型;mapped_typemap中每个关键字关联的类型;value_type对于set容器的关键字的类型,对于map中value_type为pair<const key_type,mapped_type>
遍历map<string, int> mapp = { {"a",1}, {"b",2} ,{"c",3} ,{"d",4} ,{"e",5} ,{"f",6} };mapp.insert({"g",7});auto m = mapp.begin();cout << m->first << endl;
遍历set<string>::iterator s = setted.begin();while (s != setted.end()) cout<<*s++<<endl;
关联容器的关键字是常量,所以关联容器可以用于只读取元素的算法
map插入元素 map<string,int> m; m.insert(v);//v表示是一个value_type类型的对象 c.insert({"word",1}); c.insert(make_pair("word",1)); m.insert(pair<string,int>()"word",1); c.insert(map<string,int>::value_type("word",1));
insert或者emplace返回的值依赖于容器的类型和参数,对于不包含重复关键字的容器,添加一个元素的insert或者emplace版本返回一个pair,告诉我们插入是否成功,pair的first成员是一个迭代器,指向具有给否定元素的关键字,second成员是一个bool类型,支出元素插入成功好事已经存在,成功true,存在false
关联容器定义了3个版本的erase,对于保存不重复关键字的容器,erase返回值总是0或者1,对于允许重复的容器返回删除元素的数量
map和unorderedmap容器提供了下标运算符和一个对应的at函数,word.at("aa"),set相关联容器不支持下标,multimap和unordered_multimap不能进行下标运算,因为这两个容器可能有多个值与一个关键字相联系;map下标运算接受一个关键字,word["aa"]获取与此关键字相联系的值,如果关键字不存在,则将会创建一个元素插入到map中关键值将会被初始化
关联容器c,c.fink(k)返回一个迭代器,指向第一个关键字为k的元素,若不存在则返回尾迭代器;c.count(k)返回关键字等于k的数量;c.lower_bound(k)返回一个迭代器,指向第一个关键字不小于k的元素;upper_bound(k)返回一个迭代器,指向大于k的元素;c.equal_range(k)返回一个pair迭代器,表示关键字等于k的范围,若k不存在则pair的两个成员均等于c.end()
equal_range接受一个关键字,返回一个迭代器pair,若关键字存在第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向指向最后一个匹配元素之后的位置,若找不到元素,则两个迭代器都指向关键字可以插入的位置
为了更容易的使用内存,C++11新的标准库提供了两种智能指针,智能指针的行为类似于常规指针,区别在于他负责制动释放所指向的对象,shared_ptr允许多个指针指向同一个对象,unique_ptr独占所指向的对象,wead_ptr伴随类,它是一种弱引用,指向shared_ptr所管理的对象,这三种类型定义在memory
shared_ptr智能指针也是模板,创建一个智能指针时,必须制定指向的类型 shared_ptr<string> p1;shared_ptr<list<int>> p2;默认初始化的智能指针保存一个空指针,p.get()返回p中保存的指针,make_shared<T>(args)返回一个shared_ptr指向一个动态分配类型为T的对象,使用args初始化此对象;shared_ptr<T>p(q) p是shared_ptr<T>p的一个拷贝;p.use_count()返回p共享对象的智能指针数量,p.unique()如果p.use_count()返回为1,则其 返回为true,否则为false
make_shared函数使最安全的分配和使用动态内存的方法,此函数在动态内存中分配一个对象,并且初始化它,返回一个指向此对象的shared_ptr,其也定义在memory头文件中;shared_ptr p = make_shared<int>(40)//指向一个值为40的int shared_ptr
释放动态分配的内存,传递给delete的指针必须是动态分配的指针或者是
智能指针使用规范:1.不使用相同的内置指针值初始化(或者reset)多个智能指针;2.不要delete get()返回的指针;3.不使用get()初始化或者reset另一个智能指针;3.如果你使用get()返回指针,记住当最后一个对应的智能指针销毁后,你的指针就会变成无效的;4.如果使用智能指针管理资源不是new分配的内存,记住传递给他一个删除器
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个有shared_ptr管理的对象,,他不会该表shared_ptr的引用计数器,由于weak_ptr和shared_ptr共同指向的内存可能被释放,因此不能够直接使用weak_ptr访问对象,必须调用lock() use_count()返回共享对象的shared_ptr的数量,expired()若use_count()为0,则返回true否则返回false,lock()如果expired()为true返回一个空的shared_ptr,反正放回一个shared_ptr对象
动态申请的数组需要使用delete[]释放,可以使用unique_ptr管理new分配的数组unique_ptr<int []> ptr(new int [10]);ptr.release();释放动态分配的内存,当unique_ptr指向一个数字时,不能够使用.或者->成员运算符,但是可以使用ptr[i]=1;方式访问数据
标准库allocator类定义在memory头文件中,它帮助我们将内存分配和对象构造分离开,allocator<string> alloc;auto const p = alloc.allocator(n);//分配n个未初始化的string;alloc.deallocate(p,n);//释放从T*指针p中开始的内存,这块内存保存了n个类型为T的对象,p必须是allocator分配返回的指针,调用allocator之前用户必须对每个这样的内存中创建的对象调用destroy;alloc.destory(p) p为T*类型指针,对p所指向的对象进行析构;alloc.construct(p,args)T类型的构造函数
无论我们是否定义构造函数,编译器都会为我们合成一个拷贝函数
赋值运算符通常应返回一个指向其左侧运算对象的引用,构造函数初始化对象的非static成员,析构函数释放对象使用的资源,并且销毁对象的非static成员,析构函数没有返回值也不接受参数,由于不能够接受参数,因此析构函数不能够重载,只有唯一个析构函数
如果一个雷需要自定义析构函数,几乎可以可以肯定他也需要自定义拷贝赋值元算符和拷贝构造函数
可以通过将拷贝控制成员定义为default来显示的要求编译器生成合成的版本,我们只能够对默认构造函数或者拷贝控制成员使用=default
我们可以将拷贝构造函数和拷贝赋值运算定义为删除函数来阻止拷贝;删除函数虽然我们声明了这样一个函数,但是我们不能使用任何方式打开 classname()=delete;
如果一个类有数据成员不能够默认构造、拷贝、复制或者销毁,则对应的成员函数将被应以为删除的;希望阻止拷贝类应该使用=delete来定义自己的拷贝构造函数和拷贝赋值运算符,而不应该将他声明为private
右值引用就是必须绑定到右值的引用,通过&&来获取右值引用,右值引用只能绑定到一个将要销毁的对象。左值持久,右值短暂,由于右值引用只能绑定到临时对象,我们可知:1.所有引用的对象降到销毁;2.该对象没有其他用户这两个特性意味着:使用右值引用的代码可以自由地接管所有引用的资源
当一个对象用作右值的时候,用的是对象的值(内容);当对象呗用作左值的时候,用的是对象的身份(在内存中的位置)。
不能够将一个右值引用直接绑定到一个左值上,但是可以显示的使用utility头文件中move的新标准库获得绑定到左值上的右引用 int &&r1 = 42;int && r = std::move(r1);使用move就意味着我们承诺,除了对r1赋值或者销毁以外,我们将不再使用它;我们可以销毁一个移动后源对象,也可以销毁一个赋予它新值,但是不能够使用一个移动后源对象的值。
与拷贝构造函数不同,移动构造函数不分配任何新的内存,他只是负责接管原有对象的内存。由于移动操作窃取资源而不分配资源,移动操作通常不会抛出任何异常,当我们编写一个不抛出任何异常的移动操作时,我们应该将此事通知标准库,一种通知标准库的方法是在我们的构造函数中指明noexcept,noexcept的作用就是我们承诺一个函数不抛出异常
所有五个拷贝控制成员应该看成一个整体,一般来说,如果一个类定义了任何一个拷贝操作,他就应该定义所有的五个操作
当一个重载的运算符是成员变量时,this绑定到左侧运算对象;成员运算符函数的参数数量比运算对象少一个,对于一个运算符函数来说,它或者是类成员,或者至少含有一个类类型的参数,当运算符作用于内置类型的运算对象时,我们无法改变运算符的含义
一个非成员函数的等价调用data1 + data2 等价于operator +(data1 + data2) ;data1+=data2 等价于 data1.operator+=(data2)
通常情况下不应该重载逗号,取地址,逻辑与,逻辑或运算符
下标运算符必须是成员函数,如果一个类包含下标运算符,通常会定义两个版本:1.一个返回普通引用;2.是类的常量成员并且返回常量引用。
在++后置重载中我们需要定义如下:typeclass operator++(int);传入int的目的在于让编译器区分前置++后置++的调用,前置++的定义为 typeclass operator++();--的定义类似。为了和内置版本保持一致,递增和递减运算符应该返回递增和递减后的对象的引用
重载箭头运算符必须是类的成员,必须返回类型的指针或者自定义了箭头运算符的某个类的对象
调用形式指明了调用返回的类型以及传递给调用实参类型,一个调用类型对应一个函数类型 int (int,int)是一个函数类型,他接受两个int返回一个int。函数表用来存储指向这些这些可谓调用对象的指针。嘉定所有函数相互独立,并且只处理关于int的二元运算,函数 int add(int a,int b){return a+b;} auto mod = [](int a,int b){return a%b;}....map<string,function<int(int,int)>> binops;function是一个函数模板, function<int(int,int)> f1 = add;....通过f1(1,2)就可以调用function模板, 向map中添加binops.insert({"+",add});binops["+"](1,2)就可以调用map中的函数
operator type () const 类型转换函数,他负责将一个类类型的值转换成其他类型,其中type表示某种类型。类型转换运算符可以面向任意类型(void除外)进行定义,只要该类型能够作为函数的返回类型,我们不润徐转换成数组或者函数类型,但允许转换成指针或者引用类型。类型转换函数没有明显的返回类型,也没有实参,必须定义成类的成员函数,一般定义成const
面向对象的核心思想是数据抽象、继承和动态绑定。通过使用数据抽象,我们可以将类的接口和实现分离;使用继承。可以定义相似的类型并对其相似关系建模;使用动态绑定,可以在一定程度上忽略相似类型的区别,而已统一的方式使用它们。
如果基类希望派生它的类各自定义适合自身的版本,此时积累就会将这些函数声明称虚函数。
派生类必须使用类派生列表明确指出它是从哪一个类继承来的,派生类列表的形式:首先一个冒号,后面紧跟着以逗号分隔的基类列表,每一个基类前面可以有访问控制符。派生类必须在其内部对所有重新定义的虚函数进行声明。
c++11新标准允许派生类显示的注明它将使用哪个成员函数改成基类的虚函数,具体措施是在该函数的形参列表之后增加一个override关键字。
基类通常需要定义一个虚析构函数,即使该函数不执行任何实际操作也是如此。
任何构造函数之外的非静态函数都可以是虚构函数,关键字virtual只能出现在类内部的声明语句之前,而不能够用于类外部函数定义。如果成员函数没有声明成虚函数,则其解析过程将会发生在编译时而非运行时。
派生类可以继承定义在基类中的成员,但是派生类的成员函数不一定有权访问从基类继承而来的成员,和其他使用基类成员的代码一样,派生类能够访问共有成员,而不能够访问私有成员。使用protect访问元算符声明的成员,派生类有权访问该成员。派生类必须将其继承而来的成员函数中需要覆盖的那些新的声明,比如经常覆盖虚函数(但不总是)
c++标准允许派生类显示地注明它使用某个成员函数覆盖了他继承的虚函数,具体做法是在形参列表后面,或者const成员函数的const关键字后面,或者引用函数的引用限定符后面添加一个关键字override
首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员。
如果基类定义一个静态,则在整个继承体系中只存在该成员的唯一定义。不论从基类中派生出来多少个派生类,对于每一个静态成员来说都只存在唯一的实例,静态成员遵循通用的访问控制规则,如果基类中的成员是private的,则派生类无权访问它。
派生类的声明与其他差别不大,生命中包含类名但是不能够包含派生类表,一个类不能够派生它本身。一个类为了防止继承,我们不希望它被其他类继承,我们可以在类名后跟一个关键字final。
如果表达式既不是指针也不是引用,则它的动态类型和静态类型是一致的。我们不能将基类转化成派生类。派生类向基类的自动类型转换只对指针或者引用类型有效,在派生类类型合集类类型之间不存在这样的转换。
当我们使用一个派生类对象为一个基类初始化或者赋值时,只有该派生类对象中的积累部分会被拷贝,移动,或者赋值,其他部分将会被忽略掉。
从派生类向基类的类型转换只对指针或者引用类型有效,基类向派生类不存在隐式类型转换,派生类向基类类型转换可能会由于访问受限而变得不可行。
我们必须为每一个虚函数提供一个定义,无论是否被使用,这是因为编译器无法确定到底使用哪一个虚函数。
当我们在派生类中覆盖掉一个虚函数时,可以再一次使用virtual关键字指出该函数的性质,这么做并非必须的,因为一旦声明为虚函数,则在所有的函数中都是虚函数。一个派生类的函数如果覆盖了一个继承而来的虚函数,则它的形参类型必须与被覆盖的基类函数完全一致,返回类型也必须一致。该规则存在一个例外,当类的虚函数返回类型是类本身的指针或者引用时,上述规则无效,这样的返回类型要求派生类到基类的转换是可以访问的。
派生类与基类如果定义一个名字相同而参数列表不同的函数使合法的,编译器会认为这是一个新的函数,为了防止原本希望覆盖虚函数,而造成名称相同参数列表不同的问题,可以在参数列表后面添加override,告知编译器这是一个覆盖虚函数的函数。如果在参数列表后面添加一个final,则任何试图覆盖该函数的操作都会失败。
使用作用域可以调用基类的成员,如果一个派生类虚函数需要调用它的基类版本,但没有使用作用域,则在运行时会被解析成调用对象本身,从而导致无限递归。
纯虚函数的定义int f(int a) const=0;知识在虚函数声明语句分号之前添加一个=0,我们不能够在类的内部为一个=0的函数提供函数体,,含有纯虚函数的函数体的类为抽象类,抽象基类为了定义接口,而后续其他类可以负责可以覆盖该接口,但是我们不能够创建抽象类对象,继承抽象类接口的类必须给出抽象函数的定义,不然这个继承类也将是抽象类。
派生类的成员和友元只能够访问派生类对象中的基类基类部分的受保护的成员;对于普通的基类对象中的成员不具有特殊的访问权限。
对于代码中给定的节点来说,如果基类的共有成员是可以访问的,则派生类向基类的类型转换是可以访问的,反之则不行。
友元关系不能够传递,同样也不能够传递,基类的友元在访问派生成员时不具备特殊性,同样,派生类的成员也不能够随意访问及尅的成员。
派生类只能够为那些可以访问的名字提供using声明,using声明的语句如果是private部分,则只能被类成员和友元访问;如果是public则都能够访问;如果是protected则只能够被成员友元和派生类可以访问。
默认情况下class关键字的的派生类私有继承,struct默认公有继承;关机子class和struct定义类唯一的区别在于默认派生访问说明符不同。
派生类的作用于位于基类作用于之内,一个对象,引用或者指针决定了该对象的那些成员是可见的,即静态类与动态类可能不一致,但是我们能够使用那些成员依然是由静态类型决定的。比如使用一个基类的指针指向一个派生类,如果使用这个指针访问一个基类没有的函数或者变量,将不可访问,程序访问中首先搜索静态类型的成员,因为基类没有先关变量,所以不可以调用。
当派生类的变量名称和派生类的变量名称相同时,派生类将会隐藏同名的基类成员。可以使用作用域运算符覆盖掉原有的查找规则。声明在内层作用域的函数并不会重载声明在外层作用于的函数,如果派生类和基类的摸个成员同名,则派生类将会在其作用域内隐藏该积累成员,即使派生类成员和基类成员的形参列表不同,基类成员也将会被隐藏。
和其他函数一样,成员函数无论是否是虚函数都能过被重载,派生类可以覆盖或者重载函数的0个或者多个实例,如果派生类希望所有重载版本都可见,,那么他需要覆盖所有的版本或者一个也不需要覆盖。可以通过using声明语句,这样就无需覆盖每一个版本。
如果我们需要删除一个指向派生类对象的基类指针,则需要虚析构函数,只要将基类的析构函数是虚析构函数,我们就能够确保delete基类指针时,将运行正确的析构函数版本。
如果基类中默认构造函数、拷贝构造函数、拷贝赋值运算符、析构函数是被删除的函数或者是不可访问的,则派生类中相应的函数也将会被定义成被删除的函数,原因是编译器无法对派生类中的基类部分执行相应操作。
当派生类定义了拷贝或移动操作时,该操作复杂拷贝或者移动包括基类部分成员在内的整个对象。无论基类的构造函数或者赋值运算法是自定义版本还是合成版本,派生类对应操作都能够使用它们。派生类继承基类的构造函数的方式是提供一条基类名字的using声明语句。
模板定义的关键字是template,后面紧跟模板参数列表,模板的参数列表不能够为空。声明方式template <typename T> Tfoo(T* a,class b){}。非类型模板参数的模板实参必须是常量表达式,模板函数可以声明为inline或者constexpr的,如同非模板函数一样,inline或者constexpr说明符放到模板参数列表之后,返回类型之前template <typename T> inline T min(const T&,const T&){}。
函数模板和类模板成员函数的定义通常放到头文件中。当使用模板时,所有不依赖于模板参数的名字,都必须可见,这是由模板的提供者来保证的,而且,模板的提供者必须保证,当模板被实例化时,模板的定义包括类模板的成员定义,也必须是可见的。
未完待续........