C和C++的区别?

C++是C的超集,兼容大部分C的语法的结构
C是面向过程的语言,而C++是面向对象的语言
C++支持函数重载,而C不支持函数重载
C++中有引用,而C没有
C++中的模板/泛型编程
C中没有异常处理,C++有tyr/catch, throw exception

变量的声明与定义

声明只是在名字表中注册了这个名字(使得名字为程序所知)
定义是为该名字的变量在内存中分配空间(创建与名字关联的实体)
变量要定义后才能使用

C++的三大特性

  • 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。使得代码模块化
  • 继承:可以扩展已存在的模块,它们目的都是为了:代码重用。
  • 多态:不同类型的对象调用同一接口时产生不同的行为。为了实现另一个目的:接口重用。

重载overload、覆盖(又称重写override)与隐藏

1.成员函数被“重载”的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
2.“覆盖”是指派生类重写了基类虚函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
3.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

作用域解析操作符scope resolution operator

用来限定要查找的变量、函数的范围
https://www.geeksforgeeks.org/scope-resolution-operator-in-c/

extern关键字

extern是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块或其他模块中使用,记住它是一个声明不是定义!
请记住, 只在头文件中做声明,真理总是这么简单。
https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html
注意,不要犯重复定义的错误!

在C++程序中调用被C编译器编译后的函数,为什么要加extern “C”?

答:首先,extern是C/C++语言中表明函数和全局变量作用范围的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。extern "C"是链接声明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。作为一种面向对象的语言,C++支持函数重载,而C语言则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:void foo( int x, int y );该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。
所以,可以用一句话概括extern “C”这个声明的真实目的:解决名字匹配问题,实现C++与C的混合编程。

#include<file.h>#include "file.h"的区别?

答:#include "file.h"会先在代码文件所在当前目录搜索该文件file.h,如果无法找到,再去环境变量INCLUDE指示的目录下找

类型转换

非bool型 -> bool型:初始值为0则结果为false,否则为true
bool型 -> 非bool型:初始值为false则为0,初始值为true则为1
浮点数 -> 整数类型:仅保留整数部分的值
整数 -> 浮点类型:小数部分记为0
其他类型 -> 无符号类型:如果没有超出无符号类型数的表示范围,则正常转换。否则,结果是初始值对无符号类型数的总数取模后的余数。例如 unsigned char(0~255),如果给它赋值-1,则会转换成255.
!!!无符号数和有符号数切勿混合运算

左值与右值

在C++11中可以取地址的、有名字的就是左值;反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)
++a得到左值,而a++得到右值。
int operator++ (int)
而++右操作符操作函数时,相当于这样,返回的依然是一个int型,所以无论++在a的左边多少个都是可以的。
const int operator++()
注意这里返回的是一个const的,const只能作为右值,而不能作为左值的。
http://www.cnblogs.com/catch/p/3500678.html
https://blog.csdn.net/hyman_yx/article/details/52044632

inline和宏定义

内联inline:只是向编译器建议该函数需要内联,但是编译器并不一定会做。
内联函数要做参数类型检查, 这是内联函数跟宏相比的优势。
内联函数是指用inline关键字修饰的简单短小的函数,它在编译阶段会在函数调用处替换成函数语句,从而减少函数调用的开销。在类内定义的函数被默认成内联函数。
内联函数有一定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句。如果内联函数函数体过于复杂,编译器将自动把内联函数当成普通函数来执行。
宏定义是没有类型检查的,无论对还是错都是直接替换。内联函数在编译的时候会进行类型的检查,内联函数满足函数的性质,比如有返回值、参数列表等
宏定义和内联函数使用的时候都是进行代码展开。不同的是宏定义是在预处理的时候把所有的宏名替换,内联函数则是在编译阶段把所有调用内联函数的地方把内联函数插入。
1.宏定义是在预处理阶段进行简单替换,而内联函数是在编译阶段进行替换
2.编译器会对内联函数的参数类型做安全检查或自动类型转换,而宏定义则不会;
3.内联函数在运行时可调试,而宏定义不可以
4.在类中声明同时定义的成员函数,自动转化为内联函数。
5.宏定义会出现二义性,而内联函数不会
6.内联函数可以访问类的成员变量,宏定义则不能

