20240622 C++面试八股
明昧 Lv7

前言

  • 这个题库里几乎所有东西你都你能看懂,对于面试来说,最重要的就是复述!带有自己逻辑和框架的复述!

C++11新特性

30、unique_ptr和shared_ptr的区别是什么

uptr指向的对象只能有一个uptr指针作为访问入口

sptr指向的对象可以有多个sptr指向同一个对象作为访问入口


两者都能实现内存的自动管理与释放

shared_ptr是线程安全的吗

https://www.zhihu.com/question/56836057

  • 线程安全:线程的操作都按照预期执行。如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。
  • 谈到sptr的时候,要分不同的操作对象和操作行为来讨论其线程安全性

1.shareptr引用记数的行为(是线程安全的,因为源码是原子操作)

2.shareptr们修改指针指向的行为是线程安全的吗?

修改也分几种情况

修改有的是将该指针变为空指针

有的情况的将该指针指向对象改变

image-20240622193609122

3.所管理数据的线程安全性(不是线程安全的,

image-20240622193840841


不行,我的探索欲望上来了

引用计数改变我们一般是在什么情况下改变,那么多个对象,这么多个线程,难道我们需要通过一张庞大的线程的对照表来对来查找并且维护对于相关对象的引用计数吗?

明确引用技术是在什么函数被调用的情况下实现的?

我们明确知道引用技术的修改和实际的指向修改肯定不是同一条语句

在我们通过变量修改指向的时候,如果不是整个过程实现原子操作,很容易出问题

可能执行上一条之后这个变量应该被销毁了(记数层面来看被销毁了)

1、C++11引入了哪些新的指针类型?简单描述一下它们的作用*

引入了s u w

s

u

w 弱引用:不增加引用计数,不影响对象的生命周期。

还是不太懂w会被用在哪里

C++11中的右值引用与移动语义是怎样提高性能的

  • 右值引用:通过 && 实现,允许对临时对象进行引用。

  • 在对象的传递和返回操作中。通过减少不必要的拷贝操作,右值引用和移动语义可以显著提升效率。

  • 移动语义:允许资源从一个对象“移动”到另一个对象,而不是复制资源。这对于涉及动态内存、文件句柄等资源的对象非常有用,因为移动操作可以显著减少资源管理的开销。

  • 实现所需步骤:自己实现移动构造函数和移动赋值运算符

C11中右值左值相互转换的问题*

左值可以隐式转换成右值:当左值在需要右值的地方使用时,编译器会自动完成这种转换。

右值不能直接转换成左值:右值是临时对象,不能直接转换成左值。但可以通过右值引用或 const 左值引用来间接地将右值绑定为左值,从而在需要时操作或读取它。

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

void manipulateRvalue(int&& x) {
x = 20; // x 是一个右值引用,但在函数体内它被视为左值
std::cout << "x: " << x << std::endl;
}

int main() {
manipulateRvalue(10); // 10 是右值
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

void printRvalue(const int& x) {
std::cout << "x: " << x << std::endl;
}

int main() {
printRvalue(10); // 10 是右值
return 0;
}

3、C++请你谈谈对auto关键字的理解,并且谈谈在实际应用中它的作用

  • 使用 auto 关键字时,你不需要显式地指定变量的类型,编译器会根据变量的初始化表达式来推断其类型。
  • auto 可以用于局部变量、函数参数、返回类型等场景。
  • 类型推断推断变量的类型。
  • 在泛型编程中,当类型参数非常复杂或者难以显式指定时用

4 、C++11中lambda表达式是什么,如何使用它们

是一个不用声明**(匿名)**的函数区块或者是说是函数对象


使用

{}

[=] 按值捕获

[x]

​ [&]按引用捕获

C++内存

全局变量和静态变量的区别

栈和堆的区别是什么

  • 全局区(静态区)(static):全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另外一块区域。程序结束后由系统释放。

栈区(stack)

  • 问地址分配就说高低(其实架构不同不一定遵守这个分配规则
  • 存放程序的局部变量,在函数被调用时,栈用来传递参数和返回值
  • 先进后出,一旦出了作用域就会被销毁

  • 编译器自动管理内存

堆区(heap)

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc/free等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张)/释放的内存从堆中被剔除(堆被缩减)。

  • 问地址分配就说低高
  • 堆区的内存分配使用的是alloc;
  • 需要程序员管理内存

3、内存泄漏的场景有哪些

场景和概念的层次可能会有重合,总之列出123456就可以了

1.申请与释放没有成对出现

2.类的设计中具有指针,但是析构函数没有实现释放

3.浅拷贝带来的析构函数两次释放问题

4.设计类中自身给自身赋值或者自己给自己拷贝的自身检查

5.返回局部指针

6.子类父类析构函数缺少虚继承关系

利用基类指针进行子类对象释放的适合,如果不是这样定义,可能会导致子类对象有部分数据内存未被释放


1、利用malloc申请内存,没有释放或者释放不正确对象占用的内存

2.构造函数中分配内存,析构中没有…(1的特化)

判断设计正确不正确的思路:

malloc/free new/delete必须成对出现

数组对象必须采取[ ]进行析构

指针数组对象除了对数组本身指针析构,还得析构指针所指向的空间

方括号与内存释放

方括号存在的作用:

1.告诉编译器这个指针指向的是一个对象数组

2.同时也告诉编译器正确的对象地址值并调用对象的析构函数,

如果没有方括号,那么这个指针就被默认为只指向一个对象,

对象数组中的其他对象的析构函数就不会被调用,结果造成了内存泄露。


如果在方括号中间放了一个比对象数组大小还大的数字/释放过大空间

那么编译器就会调用无效对象(内存溢出)的析构函数,会造成堆的奔溃。

如果方括号中间的数字值比对象数组的大小小的话/释放空间不到位

编译器就不能调用足够多个析构函数,结果会造成内存泄露。


1>对于单个对象,单个基本类型(如int,double等)的变量,我们肯定採用delete,不会出错;

2>对于基本类型数组,因为不须要大小參数,因而,採用delete或array delete(delete []),均能够,如上例中,我便直接採用了delete m_variety,建议为了统一,採用delete []m_variety;

3>对于自己定义的对象所组成的对象数组,则一定要採用array delete,这样编译器才会在释放内存前调用每一个对象的析构函数,并调用free释放对象数组空间;

4、指向对象的指针数组不等同于对象数组

对象数组是指:数组中存放的是对象,只需要delete []p,即可调用对象数组中的每个对象的析构函数释放空间

指向对象的指针数组是指:数组中存放的是指向对象的指针,不仅要释放每个对象的空间,还要释放每个指针的空间,delete []p只是释放了每个指针,但是并没有释放对象的空间,正确的做法,是通过一个循环,将每个对象释放了,然后再把指针释放了。

也就是说,数组的基本类型是指向对象的指针,此时,是用delete 还是delete [](array delete),并不重要,关键是指针并没有析构函数,必须用户自己调用delete语句。

5、缺少拷贝构造函数

两次释放相同的内存是一种错误的做法,同时可能会造成堆的奔溃。

按值传递会调用(拷贝)构造函数,引用传递不会调用。

在C++中,如果没有定义拷贝构造函数,那么编译器就会调用默认的拷贝构造函数,会逐个成员拷贝的方式来复制数据成员(位拷贝),如果是以逐个成员拷贝的方式来复制指针被定义为将一个变量的地址赋给另一个变量。这种隐式的指针复制结果就是两个对象拥有指向同一个动态分配的内存空间的指针。

当释放第一个对象的时候,它的析构函数就会释放与该对象有关的动态分配的内存空间。而释放第二个对象的时候,它的析构函数会释放相同的内存,这样是错误的。

所以,如果一个类里面有指针成员变量,要么必须显示的写拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符。

  1. 缺少重载赋值运算符

这种问题跟上述问题类似,也是逐个成员拷贝的方式复制对象,如果这个类的大小是可变的,那么结果就是造成内存泄露。

须要注意事实上现的细节差别:

1> 拷贝构造函数编译器会自己主动阻止自己构造自己,比方:

Point x(x); // 出错;

可是,赋值操作不会;

Point x = x; // 编译期不会出错,但执行期会出错!

上面的错误原因在于,编译器尽管为x分配了内存,但调用拷贝构造函数时,m_color还没初始化;

建议,尽量不要用这样的方法初始化,以便将错误在编译期间显示出来;

2> 赋值运算符必须差别是否自身赋值;

3> 在赋值前必须释放原有new操作分配的资源(当然,其它文件等资源也要释放,这里仅仅讨论内存溢出,略过不提!)

7.返回值为野指针

a. 返回栈上对象的引用或者指针(也即返回局部对象的引用或者指针)。导致最后返回的是一个空引用或者空指针,因此变成野指针

b. 返回内部静态对象的引用。

c. 返回一个泄露内存的动态分配的对象。导致内存泄露,并且无法回收

解决这一类问题的办法是重载运算符函数的返回值不是类型的引用,二应该是类型的返回值,即不是 int&而是int

8.没用将基类的析构函数定义成虚函数

1> 将基类构造函数定义为非虚函数,则该类不同意扩展;

2> 假设不是虚函数,则释放基类指针不会调用派生类的析构函数,即使它指向一个派生类对象;

3> 无论是不是虚函数,释放派生类指针均会调用基类的析构函数,且调用顺序不变;

4> 假设为虚函数,则释放基类指针且该指针指向一个派生类,则会先调用派生类的析构函数,再调用基类的析构函数。
————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/dddgggd/article/details/119007585

4、内存分配的方式有多少种

从静态存储区分配内存

从静态存储区域分配的内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。静态分配的区域的生命期是整个软件运行期,就是说从软件运行开始到软件终止退出。只有软件终止运行后,这块内存才会被系统回收。

从栈上分配内存

在执行函数时函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但 是分配的内存容量有限。在栈中分配的空间的生命期与这个变量所在的函数和类相关。如果是函数中定义的局部变量,那么它的生命期就是函数被调用时,如果函数运行结束,那么这块内存就会被回收。如果是类中的成员变量,则它的生命期与类实例的生命期相同。

理解:系统自动分配,如声明int a;系统自动在栈空间中为a开辟空间。

从堆上分配内存

亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存 期由我们决定,使用非常灵活,但问题也最多。在堆上分配的内存,生命期是从调用new或者malloc开始,到调用delete或者free结束。如果不 掉用delete或者free。则这块空间必须到软件运行结束后才能被系统回收。

理解:程序员申请,并指明大小
c中的malloc,如charp=(char)malloc(10);
C++中的new运算符:如int*p2=new int(10);

5、静态内存分配和动态内存分配的区别

  • 从几个层面描述区别

    谁来操作

    特点:动静

    内存区域:

    内存申请时机

    内存释放时机

    内存使用时机

  • 编译时、静态、 、程序全程、程序运行前、编译器
  • 运行时、动态、堆、程序运行时,程序运行时、用户动态申请

6、什么是内存泄漏?如何避免它?

​ 被申请但是内存没有被正确释放的情况(忘记释放,释放过少,失去控制入口(没有接受、或者是切换时没有释放或者存储))

确保没有在访问空指针

每个内存分配函数都应该有一个 free 函数与之对应,alloc 函数除外。

每次分配内存之后都应该及时进行初始化,可以结合 memset 函数进行初始化,calloc 函数除外。

每当向指针写入值时,都要确保对可用字节数和所写入的字节数进行交叉核对。

在对指针赋值前,一定要确保没有内存位置会变为孤立的。

每当释放结构化的元素(而该元素又包含指向动态分配的内存位置的指针)时,都应先遍历子内存位置并从那里开始释放,然后再遍历回父节点。

始终正确处理返回动态分配的内存引用的函数返回值。
————————————————

                     版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_44718794/article/details/107071169


内存泄露检测工具valgrind

这是一个linux下的内存泄露检测工具

这个工具检查valgrind的原理是什么

memcheck实现了一个仿真的CPU,被监控的程序被这个仿真CPU解释执行,从而有机会在所有的内存读写指令发生的时候,检测地址的合法性和读操作的合法性。

https://blog.csdn.net/u014652595/article/details/23660347

7、什么是动态内存分配?请举例

8、如何构造一个类、使得只能在堆上或只能在栈上分配内存?

9、请解释指针在内存中的表现形式

C++基础

1、C和C++有什么区别

C是面向过程的语言

C++是面向对象的语言

C++支持继承和多态、泛型编程,函数重载,具有更强大的抽象和灵活构建能力

C不支持上述功能

C是在C语言基础上发展而来的,因此C完全兼容C语言,可以将C语言代码直接编译为C代码。这意味着C可以使用C语言的库和函数,使得既有的C语言代码可以无缝地与C++代码进行交互。

C语言更加接近底层硬件和相关内存操控,具有高效性和可移植性。

2、C语言的结构体和C++的有什么区别

1.C语言中的结构体不能为空,否则会报错

C语言中的结构体只涉及到数据结构,而不涉及到算法,也就是说在C中数据结构和算法是分离的。换句话说就是C语言中的结构体只能定义成员变量,但是不能定义成员函数。

2.在C++中既可以定义成员变量又可以定义成员函数,默认为public

虽然C语言的结构体中不能定义成员函数,但是却可以定义函数指针,不过函数指针本质上不是函数而是指针,所以总的来说C语言中的结构体只是一个复杂数据类型 ,只能定义成员变量,不能定义成员函数,不能用于面向对象编程。

在 C 中,必须显式使用 struct 关键字来声明结构。 在 c + + 中,不需要在 struct 定义类型后使用关键字。

3、C语言的关键字static和C++的关键字static有什么区别?

C语言中起到两个作用

1.修饰变量;

2.修饰函数;

1.1 修饰全局变量:让全局变量只能在本文件中被直接访问,不能直接的跨文件访问

1.2 修饰局部变量:让局部变量超出了作用域之后还不会被销毁

2.1 修饰函数:修饰函数,不能直接的跨文件访问


C++

1.修饰变量

2.修饰函数

3.修饰类中成员变量

4.修饰类中成员函数

3.1 static成员变量只能类内声明,类外初始化

3.2 static成员函数只能使用static修饰的变量

3.3static 对于同一个类只有一份

https://blog.csdn.net/m0_46606290/article/details/119939553

4、C++ 和 Java有什么核心区别?

C++是一门编译语言,代码经过编译器编译后成为机器码后被执行

java是一种解释型语言,需要jvm来一面解释一面执行


重载


多重继承


C++指针允许直接在内存空间中进行值管理

5、C++中,a和&a有什么区别

变量

对变量取地址

对于数组对象的理解

image-20240627013802742

https://cloud.tencent.com/developer/article/1387793

6、C++中stactic关键字有什么用

1、全局静态变量

在全局变量加上关键字static,全局变量就定义成一个全局静态变量,存放于静态存储区,在整个程序运行期间一直存在;未经初始化的全局静态变量会被自动初始化为0;全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。

2、局部静态变量

在局部变量之前加上关键字static,局部变量就成为一个局部静态变量,局部静态变量只初始化一次。作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变。

3、静态函数

在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突;

注意:不要在头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰。

4、类的静态数据成员

在类中,静态数据成员可以实现多个对象之间的额数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态数据成员是类的所有对象中共享的成员,而不是某个对象的成员。对于多个对象来说,静态数据成员只存储一处,供所有对象共用。

5、类的静态成员函数

静态成员函数和静态数据成员一样,它们都属于类的静态成员,他们都不是对象成员。因此对静态成员的引用不需要用对象名。在静态成员函数的实现中不能直接引用类中的非静态成员,可以引用类中的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>)

7、C++中,#define和const有什么区别?

define 只是直接展开,没有类型检查

const 有类型,会在编译时期进行类型检查

8、静态链接与动态链接有什么区别

静态库会在**链接阶段将静态库中的代码复制到可执行文件中**,当程序在执行的时候,在可执行文件中本身就有了静态库的代码,可以直接调用。

静态链接将所有程序以及相关的库都打包到可执行文件中

动态链接只是将程序

动态库则不会将代码打包到可执行文件中,而是打包动态库的名称等信息,在可执行程序运行的时候,需要去找到动态库的文件,然后把动态库加载到内存中才可以使用动态库中的代码。

所需要的函数名和库名打包到可执行文件中,到了程序执行的时候,才会将这些信息传递给操作系统,操作系统再将需要的库文件加载到内存中

静态库被打包到可执行程序中,生成的可执行文件较大,但是程序加载运行速度会比较快,发布程序时,也无需提供静态库,移植比较方便。但是当多个程序链接同一个静态库时,生成的每一个可执行文件中,都会含有这个静态库,相当于在内存中同时运行着两个相同的静态库,比较浪费系统资源。另外,当静态库的内容发生更新时,依赖该静态库的程序也需要重新进行链接,导致程序的更新升级会比较麻烦。

动态库可以实现进程间资源共享,比如可执行程序A在运行时用到了某个动态库,那么系统会将该动态库的代码动态加载到内存中,此时若正在运行的程序B也需要用到该动态库中的代码,则不需要再次加载该动态库,而是可以与程序A共享。此外,当动态库的内容更新时,只需重新编译生成新的动态库即可,而不需要对依赖该动态库的程序重新进行编译链接。

https://www.nowcoder.com/discuss/551170508180516864

https://blog.csdn.net/m0_51819222/article/details/129348478

https://www.nowcoder.com/discuss/551170508180516864

9、变量的声明和定义有什么区别

变量的声明不分配地址

变量的定义为变量分配地址和存储空间

10、typedef 和define 有什么区别呢?

typedef 别名,编译过程的一部分,类型检查,作用域限定。

define 常量,宏,预编译,不进行类型检查。

原文链接:https://blog.csdn.net/qq_36585538/article/details/115696235

11、final和override 关键字

final 类 不能被继承

final 虚函数 不能被重写

override 重写基类中的虚函数(编译器会进行检查)

12、宏定义和函数有什么区别

宏定义 预处理阶段 文本替换 不做检查

函数 编译阶段 有编译器做检查

13、sizeof 和strlen 的区别

sizeof是C++中的一个运算符,用于获取特定类型或对象在内存中所占的字节大小。其值在编译时就已经确定,并且与运行时无关。

strlen是一个函数,用于计算字符串的长度,即返回字符串中字符的个数,**不包括字符串末尾的空字符’\0’。**它是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL。

14、简述strcpy、sprintf 与memcpy 的区别

strcpy 字符串 另一个复制块 含有’\0’结束符的源字符串复制到目标地址空间。

sprintf ``主要用于生成格式化字符串,其返回值是格式化后的字符串长度(不包括末尾的空字符)。如果发生错误,它可能会返回一个负数。

memcpy memcpy则是根据第三个参数来决定复制的字节数,从而控制复制的长度。它逐个字节地从源地址复制到目标地址,直到达到指定的字节数。

https://blog.csdn.net/eason22/article/details/137157714

15、结构体可以直接赋值吗

不可以

可以通过自定义构造函数 拷贝构造函数 赋值构造函数

16、volatile的作用

int volatile vInt;

当要求使用 volatile 声明的变量的值的时候,

系统总是重新从它所在的内存读取数据

21、一个指针占用多少字节

一个指针在32位的计算机上,占4个字节

一个指针在64位的计算机上,占8个字节

总结

一个指针占多少字节与计算机系统本身是多少位(32、64位)系统有关


在32位系统中,

地址总线宽度为32位,所以可以寻址的最大内存空间是4GB(2的32次方),

因此一个指针(即一个地址)需要4字节(32位)的空间来存储。

类似地,在64位系统中,地址总线宽度为64位,所以一个指针需要8字节的空间来存储。

多少位的计算机实际上说的是地址总线的宽度

地址是门牌号

里面存的东西是数据,是一个类似于真实的人的人

计算机能够处理的最小单元是 字节Byte 而不是 位bit


指针存储的是地址

地址是内存单元的编号

所以,一个指针占几个字节,等于是一个地址的内存单元编号有多长


我们都知道,在计算机中,CPU不能直接与硬盘进行数据交换,CPU只能直接跟内存进行数据交换。

而CPU是通过地址总线、数据总线、控制总线三条线与内存进行数据传输与操作。

问:假如,我们想通过CPU在内存中读取一个数字3,那么是怎样一个操作呢?

首先,CPU通过地址总线,在内存中找到数字3的地址;

然后,通过控制总线知道该操作是读还是写;

最后,通过数据总线,把数字3传输到CPU中。

地址总线的宽度决定了CPU的寻址能力,或者说寻址的最大内存容量;

控制总线决定了CPU对其他控件的控制能力以及控制方式。

数据总线的宽度决定了CPU单次数据传输的传送量,

也就是数据传输速度,或者说一次可以处理的数据的宽度;

我们平时所说的计算机是16位、32位、64位,

指的是计算机CPU中通用寄存器一次性处理、传输、暂时存储的信息的最大长度,

或者说是CPU的数据总线宽度。即CPU在单位时间内(同一时间)能一次处理的二进制数的位数。

即电脑是多少位的,与数据总线有关

地址总线

指针的本质是一个整数,它存储的是内存地址。

通过指针能找到或访问到内存中所有的地方

某计算机的地址总线是32位,那么其一次可以处理的信息是32条,每一条地址总线有0或1两种可能,那么32根地址总线一共有232种可能,也就是其描述的地址空间为0x0000 0000 ~ 232-1。

我们一般需要32个0或1的组合就可以找到内存中所有的地址,而32个0或1的组合,就是32个位,也就是4个字节的大小,因此,我们只需要4个字节就可以找到所有的数据。
所以,在32位的计算机中,指针占4个字节。
同理,在64位的计算机中,指针占8个字节。

这就是上面所说的:地址总线的宽度决定了CPU的寻址能力

同时也可以看出,由于地址总线为32,那么每次寻址的空间为

0000 0000 0000 0000 0000 0000 0000 0000 ~ 232-1,那么CPU的最大内存为

2^32 = 2^22K = 2^12M = 2^2G = 4G

而单位是内存最小操作单位Byte,因此,加上单位Byte后:

2^32Byte = 2^22KB = 2^12MB = 2^2GB = 4GB

也就是,32位的电脑,CPU的最大内存为4GB

此处单位加的是Byte,而不是bit,有兴趣的小伙伴可以查一下为什么

而64位,最大内存是264Byte = 232 * 232Byte = 232 * 4GB 约等于 17179869184GB。

这就是上面所说的:地址总线的宽度决定了CPU寻址的最大内存容量

即:在32位系统中,

地址总线宽度为32位,所以可以寻址的最大内存空间是4GB(2的32次方),因此一个指针(即一个地址)需要4字节(32位)的空间来存储。类似地,在64位系统中,地址总线宽度为64位,所以一个指针需要8字节的空间来存储。

常见硬件架构下数据总线和地址总线宽度的示例:

CPU架构 数据总线宽度 地址总线宽度 最大内存寻址 指针大小
8位 8位 16位 64KB 1字节
16位 16位 20位 1MB 2字节
32位 32位 32 位 4 GB 4字节
64位 64位 64位 16 EB 8字节

1位=1bit/比特,表示一个二进制0或1(比特币)

1字节(Byte) = 8位/bit/比特(字节跳动)

数据存储是以“字节”(Byte)为单位,数据传输大多是以“位”(bit,又名“比特”)为单位

每8个位(bit,简写为b)组成一个字节(Byte,简写为B),是最小一级的信息单位。

1个英文字母(不分大小写)占一个字节Byte的空间

计算机能够处理的最小单元是 字节Byte 而不是 位bit

位bit,是由软件通过位运算符操作的

https://blog.csdn.net/qq_44369667/article/details/129776458

https://segmentfault.com/a/1190000037537285#:~:text=既然1个字节是8,访问的最小单位。

https://blog.csdn.net/IOSSHAN/article/details/88944637

28、句柄和指针的区别和联系是什么

联系: 都是用来控制与管理资源的,指针直接操作内存,句柄则通过操作系统或者库函数进行资源管理

区别:指针式直接指向具体的内存地址,句柄则是间接引用资源,程序不能通过句柄直接访问资源的内存地址来访问和管理相关内存,而是通过操作系统提供的API或者库函数来操作资源

C++面向对象

1、什么是类

原文

原文链接:https://blog.csdn.net/h8062651/article/details/136262716

是面向对象程序设计的基本构成单位,它是一种自定义的数据类型,用于封装数据以及操作这些数据的方法。类是创建对象的模板,它定义了对象的属性和行为。对象是根据类创建的实例。

类具有以下基本特性:

封装:封装是将数据(也称为属性或成员变量)和操作这些数据的方法(也称为成员函数或方法)结合在一起的过程。这样,数据就被隐藏或封装在类内部,只能通过类提供的方法进行访问和修改。这有助于保护数据免受外部代码的非法访问和修改,同时也提高了代码的可维护性和可重用性。

继承:继承是从已有的类(称为父类或基类)创建新类(称为子类或派生类)的过程。子类继承了父类的所有属性和方法,同时还可以定义自己的新属性和方法。这样,可以重用已有的代码,提高代码的可重用性和可扩展性。

多态:多态是指允许一个接口(或函数名)有多种实现方式。在 C++ 中,多态通常通过虚函数(virtual functions)实现。子类可以重写父类的虚函数,从而实现多态。这样,在运行时可以根据对象的实际类型调用相应的函数,提高了代码的灵活性和可扩展性。

抽象:抽象是一种隐藏具体实现细节的机制,只展示必要的信息给用户。在 C++ 中,可以通过抽象类(含有至少一个纯虚函数的类)来实现抽象。抽象类不能被实例化,只能被其他类继承。这样,可以定义一种通用的接口或行为,让子类去实现具体的细节。

总的来说,C++ 中的类是一种强大的工具,它使得代码更加模块化、可重用、可维护和可扩展。通过封装、继承、多态和抽象等特性,类可以更好地模拟现实世界中的事物和行为,从而实现更加复杂和灵活的程序设计。

总结

自定义的数据类型

面向对象程序设计的基本单位

能够实现数据和操作封装、继承、多态、抽象等多种特性

封装(保护、可维护、可重用)

继承(重用、可维护、可重用)

多态(多态、灵活、可拓展性)

抽象(对用户:隐藏具体实现细节;对子类:定义一种通用的接口或行为,让子类去实现具体的细节)

2、面向对象程序设计思想是什么

封装(将数据和操作数据的方法捆绑,并对外隐藏,内部实现,提高可复用性,可维护性)

继承(重用、可维护、可重用)

多态(父类接口,子类实现,多态、灵活、可拓展性)

抽象 (提取共性、细节隐藏、保留必要接口)

25、RTTI是什么?其原理是什么?

https://www.cnblogs.com/RioTian/p/17746167.html

总结

run time type identification

运行时类型识别

运行的时候检查所指实际类型

有虚函数的时候,基类指针可以指向任何派生对象

类型的确定是要在运行的时候进行类型识别

原理

1.dynamic_cast操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

当我们进行dynamic_cast时,编译器会帮我们进行语法检查。

如果指针的静态类型和目标类型相同,那么就什么事情都不做;

否则,首先对指针进行调整,使得它指向vftable,并将其和调整之后的指针、调整的偏移量、静态类型以及目标类型传递给内部函数。其中最后一个参数指明转换的是指针还是引用。

2.typeid 运算符用于获取对象的类型信息,它返回一个 std::type_info 对象。

编译时信息

  • 当编译器遇到typeid操作符时,它会检查操作数的类型。
  • 对于静态类型(如基类指针或引用),编译器生成适当的代码来获取类型信息。

运行时信息(RTTI):

  • 如果操作数是多态类型(即包含虚函数的类类型),编译器会在对象中存储运行时类型信息。通常,这些信息存储在对象的虚表(vtable)中。
  • 在程序运行时,typeid操作符会利用这些信息来确定对象的动态类型。

typeid操作符返回一个const std::type_info&,它是一个标准库中的类,包含了类型信息。

std::type_info类通常包含以下成员函数:

  • name():返回一个C风格字符串,表示类型的名称。
  • before(const type_info& rhs) const:提供类型的顺序比较。
  • operator==operator!=:用于比较类型信息。

29、为什么析构函数一般写成虚函数

  • 虚函数的特点

定义一个函数为虚函数,不代表函数为不被实现的函数。

定义他为虚函数是为了允许基类调用子类

定义一个函数为纯虚函数,才代表函数没有被实现。

定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

  • 原因

基类指针指向派生类的时候

若基类析构函数不声明为虚函数

在析构时,只会调用基类而不会调用派生类的析构函数,从而导致相关资源可能释放管理不完全,导致内存泄漏

实际上,派生类的析构函数会自动调用基类的析构函数。 只要基类的析构函数是虚函数,那么派生类的析构函数不论是否用virtual关键字声明,都自动成为虚析构函数

C 语言面试题

请解释C语言中的extern关键字,并描述其在多文件编程中的应用

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View