C++抱佛脚自用
参考文献:
- 秋招反思录 https://zhuanlan.zhihu.com/p/102103196
- 好东西c++11在线编译器:https://www.tutorialspoint.com/compile_cpp11_online.php
关键字
常考的关键字有 const、sizeof、typedef、 inline、 static、 extern、 new、 delete、 struct、 volatile、auto
auto
用法
(1)声明变量时根据初始化表达式(所以必须初始化)自动推断该变量的类型
- 增加程序可读性,如可避免写iterator声明
- 跟动态类型语言中数据hi自动进行拓展的特性不一样
(2)声明函数时函数返回值的占位符 - auto ptr = [](double x){return x*x;};//类型为std::function<double(double)>函数对象
注意
auto* y = new auto(9); // Fine. Here y is a int* auto z = new auto(9); //Fine. Here z is a int* (It is not just an int) 默认是指针,除非用&修饰为引用
new&delete
和 sizeof 类似,new 和 delete 也不是函数,它们都是 C++ 定义的关键字,通过特定的语法可以组成表达式
operator new 和 operator delete
void *operator new(size_t); //allocate an object void *operator delete(void *); //free an object void *operator new[](size_t); //allocate an array void *operator delete[](void *); //free an array ———————————————— 版权声明:本文为CSDN博主「海风林影」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/hazir/article/details/21413833
这两个函数和 C 语言中的 malloc 和 free 函数有点像了,都是用来申请和释放内存的,并且 operator new 申请内存之后不对内存进行初始化,直接返回申请内存的指针
new&delete工作
new的工作
- 首先需要调用上面提到的 operator new 标准库函数,传入的参数为 class A 的大小,这里为 8 个字节
- 第二步就在这一块原始的内存上对类对象进行初始化,调用的是相应的构造函数
- 最后一步就是返回新分配并构造好的对象的指针
delete的工作
- 调用析构函数
- 调用 operator delete 释放内存
new[] & delete[]
delete[] 调用析构函数的次数是从数组对象指针前面的 4 个字节中取出;
传入 operator delete[] 函数的参数不是数组对象的指针 pAa,而是 pAa 的值减 4。
delete this 的后果
If that object from which this member function is called is created on the stack then deleting this pointer either crash your application or will result in undefined behavior.
If that object from which this member function is called is created on the heap using new operator, then deleting this pointer will destroy the object. It will not crash the application at that particular time but after it, if some member function will try to access the member variable through this object then the application will crash.
const
- 一个很好的总结 http://itren.xiaolee.net/p/2288418.html
- 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量
- 使用extern则声明时就分配空间,否则被引用时再分配空间。
- 使用extend将const改为外部连接,作用于扩大至全局,编译时会分配内存,而且能够不进行初始化,编译器觉得在程序其它地方进行了定义
- define 和const的区别(编译阶段、安全性、内存占用等)
- const常量在编译阶段使用,有类型检查,宏在预编译(预处理)阶段字符串替换,相对不安全
define PI 3.14 //预处理后 占用代码段空间,出现多次则占用多次
- const float PI=3.14; //本质上还是一个 float,占用数据段空间
C++中的const
- https://blog.csdn.net/dianxin113/article/details/77680327
const 成员变量
- 类似static,const成员变量也不能在类定义处初始化
- 只能通过构造函数初始化列表进行,并且必须有构造函数。
- const数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,所以不能在类的声明中初始化const数据成员,只能在构造函数中创建。
- 要想建立在整个类中都恒定的常量,应该用类中的枚举常量或者static const实现
cosnt成员函数
- 主要目的是防止成员函数修改对象的内容。即const成员函数不能修改成员变量的值,但可以访问成员变量,即成员变量是只读的。
C++的顶层const和底层const
- https://blog.csdn.net/yusiguyuan/article/details/38454825
- 顶层 const 表示的是 指针这个对象本身是一个常量, 底层 const 表示的是 指针所指的对象是一个常量
- const永远修饰const出现地点左边的声明类型. 如果左边没有声明类型,就修饰右边。
static
用途
- 静态局部变量:
- 用于函数体内部修饰变量,在全局静态数据区分配
- 在第一次被调用时初始化
- 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,便于维护。
- 静态全局变量:定义在函数体外,用于修饰全局变量,表示该变量只在本文件可见
- 别人的extern发现不了他
- 也不怕重名了,是一种对双方的保护
- 静态函数:准确的说,静态函数跟静态全局变量的作用类似
- 别人的extern发现不了他
- 不怕重名冲突
- 类的静态数据成员
- 在全局静态数据区分配空间
- 静态数据成员定义时要分配空间,所以不能在类声明中定义。
- static关键字只能用于类定义体内部的声明中,初始化时不能标示为static
- 只能这样:int Rectangle::s_sum = 0; //初始化不加static,也不加private等权限控制符
- 类的静态成员函数
- 原文链接:https://blog.csdn.net/dianxin113/article/details/77680327
- 可以用类名::函数名进行访问。也可以通过对象访问。
- static成员函数主要目的是作为类作用域的全局函数。
- 不能访问类的非静态数据成员。类的静态成员函数没有this指针,这导致:
- 1、不能直接存取类的非静态成员变量,调用非静态成员函数
- 2、不能被声明为virtual
inline
https://blog.csdn.net/linkedin_35878439/article/details/79217390
- 和#define宏定义函数的区别
- 内联函数在编译时展开,走类型检查等编译过程的功能;而宏在预编译时展开,字符串替换不安全
- 任何在类的说明部分定义的函数都会被自动的认为是内联函数,例如 Inline tablefunction(int I) {return I*I};
- 因为仅有声明不足以让编译器在调用点内联展开函数的代码,内联函数定义在.h头文件中
struct
在C++中struct得到了很大的扩充:
1.struct可以包括成员函数
2.struct可以实现继承
3.struct可以实现多态
和class区别
1.默认的继承访问权。class默认的是private,strcut默认的是public。
2.默认访问权限:struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。
3.“class”这个关键字还用于定义模板参数,就像“typename”
4.如果没有定义构造函数,struct可以用大括号初始化。如果没有定义构造函数,且所有成员变量全是public的话,class可以用大括号初始化
this
- this 是 const 指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的。
- this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的。
- 只有当对象被创建后 this 才有意义,因此不能在 static 成员函数中使用
extern
- 在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。
- extern声明的位置对其作用域也有关系,如果是在main函数中进行声明的,则只能在main函数中调用,在其它函数中不能调用。
- 其实要调用其它文件中的函数和变量,只需把该文件用#include包含进来即可,为啥要用extern?因为用extern会加速程序的编译过程,这样能节省时间。
- 在C++中extern还有另外一种作用,用于指示C或者C++函数的调用规范。比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同,用此来解决名字匹配的问题
引用
- https://blog.csdn.net/tianxiaolu1175/article/details/46889523
和指针不同
- 引用不能为空
- 必须在创建同时就初始化,不能单单声明
- 引用不能更换目标
- 存在指针数组,不存在引用数组
引用的使用场合
别名
单纯图方便引用型参数
- 不存在对象复制,避免了对象的开销
- 可以在修改形参的同时对实参的修改。
- 为了避免函数对原来实参的意外修改可以用const修饰,也就是传常引用.
引用型返回值
- 从函数中返回引用,一定要保证在函数返回以后,被引用的目标一直有效,也就是不能返回函数体内的局部对象的引用,大家都知道, 局部对象离开作用域就会被析构掉,所以不能返回对函数体内的局部对象的引用。
- 常见的用法是返回内部对象的常量引用
public: // 一能让外界访问配置,二能避免拷贝,三能避免不受控制的配置变化(const) // 如果直接返回类对象同样的会创建临时对象带来开销 // 如果返回指针则调用者需要判断是否为空,而返回引用则可以非常高效的直接使用 const Config& config() { return config_; } ... ... };
类型转换
- https://blog.csdn.net/ydar95/article/details/69822540
const_cast
- 其去除常量性的对象必须为指针或引用,不是用于去除变量的常量性
- 如果被指对象本身是常量,即使去除了指针或引用的常量性,仍然无法通过其修改对象值
- const_cast只能调节类型限定符,不能更改基础类型
static_cast 静态类型转换
- 只要C语言中能够隐式转换的所有类型都可以使用static_cast进行转换。如果类型不能兼容,编译阶段会报错,如 char -> int.
- 没有动态类型检查,子类转父类不安全
- static_cast不能转换掉原有类型的const、volatile属性
- 隐式类型转换本质都是用static_cast实现
dynamic_cast
原文链接:https://blog.csdn.net/qq_43313035/article/details/89512914 - 将一个父类的指针或引用,或者void*,转化为子类的指针或引用(安全的向下转型,向上转型总是安全的,肯定成功)
- 不能用于其他数据类型转换
- 基类中一定要有虚函数,否则编译不通过
- 对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针。
- 对引用进行dynamic_cast,失败抛出一个异常bad_cast,成功返回正常cast后的对象引用。
- dynamic_cast在运行期强制转换,运行时进行类型检查,较安全。
reinterpret_cast 重解析类型转换
- C语言的强制类型转换的效果,不考虑转换后的后果. 如 char -> int.
- 本质是对二进制位在不同语义下进行再次解释
- 用在任意的指针之间的转换,引用之间的转换,指针和足够大的int型之间的转换
内存管理
C++内存管理HK Zhang https://www.cnblogs.com/1zhk/articles/5012956.html
4个分区
栈:函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放
堆:由new分配的内存块,一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。易于产生碎片。
自由存储区:由malloc等分配的内存块,用free来结束自己的生命的。
全局/静态存储区,全局变量和静态变量被分配到同一块内存中(C++里不区分是否初始化)
常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
tips
【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
【规则3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
【规则4】动态内存的申请与释放必须配对,防止内存泄漏。
【规则5】用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
面向对象
继承、封装与多态
面向对象的三个基本特征是:封装、继承、多态
- 封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏
- 继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展
- 多态,允许将子类类型的指针赋值给父类类型的指针,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作
C++多态
C++ 多态的实现原理分析:https://blog.csdn.net/afei__/article/details/82142775
虚函数表
- 存在虚函数时,编译器会这个类生成一个虚函数表
- 对于这个类的每个对象,自动生成一个相同的指向虚函数表的指针(通常称之为 vptr 指针)
- vptr指针位于该对象内存布局的最前面(如果是多重继承,可能存在多个虚函数表)
- 内存布局:https://blog.csdn.net/zongyinhu/article/details/51276806?tdsourcetag=s_pcqq_aiomsg
虚函数表指针vptr的分步初始化
- 对象在创建时,由编译器对 vptr 进行初始化
- 子类的构造会先调用父类的构造函数,这个时候 vptr 会先指向父类的虚函数表
- 子类构造的时候,vptr 会再指向子类的虚函数表
- 对象的创建完成后,vptr 最终的指向才确定
纯虚函数
- https://blog.csdn.net/Hackbuteer1/article/details/7558868
目的
定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。这个类被叫做抽象类。定义
virtual void funtion1()=0
类的内存分布
https://blog.csdn.net/fuzhongmin05/article/details/59112081
- 每个对象所占用的存储空间只是该对象的非静态数据成员部分(虚函数指针和虚基类指针也属于数据部分)所占用的存储空间,而不包括函数代码所占用的存储空间
- 所有类成员函数和非成员函数代码存放在代码区。区别在与:类的非静态类成员函数其实都内含了一个指向类对象的指针型参数(即this指针),因而只有类对象才能调用(此时this指针有实值)
- 类的静态成员变量在类定义时就已经在全局数据区
构造函数和析构函数
为什么析构函数通常声明为虚函数
析构函数应当是虚函数,这样如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。构造函数里为什么一般不调用虚函数
虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于虚函数表还未构造成功,编译器无法知道对象的实际类型,会调用出错在析构函数中也不要调用虚函数
在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的构造函数、拷贝构造函数和赋值操作符的区别
https://www.cnblogs.com/wangguchangqing/p/6141743.html构造函数声明为explicit
通过将构造函数声明为explicit(显式)的方式可以抑制隐式转换C++11 构造函数的几种关键字(default delete)
struct NonCopyable{ NonCopyable() = default; NonCopyable(const NonCopyable&) = delete; NonCopyable& operator=(const NonCopyable&) = delete; };
构造函数的冒号
有四种情况下应该使用初始化表达式来初始化成员:
1:初始化const成员
2:初始化引用成员
3:当调用基类的构造函数,而它拥有一组参数时
4:当调用成员类的构造函数,而它拥有一组参数时。class A { public: A(int &c):n(1),m(c) { } const int n; int &m; };
#include<iostream.h> class A { public: int x; A(int a=0){x=a}; } }; class B1:publicA { public; int y1; B1(int a=0,int b=0):A(b) { y1=a; } };
STL
STL容器的几种迭代器
输入迭代器(Input Iterator):只能向前单步迭代元素,不允许修改由该迭代器所引用的元素;
输出迭代器(Output Iterator):只能向前单步迭代元素,对由该迭代器所引用的元素只有写权限;
向前迭代器(Forward Iterator):该迭代器可以在一个区间中进行读写操作,它拥有输入迭代器的所有特性和输出迭代器的部分特性,以及向前单步迭代元素的能力;
双向迭代器(Bidirectional Iterator):在向前迭代器的基础上增加了向后单步迭代元素的能力;
随机访问迭代器(Random Access Iterator):不仅综合以后4种迭代器的所有功能,还可以像指针那样进行算术计算;
vector、deque提供的是随机访问迭代器,list提供的是双向迭代器,set和map提供的是向前迭代器。
4种使用上的迭代器
- 手动实现 还真tmd考哎
编译过程和头文件
C/C++程序编译过程详解:https://www.cnblogs.com/mickole/articles/3659112.html
- 编译过程:源代码编译器=>汇编代码汇编器=>目标代码
- 链接过程:(目标代码+其他目标代码+库文件)*链接器=>可执行程序