typedef

typedef是为已存在数据类型定义一个新名字。例如

typedef struct Student
{
    int a;
}stu;

Student s1;
stu s2;

const 有什么用途?

const表示XXX是不变的。const变量需要初始化,否则以后也无法修改了,就没有意义了。
const变量
const int a=10;
const与指针
先说指向const变量的指针(底层const),它的意思是指针指向的内容是不能被修改的。它有两种写法。

  • const int* p; (推荐)
  • int const* p;

再说const指针(顶层const),它的意思是指针本身的值是不能被修改的。它只有一种写法

  • int* const p=[address];(因为指针的值是const的,所以必须要初始化)

const与引用
const int &a=10;表示a是一个常量的引用
const与函数
返回值修饰为const的
const int & func(){}
这样的话,返回值就可以当作左值。一般只出现在类的赋值函数中,目的是为了实现链式表达,例如<<。
const与类

class X{
    int i;
    int f() const;
}
int X::f() const {return i; }

const成员函数是不改变类的数据成员的一类函数,也就是只读函数。并且const成员函数不能调用非const成员函数(因为可能会造成修改)。
print() const和print()构成重载

static关键字的作用

对于类的static数据成员:若是const static 成员,可以在类内初始化,也可以在类外初始化;若是普通的static成员,必须在类外初始化。
static 全局变量只能在本文件中访问,其他文件不可见
static 函数只能在本文件中访问,其他文件不可见
static总是使得变量或对象的存储形式变成静态存储,连接方式变成内部连接(与之相反的是外部连接extern)。
对于局部变量(已经是内部连接了),它仅改变其存储方式;对于全局变量(已经是静态存储了),它仅改变其连接类型。

静态成员与非静态成员之间的可访问性?

静态成员之间可以互相访问;非静态成员可以访问静态成员及非静态成员,而静态成员不能访问非静态成员。

关于sizeof小结

答:编译阶段处理
(1) sizeof不计算static变量占得内存;
(2) 指针的大小一定是4/8(32/64位编译器)个字节,而不管是什么类型的指针(包括void*);
(3) char型占1个字节,short int占2个字节,int占4个字节,
long int占4个字节,float占4字节,double占4/8字节,string占4字节
一个空类占1个字节,单一继承的空类占1个字节,虚继承涉及到虚指针所以占4/8个字节(1个指针长度)
(4) 数组的长度
若指定了数组长度,则不看元素个数,总字节数=数组长度*sizeof(元素类型)
若没有指定长度,则按实际元素个数类确定
Ps:若是字符数组,则应考虑末尾的空字符。
(5) 结构体对象的长度
在默认情况下,为方便对结构体内元素的访问和管理,当结构体内元素长度小于处理器位数的时候,便以结构体内最长的数据元素的长度为对齐单位,即为其整数倍。若结构体内元素长度大于处理器位数则以处理器位数为单位对齐。
(6) unsigned影响的只是最高位的意义,数据长度不会改变,所以sizeof(unsigned int)=4
(7) 自定义类型的sizeof取值等于它的类型原型取sizeof
(8) 对函数使用sizeof,在编译阶段会被函数的返回值的类型代替
(9) sizeof后如果是类型名则必须加括号,如果是变量名可以不加括号,这是因为sizeof是运算符
(10)当使用结构类型或者变量时,sizeof返回实际的大小。当使用静态数组时返回数组的全部大小,sizeof不能返回动态数组或者外部数组的尺寸
(11) 对于类对象使用sizeof()。如果类是空的,至少size为1字节,因为每个对象必须有一个互相区别的地址。如果类中有虚函数,由于有vptr,它会占4字节(如果是64位编译器,则为8字节)。对于派生类对象,考虑基类部分数据成员的大小。
(12) sizeof(std::string) = 32, sizeof(string_object) = 32
sizeof(vector) = 24, sizeof(vector_object) = 24
(13) sizeof(union_object)取决于它所有的成员中,占用空间最大的一个成员的大小,然后对齐,对齐方式为补齐到成员中占用空间最大的成员的整数倍大小。
(14) sizeof(enum_object) = 4/8

