基础语法
const的作用
1.修饰变量,说明该变量不可以被改变
2.修饰指针,分别指向常量的指针(const在*左边)和指针常量(const在*右边)
3.常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改
4.修饰成员函数,说明该成员函数内不能修改成员变量
static的作用
1.修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在main函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它
2.修饰普通函数,表面函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命令函数重名,可以将函数定位为static
3.修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员
4.修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在static函数内不能访问非静态函数( 静态成员属于类而不是属于类对象,因此静态成员没有this指针)
#pragmapack(n)
设定结构体、联合以及类成员变量以n字节对齐
#pragmapack(push)//保存对齐状态
#pragmapack(4)//设定为4字节对齐
struct test
{
charm1;
doublem4;
intm3;
};
#pragmapack(pop)//恢复对齐状态
volatile
- volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器位置的因素(操作系统、硬件、其他线程等)更改。所以使用volatile告诉编译器不应对这样的对象进行优化
- volatile关键字声明的变量,每次访问时都必须从内存中取出值(没有被volatile修饰的变量,可能由于编译器的优化,从CPU寄存器中取值)
- const可以是volatile(如只读的寄存器状态)
- 指针可以是volatile
extern"C"
- 被extern限定的函数或者变量是extern类型的
- 被
extern"C"
修饰的变量和函数是按照C语言方式编译和连接的
extern"C"
的作用是让C++编译器将extern"C"
声明的代码当作C语言代码处理,可以避免C++因符号修饰导致代码不能和C语言库中的符号进行连接的问题
#ifdef__cplusplus
extern"C"{
#endif
void*memset(void*,int,size_t);
#ifdef__cplusplus
}
#endif
C++ struct和class的区别
总的来说,struct更适合看成是一个数据结构的实现体,class更适合看成是一个对象的实现体
最本质的区别就是默认的访问控制
1.默认的继承访问权限。struct是public的,class是private的
2.struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的
explicit关键字
- explicit关键字修饰构造函数时,可以防止隐式转换和复制初始化
- explicit关键字修饰转换函数时,可以防止隐式转换,但是按语境转换除外
空类占用的内存大小
1个字节
智能指针
从比较简单的层面来看,智能指针是RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制对普通指针进行的一层封装。这样使得智能指针的行为动作像一个指针,本质上却是一个对象,这样可以方便管理一个对象的生命周期。
auto_ptr:
是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存
unique_ptr:
c++11取代auto_ptr,unique_ptr是独享被管理对象指针所有权(owership)的智能指针。unique_ptr对象封装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
两个指针不能指向同一个资源,复制或赋值都会改变资源的所有权。
特性
- 基于排他所有权模式:两个指针不能指向同一个资源
- 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
- 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
- 在容器中保存指针是安全的
shared_ptr:
它的原理是使用引用计数实现对同一块内存的多个引用。在最后一个引用被释放时,指向的内存才释放,这也是和 unique_ptr 最大的区别。当对象的所有权需要共享(share)时,share_ptr可以进行赋值拷贝。
shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。
在使用shared_ptr智能指针时,要注意避免对象交叉使用智能指针的情况! 否则会导致内存泄露!
weak_ptr:
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。
在类中使用弱指针接管共享指针,在需要使用时就转换成共享指针去使用即可!
c++的四种类型转换
使用C风格的类型转换可以把想要的任何东西转换成我们需要的类型,但是这种类型转换太过松散,对于这种松散的情况,C++提供了更严格的类型转换,可以提供更好的控制转换过程,并添加4个类型转换运算符,使转换过程更规范:static_cast、dynamic_cast、const_cast、reinterpret_cast。
static_cast静态转换
用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换
o进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
o进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的
用于基本数据类型之间的转换,如把int转换成char,把char转换成int。这种转换的安全性也要开发人员来保证
dynamic_cast动态转换
dynamic_cast主要用于类层次间的上行转换和下行转换
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全
const_cast常量转换
该运算符用来修改类型的const属性
常量指针被转化成非常量指针,并且仍然指向原来的对象
常量引用被转换成非常量引用,并且仍然指向原来的对象
注意:不能直接对非指针和非引用的变量使用const_cast操作符
reinterpret_cast重新解释转换
这是最不安全的一种转换机制,最有可能出问题
主要用于将一种数据类型从一种类型转换为另一种类型,它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针
c++中的内存对齐
主要就是struct的那一块。
对于32位来说默认四字节对齐
对于64位来说采用八字节对齐
简述一下C++从代码到可执行二进制文件的过程
标准回答:
c++从代码到可执行二进制文件经过四个过程,分别是:预编译、编译、汇编、连接
1.预编译,主要的处理操作:
a.将所有的#define删除,并且开展所有的宏定义(宏替换)
b.处理所有的条件预编译指令,如#if、#ifdef
c.处理#include预编译指令,将所包含的文件插入到该预编译指令的位置
d.删除所有的注释
e.添加行号和文件名标识
2.编译:将于预处理之后的代码转换为特定的汇编代码,主要包括词法分析、语法分析、语义分析、优化代码等操作
3.汇编:将汇编代码汇编成机器指令
4.连接:将不同源文件生成的目标代码以及其他目标代码、库文件组合起来,从而形成可执行程序
加分回答:
1.静态连接:静态连接是由连接器在连接时将库内容加入到可执行程序中,将一个或多个库和目标文件(先由编译器或汇编器生成)连接到一块可执行程序
2.动态连接:动态连接在连接后动态库仍然与可执行文件分离,直到运行时才动态加载
面向对象
简述一下C++中的多态
标准回答
在现实生活中,多态是同一个事物在不同场景下的多种形态。在面向对象中,多态是通过基类的指针或者引用,在运行时动态调调用实际绑定的对象函数的行为,与之对应的编译时绑定函数称为静态绑定。所以多态分为静态多态和动态多态。
1.静态多态
静态多态是编译器在编译时期完成的,编译器会根据参数类型来选择调用合适的函数,如果有合适的函数就调用,没有的话就会发出警告或者报错。静态多态有函数重载、运算符重载、泛型编程等。
2.动态多态
动态多态是在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。当父类指针(引用)指向父类对象时,就调用父类中定义的虚函数;即当父类指针(引用)指向子类对象时,就调用子类定义的虚函数。
加分回答
1.动态多态行为的表现效果为:同样的调用语句在实际运行时有多种不同的表现形态
2.实现动态多态的条件:
- 要有继承关系
- 要有虚函数重写(被virtual声明的函数叫虚函数)
- 要有父类指针(父类引用)指向子类对象
3.动态多态的实现原理
当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类虚函数指针的数据结构,虚函数表是由编译器自动生成和维护的。virtual成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象都有一个指向虚函数表的指针(vptr指针)。在多态调用时,vptr指针就会根据这个对象对应类的虚函数表中查找被调用的函数,从而找到函数的入口地址。
说一说c++中哪些不能是虚函数
c++中,普通函数(非成员函数)、构造函数、友元函数、静态成员函数、内联成员函数 这些不能是虚函数。
1.普通函数(非成员函数)
只有类的成员函数才能被声明为虚函数
因为普通函数只能被重载,不能被重写。声明虚函数也没有什么意思,因此编译器会在编译时绑定函数。
2.构造函数
构造函数时用来初始化对象的,虚函数的主要目的是实现多态,多条是依托于类的,多态的声明必须是对象创建之后。
虚表指针的初始化是在构造函数中进行的,而虚函数要放到虚表中,在调用虚函数之前,要知道虚表指针,所以矛盾。
3.友元函数
c++不支持友元函数的继承,所以不行。
4.静态成员函数
静态成员函数理论是可继承的,但是是在编译时确定的,无法动态绑定,不支持动态,因此不能被重写,也不能声明为虚函数
5.内联成员函数
内联函数必须有实体,在编译时在代码中直接展开,减少调用花费的代价。虚函数是为了继承后对象准确的执行自己的行为,这是不可能统一的。虚函数是在运行时才能动态绑定的函数。
总结:即不能被继承和不能被重写函数
虚函数表运行时加载在虚拟地址空间的哪里?
.rodata数据段
STL常用容器
STL中容器分为顺序容器、关联式容器、容器适配器三种类型,三种容器特性分别如下:
1.顺序容器
容器并非排序的,元素的插入位置同元素的值无关,包含vector、deque、list
vector:动态数组
元素在内存连续存放。随机存取任何元素都能在常数时间内完成。在尾端增删元素具有较佳的性能。
deque:双向队列
元素在内存连续存放,随机存取任何元素都能在常数时间内完成(仅次于vector)。在两端增删元素具有较佳性能(大部分是常数时间)。
list:双向链表
元素在内存不连续存放。在任何位置增删元素都能在常数时间内完成,不支持随机存取。
2.关联式容器
元素是排序的;插入任何元素,都按排序规则来确定位置;在查找时具有非常好的性能;通常以平衡二叉树的方式实现,包含set、multiset、map、multimap。
set/multiset
set中不允许相同元素,multiset允许存在相同元素
map/multimap
map与set的不同之处在于map中存放的元素有且仅有两个成员变量,分别是first、second,map根据first值对元素大小从小到大排序,并可以快速地根据first来检索元素。map和multimap的不同在于是否允许相同first值的元素。
3.容器适配器
封装了一些基本的容器,使之具备了新的函数功能,包含stack、queue、priority_queue
stack:栈
栈是项的有限序列,并满足序列中被删除、检索和修改的项只能是最近插入序列的项(栈顶元素),后进先出
quque:队列
插入只可以在尾部进行,删除、检索和修改只允许从头部进行,先进先出。
priority_queue:优先级队列
内部维持某种有序,然后确保优先级最高的元素总是位于头部,最高优先级元素总是第一个出列。
原文链接: https://www.cnblogs.com/wxk1213/p/16917764.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/5102
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!