数据类型
基本类型
- 整型(int) 4字节
- 单精度浮点型(float) 4字节
- 双精度浮点型(double) 8字节
- 布尔型(bool) 1字节
- 字符型(char) 1字节
- 无类型(void)
- 宽字符型(wchar_t) 2或4字节
typedef short int wchar_t;
扩展类型
- 枚举类型(enum)
- 结构体(struct)
- 联合体(union)
基本类型可以使用一个或多个修饰符进行修饰,如:signed
unsigned
short
long
-
signed
和unsigned
不改变类型空间大小 -
short
修饰占用空间为2个字节 -
long
增加为2倍字节大小 - 可以使用
sizeof()
获取字节长度,但是需要清楚了解每个大小,面试经常问
枚举类型
限定作用域
enum class open_modes {input, output, append};
不限定作用域
enum color{red, yellow, green};
enum {floatPrec = 6, doublePrec = 10};
结构体
struct
和typedef struct
typedef struct Student {
int age;
} S;
等价于
struct Student {
int age;
};
typedef struct Student S;
struct
和class
- 继承访问权限:
struct
默认是public
,class
默认是private
- 内部成员访问权限:
struct
是public
,class
是private
字节对齐
各个硬件平台对存储空间的处理上有很大不同,保证字节对齐可以兼容各个硬件平台,但是会在存取效率上带来损失
内存对齐规则:
- 结构体各个成员,第一个偏移量是0,排列在后面的成员其当前的偏移量必须是当前成员类型的整数倍
- 结构体占用内存大小是体内最大数据成员的最小倍数
- 如程序中有
#pragma pack(n)
预编译指令,所有成员对齐以n
字节为准(偏移量是n
的整数倍),不再考虑当前类型以及结构体内最大类型
具体可以参考内存对齐规则之我见
联合体
- 默认访问控制为
public
- 可以含有构造函数和析构函数
- 不能含有引用类型成员
- 不能继承,不能被继承
- 不能含有虚函数
- 匿名
union
在定义所在作用域可直接访问成员 - 匿名
union
不能包含protected
和private
成员 - 全局匿名
union
必须是static
的
智能指针
shared_ptr
unique_ptr
weak_ptr
-
auto_ptr
(C++11已弃用)
shared_ptr
- 多个智能指针可以共享一个对象,对象最末一个拥有者有责任销毁对象
- 支持定制型删除器,自动解除互斥锁
weak_ptr
- 允许共享但不拥有某种对象,一旦最后一个失去了所有权,任何
weak_ptr
都会自动成空 - 只拥有
default
copy
和接受shared_ptr
的都构造函数 - 可打破环状引用的问题
unique_ptr
一种在异常时可以帮助避免资源泄露的智能指针
采用独占式拥有,可以确保一个对象和其相应的资源同一时间只能被一个指针拥有
一旦被销毁或还是拥有另一个对象,先前的资源都会被释放
用于替代
auto_ptr
auto_ptr
可以赋值拷贝;unique_ptr
无拷贝赋值,但提供了move
函数auto_ptr
不能管理数组,析构调用delete
;unique_ptr
可以管理数组,析构调用delete[]
强制类型转换
static_cast
dynamic_cast
const_cast
reinterpret_cast
static_cast
- 用于非多态类型转换
- 不执行运行时类型检查,安全性不如
dynamic_cast
- 通常用于数值类型,如
float
->int
- 可以在整个类层次结构中移动指针,子类转换为父类安全,父类转子类不安全
dynamic_cast
- 用于多态类型的转换
- 执行运行时类型检查
- 只适用于指针或引用
- 对不明确的指针的转换将失败,但不引发异常
- 可以在整个类层次结构中移动指针,包括向上转换,向下转换
const_cast
- 用于删除
const
volatile
__unaligned
特性
关键字
const
- 修饰变量,说明变量不可以被改变
- 修饰指针,指向常量的指针(
const char*
)和指针常量(char* const
) - 修饰引用,指向常量的引用,避免拷贝又避免函数对值修改
- 修饰成员函数,该成员函数内部不能修改成员变量
被const修饰(在const后面)的值不可改变
static
- 修饰普通变量,修改变量存储区和生命周期,使变量在静态区,在
main
函数运行前就分配了空间,默认会会初始化 - 修饰普通函数,表明函数作用范围,仅定义该函数的文件内才能使用
- 修饰成员变量,使所有的对象只保存一个该变量,不需要对象就可以访问该成员
- 修饰成员函数,不需要对象就可以访问,但是
static
函数内不能访问非静态成员
this
-
this
是一个隐含于每一个非静态成员函数中的一个特殊指针,指向调用该成员函数的那个对象 - 每次调用成员函数时,编译程序先将对象的地址赋给
this
指针,然后调用成员函数,每次成员函数存取数据成员时,都隐式使用this
指针 -
this
指针被隐式声明为ClassName *const this
,意味着不能给this
赋值,不可修改 -
this
是一个右值,不能取得this
的地址 - 需要显式引用
this
指针:对象的链式引用;避免同一对象进行赋值操作;实现一些数据结构时
inline
特征
- 把内联函数里面的内容写在调用内联函数处
- 不用执行进入函数的步骤,直接执行函数体
- 相当于宏,比宏多了类型检查,具有函数特性
- 编译器一般不内联包含循环、递归、switch等复杂操作的内联函数
- 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式当成内联函数
编译器处理
- 编译器会把函数体复制到
inline
函数调用点处 - 编译器为所用
inline
函数中的局部变量分配内存空间 - 编译器将
inline
函数所有的输入参数和返回值映射到调用方法的局部变量空间中 - 如果有多个返回点,编译器会将其变为
inline
函数代码块末尾的分支
优缺点
- 省去了参数压栈、栈帧开辟与回收,提高运行速度
- 会做安全检查,宏不会
- 内联函数可以访问类的成员变量,宏不能
- 内联函数运行时可调试,宏不能
- 代码会膨胀
- 是否内联,程序不可控。内联函数只是对编译器的建议,决定权在于编译器
- 虚函数可以时内联函数,但虚函数表现多态性时不能内联(因为编译期内联,运行时多态)
volatile
- 声明的类型变量可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改,所以使用
volatile
告诉编译器不应该对这样的对象进行优化 -
volatile
声明的变量,每次访问时必须从内存中取值,没有被volatile
修饰的变量,可能由于编译器的优化,从CPU寄存器取值 -
const
可以是volatile
只读状态寄存器 - 指针可以是
volatile
assert
- 是宏,而非函数
- 如果条件返回错误,终止程序执行
- 可以通过
NDEBUG
关闭assert
,但需要在源代码的开头,include <assert.h>
之前
explicit
-
explicit
修饰构造函数时,可以防止隐式转换和复制初始化 -
explicit
修饰转换函数时,可以防止隐式转换,单按语境转换除外
friend
- 能访问私有成员
- 破坏封装性
- 不可传递
- 单向性
- 声明形式及数量不受限制
using
-
using
声明 引入命名空间的一个成员using namespace_name::name;
-
using
指示,使得特定命名空间的所有名字都可见using namespace name;
- 尽量少使用
using
指示,使用声明更安全且不会包含不需要的名称
decltype
- 用于检查实体的声明类型或表达式的类型及值分类
template<typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
//todo
return *beg;
}
template<typename It>
auto fnc2(It beg, It end) -> typename remove_referenct<decltype(*beg)>::type
{
//todo
return *beg;
}
面向对象
封装
把客观失误封装成抽象的类,并且可以把变量和方法只让可信对象操作,对不可信对象进行隐藏。关键字public
private
protected
.默认不写是private
-
public
任意对象可以访问 -
protected
子类和本类成员函数访问 -
private
本类,友元类或友元函数访问
继承
- 基类 ----> 派生类
多态
消息以多种形式显示的能力,以封装和继承为基础的
- 重载多态(编译期):函数重载,运算符重载
- 子类型多态(运行期):虚函数
- 参数多态(编译期):类模板,函数模板
- 强制多态(运行期/编译期):基本类型转换,自定义类型转换
一般函数重载和虚函数两个比较最多
虚函数
注意
- 普通函数不能是虚函数
- 静态函数不能是虚函数
- 构造函数不能是虚函数(因为在调用构造函数时,虚表指针并没有对象的内存空间,必须要构造函数调用完成后才会形成虚表指针)
- 内联函数不能是表现多态时的虚函数
- 模板类中可以使用虚函数
- 类的模板成员不能时虚函数
虚析构函数
虚析构函数是为了剞劂基类的指针指向派生类对象,并用基类的指针删除派生类对象
纯虚函数
在基类中不能对虚函数给出有意义的实现,实现留给派生类去完成
- 纯虚函数只是一个接口,要留到子类里实现;虚函数在声明的时候实现
- 纯虚函数在子类里必须重写;虚函数可以不重写
- 纯虚函数关注接口统一性,实现由子类完成;虚函数继承接口的同时也继承了父类的实现
- 带虚函数的类叫抽象类,不能直接生成对象,只有被继承后并重写虚函数后才能被使用,子类可以是抽象类也可以是普通类
虚函数指针和虚函数表
- 虚函数指针:在含有虚函数类的对象中,指向虚函数表,运行时确定
- 虚函数表: 在程序只读数据段,存放虚函数指针,如果派生类实现了某个虚函数,则在虚表中覆盖原来基类的那个虚函数指针,在编译时根据类的声明创建
虚继承
- 用于解决多继承条件下菱形继承问题(浪费存储空间,存在二义性)
- 原理与编译器有关,一般通过虚基类指针和虚基类表实现
每个虚继承的子类都有一个虚基类指针和虚基类表;当虚继承的子类被当作父类继承时,虚基类指针也会被继承;实际上虚基类表指针指向了一个虚基类表,虚表中记录了虚基类与本类的偏移地址,通过偏移地址就可以找到虚基类成员,虚继承不需要像普通多继承那样维持公共基类的两份拷贝,节省了存储空间
- 虚继承和虚函数都利用了虚指针和虚表
- 虚继承的虚基类依旧存在继承类中,占用存储空间;虚函数不会韩永存储空间
- 虚继承的虚基类表存储的时虚基类相对于继承类的偏移;虚函数表存储的时虚函数地址
抽象类、接口类和聚合类
- 抽象类:含有纯虚函数的类
- 接口类:仅含有纯虚函数的类
- 聚合类:可以直接访问其成员,并且具有特殊初始化语法形式。特点
- 成员都是
public
- 没有定义任何构造函数
- 没有类内初始化
- 没有基类也没有虚函数
- 成员都是
参考文献
C++总结
C++ Primer Edition 5