strlen得到字符串的长度

strlen() returns the length of the C string str.
对于char数组,从第一个元素开始计算直到首次遇到’\0’为止(’\0’自动添加且不计算在内);如果要对string使用,记得strlen(s.c_str())。

new和delete

http://blog.jobbole.com/106923/
每次delete p;结束后记得将p置为NULL

new 运算符和 operator new()函数:
  • new:指我们在C++里通常用到的运算符,比如A* a = new A; 对于new来说,有new和::new之分,前者位于std。
  • operator new():指对new的重载形式,它是一个函数,不是运算符。对于operator new来说,分为全局重载和类重载。
new和delete操作
A* p = new A;
delete p;
A* p_a = new A[10];
delete[] p_a;

new A在堆上分配一个A对象大小的内存,并返回它的首地址。1.分配内存;2.调用A()构造对象。事实上,分配内存这一操作就是由operator new(size_t)来完成的。如果有重载,则会调用重载的operator new(size_t)。
delete p会 1.销毁给定的指针指向的对象;2.释放相应的内存;
delete p中的p必须指向一个动态分配的对象或是一个空指针(即0)

引用与指针

引用是变量的一个别名,内部实现是只读指针
引用变量内存单元保存的是被引用变量的地址。例如int &b = a;则&b(对b取地址)得到的是a的地址。

数组和指针的区别?

不是一个东西。
数组是用来表示内存中的一段连续存储空间,有基地址base和长度length,且都是不可修改的。数组的长度必须在编译时确定,因为需要为它分配内存空间。数组的地址不可修改。
指针用来存储某个对象的地址,地址可以修改(不是const指针的话)
数组名作右值时自动转换成指向首元素的指针,所以a[2]和pa[2]本质上是一样的,都是通过指针间接寻址访问元素
二维数组作为参数传递时需要指明数组的第二维,第一维不是必须要显式指定的。

内存分配函数

https://www.cnblogs.com/particle/archive/2012/09/01/2667034.html

  • malloc:在堆上申请内存,最常用;
  • calloc:malloc+初始化为0;
  • realloc:将原本申请的内存区域扩容,参数size大小即为扩容后大小,因此此函数要求size大小必须大于旧ptr内存大小。
  • alloca:唯一在栈上申请内存的,无需释放;

C++中有malloc/free,为什么还有new/delete?

答:malloc/free是C/C++标准库函数,new/delete是C++运算符operator。他们都可以用于动态申请和释放内存。
对于原始数据数据而言,二者没有多大区别。malloc申请内存的时候要指定分配内存的字节数,而且不会做初始化;new申请的时候有默认的初始化,同时可以指定初始化;
对于类类型的对象而言,用malloc/free无法满足要求的。对象在创建的时候要自动执行构造函数,消亡之前要调用析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制之内,不能把执行构造函数和析构函数的任务强加给它,因此,C++还需要new/delete。

new的过程
  1. 向堆上申请一块内存空间(做够容纳对象A大小的数据)(operator new)
  2. 调用构造函数
  3. (调用A的构造函数(如果A有的话))(placement new) 返回正确的指针
    注意:placement new是在已分配的一块内存buffer上构造对象。需要手动调用对象的析构函数来析构对象。而内存buffer的释放由buffer变量负责
    https://blog.csdn.net/d_guco/article/details/54019495

