16位编译器
char :1个字节
char*(即指针变量): 2个字节
short int : 2个字节
int: 2个字节
unsigned int : 2个字节
float: 4个字节
double: 8个字节
long: 2个字节
long long: 8个字节
unsigned long: 4个字节
32位编译器
char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节
64位编译器
char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 8个字节
long long: 8个字节
unsigned long: 8个字节
1、realloc(void *__ptr, size_t __size):更改已经配置的内存空间,即更改由malloc()函数分配的内存空间的大小。
如果将分配的内存减少,realloc仅仅是改变索引的信息。
如果是将分配的内存扩大,则有以下情况:
1. 如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
2. 如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数 据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
3. 如果申请失败,将返回NULL,此时,原来的指针仍然有效。
注意:如果调用成功,不管当前内存段后面的空闲空间是否满足要求,都会释放掉原来的指针,重新返回一个指针,虽然返回的指针有可能和原来的指针一样,即不能再次释放掉原来的指针。
2、共用体与结构体的区别
共用体:
使用union 关键字
共用体内存长度是内部最长的数据类型的长度。
共用体的地址和内部各成员变量的地址都是同一个地址
结构体:
结构体的各个成员会占用不同的内存,互相之间没有影响
遵循内存对齐原则
3、⽂件缓冲区刷新⽅式有⼏种
1. 隐式刷新:当程序正常退出或缓冲区满时,文件缓冲区会自动刷新。隐式刷新是由于缓冲区的数据写入或其释放而触发的。
2. 显式刷新:通过调用缓冲区刷新函数来执行刷新操作。常见的例子是调用C语言的fflush函数或C++语言的flush函数。
3. 行缓冲刷新:当输入文件中有换行符或缓冲区满时,文件缓冲区会自动刷新。行缓冲是指缓冲区在读取到换行符('\n')时才刷新。
4. 全缓冲刷新:当缓冲区满时,文件缓冲区会自动刷新。全缓冲是指文件缓冲区在写满或缓冲区被手动刷新之前,不会将数据写入文件。
5. 强制刷新:通过调用特定的函数或方法来强制刷新缓冲区。例如,在C语言中,使用fflush函数或者setvbuf函数来实现强制刷新;在C++中,使用std::endl或std::flush来强制刷新。 需要注意的是,缓冲区刷新方式的选择取决于具体的应用场景和需求。不同的刷新方式会影响程序的执行效率和资源消耗。在编程中,根据实际需求选择合适的刷新方式可以提高程序性能和效率。
4、野指针产生的原因:
野指针(wild pointer)指的是指针变量没有正确初始化或者指向已释放的内存地址,由于没有有效的指向对象或者内存空间,对野指针的解引用操作可能会导致程序崩溃、数据损坏或者其他不可预测的结果。
1.指针未初始化
指针变量在定义时不会自动初始化成空指针,而是随机的一个值,可能指向任意空间,这就使得该指针成为野指针。因此指针在初始化时要么指向一个合理的地址,要么初始化为NULL。
2.指针指向的变量被free或delete后没有置为NULL
在调用free或delete释放空间后,指针指向的内容被销毁,空间被释放,但是指针的值并未改变,仍然指向这块内存,这就使得该指针成为野指针。因此在调用free或 delete之后,应将该指针置为NULL。
3.指针操作超过所指向变量的生存期
当指针指向的变量的声明周期已经结束时,如果指针仍然指向这块空间,就会使得该指针成为野指针。这种错误很难防范,只有养成良好的编程习惯,才能避免这类情况发生。
注意:野指针只能避免而无法判断
无法判断一个指针是否为野指针,因为野指针本身有值,指向某个内存空间,只是这个值是随机的或错误的。而空指针具有特殊性和确定性,可以进行判断,因此要避免在程序中出现野指针
5、什么是浅拷贝和深拷贝
浅拷贝
- 对于基本数据类型的成员变量,浅拷贝直接进行值传递,也就是将属性值复制了一份给新的成员变量
- 对于引用数据类型的成员变量,比如成员变量是数组、某个类的对象等,浅拷贝就是引用的传递,也就是将成员变量的引用(内存地址)复制了一份给新的成员变量,他们指向的是同一个事例。在一个对象修改成员变量的值,会影响到另一个对象中成员变量的值。
深拷贝
- 对于基本数据类型,深拷贝复制所有基本数据类型的成员变量的值
- 对于引用数据类型的成员变量,深拷贝申请新的存储空间,并复制该引用对象所引用的对象,也就是将整个对象复制下来。所以在一个对象修改成员变量的值,不会影响到另一个对象成员变量的值。
6、描述⼀下软连接和硬链接的区别
软链接和硬链接到底有啥作用和区别_软连接和硬连接的区别_明月几时有666的博客-CSDN博客
7、c++的动态捆绑机制是怎样的?
8、重载和重写的区别
(1)范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类中。
(2)参数区别:重写与被重写的函数参数列表一定相同,重载和被重载的函数参数列表一定不同。
(3)virtual的区别:重写的基类函数必须要有virtual修饰,重载函数和被重载函数可以被virtual修饰,也可以没有
9、谈谈你对命名空间的理解 namespace
1、是⼀个关键字:随着⼯程量的增加,变量命名上不可避免的会出现重名,防⽌名称冲突 (在两个不同的命名空间中,即使2个变量名相同,也是2个不同的变量),在实际⼯作中,基本都使⽤ 标准命名空间。
2、命名空间只能全局范围内定义,不能定义在函数内部。
3、命名空间内,可以存放 变量、函数、结构体、类 ;也可以嵌套其他的命名空。
4、命名空间可以匿名(但⼀般不这样使⽤),类似静态全局变量。
5、命名空间是可以起别名的。
补充:命名空间是用来组织代码和避免命名冲突的一种机制,它可以将代码划分为不同的逻辑单元。命名空间定义在全局范围内,是为了能够被多个文件或函数访问和使用,使得不同的代码模块之间能够相互协作和共享数据。 如果将命名空间定义在函数内部,那么该命名空间只能在该函数内部访问,无法被其他函数或文件使用,这样就违反了命名空间的设计目的。此外,将命名空间定义在函数内部还会导致同名命名空间的冲突问题,因为每次函数调用都会重新定义一个新的命名空间,可能与其他函数定义的同名命名空间发生冲突。 因此,根据命名空间的设计原则和使用方式,命名空间只能定义在全局范围内,而不能定义在函数内部。
10、谈谈指针和引⽤的区别
不同点:
1、指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元,即指针是一个实体;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
2、指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
引用的值不能为NULL的原因是因为引用是一个已经存在的对象的别名,它必须引用一个有效的对象
引用在定义的时候必须初始化的原因是因为引用必须引用一个有效的对象,如果没有初始化,则无法确定引用所指向的对象,也就无法进行后续的操作
3、指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了,从一而终
4、sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;
相同点:
1、都是地址的概念;
11、引用传递”的性质像“指针传递”,而书写方式像“值传递”。实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用”这东西?
答案是“用适当的工具做恰如其分的工作”
指针能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。就像一把刀,它可以用来砍树、裁纸、修指甲、理发等等,谁敢这样用?
1、当你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),
2、是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。
3、还有一种情况,就是当你重载某个操作符时,你应该使用引用。
尽可能使用引用,不得已时使用指针。
当你不需要“重新指向”时,引用一般优先于指针被选用。这通常意味着引用用于类的公有接口时更有用。引用出现的典型场合是对象的表面,而指针用于对象内部。
12、谈谈你对内联函数和宏定义的区别
1、内联函数是在编译时展开,而宏在预编译时展开;在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。
2、内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能;宏不是函数,而inline是函数。
3、宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性。而内联函数不会出现二义性。
4、inline有点类似于宏定义,但是它和宏定义不同的是,宏定义只是简单的文本替换,是在预编译阶段进行的。而inline的引入正是为了取消这种复杂的宏定义的。
13、malloc/free和new/delete的区别
它们都可以用来在堆上分配和回收空间。new/delete是操作符、malloc、free是库函数、
(1)实现new实际上执行了两个过程:
- 分配未初始化的内存空间(malloc)
- 使⽤对象的构造函数对空间进⾏初始化;返回空间的⾸地址。
如果在第⼀步分配空间中出现问题,则抛出 std::bad_alloc 异常,或被某个设定的异常处理函数捕获处理;
如果在第⼆步构造对象时出现异常,则⾃动调⽤ delete 释放内存。
(2)⾏ delete 实际上也有两个过程
- 使⽤析构函数对对象进⾏析构
- 回收内存空间(free)。
以上也可以看出new和malloc的区别,new得到的是经过初始化的空间,而malloc得到的是未初始化的空间。所以new是new一个类型,而malloc则是malloc一个字节长度的空间。delete和free同理,delete不仅释放空间和析构对象,delete一个类型,free一个字节长度的空间
14、c++中,为什么有了malloc/free,还要new/delete?
1、对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
15、既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?
这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。
16、如果在申请动态内存时找不到足够大的内存块,malloc和new将返回null指针,宣告内存申请失败。你是怎么处理内存耗尽的?
1、判断指针是否为NULL,如果是则马上用return 语句终止本函数
2、判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行
3、为new 和malloc 设置异常处理函数。例如Visual C++可以用_set_new_hander 函数
为new 设置用户自己定义的异常处理函数,也可以让malloc 享用与new 相同的异常处
理函数
17、new一个int可以用free释放吗?
你那么写可能对于基本类型是没有问题的,但是一旦设涉及到构造函数与析构函数就不行了
18、友元函数和友元类(作用及优缺点)
友元是一种定义在类外部的普通函数,但它需要在类体内进行声明,为了与该类的成员函数加以区别,在声明时前面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员
- 友元函数可访问类的私有成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用和原理相同
利用 friend 修饰符,可以让一些普通函数 或 另一个类的成员函数 直接对某个类的保护成员和私有成员进行操作,提高了程序的运行效率;同时避免把类的成员都声明为public,最大限度地保护数据成员的安全。
但是,即使是最大限度地保护数据成员,友元也破坏了类的封装性。
在A类中声明B类为其友元类,那么可以在B类中直接访问A类的私有成员变量,但想在A类中访问B类中私有的成员变量则不行。
19、 什么是菱形继承?菱形继承的问题是什么?
菱形继承的概念:两个不同的派生类继承于同一个父类,又有一个子类多继承于这个两个派生类。
菱形继承的问题:菱形继承主要有数据冗余和二义性的问题。由于最底层的派生类继承了两个基类,同时这两个基类有继承的是一个基类,故而会造成最顶部基类的两次调用,会造成数据冗余及二义性问题。
1、 什么是菱形虚拟继承?如何解决数据冗余和二义性的?
用域访问限定符,否则无法识别是对BB对象还是对CC对象中的 _a赋值,虽然这样解决了二义性问题,但是又产生了数据冗余的问题
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。需要注意的是,虚拟继承不要在其他地方去使用。
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。虚拟继承通过使用虚基表和两个虚基表指针,利用虚基表中存的偏移量,用指针来标识会发生冗余和二义性的数据
2、继承和组合的区别?什么时候用继承?什么时候用组合?
1、继承是指一个类从另一个类继承属性和方法。通过继承,子类可以获得父类的属性和方法,并且可以在子类中添加额外的属性和方法或者重写父类的方法。继承的关系是"is-a"的关系,表示子类是父类的一种特殊类型。
2、组合是指一个类将其他类作为其成员变量,并通过调用这些成员变量来实现功能。组合的关系是"has-a"的关系,表示一个对象包含其他对象。
3、使用继承的情况通常是父子类之间存在"is-a"关系,子类是父类的一种特殊类型。通过继承可以实现代码的重用和扩展,同时可以简化代码的设计和维护。 使用组合的情况通常是需要将多个对象组合在一起来实现更复杂的功能。
4、通过组合可以将对象的功能划分为独立的部分,并将其组合起来完成整体的功能。
3、c++之哪些成员函数不能被继承
1、构造函数:在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。
2、析构函数:析构函数也不会被子类继承,只是在子类的析构函数中会调用父类的析构函数。
3、运算符重载赋值函数:赋值运算符重载函数也不会被子类继承,只是在子类的赋值运算符重载函数中会调用父类的赋值运算符重载函数。
20、C++中虚析构函数的作用
1、如果父类的析构函数不加virtual关键字
当父类的析构函数不声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数。
2、如果父类的析构函数加virtual关键字
当父类的析构函数声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,先调动子类的析构函数,再调动父类的析构函数。
否则会造成内存泄漏
21、设计模式
1.什么是设计模式?
答:
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案
2.你当初为什么要学习设计模式?
答:
1、为了看懂源代码:如果不懂设计模式的话,去看Jdk、Spring、SpringMVC、IO等的源码,会感到很迷惑,什么都看不懂。
2、为了看看前辈们的代码:比如去工作不一定是直接分配给我新项目,很有可能是前辈的项目,如果他们用了设计模式,而自己不会,那会面临很大问题。
3、为了敲自己的理想中的优质代码:生活需要仪式感,需要认真生活,对于代码,也是一样,自己开发的项目就想把项目当成自己的大宝贝一样。
22、const在c和c++中的区别
c语言中const只是指定这个变量是只读的,并非真正意义上的常量,并且可以通过指针修改。c++中常量都会有一张常量表,任何对常量的读取都是从这个表里直接读取,而通过指针进行修改,修改的是常量在栈上对应地址空间的内容,本身常量表里的内容不会被改变
23、虚函数知识点
1、每个有虚函数的类或者虚继承的子类,编译器都会为它生成一个虚表,表中的每一个元素都指向一个虚函数的地址。此外,编译器会为包含虚函数的类加上一个成员变量,是一个指向该虚函数表的指针(常被称为vptr),每一个由此类别派生出来的类,都有这么一个vptr。也就是说,如果一个类含有虚表,则该类的所有对象都会含有一个虚表指针,并且该虚表指针指向同一个虚表。 虚表的内容是依据类中的虚函数声明次序一一填入函数指针。派生类别会继承基础类别的虚表(以及所有其他可以继承的成员),当我们在派生类中改写虚函数时,虚表就受了影响;表中的元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址
24、C/C++什么是内存泄露,内存泄露如何避免?
1. 内存溢出
内存溢出 OOM (out of memory),是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个int,但给它存了long才能存下的数,那就是内存溢出。
2. 内存泄漏
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。最终的结果就是导致OOM。
内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。
3. 造成内存泄露常见的三种情况
1,指针重新赋值
2,错误的内存释放
3,返回值的不正确处理
25、怎么提高读文件的效率
-
使用缓冲区:将读取的内容存放到缓冲区中,减少磁盘I/O次数,提高读取效率。使用标准库函数fgets()、fread()、fscanf()等函数时,可以指定缓冲区大小,避免多次调用文件读写函数。
-
使用内存映射文件:内存映射技术可以将文件直接映射到进程的虚拟地址空间中,使得读取文件的速度与访问内存一样快速,并且操作简单。
-
预读取:预读取是指在程序启动时将需要读取的文件全部或部分读取到内存中,以便随时访问。可以使用mmap()函数实现。
-
多线程读取:将文件分成若干块,每个线程读取一块文件,这样可以充分利用CPU的多核心优势,提升读取速度。
-
关闭文件流:打开文件后要及时关闭文件流,以释放系统资源,提高程序效率。
26、如何在Linux中使用mmap函数进行内存映射?
打开文件
使用open函数打开要映射的文件,并获得文件描述符。
获取文件大小
使用stat函数或fstat函数获取文件的大小,以便后续进行内存分配。
分配内存
使用mmap函数将文件映射到内存中,分配内存的参数包括文件描述符、映射长度、映射标志以及映射类型等。
访问内存
使用指针对映射到内存中的文件进行读写操作,可以像操作内存一样进行操作。
解除内存映射
使用munmap函数解除内存映射,释放内存资源