编程语言
半小时以上阅读
•
9164 字
+
5764 词
面向对象的编程思想解决什么问题
- 面向对象主要是可以将复杂的系统拆解为各个小的独立的对象,每个对象负责特定的功能。这样做降低复杂性,使得系统更容易维护。
- 面向对象提供了抽象封装继承多态等机制,可以实现代码的复用,以及对扩展开放对修改关闭的原则保证了系统的稳定以及易维护。
左值右值引用,以及作用,什么是将亡值
- 首先说明左值和右值的区别,左值是能够取地址的值,而右值是不能够取地址的值。在c++中可以用取地址符号来判断。而左值引用是单个取地址符号,用来绑定一个左值的,来作为变量的别名,可以直接在引用上操作修改变量,以及避免在参数传递中发生拷贝。const左值引用是一个万能引用,也可以绑定右值。
- 而右值引用只能用来绑定右值,是两个取地址符号来表示的,右值引用最重要的作用是实现移动语义,通过移动而不是拷贝来避免不必要的资源消耗。而在模版编程中,T && t表示万能引用,可以绑定左值也可以绑定右值,可以搭配std::forward来实现完美转发。
- 将亡值是指C++11新增的和右值引用相关的表达式,通常指 将要被移动的对象 、 T&&函数的返回值 、 std::move函数的返回值 、转换为T&& 类型转换函数 的返回值,将亡值可以理解为即将要销毁的值,通过“盗取”其它变量内存空间方式获取的值,在确保其它变量不再被使用或者即将被销毁时,可以避免内存空间的释放和分配,延长变量值的生命周期, 常用来完成移动构造或者移动赋值 的特殊任务。
什么错误是编译不报,运行时报错,举例说明
- 空指针解引用,数组越界,除以0,多次释放同一个内存地址,访问已经释放过的内存, dynamic_cast失败的时候
迭代器失效原因,怎么编程能够避免迭代器失效
- vector,deque,string等容器添加元素或者删除元素,会导致迭代器失效,因为添加元素的时候,可能会发生扩容现象,这个时候需要重新分配内存,所以迭代器指向的空间还是原来的老空间上。
- 可以使用一些比较稳定的容器比如list,map,set,当删除元素的时候,其他迭代器并不会失效。第二点是当插入或删除元素后,保持重置迭代器的习惯,比如利用erase返回的迭代器进行迭代。
可重入函数和不可重入函数
可重入和线程安全的区别
- 可重入函数 :可以 安全地被中断并在中断后重新进入 。
- 线程安全函数 :可以 安全地被多个线程同时调用 。
内存分区讲一下
-
栈
(
Stack): 用于 存储局部变量和函数调用的上下文 。栈的内存分配是自动的,由编译器管理。 -
堆
(
Heap): 用于 动态内存分配 。程序员可以使用new、malloc等操作符或函数从堆上分配内存,并使用delete、free释放内存。 - 映射段 :内存映射区包含文件映射和匿名内存映射,比如mmap系统调用进行映射就是映射这块内存。
-
全局
/静态
存储区
(
Global/Static Storage): 存储全局变量和静态变量 ,包括:- 数据段:存储 初始化的 全局变量和静态变量。
-
BSS 段:存储 未初始化的 全局变量和静态变量。
-
常量存储区
(
Constant Data): 存储程序中的常量数据 ,如 字符串字面量 。此外,如果有定义了虚函数, 常量区还会存储虚表 。 -
代码段
(
Code Segment 或 Text Segment): 存储 程序的可执行代码和函数的二进制指令 。
c语言怎么实现多态
静态成员函数访问非静态数据成员的方法
内存泄漏和内存溢出的概念,怎么防止
new和delete的底层原理
-
new是C++的 操作符 ,由编译器实现,可以直接用来分配对象或数组,并且在分配失败会抛出std::bad_alloc异常。 - new表达式工作步骤有三步 1.调用operator new库函数,分配未类型化的空间,用来保存指定类型的一个对象 ,而在operator new中,会调用malloc函数 2.运行该类型的构造函数初始化对象 3.返回指向对象的指针
-
new是类型安全的,它会根据分配的对象类型进行 正确的内存分配和构造函数调用 。 -
使用
new分配的对象在对象生命周期结束时需要使用delete来释放,delete会自动调用对象的析构函数。
new
和
malloc
都是动态内存分配的手段,但
new
提供了类型安全和构造/析构的自动化
,
placement_new
允许在已经分配的内存地址上构造对象
delete表达式工作步骤有两步
1.调用析构函数,回收对象中数据成员申请资源
2.调用operator delete的标准库函数释放该对象所用的内存
malloc和free的底层原理
malloc
是一个库函数,通常需要包含头文件
,
并且只分配原始内存,分配失败时会返回NULL
。
malloc
不是类型安全的,它
只分配原始内存,不调用构造函数
。返回类型是
void*
,需要强制类型转换为具体的指针类型。
使用
malloc
分配的内存需要使用
free
来释放,
free
不会自动调用析构函数,因此如果分配的是对象数组,需要手动调用析构函数。
而
malloc
则提供了更底层的内存分配方式,需要
手动管理构造和析构
。
- malloc的底层实现是怎样的?free是怎么回收内存的? 答: 1、 当开辟的空间A小于128K的时候,malloc底层调用brk()函数 ,brk是将数据段的 最高地址指针_edata 往高地址推**,_edata+30K只是完成虚拟地址的分配,A这块内存现在还是没有物理页与之对应的,等到进程第一次读写A这块内存的时候,发送缺页中断, 这个时候,内核才会分配给A这块内存对应的物理页。也就是说, 如果用malloc分配了A这块内容,然后从来不访问它,那么A对应 的物理页是不会被分配的。 而 当开辟的空间 大于128K 的时候,利用 mmap系统调用****,从堆和栈的中间也就是 文件映射区 分配一块 虚拟内存 。这样做主要是因为: brk分配的内存需要等到高地址内存释放以后才能释放 (比如先申请A再申请B,所以B相对于A来说是高地址空间,所以在B释放 之前,A是不可能释放的, 因为只有一个_edata指针 ,这就是 内存碎片产生的原因 。而 mmap分配的内存可以单独释放 。 2、free回收内存时首先要知道这块内存的地址多大,所以在malloc返回的地址的前一小段的内存块控制块,存储这一块有多大的信息。对使用mmap机制分配空间的malloc返回的地址free时,直接将其对应的虚拟内存和物理内存一起释放。而对brk机制分配空间的malloc返回的地址free时,并不会立刻释放,默认情况下, 当最高地址空间的空闲内存超过128K时,执行内存紧缩操作(trim)
new/delete与malloc/free的区别与联系是什么?(面试常考)
this指针的原理 ,如果把this delete,还能用吗,什么场景下还能用?
push_back()左值和右值的区别是什么?
template<typename Ty> // 函数模板的类型推演 + 引用折叠
void push_back(Ty &&val) //Ty CMyString& + && = CMyString&
{
if (full())
expand();
// move(左值):移动语义,得到右值类型 (int&&)a
// forward:类型完美转发,能够识别左值和右值类型
_allocator.construct(_last, std::forward<Ty>(val));
_last++;
}
move底层是怎么实现的?
template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
return static_cast<typename remove_reference<T>::type&&>(t);
完美转发
怎么利用C++新特性实现一个无锁的并发
原子性和可见性是什么, 为什么需要保证可见性,是什么问题导致的
- 原子性:原子性指的是一个操作是不可分割、不可中断的,要么全部执行并且执行的过程不会被任何因素打断,要么就全不执行。
- 可见性: 可见性指的是一个线程修改了某一个共享变量的值时,其它线程能够立即知道这个修改 。
- 有序性:有序性指的是对于一个线程的执行代码,从前往后依次执行,单线程下可以认为程序是有序的,但是并发时有可能会发生指令重排。
CPU的缓存一致性是什么
- 什么是CPU缓存一致性?
2. 为什么需要缓存一致性?
- Modified (M) : 数据已被修改,与主内存不同。
- Exclusive (E) : 数据仅存在于当前缓存中,与主内存相同。
- Shared (S) : 数据存在于多个缓存中,与主内存相同。
- Invalid (I) : 缓存中的数据无效,需要从主内存或其他缓存中获取。
设计模式,使用单例要注意什么,双检锁是绝对安全的吗?
cpp 继承时析构函数为什么为虚?
cpp 是否可以在构造函数中调用虚函数
shared_ptr
的引用计数机制通过原子操作保证了线程安全,但对象的访问和
shared_ptr
实例的直接修改仍需用户同步。理解其线程安全边界是避免并发问题的关键。
c++11总结15——shared_ptr在多线程下的安全性问题_sharedptr线程安全-CSDN博客
stl容器线程安全
vec的pushback和clear的时间复杂度
幂等性概念?如何实现接口的幂等?
linux上open打开一个文件的过程,通过open打开一个文件的返回值是什么
vector和list之间的区别
空类中有什么函数?
explicit用在哪里?有什么作用?
成员变量初始化的顺序是什么?
指针占用的大小是多少?
野指针和内存泄漏是什么?如何避免?
多线程会发生什么问题?线程同步有哪些手段?
什么是STL?
对比迭代器和指针的区别
线程有哪些状态,线程锁有哪些?
就绪状态 :线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可以运行,只等待获取cpu的使用权。 运行状态 :就绪状态的线程获取了CPU,执行程序代码。 阻塞状态 :阻塞状态时线程因为某种原因放弃CPU使用权,暂时停止运行,直到线程进入就绪状态,才有机会转为运行状态。 阻塞的情况分三种: 等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入等待池中。可以通过notify或notifyAll方法唤醒。同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。其他阻塞:运行的线程执行sleep或join方法或者发出I/O请求时,JVM会把该线程置为阻塞状态。当sleep超时,join等待中止或超时,或I/O完毕,重新进入就绪状态。 死亡状态 :线程实行完了,或者因为异常退出了run方法,该线程结束生命周期。
如何实现线程安全,除了加锁还有没有其他的方式?
C++中空类的大小是多少?
weak_ptr是怎么实现的?
一个函数f(int a,int b),其中a和b的地址关系是什么?
f(int a, int b)
中,
a
和
b
是函数参数,以传值方式传递。这意味着它们在函数内部是局部变量,存储在栈上。它们的地址关系取决于编译器和具体的调用约定,通常情况下,
a
和
b
的地址是连续的,但具体顺序和间隔可能会有所不同。
关键点
-
传值机制
:
a和b是传值参数,函数接收的是实参的拷贝。 - 内存位置 :它们通常存储在栈上,地址关系由编译器决定,可能连续也可能不连续。
- 不可预测性 :由于栈帧的布局由编译器实现,不同编译器或编译选项可能导致不同的内存布局,因此不应依赖于它们的具体地址关系。
移动构造和拷贝构造的区别是什么?
lamda表达式捕获列表捕获的方式有哪些?如果是引用捕获要注意什么?
哈希碰撞的处理方法
unordered_map的扩容过程
类中static函数是否能声明为虚函数?
哪些函数不能被声明为虚函数?
- 普通函数(非成员函数) :定义虚函数的主要目的是为了重写达到多态,所以 普通函数声明为虚函数没有意义,因此编译器在编译时就绑定了它。
- 静态成员函数 :静态成员函数对于每个类都只有一份代码,所有对象都可以共 享这份代码,他不归某一个对象所有,所以它也没有动态绑定的必要。
- 内联成员函数 :内联函数本就是为了减少函数调用的代价,所以在代码中直接 展开。但虚函数一定要创建虚函数表,这两者不可能统一。另外,内联函数在 编译时被展开,而虚函数在运行时才动态绑定。
- 构造函数 :这个原因很简单,主要从语义上考虑。因为构造函数本来是为了初 始化对象成员才产生的,然而虚函数的目的是为了在完全不了解细节的情况下 也能正确处理对象,两者根本不能“ 好好相处 ”。因为虚函数要对不同类型的对 象产生不同的动作,如果将构造函数定义成虚函数,那么对象都没有产生,怎 么完成想要的动作呢
- 友元函数 :当我们把一个函数声明为一个类的友元函数时,它只是一个可以访 问类内成员的普通函数,并不是这个类的成员函数,自然也不能在自己的类内 将它声明为虚函数。
如何保证类的对象只能被开辟在堆或者栈上?
讲讲你理解的虚基类
-
- 主要用来解决菱形继承时可能发生的对同一基类继承多次而产生的 二义性 问题
- 为最远的派生类提供唯一的基类成员,而不重复产生多次复制
- 这时候虚基指针会指向同一个虚基类
C++哪些运算符不能被重载?
-
.:成员访问运算符 -
.*:成员指针访问运算符 -
:::作用域解析运算符 -
?::条件运算符 -
sizeof:求字节大小运算符 -
typeid:类型信息运算符
动态链接和静态链接的区别,动态链接的原理是什么?
bash
1. gcc -c add.c 得到add.o
2. ar crsv libadd.a add.o 打包
3. sudo cp libadd.a /usr/lib 将库文件放在库目录
4. gcc -o main main.c -ladd 编译时加入库名
bash
1. gcc -c add.c -fpic 得到add.o
2. gcc -shared -o libadd.so add.o
3. sudo cp libadd.so /usr/lib
4. gcc -o main main.c -ladd
- 删除原软链接
- 重新建立链接 ln -s
C++中怎么编译C语言代码?
内联函数
除了new和malloc还有什么方法申请内存
互斥量怎么实现的
1. 原子操作
pthread_mutex
使用了硬件提供的原子操作来实现锁的基本功能,如
compare-and-swap
(CAS)等。这些操作确保在多处理器环境中可以安全地执行检查和设置操作。
2. Futex(快速用户空间互斥)
pthread_mutex
常常使用 futex(fast userspace mutex)机制。futex 允许在用户空间执行快速的加锁和解锁操作,而只有在竞争的情况下才会陷入内核。
- 无竞争路径 :如果锁是可用的, 线程使用原子操作直接在用户空间获取锁,不需要陷入内核 。
- 竞争路径 :如果锁不可用,线程会调用 futex 系统调用进入内核,线程会被挂起,直到锁可用。
3. 内存屏障
pthread_mutex
使用内存屏障来防止编译器和 CPU 进行不当的指令重排序。
快速用户区互斥量futex
随着并行的增加,有效的同步和锁机制对性能而言非常重要。如果等待时间短的话,完全可以用自旋锁忙等待,因为如果阻塞进程,陷入内核的开销远远比其大。但如果等待时间长,则会浪费CPU周期。如果有很多竞争,那么阻塞该进程,并仅当锁被释放的时候让内核接触阻塞会更加有效。然而,这却带来了相反的问题:竞争不激烈时,那么不断地内核切换将花销太大。
因此引入了futex,他是linux系统的一个特性,结合了以上两者之所长。它实现了基本的锁,但避免陷入内核,除非它是真的不得不这样做。因为来回切换到内核花销很大,所以这样做可以十分可观的改善性能。一个futex包含两个部分:一个内核服务和一个用户库。内核服务提供一个等待队列,它允许多个进程在一个锁上等待,直到内核对他们解除阻塞。将一个进程放到内核等待队列需要系统调用,而系统调用将陷入内核,这会非常花费时间,我们应该尽量避免他们。因此,在没有竞争时,futex完全在用户空间工作,进程共享一个锁变量,假设锁初始态是释放状态,初始值为1.线程要进入临界区前,通过执行原子操作"减少并检验"来获得锁,接下来,这个线程检查结果,看锁是否被释放,如果为处于被锁状态,线程成功获取锁。如果该锁被其他线程持有,则进行系统调用将该线程投入内核等待队列。当一个线程将要退出临界区时,进行原子操作"增加并检验"来释放锁,并检查结果,看是否扔有进程阻塞在内核等待队列上。如果有,通知内核可以对等待队列里的一个或个线程解除阻塞。