内存对齐的原则

  1. 结构体内成员按自身长度自对齐。自身长度,如char=1,short=2,int=4,long=8,double=8。所谓自对齐,指的是该成员的起始位置的内存地址必须是它自身长度的整数倍。如int只能以0,4,8这类的地址开始
  2. 结构体的总大小为结构体的有效对齐值的整数倍,不足时应当补齐。当用#pragma pack(n)指定时,以n和结构体中最长的成员的长度中较小者为有效对齐值。

智能指针

智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装的模板类
智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除该对象)。智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。
智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。

拷贝控制

拷贝构造函数 (参数是左值引用)
拷贝赋值运算符(参数是左值引用)
移动构造函数(参数是右值引用)
移动赋值运算符(参数是右值引用)
析构函数
https://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html
CSomething c[3];//使用无参构造函数C(),创建了3个对象。
可通过将拷贝控制成员定义为=default来显示要求编译器为我们rengong合成拷贝控制成员。
可以通过在拷贝构造函数和拷贝赋值运算符后面加=delete 来阻止拷贝。

C++ 拷贝构造函数和拷贝赋值运算符

https://www.cnblogs.com/wangguchangqing/p/6141743.html
调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。
深拷贝浅拷贝主要是针对类中的指针和动态分配的空间来说的。浅拷贝仅拷贝类中成员变量的值,而深拷贝会把类中指针指向的内存拷贝一份(当然指针也会拷贝)。这两块内存互不影响。
类的静态成员是所有类的实例共有的,存储在全局(静态)区,只此一份,不管继承、实例化还是拷贝都是一份。因此类的静态成员不允许深拷贝。

仅有2种值传递方式

pass by value:实参的值被拷贝给形参
pass by reference:形参被绑定到了实参上,或者说,形参是实参的别名。

面向对象

关键知识点

派生类中和基类同名的变量,有各自的作用域,所以可以同时存在。

this指针

this是指向对象自己的指针。

多态分为哪几种?

分为编译时多态性和运行时多态性。

  • 编译时多态性:通过函数重载实现。重载允许有多个同名的函数,而这些函数的参数列表不同。编译器会根据实参类型来选定相应的函数。
  • 运行时多态性:通过虚函数实现。虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者重写。
动态绑定

运行时根据引用或指针所绑定对象的实际类型来选择执行虚函数的某一个版本
构造函数、静态函数不能是虚函数。
访问说明符:控制派生来从基类继承而来的成员是否对派生类的用户可见
类成员的初始化应该交给自己类的构造函数去完成,派生类不要初始化从基类继承而来的成员
派生类可以继承基类的静态成员,也可以覆盖基类的静态成员变量
C++11中,可以在派生类的成员函数的形参列表后使用override关键字,显示地说明它将覆盖基类的虚函数
C++11中防止继承:在类名后、函数形参列表后使用final关键字
对象切割Object Slicing:当把一个派生类对象赋给一个基类对象时,会发生对象切割。派生类对象中仅有基类部分的成员会被拷贝和赋值。

虚函数相关

虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组.而对象的隐藏成员–虚拟函数表指针是在运行期–也就是构造函数被调用时进行初始化的,这是实现多态的关键。
RTTI在C++中体现为dynamic_cast和typeid。如何实现RTTI?
VS中虚函数表的-1位置存放了指向type_info的指针。对于存在虚函数的类型,typeid和dynamic_cast都会去查询type_info.

多态性:指同样的消息被不同类型的对象接收时导致不同的行为(不同类型的对象调用同一接口时产生不同的行为)。这里讲的消息就是指对象的成员函数的调用,而不同的行为是指不同的实现,也就是调用了不同的函数。引用或指针的静态类型Base*与动态类型Derived*不同是C++语言支持多态性的根本所在(对于不是引用或指针的对象,类型是确定的,不存在动态绑定)。
只有引用或指针调用虚函数时会发生动态绑定(dynamic binding)
在构造函数中调用一个虚函数的情况,被调用的只是这个函数的本地版本(early binding的函数的版本)。
纯虚函数:在虚函数后加上=0.表明该函数没有实际意义,只能用来重写。
抽象基类:含有纯虚函数的类是抽象基类。抽象基类负责定义接口,其派生类可以覆盖/重写该接口。不能创建一个抽象基类的对象。对于一个抽象基类,如果其派生类没有重写基类中的所有虚函数,则派生类也还是抽象类(仍是不完整的)
虚函数虽然可以写inline,但是最终编译器并不会内联。因为内联函数是静态行为(编译时),虚函数是动态行为(运行时),编译时并不知道调用者是谁,内联的代码该取谁的代码;
动态绑定如何实现:需要vtable来保存特定类的虚函数的地址,而vptr指向vtable。vptr保存在有虚函数的类的对象中,占大小为1个指针的size。

