C++基本知识整理

1 关键字

1、C语言宏中的#和##的用法?

1 # 字符串操作符

将宏定义中传入参数名 转换成 用一对双引号括起来参数名字字符串

其只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。

#define example(instr) printf("the input string is:\t%s\n", #instr)
example(abc);// 在编译时将会展开成:printf("the input string is:\t%s\n","abc")
#define example1(instr) #instr 
example1(abc); // 将会展成:“abc”

2 ## 符号连接操作符

将宏定义的多个形参转换成一个实际参数名

#define exampleNum(n) num##n
int num9 = 9;
int num = exampleNum(9);// 将扩展成 int num = num9
2、关键字volatile有什么含义?

声明并行设备的硬件寄存器。存储器映射的硬件寄存器 通常加volatile,因为寄存器随时可以被外设硬
件修改。当声明指向设备寄存器的指针时 一定要用volatile,它会告诉编译器 不要对存储在这个地
址的数据进行假设。

声明一个中断服务程序中修改的供其他程序检测的变量。volatile提醒编译器,它后面所定义的变量随
时都有可能改变。因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中
读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的
值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

声明多线程应用中被几个任务共享的变量,防止编译器对代码进行优化。使用volatile声明,编译器会逐一的进行编译并产生相应的机器代码。

3、关键字static的作用是什么?

1、在函数体,被声明的变量只能被初始化一次,一个被声明为静态的变量在这一函数被调用的过程中维持其值不变

2、在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块
外其它函数访问。它是一个本地的全局变量(只能被当前文件使用)。

3、在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。

4、在c语言中,为什么static变量只能初始化一次

对于所有的对象(不仅仅是静态对象),初始化都只有一次。

而由于静态变量具有“记忆”功能,初始化后,一直都没有被销毁,都会保存在内存区域中,所以不会再次初始化。

存放在静态区的变量的生命周期一般比较长,它与整个程序“同生死、共存亡”,所以它只需初始化一次。而auto变量,即自动变量,由于它存放在栈区,一旦函数调用结束,就会立刻被销毁。

5、extern“C”的作用是什么?

能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

6、const有什么作用?

定义变量为常量;

修饰函数的参数,表示在函数体内不能修改这个参数值;

修饰函数的返回值(指针),返回值的内容不能被修改,且只能赋值给被const修饰的指针。

节省空间,避免不必要的内存分配,并将常量放置在只读存储器中。

// Pi 只被分配了一次内存
const doulbe Pi=3.14159 // 将 Pi 定义为一个常量,并且分配了内存来存储该常量的值 3.14159。
double i=Pi // 这些语句并不会重新分配内存,而是将 Pi 的值复制给变量 i 和 j
double j=Pi
总结起来,宏定义只是简单的文本替换,在编译期间进行,不会分配内存。
而常量定义在编译时分配内存,并可以被放入只读存储器中。
因此,在使用常量时更加安全,能够进行类型检查,并且不会多次分配内存。
7、什么时候使用const关键字?

修饰一般变量、修饰常数组、修饰常指针、修饰常引用、修饰函数的常参数、修饰函数的返回值、在另一个连接文件中引用const常量。

8、new/delete与malloc/free的区别是什么?

new/delete是C++中的操作符,而malloc/free是标准库函数。

new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。

9、strlen(“\0”)=? sizeof(“\0”)=?

strlen(“\0”) =0,sizeof(“\0”)=2。

