杂记1"/>
学习杂记1
1. const
目的:定义一个“不会被修改的常量”
补充:可以修饰变量、引用、指针,可以用于函数参数、成员函数修饰、成员变量,修饰成员函数本质上就是修饰“this”指针,所以不能修改函数内部的成员变量。
注意:
- 常量指针(指向常量的指针)和指针常量(地址是常量,指针指向的地址不变)
- const值一旦创建后就不能修改,所以const对象必须初始化。
- const修饰的如果是普通对象,那么非const对象不可以随便赋值给const对象,因为const对象只会初始化一次。
- const对象如果修饰的是常量指针(const int* X),常量指针不可以赋值给非常量指针,因为赋值后,非常量指针就可以修改常量指针指向的那块内存。
好处:
- 相比宏定义,const在编译期也会起作用(宏定义只是预编译期),会做一些类型检查,方便调试
- const不需要在每个用到的地方都申请一块内存空间,要更节省内存
- const只是一个编译期的语言功能,做一些简单的常量替换以及赋值限制等,他在运行时不会对内存有什么限制
补充: 不合理的使用const_cast之所以会造成运行时崩溃,是由于const修饰的某些变量是位于常量区或者其他某些只读的内存页。
const的作用域
默认状态下,const对象仅在文件内有效。可以使用extern(可以用作对象的声明而不定义),使一个非常量表达式的const变量在不同文件之间可以共享。
//file.h
extern constint temp;
//file.cpp
extern constint temp=fun();
const与引用
引用可以节省拷贝带来的内存损耗
- 对常量的引用必须要用const引用(因为非常量引用可以改变所引用对象)
- 对const的引用可能并非引用一个const对象
- 初始化常量引用时允许用任意表达式(可以是不同类型)作为初始值
const与指针
- 与常量引用相似,指向常量的指针必须要用const指针(指向常量的指针)
常量指针(指向const的指针)和指针常量(const指针)区分常量指针(指向常量的指针)和指针常量(地址是常量,指针指向的地址不变)(前两个字作为形容词修饰后两个字。同时也要注意const指针这一说法,不要理解成常量指针)
1. int const* cur; 常量指针,指向常量的指针
2.Const int * cur; 常量指针,指向常量的指针
3. int*const cur; 指针常量
4. const(int *) cur; //错误,不可以这么写
const与函数参数
带有const声明的变量本身不变是顶层const,所引用或所指向的对象不变就是底层const。在进行拷贝操作时,顶层const一般可以忽略,但是底层const不可以忽略。
实参初始化形参时会忽略顶层const
形参的初始化方式和变量的初始化方式一样,可以使用非常量初始化一个底层const,但反过来不可以
尽量使用常量引用
const与类
类中声明变量为const类型。但是不可以初始化,必须要在构造函数初始化列表中初始化。这样的变量其实只是在一个对象中是不变的,要想在整个类中都不变就得用enum(枚举)
这样的成员函数不可以修改数据成员,如果修改成员变量或者调用了其他非const成员函数就会报错
2.引用和指针
- 指针可以为空,而引用强烈建议不要指向空值。
- 指针可以不初始化,引用必须初始化。这意味着引用不需要检测合法性(是否为空)
- 指针可以随时更改指向的目标,而引用初始化后就不可以再指向任何其他对象
3.inline(内联)
简单理解就是编译时把函数的定义替换到调用的位置。
好处:
- 它消除了函数调用过程中所需的各种指令:包括在堆栈或寄存器中放置参数,调用函数指令,返回函数过程,获取返回值,从堆栈中删除参数并恢复寄存器等。
- 由于不需要寄存器来传递参数,因此减少了寄存器溢出的概率。当使用引用调用(或通过地址调用或通过共享调用)时,它消除了必须传递引用然后取消引用它们。
缺点:
- 使用不当的话就会造成代码膨胀(也就是生成的可执行程序会变大),影响cache对数据的命中
- 过多的内联代码会使原来本可以存储到ICache的指令分散,导致指令缓存的命中降低,从内存取数据会严重影响效率
- inline会导致代码膨胀,增加可执行程序(动态库、静态库)体积,造成额外的换页行为,进而可能会导致数据缓存的命中率降低
- 递归函数的内联可能造成代码的无限inline循环,特殊情况下会拒绝内联,常见的包括虚调用,函数体积过大,有递归,可变数目参数,通过函数指针调用,调用者异常类型不同,declspec宏、使用alloca、使用setjump等
4.final和override
final:禁止继承该类或者覆盖该虚函数
override:必须覆盖基类的匹配的虚函数
final:
- 不希望这个类被继承,比如vector,编码者可能不够了解vector的实现,或者说编写者不希望别人去覆盖某个虚函数
- 不希望这个函数再被其他子类覆写
override:
- 覆写一个基类的函数时,加上override关键字,编译器会帮你发现与基类函数不匹配从而给出编译错误的提示。
- 使用别人的函数库,或者继承了别人写的类时,新加函数与与原来基类的函数名称一样时,这样就会被编译器(以及其他人)误认为要重写基类的函数,加上override,别人在看到你的代码时就知道你当前的函数是否想重写基类里面的函数,也就容易发现你这个无意中重载的Bug。
5.C++中四种cast
- constcast,去掉常量属性以及volatile,但是如果原来他就是常量去掉之后千万不要修改;比如你手里有一个常量指针引用,但是函数接口是非常量指针,可能需要转换一下;成员函数声明为const,你想用this去执行一个函数,也需要用constcast
- staticcast,基本类型转换到void,转换父类指针到子类不安全
- dynamiccast,判断基类指针或引用是不是我要的子类类型,不是强转结果就返回null,用于多态中的类型转换
- reintercast,可以完成一些跨类型的转换,如int到void*,用于序列化网络包数据
6. stl里面各个容器的基础数据结构
优先级队列,hashmap,map底层的数据结构分别是Vector,hashtable以及RB—tree(红黑树)
7.内存管理
在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区。malloc在堆上分配的内存,使用free释放内存,而new所申请的内存则是在自由存储区上,使用delete来释放,不过这只是表象。进一步来讲,基本上所有的C++编译器默认使用堆来实现自由存储,即缺省的全局运算符new和delete也会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。
所以,new所申请的内存区域在C++中称为自由存储区,如果是通过malloc实现的,那么他也是在堆上的。
8.内存对齐
好处:结构体以及类成员对齐,意义就是减少cpu读取的次数,提高效率、
方法:简单来讲就是,每个变量看后面的变量,如果后面的变量大,就和后面的大小对齐并补充字节。最后一个变量,按照成员内最大的对齐值,对齐并补充字节。
9.new 与 malloc
malloc:是从C语言移植过来的语义,表示申请一定大小的内存并返回void*指针,在堆上申请内存,申请失败会返回Null
new:C++里的关键字,针对对象而言,申请一块内存的然后并在内存上构造对应的对象,最后返回该对象的指针。深入:new是从自由存储区分配的内存,自由存储区可能不在堆上,在静态存储区(全局变量做的对象池)。申请失败会抛出异常。可以通过new[]对数组进行内存申请与构造
operator new:C++里面与Malloc类似的语义,只申请内存而不构造对象
new操作的第一步就是调用operator new来执行内存的申请。深入:operator new可以基于malloc实现,一般也都是这么做的,可以被重载
关于new的重载:我们可以重载operator new来使用自己的分配器,C++标准规定重载的operator new必须是类成员函数或全局函数。在全局中重载operator new会修改所有默认的new方式,具体类中的重载只会影响到该类的内存分配与释放,不过由于operator new是在类的具体对象被构建出来之前调用的,所以必须声明为static。operator new还有另一种声明,可以定位内存申请的文件与位置
更多推荐
学习杂记1
发布评论