构造函数为什么不能是虚函数?

答:构造函数如果是虚函数,那么虚函数机制、动态绑定生效之前,需要知道指针到底指向哪个对象(即对象已存在)。但是构造函数还未完成,对象并没有产生。矛盾。

基类的析构函数不是虚函数会怎样?

答:如果有一个基类指针指向派生类对象,当对象销毁时,由于没有虚函数,不会发生动态绑定,调用的是基类的析构函数(因为是基类对象),就会造成内存泄漏。

成员访问权限控制

public:用该关键字修饰的成员表示公有成员,该成员不仅可以在类内可以被访问,在类外也是可以被访问的,是类对外提供的可访问接口;
private:用该关键字修饰的成员表示私有成员,该成员仅在类内可以被访问,在类体外是隐藏状态;
protected:用该关键字修饰的成员表示保护成员,保护成员在类体外同样是隐藏状态,但是对于该类的派生类来说,相当于公有成员,在派生类中可以被访问。
详见下图:

C++的3种继承方式

public、protected、private
派生类中派生类部分成员的访问权限就是取决于派生类中定义的权限,而基类部分成员的访问权限受到继承方式的影响。见下图。

C++友元

一个类class A想要允许非自己类的成员函数或一般函数访问自己类A的私有成员(及受保护成员),而同时仍阻止一般的访问的情况下,可以使用友元。

  • 友元函数:在类中将一般函数、其他类的成员函数声明成友元函数,那么这些友元函数就能够访问该类的私有、保护成员
    友元类:在类A中将类B声明成友元类,那么这个友元类B的所有成员函数都是类A的友元函数,能够访问类A的私有、保护成员

https://www.cnblogs.com/zhuguanhao/p/6286145.html

内存区域划分(五个段)

https://blog.csdn.net/chenlycly/article/details/53377942
栈起始地址在高地址,堆起始地址在低地址。栈stack是从高地址向低地址生长,而堆heap是由低地址向高地址生长。

代码段 --text(code segment/text segment)
.text段在内存中被映射为只读,但.data和.bss是可写的。
text段是程序代码段,在AT91库中是表示程序段的大小,它是由编译器在编译连接时自动计算的,当你在链接定位文件中将该符号放置在代码段后,那么该符号表示的值就是代码段大小,编译连接时,该符号所代表的值会自动代入到源程序中。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
全局数据区(静态存储区)(static):
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域。另外文字常量区,常量字符串就是放在这里,程序结束后由系统释放。
(1) BSS段(bss segment):通常是指用来存放程序中未初始化的全局变量、静态全局变量的一块内存区域。BSS段属于静态内存分配。
(2) 数据段(data segment):通常是指用来存放程序中已初始化的全局变量、静态全局变量和静态局部变量(无论是否初始化)的一块内存区域。数据段属于静态内存分配。
stack:
栈,也称堆栈(stack)保存函数的局部变量(但不包括static声明的变量, static 意味着 在数据段中 存放变量),参数以及返回值。是一种“后进先出”(Last In First Out,LIFO)的数据结构,这意味着最后放到栈上的数据,将会是第一个从栈上移走的数据。对于哪些暂时存贮的信息,和不需要长时间保存的信息来说,LIFO这种数据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈(stack)的顶部在可读写的RAM区的最后。
heap:
堆(heap)保存函数内部动态分配内存,是另外一种用来保存程序信息的数据结构,更准确的说是保存程序的动态变量。堆是“先进先出”(First In first Out,FIFO)数据结构。它只允许在堆的一端插入数据,在另一端移走数据。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。

