C++ 基础

C++中int* a = new int(10)与int* a = new int[10]的区别

  1. int* a = new int(10)表示申请一个内存空间,存放的初值是10。 释放:delete a;
  2. int* a = new int[10]表示申请一个存放十个元素的数组。 释放:delete[] a;

map和unordered_map的区别和联系

一、map和unordered_map的实现机理: map:是基于红黑树来实现的(红黑树是非常严格的平衡二叉搜索树),红黑树具有自动排序功能,红黑树的每一个节点都代表着map中的一个元素,因此对于map的查找,删除和插入操作都是对红黑树的操作。 unordered_map:是基于哈希表来实现的,查找的时间复杂度是O(1),在海量数据处理中有着广泛的应用。

二、map和unordered_map的优缺点 map的优点:(1)map是有序的(2)基于红黑树实现,查找的时间复杂度是O(n) map的缺点:空间占用率比较高,因为内部实现了红黑树,虽然提高了运行效率,但是每个节点都要保存父亲节点和孩子节点和红黑树的性质,使得每一个节点都占用大量的空间。 适用的情况:对于要有序的结构,适用map

unordered_map的优点:因为内部是哈希表来实现的,所以查找效率会非常高 unordered_map的缺点:哈希表的建立比较费时 适用的情况:对于查找问题,适用unordered_map会更好一点。


C++多态

面向对象思想的“三大特征”:多态、封装、继承 多态的定义:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果

  1. 编译时的多态:通过重载实现
  2. 运行时的多态:通过虚成员实现

作用: 增加程序的可扩充性,降低后期修改代码的难度。

多态的实现:拥有虚函数的类,通过基类指针或引用动态调用基类函数或继承类函数,每个虚函数的类都有一个虚函数表,该类任何对象都有虚函数指针。


虚函数是怎么实现的

虚函数通过虚函数表实现


结构体内存对齐

内存对齐:结构体内数据将对齐最长的数据 原因:

  1. 操作系统为了提高访问内存的策略,降低二次访问
  2. 便于移植,不是所有硬件平台都能任意访问地址上的任意数据
  3. 防止空间浪费

常见类型长度(64位操作系统): char(1)、int(4)、double(8)、float(4)、string(40)、指针(8)


函数重载与重写

重载: 概念: 同一作用域内,定义相同名称的函数,参数列表必须不同(个数或类型),返回值可相同可不相同。 作用: 命名一组功能相似的函数,避免名字空间污染,增加程序可读性。

重写: 概念: 也称覆盖,子类重新定义父类中有相同名称和参数的虚函数,返回类型可以不同但必须是父类返回类型的子类。 作用: 实现面向对象的三大特性之一的多态

tips:

  1. 不是虚函数也能进行重写,但在一个类中声明一个非虚函数实际上为这个类建议一种特殊性上的不变性,没必要重写这个非虚函数。
  2. 父实子虚(子类无法更改父类函数)或父虚子实(子类屏蔽父类函数)都是设计错误。

赋值符重载与拷贝构造函数

赋值符重载: 重载operator=函数,给一个对象赋值时会调用重载过的赋值运算符。 拷贝构造函数: 以拷贝的方式初始化一个对象,调用拷贝构造函数

区别:

  1. 赋值运算符没有新的对象生成,拷贝构造函数会生成新的对象。

拷贝构造函数何时调用

  1. 用类的一个对象初始化类的另一个对象时。
  2. 函数的形参或函数的返回值是类的对象时。

四种类型转换运算符

  1. static_cast:用于良性转换
  2. const_cast:用于const和非const、volatile和非volatile之间的转换。
  3. reinterpret_cast:高度危险转换,对二进制位重新解释。
  4. dynamic_cast:用于基类对象的指针或引用转换为继承类对象的其他指针或引用

const的用法

const:常量限定符,用来限定特地变量,使其不可被编译器修改。 用法:

  1. const修饰基本数据类型,例如:const int a = 1;
  2. const修饰指针:const int* p(指针常量)、int* const p(常量指针)、const int* const p(常量指针常量)
  3. const修饰引用
  4. const修饰函数形参,修饰函数返回值,修饰成员变量,修饰整个函数

