C++基础
-
static的作用
改变作用域(隐藏)和生命周期
- 修饰全局变量:只在本文件中可见(可使用),其他文件不可见,使其他文件中可出现同名全局变量防止混淆
- 修饰局部变量:说明变量是静态的,程序开始时就创建,结束时才释放(正常局部变量是跳出作用域就被释放)
- 修饰函数:只在本文件中可见(可调用),其他文件不可见,使其他文件中可出现同名函数防止混淆
- 修饰类成员变量:不需要对象也可以调用,只能在类外定义(分配内存),可在类内初始化
- 修饰类成员函数:只属于类而不属于对象,所以1)只能访问静态变量和静态函数而不能访问非静态的;2)无this指针,不能修饰虚函数
-
const的作用
只读(不要说常量)
- 修饰变量:定义时初始化,不可改变
- 修饰形参:表示在此函数中改形参不修改(即只读)
- 修饰类成员变量:只能在构造函数中初始化
- 修饰类成员函数:1) const typename func() 表示函数返回值只读不能做左值操作;2) typename func() const 表示函数只能对成员变量做只读操作
const和static不能同时修饰成员函数: const typename func() = const this * typename func() 隐式转换 上面说了static修饰成员函数时无this指针
const 修饰指针: 1) const typename * ptr :常量指针 指针指向的值不可变,地址可变
2)typename * const ptr :指针常量 指针的地址不可变,指向的值可变 定义时必须初始化
3)const typename * const ptr: 都不可变
-
volatile
- 访问寄存器要比访问内存要快,因此CPU会优先访问该数据在寄存器中的存储结果,但是内存中的数据可能已经发生了改变,而寄存器中还保留着原来的结果。为了避免这种情况的发生将该变量声明为volatile,告诉CPU每次都从内存去读取数据。
- 一个参数可以即是const又是volatile的吗?可以,一个例子是只读状态寄存器,是volatile是因为它可能被意想不到的被改变,是const告诉程序不应该试图去修改他。
-
extern
extern关键字可以置于变量或函数前,以标示变量或函数的定义在别的文件中
extern "C" :让编译器以C风格编译代码
C++ 函数编译后生成的符号是 _函数名_参数类型 如:func(int a, int b) -> _func_int_int (C++重载原理)
C 函数编译后生成的符号是 _函数名 如:func(int a, int b) -> _func
-
new/delete和malloc/free的区别
- new/delete是c++关键字,malloc/free是库函数
- c++允许对new/delete重载,malloc/free则不行
- new分配地址时无需指定内存大小,成功返回对应类型的指针,失败则抛出异常;malloc使用时需指定内存大小,成功返回void * 需要强制类型转换成想要的类型,失败则返回NULL
-
说说内联函数(inline)
内联函数函数体只能包含简单的语句而不能包含复杂的结构控制语句(for,while......),能否内联成功取决于编译器
inline 只能放在函数定义时,放在函数声明时无校
作用:解决频繁调用函数造成的内存消耗
inline 和define 的区别:
1、inline在编译阶段进行,define是预处理阶段
2、define只是进行简单是替换,inline会对函数参数进行类型检查和访问控制
-
引用和指针的区别
- 引用必须在定义的时候初始化且不可更改,指针定义时可不用初始化(最好初始化),可更改
- 引用是一个对象的别名,不可为空,不会分配存储空间;指针是一个实体,可为NULL,有具体的地址空间
- 引用的大小是所指向的变量的大小;指针是指针本身的大小 (sizeof)
- 自增(++)时引用是值自增,指针是地址
- const 对指针和引用的差别
-
C++中struct和class区别
struct的默认访问权限是public
class的默认访问权限的private
-
c++类型转换
static_cast:用于基本类型转换,有关联的类(基类、派生类) 编译时生效
const_cast:添加或去除const 编译时生效
reintpret_cast:任何内置类型 ,甚至数据到指针的转换( a -> * a) 编译时生效
dynamic_cast:将基类的指针或引用安全地转换成派生类的指针或引用,并用派生类的指针或引用调用非虚函数。如果是基类指针或引用调用的是虚函数无需转换就能在运行时调用派生类的虚函数 运行时生效
-
c++特性
- 多态:一个接口多种实现(虚函数);前提:类与类之间有关联
- 封装(类):将数据和对数据的操作封装在一起,只对外提供接口(隐藏对象的属性和实现细节,仅对外提供公共访问方式);作用:提高代码重用性
- 继承:子类具有父类的相关属性和方法,并添加属于自己的新属性或方法
-
重载、重写、隐藏
重载:发生在同一个作用域(或类)中,函数名相同参数或返回值不同
重写:发生在父子类中,父类中必须被virtual修饰,函数名参数返回值相同隐藏:virtual
隐藏:发生在父子类中,父类中无virtual修饰,函数名参数返回值相同,只能显式调用父类中的函数
-
深拷贝和浅拷贝
拷贝构造函数:一个参数必须是本类型的引用
调用时机:1、对象以值传递的方式传入函数参数
2、对象以值传递的方式返回
3、对象需要等待另一个对象初始化
浅拷贝:简单赋值
深拷贝:动态分配内存
-
虚表
包含虚函数的类都有虚表,虚表是属于类的而不是对象,每个对象会有一个虚指针指向虚表;
派生类的虚表是在基类的虚表后面加上自己的虚函数;
析构函数为什么定义成虚函数?:拥有派生类时虚指针会现调用派生类的析构函数再调用基类的,防止内存泄漏
-
纯虚函数
virtual typename func() = 0;
含有纯虚函数的类是抽象类,不能实例化对象,在派生类中去实现(工厂模式)
-
c++内存机制
堆:程序员分配释放 new/delete
栈:编译器分配释放 局部变量、函数参数
全局区/静态区: 全局变量和静态变量 (C语言中 初始化的放在一起,未初始化的放在一起)
自由存储区:malloc/ free
常量区:常量
C语言内存机制(堆、栈、全局/静态区、常量区、代码区(二进制代码))
堆、栈区别:
堆:内存地址不连续、地址分配由低到高、容易产生内存碎片,效率比栈慢
系统中有记录空闲地址的链表,申请空间时先遍历链表找到第一个大于所需空间的内存,删除节点分配空间
栈:内存地址连续、地址分配由高到低,先进后出、效率比堆快
剩余空间大于所需空间就分配内存否则栈溢出
空间大小不同:堆比栈大(32位系统下 堆4G,栈默认1M)
-
内存泄漏
内存泄漏是分配的内存没有释放引起的
检查内存泄漏:
windows: 1、CRT库: #defie ——CRTDBG_MAP_ALLOC
#include "crtdbg.h"
_CrtDumpMemoryLeaks(); //打印内存信息
2、一些检测工具(VLD)
Linux: valgrind工具 valgrind --leak-check=full ./a.out //开启内存检测
valgrind 命令参数:--log-file=text.log 生成日志文件text.log
--num-callers=10 追踪错误行数(10)
防止内存泄漏:1、智能指针 2、RAII
-
智能指针shared_ptr
智能指针是一个模板类(具有指针行为的类),不能直接将指针赋值给智能指针
shared_ptr:基类指针指向引用计数管理区,原子操作保证互斥访问,指向同一资源的ptr共享计数
引用计数改变:调用构造函数 +1;拷贝构造函数 +1;调用析构函数 -1;计数为0时释放内存
weak_ptr 和share_ptr ? 循环引用计数?
auto_ptr ?
-
大小端
大端:高字节在地地址
小端:高字节在高地址
判断大小端?
-
STL
顺序容器:vector、list、deque ; 关联容器:set、multiset、map、multimap
各容器的特点和原理
vector迭代器失效问题
map的key值能否为struct? 可以 原因:map内部key值默认升序排列,只要数据类型可以比较就能为key,所以只要重 载 '<' 操作符就行
STL接口:erase()、sort()、size() 等等的各种用法
-
定义只能在堆或栈上生成的对象(静态绑定/动态绑定)
只在堆上生成: new()将对象建立在堆中分为两步:1、执行operator new() 在堆中搜索分配内存 2、调用构造函数
class A{ public: void creat(); void dele(); private: A(); ~A(); } //无法作为基类 //为基类时需将析构设置为virtual子类继承,设置为private权限时子类无法访问 class A{ public: void creat(); void dele(); protected: A(); ~A(); }
只在栈上生成:将operator new() 重载为私有,使其不能再类外被调用
class A{ public: A(); ~A(); private: void *operator new(size_t t){}; void *operator delete(void *ptr){}; }
-----------------------------------------------------------------------------更 新------------------------------------------------------------------------------------
什么函数不能声明为虚函数?(虚函数要求能被继承,实现多态,动态绑定)
1、普通函数(非成员函数)
2、友元函数:c++中不支持友元函数继承
3、内联成员函数:内联函数实在编译时展开(无函数调用),动态绑定是运行时确定
4、构造函数:
5、静态成员函数:
堆栈溢出一般由什么造成的?
函数递归调用层数太多:函数调用时系统会在栈上保存运行环境和产生的变量,调用次数太多会导致栈溢出函数无法返回
动态分配内存没释放
数组越界
指针非法使用
float或double怎么与 0 比较?
浮点数类型会受到精确度影响比较的时候进行某些隐式的转换,不能用 == 或 != 来比较,最好转换成 >= 和 <= 的比较,可以转换成与足够小的数比较(如0.0000001):if(x >= -0.0000001 && x<= 0.0000001)可认为 x == 0