一些疑问
char str1[] = “abcd”;
const char str3[] = “abcd”;
const char *str5 = “abcd”;
char *str7 = “abcd”;
char数组是局部变量,存储在栈中
char指针指向的字符串是存储在静态存储区中

内存分配方式

静态分配和动态分配

  • 时间不同。静态分配发生在程序编译和连接的时候。动态分配则发生在程序调入和执行的时候。
  • 空间不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配是由malloc或new进行分配。不过栈的动态分配(使用alloca函数)和堆不同,它动态分配得到的内存是由编译器进行释放,无需我们手工实现。

动态内存分配与智能指针

在调用构造函数之前,对象就已经创建好了(内存已分配)
new与delete成对操作(对于数组,type *p = new type[100]; delete [] fp;
C++11后提供智能指针,shared_ptr、unique_ptr、weak_ptr、
auto_ptr(C++11后弃用)。auto_ptr不可作为容器元素,但unique_ptr,shared_ptr可以作为容器元素。。原因:stl的容器中有很多需要拷贝元素的操作,如排序、查找等。(unique_ptr)auto_ptr拷贝赋值时是所有权转移,而在容器中可能会产生临时对象拷贝赋值,临时对象获得所有权之后就析构了释放了对象。并且很难避免STL内部对容器中的元素实现赋值,这样便会使容器中多个元素被置位NULL。。
auto_ptr有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而unique_ptr则无拷贝语义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move()进行转移。
auto_ptr不可指向动态数组,unique_ptr可以指向动态数组。因为unique_ptr有unique_ptr<T[]>重载版本,销毁动态对象时调用delete[]。

auto_ptr

auto_ptr是C++标准库中(<utility>)为了解决资源泄漏的问题提供的一个轻量级智能指针类模板(注意:这只是一种简单的智能指针)
auto_ptr的实现原理其实就是RAII,在构造的时候获取资源,在析构的时候释放资源,并进行相关指针操作的重载,使用起来就像普通的指针。

auto_ptr<A> a(new A());

auto_ptr没有考虑引用计数,因此一个对象只能由一个auto_ptr所拥有,在给其他auto_ptr赋值的时候,会转移这种所有权。
auto_ptr不能共享所有权,即不要让两个auto_ptr指向同一个对象。
auto_ptr不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。

shared_ptr

允许多个shared_ptr指向同一个对象。它通常使用make_shared()来构造。
shared_ptr有一个引用计数来记录有多少个其他shared_ptr指向相同的对象。
当shared_ptr对象被拷贝时,计数加1;当shared_ptr对象被赋予新值或销毁时,计数减1;
当一个share_ptr的引用计数为0时,它会自动释放所管理的对象(包括动态分配的内存)。

unique_ptr

unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。
通过reset方法重新指定、通过release方法释放所有权、通过移动语义auto new_p = std::move(origin);转移所有权。

weak_ptr

weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。
使用weak_ptr的成员函数use_count()可以观测资源的引用计数.
另一个成员函数
expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。
weak_ptr可以使用一个非常重要的成员函数
lock()*从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。
!!!不要混合使用普通指针和智能指针

强制类型转换

C风格的类型转换
(int) a,或者 int (a)

C++11中四种类型转换

1. static_cast

功能:完成编译器认可的隐式类型转换。
type2 b = staic_cast<type1>(a);将type1的类型转化为type2的类型;
使用范围:
(1)基本数据类型之间的转换,如int->double;
(2)派生体系中向上转型:将派生类指针或引用转化为基类指针或引用(向上转型);

2. dynamic_cast

功能:执行派生类指针或引用与基类指针或引用之间的转换。
type2 b = dynamic_cast<type1>(a);将type1的类型转化为type2的类型;
(1)其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行运行时类型检查;
(2)基类中要有虚函数。因为运行时类型检查的类型信息在虚函数表中,有虚函数才会有虚函数表;
(3)可以实现向上转型和向下转型,前提是必须使用public或protected继承;

3. const_cast

功能:主要是用来去除复合类型中const和volatile属性(没有真正去除)。
原因是,我们可能调用了一个参数不是const的函数,而我们要传进去的实际参数确实const的,但是我们知道这个函数是不会对参数做修改的。于是我们就需要使用const_cast去除const限定,以便函数能够接受这个实际参数。
应该遵从这样的原则:使用const_cast去除const限定的目的绝对不是为了修改它的内容,只是出于无奈。

4. reinterpret_cast

reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位.

模板与泛型

编译时就知道类型是什么了
编译器用推断出的模板参数来为我们**实例化(instantiate)**一个特定版本的函数。
实例化:使用实际的模板实参代替对应的模板参数来创建出模板的一个新“实例”(instantiation)。
编译器遇到模板定义时,并不生成代码。只有当我们实例化出模板的一个特定版本(使用模板)时,编译器才生成代码。

函数模板

template <typename T>
int compare(const T &v1, const T &v2){
    if(v1 < v2) return -1;
    else if(v2 < v1) return 1;
    else return 0;
}

**模板实参推断:**调用函数模板时,编译器通常用函数实参来为我们推断模板实参。
类模板因为没有模板实参推断,为了使用类模板,必须要提供额外的信息(在模板名后面的尖括号中)
可以为函数模板提供显示模板实参,显示模板实参按由左至右的顺序与对应的模板参数匹配。例如:

// 编译器无法推断T1,因为它未出现在函数参数列表中
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);