static作用

  1. 修饰全局变量,使其他文件不可见。
  2. 修饰局部变量,使其不会因为函数终止而删除。
  3. 修饰函数,使其他文件不可见。
  4. 修饰类的数据成员,该类所有对象这个数据只有一个实列。
  5. 修饰类成员函数,该函数只能访问它的参数,类静态数据,以及全局变量。

vector的push_back原理

vector有预存内存(大小为capacity),当存入元素溢出,会重新分配一个比原来大2倍或1.5倍的内存。

tips: 原来的指向旧内存的指针会变为野指针。


析构函数可以是虚函数吗

析构函数可以是虚函数,有时必须声明为虚函数,否则会出现:父类析构,子类没析构的情况,从而产生析构不完全。


析构函数为什么不能是虚函数

  1. 创建对象时需要确定对象类型,而虚函数在运行时动态确定其类型,而构造对象时,由于对象未创建,编译器无法知道对象类型,因此虚函数无法动态确定其类型
  2. 虚函数调用需要虚函数表指针,而该指针存放在对象内存空间中,对象还没创建,没有内存存放虚函数表,无法调用虚构造函数。

引用和指针的差别

  1. 指针是变量,存放地址,指向内存的存储单元。引用是原变量的别名,与原变量是同一个东西。
  2. 指针可以多级指针,引用只能一级。
  3. 指针可以不初始化,引用必须初始化。
  4. 指针可以指向NULL,引用不可以。
  5. 指针可以改变,引用不可以(引用的值改变,被引用的值也会被改变)。

STL是什么?

STL:标准模板库,一些容器、算法和一些组件的集合,容器包括vector、list、map、set等。 六大类组件: 容器、迭代器、算法、函数对象、适配器、分配器。

Container(容器):各种数据结构,如Vector,List,Deque,Set,Map,用来存放数据,STL容器是一种Class Template,就体积而言,这一部分很像冰山载海面的比率。
Adapter(适配器):一种用来修饰容器(Containers)或仿函数(Functors)或迭代器(Iterators)接口的东西,例如:STL提供的Queue和Stack,虽然看似容器,其实只能算是一种容器配接器,因为 它们的底部完全借助Deque,所有操作有底层的Deque供应。改变Functor接口者,称为Function Adapter;改变Container接口者,称为Container Adapter;改变Iterator接口者,称为Iterator Adapter。配接器的实现技术很难一言蔽之,必须逐一分析。
Algorithm(算法):各种常用算法如Sort,Search,Copy,Erase,从实现的角度来看,STL算法是一种Function Templates。
Iterator(迭代器):扮演容器与算法之间的胶合剂,是所谓的“泛型指针”,共有五种类型,以及其它衍生变化,从实现的角度来看,迭代器是一种将:Operators*,Operator->,Operator++,Operator--等相关操作予以重载的Class Template。所有STL容器都附带有自己专属的迭代器——是的,只有容器设计者才知道如何遍历自己的元素,原生指针(Native pointer)也是一种迭代器。
Function object(函数对象):行为类似函数,可作为算法的某种策略(Policy),从实现的角度来看,仿函数是一种重载了Operator()的Class 或 Class Template。一般函数指针可视为狭义的仿函数。
Allocator(分配器):负责空间配置与管理,从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的Class Template。

作用: 可以更加方便灵活地处理数据

map:关联容器,提供一对一地hash 特性:

  1. 内部实现是红黑树结构,具有自动排序的功能。

vector:封装了动态大小数组的顺序容器 特性:

  1. 容器内元素按严格的线性顺序排序,支持索引访问,随机存取时间复杂度为O(1),查找时间复杂度为O(n)
  2. 添加元素时会进行动态分配内存。
  3. 常用函数:push_back()、pop_back()、empty() 、sort()等

list:用双向环形链表实现,内存空间不连续。插入和删除时间复杂度O(1)

haspmap:基于哈希表,具有快速存储和查找的优点,但消耗内存多。 原理:使用一个下标范围比较大的数据存储元素,存储key时,将其哈希化,获得对应桶号,将value存入桶中。


