第一部分
1.1类型转换:
如果表达式里有带符号类型,又有不带符号类型,当带符号类型取值 为负时会出现异常结果,因为带符号数会自动转换成无符号数。负的无符号数会转化为十分大的数字。
1.2const和指针
1.2.1从右往左进行阅读,const离*近,表示const作用的是指针指向的值。顶层const表示指针本身是常量,底层const表示指针所指对象是常量。
const char *p,p所指向的内容是不能改变的。
char * const p,p所指向的内容可以改变,p本身不可以改变。
1.2.2 constexper类型放在变量之前表示变量的指是一个常量表达式。声明为constexper类型的变量一定是一个常量,而且必须使用常量表达式来初始化。
1.3auto 和decltype类型指示符
auto获取表达式返回的值类型,会计算表达式。 decltype获取表达式类型,并不会实际计算表达式的值。
decltype((var))(双层括号)的结果类型永远是引用,而decltype(var)结果只用当var本身是是引用时才是引用。
1.4运算符
在一个表达式中使用多个运算符时一定要注意运算符优先级别,或者一定注意*加括号,加括号,加括号!!!!!!!!!!!!!!!!!重要的事情说三遍。*
1.5try catch 的使用
try {
想要检测的语句
}
catch (CException e) {
cout << e.msg << endl;
TCHAR szError[1024];
e->GetErrorMessage(szError,1024); // e.GetErrorMessage(szError,1024);
::AfxMessageBox(szError);
}
1.6参数传递
在参数传递时考虑能不能使用引用,尽量使用引用来进行传参,对于参数是否要加const。
1.7内联函数
内联函数可以避免函数调用所带来的开销,当函数需要频繁调用,并且函数行数比较少的时候可以将函数定义为内联函数。
1.8 类
1友元,为了访问类中的private成员,可以将成员函数声明为友元,也可以将类声明为友元,也可以将类中的成员函数声明为友元,但是必须指出该成员函数属于哪个类。
2类的静态成员变量和静态函数不算类大小,sizeof不算类大小。静态类成员变量为所有对象所共有,不属于任何一个对象。
explict关键字
C++中的explicit关键字只能用于修饰只有**一个**参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).
第二部分
2.1顺序容器的分类和选择
vector:可变大小数组,支持快速随机访问。但在尾部之外的位置插入或删除元素可能很慢。
deque:双端队列,支持快速随机访问,在头,尾插入或删除元素速度很快。
list:双端列表,支持双向顺序访问,在任何位置插入或删除元素都很快。
forward——list:单向列表,支持单向顺序访问,在任何位置进行插入或删除都很快。
array:固定数组大小。
string:和vector相似的容器,但是专门用来保存字符。
单向列表和双向列表不支持元素的随机访问,想要找到一个元素只能便利整个列表。
以下为选择容器的基本原则:
1除非有更好的理由选择其他容器,否则应该使用vector。
2如果要求随机访问元素,应该用vector或deque。
3如果要求在中间插入元素,使用list或forward——list。
4如果要在头或尾插入元素,但不会在中间插入元素,应该用deque。
2.2关联容器的分类和选择
关联容器中元素是按照关键字来进行访问和保存的,而顺序容器中元素是按照他们在容器中的位置来进行顺序保存和访问的。
关联容器如下:
map:关键字-值
set: 关键字
multimap:关键字可以重复。
multiset:关键字可以重复。
无序关联容器:使用哈希函数组织的 unordered_map; unordered_set; unordered_multimap; unordered_multiset;
2.1动态内存
智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。 智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。
shared_ptr sp;
shared_ptr维护了一个引用计数,用于检测当前对象所管理的指针是否还被其他智能指针使用(必须是shared_ptr管理的智能指针),在析构函数时对其引用计数减一,判断是否为0,若为0,则释放这个指针和这个引用计数的空间。 声明一个空智能指针,指向类型为T的对象。 make_shared(args) ,返回一个shared_ptr,指向动态分配的类型为T的对象,使用args来初始化该对象。 shared_ptr p3 = make_shared(42);或 auto p3 = make_shared(42); auto q§;
p.get();返回一个内置指针,指向智能指针管理的对象。
注意:get用来迹象指针的访问权限传递给代码,但只有在确保代码不会delete指针的前提下才能get。特别是永远不要使用get初始化另一个智能指针或者为另一个智能指针赋值。
有循环引用的问题:
现在我们假设一种最简单的情况,这个双向链表中只有两个节点,并且p1的prev和p2的next都指向空。但注意,这时p1本身管理一段空间,p2的prev也管理p1管理的这块空间,所以p1下的引用计数为2,在p1的析构函数时对其引用计数减一,发现并没有为0,所以选择不释放p1的空间和p1的引用计数的空间。这样就造成了内存泄漏。我们称之为:循环引用
weak_ptr:
顾名思义,weak_ptr是一个弱引用,只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。
unique_ptr:
unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。
std::unique_ptr<int> uptr(new int(10)); //绑定动态对象
auto_ptr:
不要使用两个auto_ptr指针指向同一个指针,
int a=10;
int *p=&a;
auto_ptr p1(p);
auto_ptr p2(p1);
此时,p1这个智能指针就无法管理p这块内存内存空间了,因为在auto_ptr的拷贝构造函数中是这样实现的:
auto_ptr(auto_ptr& p)
:_ptr(p._ptr)
{
_ptr=NULL;
}
3>不要使用auto_ptr指向一个指针数组,因为auto_ptr的析构函数所用的是delete而不是delete[],不匹配;
总的来说auto_ptr并不推荐使用。
第三部分,
拷贝控制
拷贝构造函数自己的参数必须为引用类型。
因为当一个参数具有非引用的返回类型时,返回值会被用来初始化调用方法的结果。如果拷贝构造函数参数不是引用,则为了调用拷贝构造函数,我们必须拷贝它的实参,而为了拷贝实参,我们必须调用拷贝构造函数,这样就产生了无限循环。
运算符重载
输入输出运算符重载必须为非成员函数。
opp(object-oriented programming)面向对象程序设计
面向对象程序设计的核心思想是数据抽象,继承和动态绑定。
通过使用数据抽象,我们可以将接口与实现分离。
通过使用继承,可以定义相似的类型并对其相似关系建模。
使用动态绑定,可以在一定程度上忽略相似类型的区别,而以统一的方式使用他们的对象。
虚函数
任何构造函数之外的非静态函数都可以是虚函数。
子类中有需要在堆中申请空间的成员变量,那需要将父类的析构函数声明为虚函数。
当我们用一个派生类对象初始化一个基类对象时,对象的派生类部分会被“切掉”,只剩下基类部分赋值给基类对象。所以派生类可以转为基类,而基类不一定会转为派生类。
模板与泛型编程
类模板与函数模板,模板特例化,可变参数模板。
可变参数模板接受数目和类型可变的参数。
tuple类型
和pair类型类似,pair只有两个成员,而tuple可以有多个。
初始化:
tuple<T1,T2,......,Tn> T(v1,v2,....,vn);
赋值
auto T1=make_tuple(v1,v2,....,vn);
get<I>(T1);获取T1中第i个元素
bitset类型
bitset<n> b(u);b是 u的低n位拷贝。
如果bitset类型长度大于unsigned long的值的二进制位数,则其余的高阶位将置为0;如果bitset类型长度小于unsigned long值的二进制位数,则只使用unsigned值中的低阶位,超过bitset类型长度的高阶位将被丢弃。
bitset<n> b;b有n位,每一位均是0;
string str("11100");
bitset<8> bitvec5(str);//用string来初始化bitset对象,string对象直接表示为位模式。从string对象读入位集的顺序是从右往左。
bitset的相关操作函数如下:
左值引用和右值引用
在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。
int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。
new和delete执行时的操作
string *sp=new string("aaa");
以上new进行了3部操作:
1new表达式调用一个名为operator new的标准库函数。该函数分配一个足够大的,原始的,未命名的内存空间一边存储特定类型的对象。
2编译器运行相应的构造函数以构造这些对象,并为其传入初始值。
3对象被分配了空间并构造完成,返回一个指向该对象的指针。
delete sp;
delete进行了一下部分:
1对sp所指的对象中的元素执行相应的析构函数。
2编译器调用名为operator delete的标准库函数释放内存空间。
union的使用
union是一种“类似”与struct的联合体,联合的所有成员引用的是内存中的相同位置,以最大的成员的内存长度作为union的内存大小。union主要用来节省空间,默认的访问权限是公有的。
(1)同一个内存段可以用来存放几种不同类型的成员,但在每一个时刻只能存在其中一种,而不能同时存放几种,即每一瞬间只有一个成员起作用,其它的成员不起作用,不能同时都存在和起作用;
(2)共用体变量中起作用的成员是最后一个存放的成员,在存入一个新的成员后,原有的成员就会失去作用,即所有的数据成员具有相同的起始地址。
volatile限定符
对于变量,将其声明为volatile可以提高其效率。
|