调用时可以使用sum<long long>(i,long);//long long sum(int, long)来显示指定T1的类型。

成员函数模板

和函数模板类似。
如果在类模板外定义成员模板,必须同时为类模板和成员模板提供模板参数列表。类模板的参数列表在前,成员函数模板的参数列表在后。

重载与模板

函数模板可以被另一个模板或一个普通非模板函数重载。

模板特例化与偏特例化

函数模板特例化
当特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参。使用关键字template后跟一个空尖括号<>,即template <>,以指出我们正在特例化一个模板。

template <typename T>
void fun(T a)
{
	cout << "The main template fun(): " << a << endl;
}

template <>   // 对int型特例化
void fun(int a)
{
	cout << "Specialized template for int type: " << a << endl;
}

int main()
{
	fun<char>('a');
	fun<int>(10);
	fun<float>(9.15);
	return 0;
	//output
	/*
	The main template fun(): a
    Specialized template for int type: 10
    The main template fun(): 9.15
    */
}

对于除int型外的其他数据类型,都会调用通用版本的函数模板fun(T a);对于int型,则会调用特例化版本的fun(int a)。**注意,一个特例化版本的本质是一个实例,而非函数的重载。**因此,特例化不影响函数匹配。
!!!函数模板不能偏特化

类模板特例化

template <typename T>
class Test{
public:
	void print(){
		cout << "General template object" << endl;
	}
};

template<>   // 对int型特例化
class Test<int>{
public:
	void print(){
		cout << "Specialized template object" << endl;
	}
};

另外,与函数模板不同,类模板的特例化不必为所有模板参数提供实参。我们可以只指定一部分而非所有模板参数,这种叫做类模板的偏特化 或部分特例化(partial specialization)。

类模板偏特例化

template <typename T, typename Allocator>
class vector
{
	/*......*/
};

// 部分特例化
template <typename Allocator>
class vector<bool, Allocator>
{
	/*......*/
};

在vector这个例子中,一个参数被绑定到bool类型,而另一个参数仍未绑定需要由用户指定。**注意,一个类模板的部分特例化版本仍然是一个模板,**因为使用它时用户还必须为那些在特例化版本中未指定的模板参数提供实参。

10-07 10:14