C++内存分配方式

静态存储区分配内存: 程序编译时就分配好,程序地整个运行期间都存在,例如全局变量,静态变量。 栈上分配内存: 系统自动分配,如函数内部的局部变量,随着函数结束而释放。 堆上分配内存: 程序员动态分配内存,例如new、malloc。


malloc/free和new/delete的区别

共同点:都是从堆上申请空间,并且需用用户手动释放。 区别:

  1. malloc和free是函数,new和delete是操作符。
  2. malloc申请空间要手动计算空间大小,new只需在其后输入空间类型。
  3. malloc申请空间不会初始化,new可以初始化。
  4. malloc申请空间失败,返回NULL,new申请失败,抛出异常。
  5. malloc返回void*,new返回空间类型。

C++11新特性

  1. auto(值推导)
  2. 左值右值:
    1. 左值:能取地址且有名字
    2. 右值:不能取地址且没有名字
  3. 列表初始化
  4. lambda表达式
  5. 智能指针
  6. nullptr
  7. const/constexpr
  8. sizeof
  9. 内存对齐

智能指针(动态指针)

智能指针:利用对象生命周期控制程序资源的技术 作用:防止内存泄漏、二次释放等问题 指针类型:

  1. shared_ptr:多个指针指向相同的对象。共享资源所有权的指针.
  2. unique_ptr:“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象。独占资源所有权的指针。
  3. weak_ptr:配合shared_ptr而引入的一种智能指针。

lambda表达式

lambda表示式:定义一个匿名函数,可以捕获一定范围内的变量。 优点: 1. 可维护性高。 2. 简洁:避免代码膨胀,功能分散。 3. 在需要的时间和地点实现功能闭包,使程序更灵活。 缺点:

  1. 代码可读性变差。
  2. 学习难度高。 3. 不容易调试。

强枚举类型(enum class)

传统枚举类型:由用户定义的若干枚举常量的集合。枚举值对应整数数值,默认从零开始。 存在问题:

  1. 同作用域同名枚举报错。
  2. 无法自定义枚举常量定义类型。(c++11可以)
  3. 枚举常量占用存储空间及符号不确定。 因此强枚举类型(enum class)诞生,优点:
  4. 强作用域:不同枚举集合可取同名枚举。
  5. 转换限制:不再与整型发生隐式转换。
  6. 自定义枚举常量定义类型。

C++编译连接过程

源代码(预编译检查) -》 编译器 -》 汇编代码 -》 汇编器 -》目标代码(机器指令) -》 连接器 -》 可执行程序。


一个空的class类里有什么

包含六个默认函数:

  1. 默认构造函数
  2. 拷贝构造函数
  3. 析构函数
  4. 赋值运算符重载
  5. 取地址操作符重载
  6. const修饰的取地址操作符重载

内联函数的含义?

内联函数:用关键字inline修饰的函数,每次调用,都写一次函数在对应位置 优点:避免指针跳转,加快程序执行速度 缺点:导致程序代码量庞大,占用更多内存空间


数组越界要怎么知道?

  1. 发现是否有数组越界问题,没有特殊的好方法,一般采用静查法(认真阅读,检查程序是否按照设计要求编写)。
  2. 调试法,通过测试例子,测试结果是否与预期结果一致,进行判断。

野指针

野指针:没有指向有效内存的指针 造成野指针的原因:

  1. 指针变量未初始化。
  2. 指针指向内存被释放,而指针没有处理。
  3. 指针超过变量作用范围,例如函数局部变量的地址值被传出来,而局部变量会在函数执行完自动清理

函数运行全过程的底层机制

  1. 主函数正常运行,遇到被调用函数。
  2. 分配被调用函数所需要的栈空间。
  3. 保存当前函数运行状态和返回地址。
  4. 传递参数给被调用函数,并将控制权转交给被调函数。
  5. 被调函数执行完毕,通过返回地址返回之前函数的状态,释放栈空间。

typedef和define有什么区别

typedef:常用来定义一个标识符及关键字的别名,增加程序的可读性 常用于:

  1. 定义类型别名
  2. 辅助struct定义别名
  3. 定义与平台无关的类型

