C++知识点小记
C++知识点小记
一.\(Static\)关键字
静态变量
在变量之前加上关键字static,该变量就被定义成了一个静态变量。
静态全局变量和全局变量
- 它存储在静态区,局部变量存在栈区。
- 在C++中它被默认为初始化为0,C语言是任意的
- 全局变量和静态全局变量的存储方式一样,但是全局变量在整个源代码中都可以使用,静态全局变量只能存在于当前文件中。
静态函数
- 静态函数不能被其他文件使用
- 其他文件可以定义相同名字的函数,不会发生冲突
- 静态成员可以相互访问
- 非静态成员函数可以访问静态成员函数
- 静态成员函数不可以访问非静态成员函数
静态成员存在全局区。
静态成员函数没有\(this\)指针。
二.四类\(cast\)转换
\(C++\)引入了四种类型转换:static_cast,dynamic_cast,const_cast,reinterpret_cast。
1.static_cast
隐式转换,\(eg\):非const转const,void*转指针
static_cast还用于多态向上转换。
2.dynamic_cast
动态类型转换,只用与含有虚函数的类,用于类层次间的转换。
3.reinterpret_cast
万能的,但是有危险
4.const_cast
const变量转非const变量
三.智能指针
动态内存管理会出现两种问题:1.忘记释放内存,造成内存泄漏;2.尚有指针引用内存的情况下释放了它,会产生引用非法内存的指针
为此引入智能指针的概念:自动释放所指向的对象。
分类:
智能指针在C++11之后提供,包含在
1、shared_ptr
实现原理:
采用引用计数器的方法,让多个智能指针指向同一个对象A,每当多一个指针指向对象A,所有指向该对象的引用计数器加1,每当减少一个对象时,计数器减1,计数为0的时候会自动释放动态分配的资源。
code:
template<typename T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL):_ptr(ptr), _pcount(new int(1))
{}
SharedPtr(const SharedPtr& s):_ptr(s._ptr), _pcount(s._pcount){
(*_pcount)++;
}
SharedPtr<T>& operator=(const SharedPtr& s){
if (this != &s){
if (--(*(this->_pcount)) == 0){
delete this->_ptr;
delete this->_pcount;
}
_ptr = s._ptr;
_pcount = s._pcount;
*(_pcount)++;
}
return *this;
}
T& operator*(){
return *(this->_ptr);
}
T* operator->(){
return this->_ptr;
}
~SharedPtr(){
--(*(this->_pcount));
if (*(this->_pcount) == 0){
delete _ptr;
_ptr = NULL;
delete _pcount;
_pcount = NULL;
}
}
private:
T* _ptr;
int* _pcount;//指向引用计数的指针
};
2、unique_ptr
unique_ptr采用独享所有权语义,一个非空 unique_ptr总是拥有它指向的资源。转移一个unique_ptr就会把所有权从原指针指向目标指针,原指针被置空,所以unique_ptr不支持普通的拷贝和赋值操作,不可以用在STL容器中,如果拷贝一个unique_str,那么拷贝结束后,两个unique_ptr都会指向相同资源,造成结束时对同一内存指针多次释放导致程序崩溃。
3、 weak_ptr
weak_ptr:弱引用。 引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak_ptr打破环形引用。weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。
4、 auto_ptr
主要是为了解决“有异常抛出时发生内存泄漏”的问题 。因为发生异常而无法正常释放内存。
auto_ptr有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而unique_ptr则无拷贝语义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move()进行转移。
auto_ptr不支持拷贝和赋值操作,不能用在STL标准容器中。STL容器中的元素经常要支持拷贝、赋值操作,在这过程中auto_ptr会传递所有权,所以不能在STL中使用。
四.三大特性
面向对象的三大特征是封装、继承、多态。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互。封装本质上是一种管理:我们如何管理兵马俑呢?不想给别人看到的,我们使用protected/private把成员封装起来。开放一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。
继承:可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
让某种类型对象获得另一个类型对象的属性和方法。
三种继承方式
继承方式 | private继承 | protected继承 | public继承 |
---|---|---|---|
基类的private成员 | 不可见 | 不可见 | 不可见 |
基类的protected成员 | 变为private成员 | 仍为protected成员 | 仍为protected成员 |
基类的public成员 | 变为private成员 | 变为protected成员 | 仍为public成员仍为public成员 |
多态:用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。实现多态,有二种方式,重写,重载。
(重载实现编译时多态,重写实现运行时多态)
五.虚函数
首先整理一下虚函数表的特征:
-
虚函数表是全局共享的元素,即全局仅有一个,在编译时就构造完成
-
虚函数表类似一个数组,类对象中存储vptr指针,指向虚函数表,即虚函数表不是函数,不是程序代码,不可能存储在代码段
-
虚函数表存储虚函数的地址,即虚函数表的元素是指向类成员函数的指针,而类中虚函数的个数在编译时期可以确定,即虚函数表的大小可以确定,即大小是在编译时期确定的,不必动态分配内存空间存储虚函数表,所以不在堆中
六.堆和栈的区别
关于C++的内存这篇文章说的很好
首先说明,在C++中,内存分为5个区:堆、占、自由存储区、全局/静态存储区、常量存储区。
栈:是由编译器在需要时自动分配,不需要时自动清除的变量存储区。通常存放局部变量、函数参数等。
堆:是由new分配的内存块,由程序员释放(编译器不管),一般一个new与一个delete对应,一个new[]与一个delete[]对应。如果程序员没有释放掉, 资源将由操作系统在程序结束后自动回收。
自由存储区:是由malloc等分配的内存块,和堆十分相似,用free来释放。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中(在C语言中,全局变量又分为初始化的和未初始化的,C++中没有这一区分)。
常量存储:这是一块特殊存储区,里边存放常量,不允许修改。
堆和栈的区别:
申请方式不同:栈是由系统自动分配的,堆是自己申请和释放的(new出来的)。
申请大小限制不同:栈顶和栈底是之前预设好的,是向栈底扩展的,大小固定。堆向高地址扩展,是不连续的内存空间,大小可灵活调整。
申请效率不同:栈由系统分配,速度快,不会有碎片;堆由程序员分配,速度慢,且会有碎片。
七、final和override关键字
override
检验继承是否正确。
比如说:父类有一个virtual void f1();
子类继承写成了 void fL();
编译器不会 报错,如果后面加上了override。就会报错。
final
当不希望某个类被继承,或不希望某个虚函数被重写,可以在类名和虚函数后添加final关键字,添加final关键字后被继承或重写,编译器会报错。
(这个关键字是C++11的新关键字,博主面试tx的时候,面试官问了我怎么实现这个关键字答案在这里,这是07年美国一家公司出的面试题)。
八、define宏定义和const的区别
编译阶段:
define是在预编译阶段起作用,const是在编译、运行时起作用。
安全性:
- define只做替换,不做类型检查和计算。
- const常量有数据类型,编译器可以对其进行安全检查。
内存占用:
- define只是将宏定义替换,内存会有很多备份。const在程序运行中只有一份备份,且可以执行常量折叠,可以将复杂的表达式放入常量表中。
九、顶层const和底层const
针对指针的概念。
区分:
指向常量的指针:
代表不能改变其指向内容的指针。声明时const可以放在类型名前后都可,拿int类型来说,声明时:const int 和 int const是等价的。
声明 指向常量的指针 就是 底层const。
常量指针
代表指针本身是常量,声明时必须初始化,存储的地址不会改变。
十、指针和引用的区别
- 指针是一个变量,存储的是一个地址,引用跟原来的变量实质上是同一个东西,是原变量的别名
- 指针可以有多级,引用只有一级
- 指针可以为空,引用不能为NULL且在定义时必须初始化
- 指针在初始化后可以改变指向,而引用在初始化之后不可再改变
- sizeof指针得到的是本指针的大小,sizeof引用得到的是引用所指向变量的大小
- 当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同一个变量,在函数中改变这个变量的指向不影响实参,而引用却可以。
- 引用本质是一个指针,同样会占4字节内存;指针是具体变量,需要占用存储空间(,具体情况还要具体分析)。
十一、new / delete 与 malloc / free的区别
-
前者是C++运算符,后者是C/C++语言标准库函数
-
new自动计算要分配的空间大小,malloc需要手工计算
-
new调用名为operator new的标准库函数分配足够空间并调用相关对象的构造函数,delete对指针所指对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。后者均没有相关调用
后者需要库文件支持,前者不用
new是封装了malloc,直接free不会报错,但是这只是释放内存,而不会析构对象
十二、extern"C"的用法
为了能够正确的在C++代码中调用C语言的代码:在程序中加上extern "C"后,相当于告诉编译器这部分代码是C语言写的,因此要按照C语言进行编译,而不是C++;
哪些情况下使用extern "C":
(1)C++代码中调用C语言代码;
(2)在C++中的头文件中使用;
(3)在多个人协同开发时,可能有人擅长C语言,而有人擅长C++;
十三、placement new
这种new允许在一块已经分配成功的内存上重新构造对象或对象数组。placement new不用担心内存分配失败,因为它根本不分配内存,它做的唯一一件事情就是调用对象的构造函数。定义如下:
void* operator new(size_t,void*);
void operator delete(void*,void*);
十四、malloc与free的实现原理?
1、 在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk、mmap、munmap这些系统调用实现的;
2、 malloc是从堆里面申请内存,也就是说函数返回的指针是指向堆里面的一块内存。
操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
十五、字节对齐
为什么要有字节对齐?
实际上就是一种空间换取时间的做法,
内存对齐应用于三种数据类型中:struct/class/union
struct/class/union内存对齐原则有四个:
-
数据成员对齐规则:结构(struct)或联合(union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始。
-
结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部"最宽基本类型成员"的整数倍地址开始存储。
-
收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的"最宽基本类型成员"的整数倍。不足的要补齐。
-
sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。
十六、编译过程
四个阶段:
预处理阶段:
预处理器根据以#开头的命令,修改C程序。
包括宏定义的替换,注释的消除。
生成.i文件。
编译过程:
将预处理之后的程序转换成汇编代码。
(1) 词法分析:将源代码的字符序列分割成一系列的记号,有限状态自动机。
(2) 语法分析:对记号进行语法分析,产生语法树。
(3) 语义分析:判断表达式是否有意义。
(4) 代码优化:
(5) 目标代码生成:生成汇编代码。
(6) 目标代码优化:
生成.s文件。
汇编过程:
汇编成为目标代码(机器码)生成.o文件
链接过程:
生成可执行文件。
将不同的源文件产生的目标文件进行链接。
链接的方式有两种。
静态链接和动态链接。
静态链接:
把要调用的函数链接到了生成的可执行文件,就算把静太库删掉也不会影响程序执行。
动态链接:
函数没有直接链接,执行过程中去找对应的函数。