八股c++学习-day1

文章详细介绍了C++中的const关键字的用法,const与#define的区别,内存对齐的原则和特殊情况,虚函数的概念和调用机制,volatile关键字的作用,以及C++中的几种容器如vector、deque和list的特点。此外,还讨论了内存分配、静态成员、初始化过程以及C++编译过程中的预编译、编译、汇编和链接阶段。内联函数和new/malloc的区别以及堆栈内存管理也有所涉及。
摘要由CSDN通过智能技术生成

const

const int *a==int const *a:都是指a所指向的值不能改,但是a可以指向别的东西
const int a:a变量变成常量,不可修改
int *const a:a的值可以更改,但是指向它的指针不能更改
int const *const a:a本身和指向它的指针都不能更改
 

const和#define的区别

(1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。
而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误.
(2)有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。
(3)#define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用。
(4)#define定义的常量不分配内存,而const定义的常量会分配在常量存储区中。

  • 如果const修饰的是全局变量放到常量区
  • 如果const修饰的是局部变量放在栈区
  • 如果const修饰的变量没有被使用则会放到符号表中,其内容不会分配空间

C++的内存分布:堆区、栈区、全局/静态区、常量区、程序代码区

为了方便计算机去读写数据要进行内存对齐,不同字节变量放在不同地址上,经过内存对⻬之后,CPU 的内存访问速度⼤⼤提升。因为 CPU 把内存当成是⼀块⼀块的,块的⼤⼩可以是 2,4,8,16 个字节,因此 CPU 在读取内存的时候是⼀块⼀块进⾏读取的,块的大小称为内存读取粒度。

什么时候不应该内存对齐?

一般来说,当我们追求空间效率而不是时间效率时,我们可以选择取消或者减小内存对齐。例如,在嵌入式系统中,由于资源有限,我们可能更关心节省空间而不是提高速度。此时我们可以使用编译器提供的选项来调整或者关闭内存对齐。

虚函数

父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。

虚函数的调用:

每个虚函数都会有一个与之对应的虚函数表,该虚函数表的实质是一个指针数组,存放的是每一个对象的虚函数入口地址。对于一个派生类来说,他会继承基类的虚函数表同时增加自己的虚函数入口地址,如果派生类重写了基类的虚函数的话,那么继承过来的虚函数入口地址将被派生类的重写虚函数入口地址替代。那么在程序运行时会发生动态绑定,将父类指针绑定到实例化的对象实现多态。每个类只有一个虚函数表,虚函数表是在编译的时候就确定的了。一般来说,在32位系统下,指针占4个字节,在64位系统下,指针占8个字节。

虚函数什么时候调用?

运行时根据实际对象的类型来确定调用哪个函数,而不是根据指针或引用的类型来确定。当一个虚函数被定义为类的成员函数时,它会被标记为虚函数。

volatile关键字

C++ 中,关键字 volatile 用于声明一个变量是易变的(volatile variable),即该变量可能会在程序中的任意时刻被意外地改变。这意味着,当读取一个易变的变量时,编译器不会从缓存中读取该变量的值,而是每次都会从内存中重新读取该变量的值。同样地,当写入一个易变的变量时,编译器也不会将该变量的值存储在缓存中,而是立即将该变量的值写入内存中。

容器

vector

1、顺序序列;2、动态数组;3、尾删有较佳性能。

使用分配器(allocator)进行内存分配和管理。

进行 vector 扩容时,如果存储的是自定义类型,会挨个复制构造元素,可能会造成性能问题。为了避免这一点,可以使用移动语义来优化。

在 C++11 引入的移动语义中,我们可以通过 std::move() 函数将一个对象转化为右值引用,这样就可以在元素的拷贝构造函数中实现移动语义,将对象的资源所有权从一个对象转移到另一个对象中,而不是进行深拷贝。

deque

1、双向队列;2、在两端增删元素

list

1、双向链表;2、不支持随机存取

map

1、根据first和second排序;2、通过红黑树实现;3、不允许容器中有重复的key元素。

空间配置器

在C++ STL中,空间配置器便是用来实现内存空间(一般是内存,也可以是硬盘等空间)分配的工具,他与容器联系紧密,每一种容器的空间分配都是通过空间分配器alloctor实现的。

一级配置器主要是考虑大块内存空间,利用malloc和free实现;二级配置器主要是考虑小块内存空间而设计的(为了最大化解决内存碎片问题,进而提升效率),采用链表free_list来维护内存池(memory pool),free_list通过union结构实现,空闲的内存块互相挂接在一块,内存块一旦被使用,则被从链表中剔除,易于维护。

迭代器

迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,重载了指针的一些操作符,–>、++、–等。迭代器封装了指针,是一个”可遍历STL( Standard Template Library)容器内全部或部分元素”的对象,本质是封装了原生指针,是指针概念的一种提升,提供了比指针更高级的行为,相当于一种智能指针,他可以根据不同类型的数据结构来实现不同的++,–等操作。

迭代器失效:顺序容器使用删除会使后面的迭代器失效(自动往前进一,导致地址全变,所以会失效),解决办法:it=earse(it),即返回删除元素下一个的迭代器

stl的vector和list具体怎么实现?常见操作的时间复杂度是多少?

vector:开辟三倍内存,旧数据开辟到新内存,释放旧的内存,指向新内存

static关键字

面向过程:

静态全局变量:静态全局变量在声明它的整个文件中都是可见的,而在文件之外是不可见的;(作用域是整个文件)变量的生存周期存在于整个程序运行期间。
静态局部变量:内存存放在程序的全局数据区中,静态局部变量在程序执行到该对象声明时,会被首次初始化。其后运行到该对象的声明时,不会再次初始化(只会被初始化一次),变量的生存周期存在于整个程序运行期间。
静态函数(主要目的确定作用域):作用域只在声明它的文件当中,不能被其他文件引用,其他文件可以定义同名的全局函数,其他文件想要调用本文件的静态函数,需要显示的调用extern关键字修饰其声明。

面向对象:

静态成员变量:用于修饰 class 的数据成员,即所谓“静态成员”。这种数据成员的生存期大于 class 的对象(实体 instance)。静态数据成员是每个 class 有一份,普通数据成员是每个 instance 有一份,因此静态数据成员也叫做类变量,而普通数据成员也叫做实例变量。诞生比构造函数早,在类声明的时候就产生了。
静态成员函数:静态成员函数不能访问非静态(包括成员函数和数据成员),但是非静态可以访问静态

初始化
对于C语言的全局和静态变量,初始化发生在任何代码执行之前,属于编译期初始化。

而C++标准规定:全局或静态对象当且仅当对象首次用到时才进行构造(静态全局和静态局部)。
然而,静态成员变量与静态局部变量和全局变量不同。它们必须在类的外部进行初始化,并且在程序开始执行之前就已经被分配内存并初始化了。
 

C++编译过程

  • 预编译:展开所有宏定义,处理#ifdef等,过滤所有注释
  • 编译:产生语法树,产生汇编代码
  • 汇编:将汇编代码转为机器语言,生成目标文件
  • 链接:将不同目标文件连接到一起,形成可执行文件

动态链接和静态链接

  • 静态:连接的时候就把需要的函数或者过程放进了可执行文件中,即使静态库删除了依旧可以运行
  • 动态:是在链接的时候没有把调用的函数代码链接进去,而是在执行的过程中,再去找要链接的函数,生成的可执行文件中没有函数代码,静态库删除就找不到函数了。

内联函数

内联函数是在 C++ 中增加的一个功能,可以提高程序执行效率。如果函数是内联的,编译器在编译时,会把内联函数的实现替换到每个调用内联函数的地方,可以与宏函数作类比,但宏函数不会进行类型检查。解决一些频繁调用的小函数消耗大量空间的问题。

那些函数不适合作为内联函数:

1、递归调用本身的函数
2、包含复杂语句的函数,例如:for、while、switch 等;
3、函数包含静态变量(内联函数的定义和调用是在编译期进行的,而不是在运行期1。编译器会将内联函数的代码直接嵌入到调用它的地方,从而避免了函数调用的开销。但是,这也意味着每次调用内联函数时,都会生成一份新的函数代码。
如果内联函数中有静态变量,那么每次生成新的函数代码时,也会生成新的静态变量3。这样就会导致多个静态变量共存于程序中,并且互相独立,无法保持一致性);

struct和class的区别

struct默认公有继承,class默认私有继承
struct内不能声明函数,class可以

导入C函数的关键字是什么,C++编译时和C有什么不同?


C++中,导入C函数的关键字是extern,表达形式为extern “C”, extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

new和malloc

  • new是操作符 malloc是函数
  • new在调用的时候先分配内存,在调用构造函数,释放的时候调用析构函数;而malloc没有构造函数和析构函数。
  • new发生错误抛出异常,malloc返回null
  • new返回具体类型指针,malloc需要强制转换

delete如何知道该释放多大的空间,这些信息存在什么位置


一种是在分配内存时,在内存首地址之前存储一个额外的值,表示数组的大小或者元素个数。这样,在释放内存时,就可以根据这个值来确定要释放多少空间。
另一种是在编译时,编译器会记录数组类型和大小的信息,并在生成代码时,将这些信息传递给delete[]操作符。这样,在运行时,delete[]就可以根据类型和大小来调用相应的析构函数和free函数
 

delete[]和delete的区别,基本数据类型的数组使用delete可以释放完全吗


当new申请的是C++对象数组时,delete和delete []差别就很大了,delete只会析构一个对象
delete和delete[]的区别主要在于是否调用析构函数。如果用delete[],则在回收空间之前所有对象都会首先调用自己的析构函数。基本类型的对象没有析构函数,所以回收基本类型组成的数组空间用delete和delete[]都是应该可以的;但是对于类对象数组,只能用delete[]。否则可能会造成内存泄漏或者其他错误。

堆和栈的区别

  • 堆栈空间分配不同:栈由操作系统自行释放,堆一般由程序员释放
  • 缓冲方式不同:堆一般是二级存储,会慢一点,栈是一级存储,函数调用完直接释放
  • 数据结构不同:栈类似栈,而堆类似数组

内存泄漏

  • 父类析构不是虚析构
  • 用malloc或new申请资源后,没有释放
    • share_ptr互相引用对方

64位系统存一个地址多大空间


一个内存地址对应一个字节(8位),所以64位系统可以表示16个16进制数(64位)的内存地址。这样,64位系统的最大寻址空间为2的64次方字节,即16384PB或16777216TB。但是,并不是所有的64位系统都能使用这么大的寻址空间,因为有些CPU只有40位或48位的地址线,而且操作系统也有自己的限制

检查内存泄漏的方法

  • 使用第三方工具
  • 重写new和delete,给出关键信息
  • 使用智能指针

C++编译和C编译的区别

链接库:C语言和C++语言使用的链接库不同,C语言使用C标准库,C++语言使用C++标准库。C++标准库中包含了C标准库中的所有函数,同时还包含了STL(标准模板库)和一些面向对象的特性,如命名空间、类、继承等。

C++在哪些情况下会产生临时对象

C++中,临时对象是编译器在不同的情况下创建的没有名字的对象。临时对象通常出现在以下场景:

引用初始化,例如 const int& r = 42;
参数传递,例如 f(42);
表达式求值,例如 a + b;
函数返回,例如 return x + y;
异常抛出,例如 throw x;
临时对象有一个生命周期,由它们的创建点和销毁点决定4。任何创建多个临时对象的表达式最终会按照创建的逆序销毁它们3。临时对象的销毁时间取决于它们的使用方式4:

C++静态链接库(lib)和动态链接库(dll)的区别


静态链接库(lib)是在编译时将库的代码直接复制到可执行文件中,所以在程序运行时不需要依赖任何外部库文件所有的代码都在一个可执行文件中。因此,静态链接库的优点是移植方便,无需安装其他库文件,程序运行时速度较快。缺点是占用硬盘空间较大,同时也存在代码重复的情况,不利于代码的更新和维护。

动态链接库(dll)是在程序运行时才被加载到内存中,程序需要调用库函数时才会加载对应的库文件。因此,动态链接库的优点是共享库文件,节省了硬盘空间,同时也方便了库文件的更新和维护。缺点是相对于静态链接库,程序运行时会存在一定的额外开销,如加载库文件、解析符号等。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值