define:宏定义语句,定义常量,在预编译过程完成 区别: 宏定义只是简单的字符串代换(原地扩展),typedef具有一定的封装性。

例如: typedef (int*) pINT; pINT a,b效果等于int *a, b; define pINT int; pINT a,b效果等于int *a, b;


define与const的区别

共同作用:定义常量 区别:

  1. 编译器处理方式:define作用于预处理,const作用于编译阶段。
  2. 类型检查:define无类型,不进行类型安全检查,const有数据类型,进行类型检查
  3. 内存空间:define不分配内存,const在静态存储区分配内存。

vector中resize()和reserve()区别

resize(): 重新申请并改变当前vector对象的有效空间大小,改变有效空间大小:小于空间大小,裁剪多出来的数据,capacity不变,大于空间大小,赋值为定义数据,capacity大小与改变大小相同。

reserve():重新申请并改变当前vector对象的总空间(capacity)大小,不改变有效数据大小。


递归与循环(迭代)的区别

区别在于:函数或算法是否具备收敛性,具备收敛性,才能使用递归。 优越点: 递归:内存占用高,但容易理解 迭代:运行效率高,但不易理解


C和C++的区别

C和C++的主要区别在于C是过程式编程语言,不支持类和对象,而C++是过程式和面向对象编程语言的结合;因此C++可以被称为混合语言。


拷贝构造函数和移动构造函数

  1. 拷贝构造函数: 它是一种特殊的构造函数。它的作用就是用一个已经生成的对象来初始化另一个同类的对象。 当类中拥有指针类型的成员变量时,拷贝构造函数中需要以深拷贝(而非浅拷贝)的方式复制该指针成员。
1
2
3
4
Integer(const Integer& source)
	: m_ptr(new int(*source.m_ptr)) {
	cout << "Call Integer(const Integer& source)拷贝" << endl;
}
  1. 移动构造函数: 指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。
1
2
3
4
5
Integer(Integer&& source)
  : m_ptr(source.m_ptr) {
	source.m_ptr= nullptr;
	cout << "Call Integer(Integer&& source)移动" << endl;
}

std::move() 能把左值强制转换为右值。


memcpy的用法

c和c++使用的内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。

  1. 如果复制的字节数n超出了dest的空间容量,或者n超出src的容量,这个函数是不会进行判断的,这样就会很危险。需要程序员自己检查是否有溢出的情况出现。
  2. 这个函数不会检查参数dest与参数src所指向的数组(或其他类型)是否具有同样的空间。

memset函数

函数功能:为指针变量s所指的前n个字节的内存单元填充给定的int型数值c,它可以为任何数据进行初始化。换句话说,就是将数值c以单个字节逐个拷贝的方式放到指针变量s所指的内存中去。 注意:只将数值c的最低一个字节填充到内存。


内存分配机制

1、栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) : 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
4、文字常量区 :常量字符串就是放在这里的。程序结束后由系统释放。
5、程序代码区:存放函数体的二进制代码。


进制之间的转换

  1. R进制数转换为十进制数: 按权展开相加(系数乘以基数的权次方相加)
  2. 十进制转换R进制: 将十进制整数转换成R进制整数采用“除R取倒余法”。即将十进制整数除以R,得到一个商和一个余数;再将商除以R,又得到一个商和一个余数; 以此类推,直到商等于零为止。每次得到的余数的倒排列,就是对应R进制数的各位数。

各种容器的迭代器失效问题

迭代器失效是指当容器底层存储发生变动时,原来指向容器中某个或某些元素的迭代器由于元素存储位置发生了改变而不再指向他们,从而成为无效的迭代器。 引起迭代器失效的主要操作有: 改变容器容量的方法:reserve()、resize()、push_back()、pop_back()、insert()、erase()、clear() 一些泛型算法:sort()、 copy() 、replace()、 remove()、 unipue()


#include<>和#include“”

#include<>:编译器直接从系统类库目录里查找头文件: #include"":默认从项目当前目录查找头文件,所谓项目当前目录,就是项目工程文件(*.vcxproj)所在的目录

使用 Hugo 构建
主题 StackJimmy 设计