strlen用来计算字符串的长度(在C/C++中,字符串是以"\0"作为结束符的),它从内存的某个位置(可
以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描直到碰到第一个字符串结束
符\0为止,然后返回计数器值。

sizeof是C语言的关键字,它以字节的形式给出了其操作数的存储大小,操作数可以是一个表达式或括在括号内的类型名,操作数的存储大小由操作数的类型决定。

10、sizeof和strlen有什么区别?

sizeof是运算符,而strlen是函数

sizeof可以用类型作为参数, strlen只能用char作参数,而且必须是以“0结尾*的。

大部分编译程序的 sizeof都是在编译的时候计算的,所以可以通过 sizeof(x)来定义数组维数。
而 strlen则是在运行期计算的,用来计算字符串的实际长度,不是类型占内存的大小。

11、C语言中struct与union的区别是什么?

struct(结构体)与 union(联合体)是C语言中两种不同的数据结构:

联合体中所有成员共用一块地址空间,即联合体只存放了一个被选中的成员。所有成员不能同时占用内存空间,它们不能同时存在,所以一个联合型变量的长度等于其最长的成员的长度

结构体中所有成员占用空间是累加的,其所有成员都存在,不同成员会存放在不同的地址。其内存空
间大小等于所有成员长度之和(需要考虑字节对齐)。

对于联合体的不同成员赋值,将会对它的其他成员重写,原来成员的值就不存在了,而对结构体
不同成员赋值是互不影响的。

12、左值和右值是什么?

左值是指可以出现在等号左边的变量或表达式,它最重要的特点就是可写(可寻址),它的值可以被修改;

右值是指只可以出现在等号右边的变量或表达式。它最重要的特点是可读

13、什么是短路求值?
if(i>0||(j++)>0);

对于||操作,由于在两个表达式的返回值中,如果有一个为真则整个表达式的值都为真。

对于&&操作,由于在两个表达式的返回值中,如果有一个为假则整个表达式的值都为假。

论后一个语句的返回值是真是假,整个条件判断都为假(真),不用执行后一个语句。

14、++a和a++有什么区别?两者是如何实现的?

后置自增运算符需要把原来变量的值复制到一个临时的存储空间,等运算结束后才会返回这个临时变量
的值。所以前置自增运算符效率比后置自增要高

2 内存

1、 c语言中内存分配的方式有几种?

静态存储区分配:内存分配在程序编译之前完成,且在程序的整个运行期间都存在,例如全局变量静态变量等

栈上分配:在函数执行时,函数内的局部变量存储单元在栈上创建,函数执行结束时这些存储单元自动释放

堆上分配:程序员手动创建和释放。

2、堆与栈的区别?

申请方式:栈的空间由操作系统自动分配/释放,堆上的空间手动分配/释放

申请大小的限制:从栈获得的空间较小;堆获得的空间比较灵活,也比较大。

申请效率:栈由系统自动分配,速度较快。但程序员是无法控制的。

​ 堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。

3、栈在c语言中有什么作用?

存放函数中的临时变量和寄存器:函数参数、函数内部定义的临时变量;

多线程编程的基础是栈,每一个线程都最少有一个自己专属的栈,,用来存储本线程运行时各个函数的临时变量。

4、C语言函数参数压栈顺序是怎么样的?

C语言函数参数采用自右向左的入栈顺序。方便确定参数的个数。

5、C++如何处理返回值?

C++函数返回可以按值返回按常量引用返回

6、C++的内存管理是怎样的?

在C++中,虚拟内存分为 代码段、数据段、BSS段、堆区、文件映射区、栈区 六部分。

代码段:包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码

数据段:存储程序中已初始化的全局变量和静态变量
BSS 段:存储未初始化的(局部+全局)变量,以及所有被初始化为0的全局变量和静态变量。
堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。
映射区: 存储
动态链接库
以及调用mmap函数进行的文件映射
: 使用栈空间存储函数的返回地址、参数、局部变量、返回值

7、什么是内存泄漏?

申请了一块内存空间,使用完成后没有释放掉。

它的一般表现方式是程序运行时间越长,占用内存越多最终用尽全部内存,整个系统崩溃。由程序申
请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。

8、如何判断内存泄漏?

使用了内存分配的函数,一旦使用完毕,要记得要使用其相应的函数释放掉。

将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链
表。

3 指针

1、数组指针和指针数组有什么区别?

数组指针就是指向数组的指针,它表示的是一个指针,这个指针指向的是一个数组,它的重点是指针。

指针数组表示的是一个数组,而数组中的元素是指针。

2、函数指针和指针函数有什么区别?

函数指针:在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空
间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指
针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。

指针函数:首先它是一个函数,只不过这个函数的返回值是一个地址值。

3、数组名和指针的区别与联系是什么?

区别:

  1. 类型:数组名是一个常量指针,指向数组的首元素,而指针是一个变量,保存某个地址的值。
  2. 内存分配:数组名在编译时就确定了数组的地址和大小,并且分配了对应的内存空间;而指针需要在运行时通过赋值或动态分配来指向特定的内存地址。

联系:

  1. 访问元素:数组名和指针都可以用于访问数组元素。数组名可以通过下标操作来直接访问数组元素,指针可以通过解引用来访问指针所指向的内容。
  2. 表达式中的使用:数组名在很多情况下会被隐式转换为指向首元素的指针,例如作为函数参数传递时。因此,数组名可以像指针一样在表达式中使用,进行赋值、比较等操作。
  3. 数组名与指针的关系:数组名可以看作是指向首元素的指针常量,即不能被修改,但可以通过指针算术运算改变指向的位置。而当数组名被当做函数参数传递时,它退化为指针,只保留了指向首元素的信息而失去了长度等其他信息。
4、指针进行强制类型转换后与地址进行加法运算,结果是什么?

在 C 语言中,指针进行强制类型转换后与地址进行加法运算,结果是根据指针类型的大小决定的。

当将一个指针与一个整数进行相加时,编译器会根据指针所指向的类型的大小来进行计算。指针加法的结果是新地址,该地址是原始地址加上偏移量的结果。

5、指针常量,常量指针,指向常量的常量指针有什么区别?

指针常量是指针本身的值不可修改,但可以通过该指针修改其指向的数据;

常量指针是指指针所指向的数据不可修改,但指针本身的值可以修改;

指向常量的常量指针既不能修改指针的值也不能修改指针所指向的数据

6、指针和引用的异同是什么?

相同

  1. 都是地址的概念,指针指向某一内存、它的内容是所指内存的地址;引用则是某块内存的别名。
  2. 从内存分配上看:两者都占内存,程序为指针会分配内存,一般是4个字节;而引用的本质是指针
    常量,指向对象不能变,但指向对象的值可以变。两者都是地址概念,所以本身都会占用内存。

区别

  1. 指针是实体,而引用是别名。
  2. 指针和引用的自增(++)运算符意义不同,指针是对内存地址自增,而引用是对值的自增。
  3. 引用使用时无需解引用(*),指针需要解引用;(关于解引用大家可以看看这篇博客,传送门)。
  4. 引用只能在定义时被初始化一次,之后不可变;指针可变。
  5. 引用不能为空,指针可以为空。
  6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小,在32
    位系统指针变量一般占用4字节内存。

转换

  1. 指针转引用:把指针用*就可以转换成对象,可以用在引用参数当中。
  2. 引用转指针:把引用类型的对象用&取地址就获得指针了。
7、野指针是什么?
  1. 野指针是指向不可用内存的指针,当指针被创建时,指针不可能自动指向NULL,这时,默认值是
    随机的,此时的指针成为野指针。
  2. 指针被free或delete释放掉时,如果没有把指针设置为NULL,则会产生野指针,因为释放掉的仅
    仅是指针指向的内存,并没有把指针本身释放掉。
  3. 第三个造成野指针的原因是指针操作超越了变量的作用范围
8、如何避免野指针?

对指针进行初始化。

指针用完后释放内存,将指针赋NULL。

9、C++中的智能指针是什么?

智能指针是一个类,用来存储指针(指向动态分配对象的指针)。

C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己
管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的
**概念,方便管理堆内存。**使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常
时内存泄露等问题等,使用智能指针能更好的管理堆内存。

10、智能指针的内存泄漏是如何解决的?

为了解决循环引用导致的内存泄漏,引入了弱指针 weak_ptr 。

weak_ptr 可以检测到所管理的对象是否已经被释放,从而避免非法访问。

4 预处理

1、typedef和define有什么区别?

语法形式:

  • typedeftypedef 是一个关键字,需要使用特定的语法形式来定义别名。例如:typedef int MyInt;
  • #define#define 是一个预处理指令,使用宏定义的形式来创建别名。例如:#define MyInt int

作用域:

  • typedeftypedef 定义的别名具有局部或全局作用域,取决于它在哪个作用域中定义。
  • #define#define 定义的别名没有作用域限制,它在预处理阶段进行简单的文本替换。

适用范围:

  • typedeftypedef 只能用于定义新的类型别名,可以为各种类型(如基本类型、结构体、联合体、枚举等)创建别名。
  • #define#define 不仅可以定义类型别名,还可以定义常量、函数、表达式等。

替换方式:

  • typedeftypedef 创建的别名是一种类型替代,编译器在编译过程中会对其进行类型检查。例如:MyInt num; 将被视为 int num;
  • #define#define 创建的别名是文本替代,预处理器在编译前会直接将别名替换为定义的内容。例如:MyInt num; 将被替换为 int num;
2、如何使用 define声明个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECOND_PER_YEAR (60*60*24*365)UL
3、# include< filename. h>和# include" filename. h"有什么区别?

对于 include< filename. h>,编译器先从标准库路径开始搜索 filename.h,使得系统文件调用较快。而
对于# include“ filename.h"”,编译器先从用户的工作路径开始搜索 filename.h,然后去寻找系统路
径,使得自定义文件较快

4、头文件的作用有哪些?

头文件的作用主要表现为以下两个方面:

  1. 通过头文件来调用库功能。出于对源代码保密的考虑,源代码不便(或不准)向用户公布,只要向
    用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关
    心接口是怎么实现的。编译器会从库中提取相应的代码。
  2. 头文件能加强类型安全检查。当某个接口被实现或被使用时,其方式与头文件中的声明不一致,编
    译器就会指出错误,大大减轻程序员调试、改错的负担。
5、在头文件中定义静态变量是否可行,为什么?

不可行,如果在头文件中定义静态变量,会造成资源浪费的为题,同时也可能引起程序错误。

因为如果在使用了该头文件的每个C语言文件中定义静态变量,按照编译的步骤,在每个头文件中都会单独存在
一个静态变量,从而会引起空间浪费或者程序错误所以,不推荐在头文件中定义任何变量,当然也包括静态变量。

6、写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个?
#define MIN(A, B)  ((A) < (B)) ? (A) : (B)

5 变量

1、全局变量和静态变量的区别是什么?

作用域:全局变量具有全局作用域和可见性,可以在多个源文件中共享数据。而静态变量具有局部作用域和文件作用域,只能在同一函数或文件内部使用,并且其值在函数调用之间保持不变。

内存存储方式:全局变量(静态全局变量,静态局部变量)分配在全局数据区(静态存储空
间),后者分配在栈区。

生命周期:生命周期不同。全局变量随主程序创建而创建,随主程序销毁而销毁,局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在了

2、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?

可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值。

3、局部变量能否和全局变量重名?

局部变量和全局变量可以重名,但使用时会根据作用域的范围来决定具体使用哪个变量。建议在编码时避免过多使用同名的变量,以避免引起代码的混淆和不易理解。

6 函数

1、请写个函数在main函数执行前先运行
2、为什么析构函数必须是虚函数?

主要是为了实现正确的资源释放和避免内存泄漏以及多态性,当存在继承关系时,基类的析构函数应当被声明为虚函数。这样可以实现多态性,确保派生类对象在通过基类指针删除时,能够调用到派生类的析构函数,从而完成正确的资源清理。

3、为什么C++默认的析构函数不是虚函数?

因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

4、C++中析构函数的作用?

在对象被销毁时执行必要的清理工作,例如释放内存、关闭文件、释放资源等。

它确保对象的生命周期结束时,相关资源被正确释放,避免资源泄漏和错误。

5、静态函数和虚函数的区别?

静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。

虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销,而静态函数没有虚函数表。。

6、重载和覆盖有什么区别?

重载同一个类之间方法之间的关系,是水平关系;覆盖是子类和父类之间的关系,垂直关系。

重载是多个方法之间的关系;覆盖只能由一个方法或者只能由一对方法产生关系。

重载关系是根据调用的实参表和形参表来选择方法体的;覆盖是根据对象类型(对象对应存储空间类型)来决定的。

7、虚函数表具体是怎样实现运行时多态的?

定义含有虚函数的基类、编译器生成虚函数表、派生类继承虚函数表并覆盖虚函数、调用虚函数。

由于虚函数表的存在,可以实现动态绑定和多态性。在运行时,通过基类指针或引用调用虚函数时,会根据对象的实际类型在虚函数表中查找正确的函数地址,从而调用到了相应的派生类函数。这就实现了运行时多态性使得基类指针或引用可以以统一的方式处理不同派生类的对象,而不需要显式判断对象的类型。

8、C语言是怎么进行函数调用的?

使用栈来支持函数调用

栈被用来传递函数参数、存储返回信息、临时保存寄存器原有的值以备恢复以及用来存储局部变量。

7 虚函数

1、什么是虚函数?

虚函数是一种特殊类型的成员函数,它在基类中声明为虚拟(virtual),并且可以在派生类中被重写(覆盖)。通过使用虚函数,可以实现运行时多态性。

虚函数的主要特点和用途如下:

  1. 多态性:当基类指针指向派生类对象,并通过该指针调用虚函数时,会根据实际指向的对象类型来确定调用哪个类的虚函数。这样就能够在运行时根据对象的具体类型来执行相应的操作,实现多态性。
  2. 动态绑定:虚函数通过动态绑定(dynamic binding)的方式进行调用,即在程序运行期间根据对象的实际类型来确定函数的执行版本,而不是根据指针的静态类型。
  3. 重写(覆盖):派生类中可以重新定义基类中的虚函数,提供自己的实现。重写时,需要保持函数签名(名称、参数列表和返回类型)一致,并使用 override 关键字进行标记。
2、C++如何实现多态?

C++中通过虚函数实现多态。

虚函数的本质就是通过基类指针访问派生类定义的函数。

每个含有虚函数的类,其实例对象内部都有一个虚函数表指针。该虚函数表指针被初始化为本类的虚函数表的内存地址。所以,在程序中,不管对象类型如何转换,该对象内部的虚函数表指针都是固定的,这样才能实现动态地对对象函数进行调用,这就是C++多态性的原理。

3、纯虚函数指的是什么?

纯虚函数(Pure Virtual Function)是在基类中声明的虚函数,但没有提供具体的实现。

纯虚函数的主要作用是定义一个接口、约束派生类,并提供一种抽象的行为。基类中的纯虚函数必须在派生类中被重写以提供具体的实现。

4、什么函数不能声明为虚函数?
  1. 静态成员函数(Static Member Function):静态成员函数是属于类本身而不是对象的函数,它们在内存中只有一份拷贝,不与任何特定对象相关联,因此无法实现多态性。静态成员函数没有虚函数的特性,不能被声明为虚函数。
  2. 构造函数(Constructor)和析构函数(Destructor):构造函数和析构函数是用于创建和销毁对象的特殊成员函数,它们在对象的生命周期中具有特殊的作用。由于构造函数在对象创建过程中被调用,而虚函数的多态性是基于对象的动态类型进行调用的,因此构造函数不能声明为虚函数。析构函数在对象销毁时被调用,而虚函数的多态性需要通过对象的虚函数表来实现,在对象销毁时虚函数表已经被释放,因此析构函数也不能声明为虚函数。
  3. 内联函数(Inline Function):内联函数是通过内联展开减少函数调用开销的一种方法,编译器会将函数的代码插入到调用处,以达到减少函数调用的开销。由于内联函数的展开是在编译期间完成的,而虚函数的调用是在运行期间根据对象类型动态决定的,两者机制不兼容,所以内联函数不能声明为虚函数。
5、C++中如何阻止一个类被实例化?
  1. 将构造函数设为私有(Private):将类的构造函数声明为私有,即使在类的外部也无法直接创建对象。只有类内部的成员函数或友元函数才能调用私有构造函数。这样可以有效地阻止类的实例化。
  2. 将类声明为抽象类(Abstract Class):将类中至少包含一个纯虚函数,使得该类成为抽象类。抽象类不能直接实例化,只能作为其他类的基类使用。
  3. 删除默认构造函数:可以通过将默认构造函数删除来阻止类的实例化。当类中的构造函数被删除时,该类不能被实例化,但仍然可以通过其他非删除的构造函数进行实例化。

8 数组

1、以下代码表示什么意思?
*(a[1]+1) //因为a[1]是第2行的地址,a[1]+1偏移一个单位(得到第2行第2列的地址),然后解引用取值,得到 a[1][1] 
*(&a[1][1]) //[]优先级高,a[1][1]取地址再取值。
(*(a+1))[1] // a+1相当于&a[1],所以* (a+1)=a[1],因此*(a+1)[1]=a[1][1]
2、数组下标可以为负数吗?

数组的下标是可以为负数的,当数组下标为负数时,编译可以通过,而且也可以得到正确的结果。

其下标只是表示当前地址的偏移量,只要根据这个偏移量可以定位到目标地址即可。

下标为负数表示的意思却是从当前地址向前寻址.

9 类和数据抽象

1、C++中类成员的访问权限?

在C++中,类成员有三种访问权限:public、protected和private。

  1. public:
    • public成员在类的内部和外部都可以被访问。
    • 对于派生类来说,public成员会保持其原有的访问权限。
  2. protected:
    • protected成员在类的内部可以被访问,但在类的外部不能直接访问。
    • 对于派生类来说,protected成员可被派生类的成员函数和友元函数访问,但不能通过派生类的对象访问。
  3. private:
    • private成员只能在类的内部访问,外部无法直接访问。
    • 对于派生类来说,private成员在派生类中不可访问。
2、C++中struct和class的区别是什么?

在C++中,可以用struct和class定义类,都可以继承。区别在于:structural的默认继承权限和默认访问
权限是public,而class的默认继承权限和默认访问权限是private。另外,class还可以定义模板类形参,
比如template。

3、C++类内可以定义引用数据成员吗?

可以,必须通过成员函数初始化列表初始化。

4、什么是右值引用,跟左值又有什么区别?

左值和右值的概念:
左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。
右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。
右值引用和左值引用的区别:

  1. 左值可以寻址,而右值不可以。
  2. 左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。
  3. 左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。
5、析构函数可以为 virtual 型,构造函数则不能,为什么?

构造函数无法声明为虚函数是因为构造函数的调用顺序和虚函数的调用机制不兼容。在对象创建期间,虚函数表尚未构建完成,无法进行虚函数的动态调度。因此,将构造函数声明为虚函数可能会导致错误的行为和不可预料的结果。为了避免这种问题,C++语言规范明确禁止将构造函数声明为虚函数。

10 面向对象

1、面向对象和面向过程有什么区别?
  • 面向对象:以对象为基本单位,通过将数据和相关操作封装在一起来表示真实世界中的事物,强调对象之间的交互和消息传递。通过类、对象、继承、多态等机制来实现代码的抽象和复用。
  • 面向过程:以过程或函数为基本单位,以解决问题的步骤和流程为核心,强调对问题的分解和模块化设计。使用函数、变量和数据结构等来实现代码的组织和控制。
2、面向对象的基本特征有哪些?
  1. 封装(Encapsulation):将数据和操作封装在一个对象中,只暴露必要的接口供外部使用。封装提供了对数据的保护,使得对象的状态安全且可控,同时也提高了代码的模块化和可维护性。
  2. 继承(Inheritance):通过继承机制,可以创建新的类(派生类)并从已有类(基类)继承属性和方法。继承允许派生类重用基类的代码,并且可以通过添加新的属性和方法来扩展或修改基类的功能。
  3. 多态(Polymorphism):多态性允许不同类型的对象对同一消息作出不同的响应。通过继承和虚函数(或接口)的机制,可以在运行时决定调用哪个具体对象的方法,实现动态绑定。多态性提高了代码的灵活性和可扩展性,使得程序更加适应需求的变化。
3、什么是深拷贝?什么是浅拷贝?

在深拷贝操作中,原始对象及其所有嵌套对象的数据将被完全复制,而且对复制后的对象做任何修改不会影响到原始对象。

在浅拷贝操作中,原始对象及其成员对象的引用将被复制到新对象中,因此修改新对象中的成员对象可能会影响到原始对象的相应成员对象。

4、什么是友元?

在类的声明中使用friend关键字来声明友元类或友元函数。被声明为友元的类或函数可以访问声明为友元类的私有成员。

通过将一个类或函数声明为另一个类的友元,就可以使得该友元类或函数可以在其它类的作用域中访问另一个类的私有成员。

5、基类的构造函数/析构函数是否能被派生类继承?

基类的构造函数和析构函数都会被派生类继承和调用。派生类的构造函数会隐式调用基类的构造函数,确保正确初始化基类成员。派生类的析构函数会隐式调用基类的析构函数,确保正确释放基类和派生类资源。

6、初始化列表和构造函数初始化的区别?
  1. 初始化列表:初始化列表是在构造函数的定义中使用冒号(:)后面跟随一系列初始化语句的方式。它允许在对象构造之前对成员变量进行初始化。
  2. 构造函数初始化:构造函数初始化是在构造函数体内使用赋值操作符(=)或其他赋值语句对成员变量进行初始化。它是在对象构造完成后执行的初始化过程。
7、C++中有那些情况只能用初始化列表,而不能用赋值?
  1. const 成员变量:const 成员变量只能在初始化列表中进行初始化,因为它们的值在对象构造后是不可更改的。
  2. 引用成员变量:引用必须在创建时进行初始化,并且无法再次赋值。因此,初始化列表是唯一可以对引用成员进行初始化的方式。
  3. 类型没有默认构造函数:如果一个类的成员变量没有默认构造函数,那么它必须在初始化列表中进行初始化,而不能使用赋值操作符。
8、类的成员变量的初始化顺序是什么?

类的成员变量的初始化顺序是根据它们在类中的声明顺序确定的。具体来说,初始化顺序是按照成员变量在类中的声明顺序进行的。

9、当一个类为另一个类的成员变量时,如何对其进行初始化?

使用成员初始化列表、在构造函数体内初始化

10、C++能设计实现一个不能被继承的类吗?

C++提供了一种方式来设计一个不能被继承的类,即通过使用关键字 final

在C++11标准及以后的版本中,可以在类定义中使用 final 关键字来禁止其他类继承该类。

11、构造函数没有返回值,那么如何得知对象是否构造成功?

如果在构造函数中发生了异常,对象的构造将会失败。可以在构造函数中使用throw语句来抛出异常,然后在对象创建的地方使用异常处理机制try-catch语句)来捕获并处理异常

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值