CPP----C++常识100例

文章探讨了C++和C语言中的一些关键特性,包括静态全局变量的作用域,程序判断是C还是C++的方式,函数的参数传递方式,虚函数的概念和用途,以及C++中的模板和类。同时,文章还讨论了内存管理,如new和delete与malloc和free的区别,以及如何处理内存耗尽的情况。此外,提到了多态在隐藏实现细节和代码模块化中的作用,以及封装、继承和多态等面向对象的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 静态全局变量的作用域
本文件

2 判断一个程序是C还是C++编译的

#ifdef __cplusplus
    cout << "c++";
#else 
    cout << "c";
#endif

3 C++函数传递方式,有什么区别
值传递,引用传递,指针传递,C++ 中函数的参数传递方式有值传递、引用传递和指针传递,它们之间有一些区别:1. 值传递:将实参的值复制给形参,函数内部对形参的修改不会影响到实参。这种传递方式适用于参数较小的情况,比如基本数据类型的传递。2. 引用传递:通过使用引用作为函数参数,可以直接操作实参的值,函数内部对形参的修改会影响到实参。这种传递方式适用于参数较大的情况,避免了复制大量数据的开销。3. 指针传递:通过使用指针作为函数参数,可以直接操作实参的值,函数内部对形参的修改会影响到实参。与引用传递类似,但需要注意的是指针可以为空,而引用不能。需要根据具体的情况选择适合的传递方式,平衡参数的大小、性能开销和代码的可读性。

4 虚函数定义及用法
虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
虚函数只能借助于指针或者引用来达到多态的效果。
(1):只有类成员函数才能声明为虚函数,是因为虚函数只适用于有继承关系的类对象中。
(2):静态成员函数不能说明为虚函数,因为静态成员函数不受限与某个对象,整个内存中只有一个,所以不会出现混淆的情况
(3):内联函数不可以被继承,因为内联函数是不能子啊运行中动态的确认其位置的。
(4):构造函数不可以被继承。
(5):析构函数可以被继承,而且通常声明为虚函数。

5 C和C++的不同,C的动态内存分配器
C语言和C++语言有很多不同之处,尤其是在动态内存分配方面。

  1. 内存分配方式:C语言使用malloc和free函数进行动态内存分配和释放,而C++语言使用new和delete操作符。
  2. 内存分配的对象类型:C语言中,内存分配的对象类型是void指针,需要进行强制类型转换才能使用。而C++语言中,new操作符会返回分配的具体类型的指针,无需进行强制类型转换。
  3. 构造函数和析构函数:C++语言中,通过new操作符分配的内存会调用对象的构造函数进行初始化,而通过delete操作符释放内存时会调用对象的析构函数释放资源。C语言中没有对象的概念,不涉及构造函数和析构函数。
  4. 操作符重载:C++语言支持操作符重载,可以对new和delete操作符进行重载,从而实现自定义的内存分配和释放策略。

6 C和C++中struct有什么区别;C++中struct和class有什么区别
C的struct没有protection行为,而C++有,默认是private
C的struct不可以定义函数,但可以有函数指针,而C++可以定义函数
C++的class和struct的默认继承权限和成员的默认访问权限不同,struct是public权限,class是private权限

7 int id[sizeof(unsigned long] 是否正确,为什么
正确,这个sizeof是编译时的运算符,编译时就确定了,可以看成和机器有关的常量

8 “new”in C++ is a operator, new 和 malloc有什么区别
当涉及到动态内存分配时,newmalloc是两种不同的方法。1. new是C++的关键字,而malloc是C语言的库函数。2. new用于创建对象,而malloc用于分配内存块。3. new会调用构造函数来初始化对象,而malloc只是分配内存,不会调用构造函数。4. new返回的是指向对象的指针,而malloc返回的是指向分配内存的指针,需要进行类型转换。5. new会自动计算所需的内存空间,而malloc需要手动指定要分配的内存大小。6. new抛出异常(bad_alloc)如果内存分配失败,而malloc返回NULL。7. newdelete是成对使用的,mallocfree是成对使用的。

9 变量的指针含意是指变量的
地址。地址值是内存中的字节编号,指针只有内存中字节编号的一部分

10 多态的作用
隐藏实现细节,使得代码能够模块化,扩展代码模块,实现代码复用
接口重用,为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性的正确调用

11 多态类中的虚函数表是Compile-Time,还是Run-Time时建立的
虚函数表在编译时就建立了,各个虚函数此时被组织成一个虚函数的入口地址的数组,而对象的隐藏成员—虚拟函数表指针是在运行时,也就是构造函数被调用时进行初始化的,这是实现多态的关键

12 面向对象的三个基本特征,简述,介绍面向对象
封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection
继承:继承有三种实现形式,实现继承,可视继承,接口继承,实现继承,可视继承称为类继承与接口继承构成了接口复用的两种方式
多态:允许将子类类型的指针赋值给父类类型的指针;1. 类和对象:C++是一种面向对象的编程语言,它支持类和对象的概念。类是一种用户自定义的数据类型,用于封装数据和方法。对象是类的一个实例,可以通过类来创建多个对象。2. 封装:封装是面向对象编程的一个重要概念,它指的是将数据和相关的方法组合在一起,形成一个类。通过封装,我们可以隐藏类的内部实现细节,只暴露必要的接口给外部使用。3. 继承:继承是面向对象编程中的另一个重要概念,它允许一个类继承另一个类的属性和方法。通过继承,我们可以创建一个新的类,该类继承了父类的特性,并可以添加自己的特性。4. 多态:多态是指同一个方法可以在不同的对象上产生不同的行为。C++通过虚函数和函数重写来实现多态性。

13 内联函数在编译时是否做参数类型检查
内联函数要做参数类型检查,这是内联函数相比宏的优势

14 内存的分配方式
静态存储区域分配
在栈上创建
在堆上分配

15 对于一个频繁使用的短小函数,C和C++中应用什么实现
C用宏定义,C++用inline

16 全局变量和局部变量有什么区别
全局变量随主程序的创建而创建,随主程序的销毁而销毁,局部变量在局部函数的内部,退出就不存在;内存中分配在全局数据区。使用方式不同,通过声明后全局变量程序的各个部分都可以用到,局部变量只能在局部使用,分配在栈区,操作系统和编译器通过内存分配的位置来知道,全局变量分配在全局数据段并且在程序开始运行的时候被加载,局部变量则分配在堆栈里面。

17 怎么处理内存耗尽
判断指针是否为null,如果是则马上用return语句终止本函数

18 C++是不是内存安全的
不是,两个不同类型的指针之间可以强制转换

19 如何判断操作系统是16位还是32位
定义一个指针p,打印出sizeof(p),如果打印后是4,则是32位,如果是2,则是16位

int a = ~0;
if(a > 65536){
cout << "32 bit"<<endl;}
else{cout << "16 bits" <<endl;}

20 为什么要使用堆,使用堆空间的原因
不清楚要使用多大的内存空间,也不清楚对象的生存期到底多长

21 若数组名做实参而指针变量做形参,函数调用实参传给形参的是数组第一个元素地址。

22 有malloc和free为何还要new/delete
malloc和free是C语言提供的内存管理函数,用于分配和释放内存空间。
new和delete是C++语言提供的内存管理操作符,也可以用于分配和释放内存空间。
不同之处在于:

  1. malloc和free是C语言的标准库函数,而new和delete是C++语言的操作符。
  2. malloc返回的是void指针,需要进行显式的类型转换,而new返回的是对象的指针,不需要进行显式的类型转换。
  3. malloc只分配内存空间,不会自动调用构造函数,而new不仅会分配内存空间,还会自动调用构造函数初始化对象。
  4. free只释放内存空间,不会自动调用析构函数,而delete不仅会释放内存空间,还会自动调用析构函数销毁对象。
    在C++中,建议使用new和delete操作符进行内存管理,因为new和delete更符合C++的语法和对象生命周期的管理。而对于C语言或需要与C语言兼容的情况下,可以使用malloc和free来进行内存管理。

23 为什么数组名作为参数会改变数组内容而int却不会
数组名代表数组首元素的地址

24 析构函数和虚函数的用法和作用
析构函数(Destructor)是一种特殊的成员函数,用于在对象销毁时执行特定的清理工作。它的名称与类名相同,但前面加上一个波浪号(~)作为前缀。析构函数没有返回类型,也不接受任何参数。
虚函数(Virtual Function)是一种在基类中声明的函数,可以在派生类中重新定义。它通过在基类中使用virtual关键字进行声明,使得在运行时根据对象的实际类型来调用相应的函数。
析构函数的作用是在对象销毁时释放资源,如关闭文件、释放内存等。它通常用于释放在构造函数中分配的资源,以避免内存泄漏。
虚函数的作用是实现多态性,使得在运行时根据对象的实际类型来调用相应的函数。当基类指针指向派生类对象时,通过调用虚函数可以实现对派生类中的函数进行动态绑定,从而实现多态性的效果。
使用析构函数和虚函数的注意事项:析构函数应该是虚函数,以确保在派生类对象被销毁时正确调用派生类的析构函数。析构函数应该释放在构造函数中分配的资源,以避免内存泄漏。虚函数应该在基类中声明为虚函数,以便在派生类中进行重新定义。虚函数应该通过在基类中使用virtual关键字声明,并在派生类中使用override关键字进行重新定义。虚函数的调用是通过基类指针或引用来实现的,因此需要注意指针或引用的类型是否正确。

25 转义字符虽然包含两个或多个字符,但它只代表一个字符。编译系统在见到字符“\”时,会接着找它后面的字符,把它处理成一个字符,在内存中只占一个字节

26 引用和指针有什么区别
1.引用必须初始化,指针不必;2.引用初始化以后不能被改变,指针可以改变所指的对象;3.不存在指向空值的引用,但是存在指向空值的指针

27 不使用常量,直接在程序中填写数字或字符串,将会有什么麻烦
1.程序的可读性变差 2.在程序很多地方输入同样的字符或字符串易造成书写错误 3.如果修改数字或字符串,则会修改很多地方改动,麻烦且容易出错

28 C++有没有纯虚构造函数
构造函数只有new的时候才能不构造,构造函数不能是虚的,只能有虚的析构函数

*29 重复多次fclose一个打开过的FILE fp指针会有什么结果,并解释
导致文件描述符结构中指针指向的内存被重复释放,进而导致一些不可预期的异常

30 重载重写的区别
重载是指在同一个作用域内使用相同的函数名,但是参数列表不同的情况下,编译器会根据不同的参数列表选择调用不同的函数来实现不同的功能。
重写是指在继承关系中,子类重新定义了父类中的一个或多个方法,用以实现自己的功能。子类重写的方法必须具有相同的方法名、参数列表以及返回类型。
区别如下:

  1. 重载可以在同一个类中进行,也可以在不同的类中进行,且方法名相同但参数列表不同,不同之处在于参数个数、类型或者顺序。重载可以提高代码的复用性和可读性。
    重写只能在继承关系中进行,子类只能重写父类的方法,重写方法必须具有相同的方法名和参数列表。重写方法是实现多态的一种方式。
  2. 重载是编译时的多态,通过静态绑定实现,编译器根据不同的参数列表选择调用不同的方法。
    重写是运行时的多态,通过动态绑定实现,运行时根据对象的实际类型选择调用相应的重写方法。
  3. 重载不涉及继承关系,方法的重载在同一个类中进行,可以返回不同的结果或者执行不同的操作。
    重写涉及继承关系,子类重写父类的方法,方法的返回类型和方法名必须相同。
    总结起来,重载是在同一个类中多态的一种表现形式,而重写是多态的一种实现方式,在继承关系中体现子类对父类方法的重新定义。
     重载重写
    31 C++是不是类型安全的
    不是,两个不同类型的指针之间可以强制转换

32 C++里面是不是所有动作都是main()引起的,main函数执行以前,还会执行什么代码
全局变量初始化,就不是由main函数引起的,class A{}; A a; int main() {}
全局对象的构造函数会在main函数之前执行

33 C++中virtual与inline的含义分别是什么
在基类成员函数的声明前加上virtual关键字,意味着将该成员函数声明为虚函数。inline与函数的定义体放在一起,使该函数称为内联。inline是一种用于实现的关键字,而不是用于声明的关键字。
虚函数的特点:如果希望派生类能够重新定义基类的方法,则在基类中将该方法定义为虚方法,这样可以启用动态联编。
内联函数的特点:使用内联函数的目的是为了提高函数的运行效率。内联函数的代码不能过长,因为内联函数省去调用函数的时间是以代码膨胀为代价的,内联函数不能包含循环语句,因为执行循环语句要比调用函数开销大。

34 const关键字,有哪些作用
1 定义const变量时,需要对其初始化
2 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const
3 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值
4 对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量
5 对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为左值

35 debug与release选项是什么含义
debug为调式版本,release为发布版本,debug含有大量调试代码,release进行了各种优化,不含有调试代码信息,直接可以运行

36 子类覆盖父类的virtual函数不加virtual也能实现多态
virtual修饰符会被隐式继承

37 静态局部变量内存位置在已初始化数据段;

38 在不使用第三方参数情况下,交换两个参数的值。
a=a+b;
b=a-b;
a=a-b;

39 打印当前源文件的文件名与源文件的当前行号
FILE,__LINE__是预定义宏,并不在文件中,而是由编译器定义

cout << __FILE__;
cout << __LINE__;

40 类A没有声明任何成员变量与成员函数,size(A)的值,为何不为0
size(A)为1,如果声明对象为对象数组,而每一个对象占用为零,就无法区分A[0],A[1]…

41 排序算法中,关键码比较次数与记录初始排序无关的是选择排序

42 函数模板与类模板有什么区别
函数模板实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显示地指定

43 怎么区分函数重载调用的哪个函数
在C++中可以忽略函数的返回值,所以只能靠参数而不能靠返回值类型的不同来区分重载函数

44 所有的运算符都能重载吗
不是,不能改变C++内部数据类型(int,float)的运算符,不能重载“.’”,因为在类中对任何成员都有意义,不能重载C++运算符集合中没有的符号,如#@%$

45 基类的析构函数不是虚函数,会带来什么问题

46 模板容器如何实现
模板编程与当前的泛型编程本质相同,STL有7种主要容器,vector,list,deque,map,multimap,set,multiset

47 深拷贝和浅拷贝,临时对象
深拷贝意味着拷贝了资源和指针,而浅拷贝只是拷贝了指针,没有拷贝资源,这使得两个指针指向同一份资源,造成对同一份析构两次,程序崩溃。临时对象的开销比局部对象小。

48 类中如何使用const
const数据成员的初始化只能在类构造函数的初始化表中进行

49 前缀++可以当左值,后缀++不可以当左值

50 return不可返回指向栈内存的指针或引用,因为该内存函数体结束时被自动销毁

51 三元表达式

cout << (true?1:2)<<endl;  //正确  
cout << (true?1:1)<<endl; //错误,三元表达式问号后面的两个操作符必须为同一类型

52 首地址
unsigned short array[] = {1,2,3,4,5,6};
int i =3;
*(array+i)= 4

53 能做左值的函数
能做左值的函数是指可以作为赋值操作的左侧的函数。在C++中,只有类的成员函数可以被重载为左值函数。

int &max(int &x, int &y)
{
    return x >y ? x : y;
}
int x=55,y=88;
max(x,y) += 12 +11;
cout << "x="x <<";y=" << y<<endl;
    

54 默认构造函数内部调用带参构造函数属用户行为而非编译器行为
编译期间常量是在编译过程中就能确定其值的常量。这些常量的值在编译期间就可以计算出来,并且在运行时不会发生变化。例如,整数常量、字符常量、字符串常量等都属于编译期间常量。

非编译期间常量是在运行时才能确定其值的常量。这些常量的值在编译期间无法确定,需要在程序运行时通过计算或用户输入等方式来确定。例如,通过函数调用、表达式计算等方式得到的常量就属于非编译期间常量。

编译期间常量的优点是在编译过程中就能确定其值,可以在编译时进行优化,提高程序的执行效率。而非编译期间常量的值需要在运行时确定,可能会导致程序的执行速度较慢。因此,在实际编程中,尽量使用编译期间常量来提高程序的性能。

55 比较大小
((a+b)+ abs(a-b))/2

56 字符指针,浮点数指针,以及函数指针这三种类型的变量也占用内存单元,所占用的内存单元的数量是相同的。

57 对调用的虚函数和模板类都进行迟后编译
对于虚函数的调用,编译器会根据对象的动态类型来确定调用的具体函数。因此,虚函数的调用是在运行时进行的,而不是在编译时。这意味着虚函数的调用会被延迟到程序运行时才确定。

对于模板类,模板参数的具体类型是在编译时确定的。编译器会根据模板参数的具体类型生成相应的代码。因此,模板类的实例化也是在编译时进行的。

综上所述,对于虚函数和模板类,它们的调用和实例化都是在运行时进行的。编译器会根据运行时的具体情况来确定调用的虚函数和实例化的模板类。

58 C++的类,默认产生哪些类成员函数
C++的类默认产生以下成员函数:

默认构造函数(Default Constructor):如果没有定义任何构造函数,编译器会自动生成一个默认构造函数。它没有任何参数,用于创建对象时进行初始化操作。

拷贝构造函数(Copy Constructor):如果没有定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。它用于通过已存在的对象创建一个新对象。

拷贝赋值运算符(Copy Assignment Operator):如果没有定义拷贝赋值运算符,编译器会自动生成一个默认的拷贝赋值运算符。它用于将一个对象的值赋给另一个对象。

析构函数(Destructor):如果没有定义析构函数,编译器会自动生成一个默认的析构函数。它在对象销毁时执行清理操作。

class Empty
{
    public:
        Empty(); //缺省构造函数
        Empty(const Empty&); //拷贝构造函数
        ~Empty(); // 虚构函数
        Empty&operator(const Empty&) // 赋值运算符
        Empty& operator&(); //取址运算符
        const Empty* operator&() const; //取址运算符 const
}

59 C++中为什么用模板类

  1. 用来创建动态增长和减小的数据结构
  2. 它是类型无关的,因此具有很高的可复用性
  3. 它在编译时而不是运行时检查数据类型,保证了类型安全

60 C++哪些函数不能被声明为虚函数
普通函数(非成员函数),虚函数用于基类和派生类,普通函数所以不能
构造函数不能是因为虚函数采用的是虚调用的方法,允许在只知道部分信息的情况工作机制,特别允许调用只知道接口而不知道对象的准确类型的方法,但是调用构造函数即使要创建一个对象,那势必要知道对象的准确类型
内联成员函数的实质是在调用的地方直接将代码扩展开
静态成员函数是不能被继承的,它只属于一个类,因为也不存在动态联编等
友元函数不是类的成员函数,因此也不能被继承

61 delete 会自动调用析构函数,所以析构中调用delete引起了无限递归

62 定义一个无限循环
while(1){}
for(;1; ; )

63 int a=5,b=7,c; c = a+++b;
a = 6,b=7,c=12;

64 初始值

int x(0);    // 初始值在圆括号内
int y = 0;   // 初始值在等号后面
int z{0};    // 初始值在大括号内

65 STL如何实现vector
STL由容器算法迭代器实现,标准模板库可以方便容易地实现搜索数据或对数据排序等一系列的算法,调式程序时更加安全和方便;即使是人们用STL在UNIX平台下写的代码也可以很容易的理解,vector实质上是一个动态数组,会根据数据的增加,动态地增加数组空间。

66 编译期间常量 || 非编译期间常量
编译期间常量是在编译过程中就能确定其值的常量。这些常量的值在编译期间就可以计算出来,并且在运行时不会发生变化。例如,整数常量、字符常量、字符串常量等都属于编译期间常量。

非编译期间常量是在运行时才能确定其值的常量。这些常量的值在编译期间无法确定,需要在程序运行时通过计算或用户输入等方式来确定。例如,通过函数调用、表达式计算等方式得到的常量就属于非编译期间常量。

编译期间常量的优点是在编译过程中就能确定其值,可以在编译时进行优化,提高程序的执行效率。而非编译期间常量的值需要在运行时确定,可能会导致程序的执行速度较慢。因此,在实际编程中,尽量使用编译期间常量来提高程序的性能。

67 局部类与内部类
在一个函数内部定义的类,称为局部类
局部类的特点:
作用域仅限于所在函数的内部
所有成员必须定义于函数的内部,不允许定义static成员变量
成员函数不能直接访问函数的局部变量,不允许定义static成员变量
内部类又可以称为嵌套类

68 & 与*
int * value ;int & value; int * *value; int * &value; int &&value;int & value;
&如果旁边是数据类型,就是取地址操作符,而 * 是取值操作符
int * value: 基类型为整型的指针变量value
int & value: value变量的别名
int ** value: 基类型为整型的指针变量value的指针
int & value: 指针引用的别名
int && value: 引用本身不是一个对象,所以不能定义引用的引用
int & value: value是一个指针,指针是无法被引用的
int x = *p :
int *p = &a :
int p = &a : 。。。。。。。。。。

69 编译器是否会为每个类都生成空的无参的构造函数
在C++中,编译器并不会为每个类都生成空的无参构造函数
那么在什么情况下,会为类生成空的无参构造函数呢
具体有以下几种情况:
1.成员变量在声明的同时进行了初始化
2.有定义虚函数
3.包含了对象类型的成员,且这个成员有构造函数
4.父类有构造函数
总之,对象创建时,需要做一些额外操作(比如内存操作,函数调用),编译器一般都会为其生成空的无参构造函数。

70 右值引用
右值引用应该是C++11引入的一个非常重要的技术,
因为它是移动语义(Move semantics)与完美转发(Perfect forwarding)的基石:
移动语义:将内存的所有权从一个对象转移到另外一个对象,高效的移动用来替换效率低下的复制,对象的移动语义需要实现移动构造函数(move constructor)和移动赋值运算符(move assignment operator)。
完美转发:定义一个函数模板,该函数模板可以接收任意类型参数,然后将参数转发给其它目标函数,且保证目标函数接受的参数其类型与传递给模板函数的类型相同。

71 封装-继承-多态
封装
定义:将一个函数的实现方法在方法域外无法获取,只能方法域内的成员可以获取.
优点:减少耦合,更好地控制类成员,可以自由调整类内部结构,隐藏信息,实现细节
作用:封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法
继承
定义:保持已有类的特性而创造新类的过程被称之为继承;
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类,通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。
在已有类的基础上新增自己的特性而产生新类的过程称之为派生;
被继承的已有类被称之为基类;
派生出的新类称之为派生类;
继承是为了实现代码的重用;
派生是新问题出现,原有的程序无法解决问题,需要对原有代码进行改造;
继承的特性:
1、子类拥有父类非private的属性和方法。
2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3、子类可以用自己的方式实现父类的方法
多态
同一个函数名,有多个表现形式,参数的类型不同,参数的个数不同,函数的爷爷生的孩子叫伯叔仲季,龙生九子,各有不同,就是这个道理.函数的多态是

public Students(){
    private String name;
    private String sex;
    private int age;

    public String getName(){
        return name;
    }

    public void setName(String name){
        this.name = name; 
    }

    public String getSex(){
        return sex;
    }

    public void setSex(String sex){
        this.sex = sex;
    }

    public int getAge(){
        return age;
    }

    public void setAge(int age){
        this.age = age;
    }
}

72 类的访问权限
C++中成员访问限定符只能用来修饰类中的成员,不能修饰类本身;
C++中的类没有公有私有之分;
在类的内部,无论成员访问限定符是否为public,都可以互相访问;
在类的外部,只能通过对象访问成员,并且通过对象只能访问public属性的成员,不能访问protected与private成员
类中的成员变量一般以m_开头,成员变量一般是私有的;
类中的成员函数一般是公有的;

73 new-delete-malloc-free
new是C++中的运算符,可用于申请动态内存,
delete是C++中的运算符,可用于申请释放内存,
malloc是C/C++中的标准库函数,可用于申请动态内存
free是C/C++中的标准库函数
1)申请了内存空间后,必须检查是否分配成功。
2)当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。
3)这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。
4)虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。

74 C++ 和C的区别
一个不带参数的C函数原型必须把void写出来,而C++可以使用空参数列表。
C++中new和delete是对内存分配的运算符,取代了C中的malloc和free
标准C++中的字符串类取代了C标准C函数库头文件中的字符数组处理函数
C++中用来控制输入输出的是iostream,C是stdio函数库
C++中try/catch/throw异常处理机制取代了标准C中的setjmp()和longjmp()函数

2.关键字
typename;bool;dynamic_cast;mutable; namespace;static_cast;using;catch;

75 sizeof与strlen的区别
制作函数的人叫做函数定义者;使用函数的人叫做函数调用者。
sizeof()是一个运算符,返回申请内存字节大小
strlen()是一个函数,要遇到第一个\0结束,如果数组大小无法容纳所存内容加字符串结束标志\0,还有一个奇怪的越界标志,加1.

76 内存分区:全局区、堆区、栈区、常量区、代码区
内存分区
代码区:存放代码的地方
常量区:存放常量的地方
全局区:存放静态变量和全局变量的地方
堆区:调用malloc函数主动申请,如果不调用free函数就不会自动释放
栈区:存放函数的局部变量,返回值以及形参

77 常识
1.机器码和汇编是一对一的关系;
2.每一句代码都有自己的内存地址;
3.std::move执行一个无条件的转化到右值。它本身并不移动任何东西;
4.std::forward把其参数转换为右值,仅仅在那个参数被绑定到一个右值时;
5.std::move和std::forward在运行时(runtime)都不做任何事;
6.默认参数只能先写在右边;同时有声明和实现就需要写在声明里面;
7.头文件一旦改变,相关的源文件必须重新编译以获取更新过的声明。
8.未初始化的变量含有一个不确定的值,编译器未被强制要求检查此类错误,因此建议初始化每一个内置类型的变量;变量能且只能被定义一次,但是却可以被多次声明;在块作用域内定义的指针如果没有被初始化,也将有一个不确定的值;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
78 struct与class的区别
struct的默认的继承访问权是public;
class的默认的继承访问权是private

关于this指针的一个精典回答:
当你进入一个房子后,
你可以看见桌子、椅子、地板等,
但是房子你是看不到全貌了。
对于一个类的实例来说,
你可以看到它的成员函数、成员变量,
但是实例本身呢?
this是一个指针,它时时刻刻指向你这个实例本身。

79 指针与引用的区别
指针和内存都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名;
指针是一个实体;而引用是一个别名;
引用使用时无需解引用,指针需要解引用
引用只能在定义时被初始化一次,之后不可变;指针可变
引用没有const,指针有const,const的指针不可变
引用不能为空,指针可以为空
sizeof引用得到的是所指向的变量的大小,而sizeof指针得到的是指针本身的大小
指针和引用的自增运算意义不一样
联系:
1.引用在语言内部用指针实现
2.对一般应用而言,把引用理解为指针,不会犯严重语义错误;引用是操作受限了的指针

80 浅拷贝与深拷贝
直接赋值:其实就是对象的引用(别名)。
浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。
深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

#!/usr/bin/python
# -*-coding:utf-8 -*-
import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
b = a                       #赋值,传对象的引用
c = copy.copy(a)            #对象拷贝,浅拷贝
d = copy.deepcopy(a)        #对象拷贝,深拷贝
a.append(5)                 #修改对象a
a[4].append('c')            #修改对象a中的['a', 'b']数组对象
print( 'a = ', a )    # ('a = ', [1, 2, 3, 4, ['a', 'b', 'c'], 5])
print( 'b = ', b )    # ('b = ', [1, 2, 3, 4, ['a', 'b', 'c'], 5])
print( 'c = ', c )    # ('c = ', [1, 2, 3, 4, ['a', 'b', 'c']])
print( 'd = ', d )    # ('d = ', [1, 2, 3, 4, ['a', 'b']])

81. 错误处理
申请内存用new操作符。
释放内存用delete操作符。
new和delete,new[]和delete[]成对使用。
申请内存完成之后,要检测指针是否申请成功,处理申请失败的情况。
谁申请谁释放。优先级:函数层面,类层面,模块层面。
释放内存完成后将指针赋空,避免出现野指针。
使用指针前进行判断合法性,应考虑到为空的情况的处理。
使用数组时,应先判断索引的有效性,处理无效的索引的情况。
代码不能出现编译警告。
使用错误传递的错误处理思想。
卫句风格:先处理所有可能发生错误的情况,再处理正常情况。
嵌套do-while(0)宏:目的是将一组语句变成一个语句,避免被其他if等中断。

82. 性能提升
使用前向声明代替#include指令。Class M;
尽量用++i代替i++。即用前缀代替后缀运算。
尽量在for循环之前,先写计算估值表达式。
尽量避免在循环体内部定义对象。
避免对象拷贝,尤其是代价很高的对象拷贝。
避免生成临时对象,尤其是大的临时对象。
注意大尺寸对象数组。
80-20原则。

83. 兼容性
遵守ANSI C和ISO C++国际标准。
确保类型转换不会丢失信息。
注意双字节字符的兼容性。
注意运算溢出问题。
不要假设类型的存储尺寸。
不要假设表达式的运算顺序。
不要假设函数参数的计算顺序。
不要假设不同源文件中静态或全局变量的初始化顺序。
不要依赖编译器基于实现、未明确或未定义的功能。
将所有#include的文件名视为大小写敏感。
避免使用全局变量、静态变量、函数静态变量、类静态变量。在使用静态库,动态库,多线程环境时,会导致兼容性问题。
不要重新实现标准库函数,如STL已经存在的。

84. 类
构造函数
构造函数的初始化列表,应和类的顺序一致。初始化列表中的每个项,应独占一行。避免出现用一个成员初始化另一个成员。构造函数应初始化所有成员,尤其是指针。不要在构造函数和析构函数中抛出异常。
纯虚函数
M类的虚函数应设计为纯虚函数。
构造和析构函数
如果类可以继承,则应将类析构函数设计为虚函数。如果类不允许继承,则应将类析构函数设计为非虚函数。如果类不能被复制,则应将拷贝构造函数和赋值运算符设计为私有的。如果为类设计了构造函数,则应有析构函数。
成员变量
尽量避免使用mutable和Volatile。尽量避免使用公有成员变量。
成员函数
努力使类的接口少而完备。尽量使用常成员函数代替非常成员函数,const函数。除非特别理由,绝不要重新定义(继承来的)非虚函数。(这样是覆盖,基类的某些属性无初始化)
继承
继承必须满足IS-A的关系,HAS-A应采用包含。
虚函数不要采用默认参数。
除非特别需要,应避免设计大而全的虚函数,虚函数功能要单一。
除非特别需要,避免将基类强制转换成派生类。
友元
尽量避免使用友元函数和友元类。

85 函数
引用
引用类型作为返回值:函数必须返回一个存在的对象。引用类型作为参数:调用者必须传递一个存在的对象。
常量成员函数
表示该函数只读取对象的内容,不会对对象进行修改。
返回值
除开void函数,构造函数,析构函数,其它函数必须要有返回值。当函数返回引用或指针时,用文字描述其有效期。
内联函数
内联函数应将函数体放到类体外。只有简单的函数才有必要设计为内联函数,复杂业务逻辑的函数不要这么做。虚函数不要设计为内联函数。
函数参数
只读取该参数的内容,不对其内容做修改,用常量引用。修改参数内容,或需要通过参数返回,用非常量应用。简单数据类型用传值方式。复杂数据类型用引用或指针方式。

86. 表达式
避免在表达式中用赋值语句。
避免对浮点类型做等于或不等于判断。
不能将枚举类型进行运算后再赋给枚举变量。
在循环过程中不要修改循环计数器。
检测空指针,用 if( p )
检测非空指针,用 if( ! p )

87. 类型
定义指针和引用时*和&紧跟类型。
尽量避免使用浮点数,除非必须。
用typedef简化程序中的复杂语法。
避免定义无名称的类型。例如:typedef enum { EIdle, EActive } TState;
少用union,如果一定要用,则采用简单数据类型成员。
用enum取代(一组相关的)常量。
不要使用魔鬼数字。
尽量用引用取代指针。
定义变量完成后立即初始化,勿等到使用时才进行。
如果有更优雅的解决方案,不要使用强制类型转换。

88. vector

# include<vector>
int<vector> vec(n); // n指定长度
// 常用函数
vec.push_back(number);
vec.pop_back();
int n=vec.size();
// 其它函数
vec.clear();
bool flag=vec.empty();
// 遍历方法
//1. 传统方法
int n=vec.size();
for(int i=0;i<n;i++{
  cout<<vec[i]<<endl;
}
//2. C++11中的方法
  for (auto num:vec)
  {
    cout<<num<<endl;
  }

89. set 与 map

#include<set>
#include<unordered_set>
#include<map>
#include<unordered_map>

set<int> s; // 集合

// 常用函数
int n=s.size();
s.insert(num);
s.erase(num);
s.count(num); // 常用来判断有无这一元素

// 遍历元素
set<int>::iterator it;
for(it = s1.begin(); it!=s1.end(); it++){ //自动排序元素
    cout<<*it<<endl;  // 这里的it是个指向数据的指针
}
//c++ 11
for(auto it : s){
    cout<<it<<endl;   // 这里的it就是数据本身
}
 
// map
map<string, int> m;

// 常用函数
int n=m.size();
m[key]=value; // 最简单的插入方法(个人认为)
m.erase(key);
s.count(key); // 常用来判断有无这一元素

// 遍历元素
map<string, int>::iterator it;
for (it = m.begin(); it != m.end(); it++) {
    cout<<it->first<<" "<<it->second<<endl;
}
// c++ 11
for(auto it : m){
    cout<<it.first<<" "<<it.second<<endl;
}

90. sort

#include<algorithm>
using namespace std;
int max_value=max(a,b);
sort(vec.begin(),vec.end());

91. stack和queue

#include <queue>
#include <stack>

// 队列
queue<int> q;
q.push(num);
q.pop();
int n=q.size();
bool isEmpty=q.empty();
int first=q.front(); // 队头
int last=q.back();   // 队尾:最后一个push进去的元素是队尾!

// 栈
stack<int>st;
st.push(num);
st.pop();
int top=q.top(); //栈顶元素
int n=st.size();
bool isEmpty=st.empty();

92. 优先队列和双端队列
优先队列

#include <queue>
priority_queue<int> q;
// 大根堆
priority_queue<int> q; //等同于 priority_queue<int, vector<int>, less<int>> a;
// 小根堆
priority_queue<int, vector<int>, greater<int>> q;
// 常用函数
q.push(num);
int top=q.top();
q.pop();

// 示例
# include<iostream>
# include<queue> 
using namespace std;
int main(){
  priority_queue<int>q;
  q.push(1);
  q.push(3);
  q.push(2);
  cout<<q.top()<<endl; // 类似于普通队列的back()
  q.pop();
  cout<<q.top()<<endl; 
}
// 结果:3 2
# include<iostream>
# include<queue> 
using namespace std;
int main(){
  priority_queue<int,vector<int>,greater<int>>q;
  q.push(1);
  q.push(3);
  q.push(2);
  cout<<q.top()<<endl;
  q.pop();
  cout<<q.top()<<endl; 
}
// 结果:1 2

双端队列

#include<deque>
queue<int>q;
// 常用函数: front指队头,back指队尾(新插入的)
q.push_back(num);
q.push_front(num);
q.pop_back();
q.pop_front();
int num=q.front();
int num=q.back();
int n=q.size();
bool flag=q.empty();

93. C++关键字
在这里插入图片描述
94 移动构造函数与拷贝构造函数对比
移动构造函数和拷贝构造函数都是 C++ 中的特殊成员函数,用于创建对象的副本。它们的区别在于:
拷贝构造函数会在创建对象的副本时,将原对象的所有成员变量都复制一遍。而移动构造函数则是将原对象的资源所有权转移给新对象,不进行复制。拷贝构造函数的参数是一个 const 引用类型的对象,而移动构造函数的参数是一个右值引用类型的对象。当对象被传递给函数或从函数返回时,会调用移动构造函数来避免不必要的复制。这种操作称为移动语义。而拷贝构造函数只能进行复制操作。因此,如果你需要创建对象的副本并保留原始对象,应该使用拷贝构造函数。如果你需要将资源所有权转移给新对象,并销毁原始对象,应该使用移动构造函数.

95 类的构造函数、析构函数、赋值函数、拷贝函数
类的构造函数、析构函数、赋值函数和拷贝函数都是 C++ 中的特殊成员函数,用于创建、销毁对象以及对象之间的操作。
构造函数:用于创建对象时进行初始化操作。可以定义多个构造函数,以便对象可以以不同的方式进行初始化。构造函数的名称必须与类名相同,没有返回值,可以带参数。
析构函数:用于销毁对象时执行的操作。析构函数的名称前面加上一个波浪线 ~,没有参数,也没有返回值。在对象被销毁时自动调用,用于释放对象占用的资源。
赋值函数:用于将一个对象的值赋给另一个对象。赋值函数的名称为 operator=,没有返回值,参数为一个指向类对象的引用。赋值函数用于处理对象之间的赋值操作。
拷贝函数:用于将一个对象的值复制到另一个对象中。拷贝函数有两种:拷贝构造函数和移动构造函数。拷贝构造函数用于创建对象的副本,参数为一个指向类对象的常量引用;移动构造函数用于将一个对象的资源所有权转移给另一个对象,参数为一个指向类对象的右值引用。
以上这些函数都是类的特殊成员函数,需要根据需要进行定义和实现,以便在不同的场景下进行对象的创建、销毁和操作。

96 空类有哪些函数?空类的大小?
空类是指没有任何成员变量和成员函数的类。在 C++ 中,空类默认会自动生成以下函数:默认构造函数:用于创建空类对象时进行初始化操作。默认析构函数:用于销毁空类对象时执行的操作。拷贝构造函数:用于创建空类对象的副本。赋值函数:用于将一个空类对象的值赋给另一个空类对象。空类的大小取决于编译器的实现方式和对齐方式。一般来说,空类的大小为 1 字节,因为 C++ 语言规定,每个对象都应该有一个唯一的地址,即使是空对象。但是,某些编译器可能会对空类进行优化,将其大小设置为 0 字节。

97 多态 静态多态与动态多态
多态是 C++ 中一个重要的特性,指同一函数名可以在不同的上下文中具有不同的含义。C++ 中的多态分为动态多态和静态多态。
动态多态:指在运行时确定函数调用的具体实现,也称为运行时多态。动态多态是通过虚函数实现的,通过将基类中的函数定义为虚函数,派生类可以重写该函数,并在运行时根据对象的实际类型调用相应的实现。
静态多态:指在编译时确定函数调用的具体实现,也称为编译时多态。静态多态是通过函数重载和模板实现的,通过定义多个函数或模板函数,实现同一函数名可以接受不同类型的参数,编译器在编译时根据参数类型选择相应的函数实现。
总的来说,动态多态是基于对象的多态,静态多态是基于类型的多态。动态多态的实现需要运行时类型信息,因此会带来一定的性能开销,而静态多态的实现不需要运行时类型信息,因此更加高效。

98 静态多态:重写、重载、模板
在 C++ 中,静态多态是通过函数重写、函数重载和模板实现的。
函数重写:在继承关系中,派生类可以重写基类中的虚函数,使得不同的对象在调用同一个函数时可以有不同的行为。派生类中的函数必须与基类中的虚函数具有相同的名称、参数列表和返回类型,然后在函数定义中使用 override 关键字说明该函数是对基类函数的重写。
函数重载:在同一个作用域中,可以定义多个函数具有相同的名称但是参数列表不同,编译器会根据调用的函数和参数类型确定具体的函数实现。函数重载可以提高代码的可读性和复用性,但是需要注意函数定义必须具有不同的参数列表。
模板:通过定义模板函数或模板类,在编译时根据实际参数类型生成具体的函数或类,从而实现同一函数名或类名可以适用于不同的数据类型。模板可以提高代码的可重用性和通用性,但是需要注意模板实例化时会导致代码膨胀。
以上这些方式都是静态多态的实现方式,可以根据需要进行使用。函数重写适用于多态的情况,函数重载适用于相似但不完全相同的操作,模板适用于通用的算法和数据类型。

99 extern关键字:修饰全局变量
在 C++ 中,extern 关键字用于声明一个全局变量或函数,表示该变量或函数在其他文件中定义。当多个文件需要共享同一个全局变量时,可以在其中一个文件中定义该变量,然后在其他文件中使用 extern 关键字声明该变量即可。例如,我们在文件 a.cpp 中定义一个全局变量:int global_var = 10;然后在文件 b.cpp 中使用 extern 关键字声明该变量:extern int global_var;这样,在文件 b.cpp 中就可以使用 global 这个全局变量了。需要注意的是,使用 extern 关键字声明全局变量时不需要指定初始值,因为初始值已经在定义时指定过了。另外,如果要在多个文件中共享同一个全局变量,需要确保所有文件中的声明和定义都是一致的,否则会导致链接错误。一般来说,可以将所有的全局变量声明放在一个头文件中,在需要使用的文件中包含该头文件即可。

100. 野指针产生与避免
野指针是指指向未知或无效内存地址的指针,使用野指针会导致程序崩溃或产生不可预知的结果。常见的野指针产生原因包括:指针未初始化:未初始化的指针变量的值是不确定的,指向任意内存地址,使用时会产生野指针。指针被释放:指针所指向的内存已经被释放,再次使用指针会产生野指针。指针越界:指针访问了超出其所指向存范围的地址,这样也会产生野指针。为避免产生野指针,可以采取以下措施:初始化指针:在定义指针变量时,应该立即将其初始化为 NULL 或有效地址,避免未初始化的指针变量产生野指针。及时释放指针:在使用完指针所指向的内存后,及时将指针置为 NULL 或释放内存,避免指针被重复使用,产生野指针。检查指针越界:在访问指针所指向的内存时,应该确保访问的地址在指针所指向的内存范围内,避免指针越界产生野指针。使用智能指针:使用智能指针可以避免手动管理内存和产生野指针的问题,智能指针会在对象不再使用时自动释放内存。总之,避免野指针的产生需要程序员编写高质量的代码,谨慎地处理指针变量,并采取适当的措施来管理内存。

101 static关键字:修饰局部变量、全局变量、类中成员变量、类中成员函数
在 C++ 中,static 关键字可以用于修饰局部变量、全局变量、类中成员变量和类中成员函数,具体含义如下:
修饰局部变量:表示该变量在程序生命周期内只被初始化一次,不会随着函数的调用而重复初始化。此外,静态局部变量的作用域仅限于定义它的函数内部,但其存储空间在全局数据区,因此在函数调用结束后仍然可以保持其值不变。修饰全局变量:表示该变量仅在定义它的文件中可见,不会被其他文件访问。静态全局变量的生命周期与程序的生命周期相同,但其作用域仅限于定义它的文件内部。修饰类中的成员变量:表示该变量属于类本身而不属于类的任何一个对象,所有对象共享同一个静态成员变量。静态成员变量可以通过类名来访问,也可以通过对象名来访问,但推荐使用类名来访问。修饰类中的成员函数:表示该函数属于类本身而不属于类的任何一个对象,可以通过类名来访问,也可以通过对象名来访问。静态成员函数只能访问静态成员变量和其他静态成员函数,不能访问非静态成员变量和非静态成员函数,因为非静态成员需要先创建对象才能访问。总之,static 关键字可以用于修饰不同的变量和函数,其作用也各不相同。需要根据具体的情况选择和使用,以达到最优的效果。

102 内存泄露的情况
内存泄漏指程序在运行过中,分配的堆内存空间没有被及时释放,导致这些空间不能再次被分配,最终导致内存不足,程序崩溃等问题。常见的内存泄漏情况包括:忘记释放动态分配的内存:如使用 new 或 malloc 分配了内存,但在使用完后没有使用 delete 或 free 函数释放空间。指针赋值导致内存泄漏:如将一个指向动态分配内存的指针重新指向其他内存时,原先分配的内存将会泄漏。异常导致内存泄漏:如在使用动态分配内存的过程中,程序突然发生异常导致程序终止,此时分配的内存将不能被释放。没有正确使用 RAII 技术:如在使用 STL 容器时,没有正确使用智能指针或其他 RAII 技术,容易出现内存泄漏。在循环中动态分配内存,但是没有释放:例如,在循环中使用 new 分配内存,但是在每次循环结束后没有使用 delete 释放内存,从而导致内存泄漏。对象构造时分配内存,但是在析构时没有释放:例如,在对象的构造函数中分配了内存,但是在析构函数中没有使用 delete 释放内存,从而导致内存泄漏。
为避免内存泄漏,我们应该养成良好的编程习惯,及时释放不再使用的内存空间,使用智能指针等 RAII 技术,防止程序出现异常等情况,从而保证程序能够正常运行,并且不会因为内存泄漏而导致系统崩溃。

103 四种类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast
在 C++ 中,有四种类型转换操作符,分别是 static_cast、dynamic_cast、const_cast 和 reinterpret_cast。它们的使用场景和含义如下:static_cast:用于非多态类型之间的转换,如基本数据类型的转换、指针和引用的转换,以及具有继承关系的类型之间的转换。static_cast 不能进行动态类型检查,因此在使用时需要注意类型转换的安全性。
dynamic_cast:用于多态类型之间的转换,即在存在虚函数的类中进行类型转换。dynamic_cast 会在运行时检查类型转换的安全性,如果转换不安全则返回空指针或引发 std::bad_cast 异常。因此,dynamic_cast 转换的类型必须是多态类型,即具有虚函数的类或结构体类型。const_cast:用于去除常量类型的 const 或 限定符,将常量对象转换为非常量对象,或将 volatile 对象转换为非 volatile 对象。const_cast 一般用于函数重载时去除常量限定符,或在代码中修改被声明为常量的变量值。reinterpret_cast:用于将一个指针或引用转换为另一个指针或引用,或将一个整数转换为指针类型。reinterpret_cast 的使用非常危险,因为它不会检查类型安全性,因此如果使用不当会导致程序崩溃或安全漏洞。在使用类型转换时,应根据具体的情况选择合适的转换方式,并且在使用时需要注意类型转换的安全性,以避免出现不必要的错误或安全漏洞。

104 struct内存对齐
在 C++ 中,结构体(struct)是一种由不同类型的成员组成的数据类型,其成员在内存中是依次存放的。为了提高内存访问的效率,编译器会对结构体进行内存对齐,即按照某种规则将结构体的成员排列在内存中的地址上,以保证每个成员都能够被高效地访问。结构体内存对齐的规则如下:
结构体的第一个成员的偏移量为 0。
结构体的每个成员相对于结构体首地址的偏移量必须是该成员大小的整数倍,如果不是,则编译器会在该成员前面填充一些字节,使其对齐。
结构体的总大小必须是结构体中最大成员大小的整数倍,如果不是,则编译器会在结构体末尾填充一些字节,使其对齐。例如,考虑以下结构体定义:

struct Student {
    int id;
    char name[20];
    double score;
};

假设 int 和 double 类型的大小分别为 4 和 8 字节,char 类型的大小为 1 字节,则该结构体的内存对齐方式如下:
第一个成员为 int 类型的 id,偏移量为 0。
第二个成员为 char 数组类型的 name,偏移量为 4(int 类型大小),由于 char 类型大小为 1 字节,因此不需要进行填充。
第三个成员为 double 类型的 score,偏移量为 24,由于 double 类型大小为 8 字节,因此需要在 char 数组后填充 4 字节的空间,使其偏移量为 28。
因此,该结构体的大小为 32 字节。
结构体内存对齐的规则可以通过编译器的指令进行设置和修改,以满足不同的代码需求。

105 vector 与 list
在 C++ STL 中,vector 和 list 都是常用的容器类型,它们都提供了不同的数据结构和操作方法,适用于不同的场景。
vector 是一种动态数组,它在内存中是连续存储的一段空间,可以随机访问元素,支持快速的插入和删除操作,但在插入或删除元素时需要移动后面的元素,因此效率较低。由于是连续存储的一段空间,因此 vector 的内存占用率比较高,对于大量的元素或需要频繁的插入和删除操作,vector 的效率会受到影响。
list 是一种双向链表,它在内存中不是连续存储的,因此可以快速地插入和删除元素,不需要移动后面的元素,但不能随机访问元素,只能从头或尾开始遍历链表。由于不需要连续的内存空间,因 list 的内存占用率比较低,在处理大量元素或需要频繁插入和删除操作时,list 的效率比 vector 高。
下面是 vector 和 list 的一些比较:
访问元素:vector 支持随机访问,可以通过下标或迭代器直接访问元素;list 不支持随机访问,只能从头或尾开始遍历链表。
插入和删除操作:vector 在插入和删除元素时需要移动后面的元素,因此效率较低;list 可以快速地插入和删除元素,不需要移动后面的元素,因此效率较高。
内存占用率:vector 内存占用率高,因为需要连续的内存空间;list 内存占用率低,因为可以不连续地分配内存空间。
迭代器失效:vector 在插入删除元素时,迭代器可能会失效;list 在插入或删除元素时,迭代器不会失效。
排序:vector 支持快速排序等高效的排序算法;list 不支持快速排序,只能使用归并排序等较慢的排序算法。
总之,vector 适用于需要随机访问元素,不需要频繁插入和删除操作的场景,而 list 适用于需要频繁插入和删除操作,不需要随机访问元素的场景。

106 map与unordered_map对比
在 C++ STL 中,map 和 unordered_map 都是常用的关联容器类型,它们都提供了不同的数据结构和操作方法,适用于不同的场景。map 是一种红黑树,它通过二叉搜索树的方式来存储键值对,可以快速查找、插入和删除元素,但在插入和删除操作时需要维护平衡性,因此效率较低。由于是序的数据结构,map 的元素按照键值的大小进行排序,因此支持按键值范围查找元素。unordered_map 是一种哈希表,它将键值映射到哈希桶中,可以快速查找、插入和删除元素,但需要占用更多的内存空间。由于是无序的数据结构,unordered_map 的元素没有固定的顺序,因此不支持按键值范围查找元素。下面是 map 和 unordered_map 的一些比较:
查找元素:map 通过二叉搜索树的方式来查找元素,时间复杂度为 O(log n);unordered_map 通过哈希表的方式来查找元素,时间复杂度为 O(1)。
插入和删除操作:map 在插入和删除元素时需要维护平衡性,因此效率较低;unordered_map 可以快速地插入和删除元素,不需要维护平衡性,因此效率较高。
内存占用率:map 内存占用率低,因为每个节点只需要存储一个键值对和指针;unordered_map 内存占用率高,因为需要分配哈希桶和链表结构。
迭代器失效:map 在插入和删除元素时,可能会使迭代器失效;unordered_map 在插入和删除元素时,不会使迭代器失效。
排序:map 是有序的数据结构,支持按键值范围查找元素;unordered_map 是无序的数据结构,不支持按键值范围查找元素。
总之,map 适用于需要按键值范围查找元素,不需要频繁插入和删除操作的场景,而 unordered_map 适用于需要快速插入和删除元素,不需要按键值范围查找元素的场景。

107 volatile关键字:避免编译器指令优化
在 C++ 中,volatile 是一个关键字,用来告诉编译器,该变量的值可能会被意外地改变,因此编译器应该避免对该变量的操作进行优化。volatile 可以应用于以下两种情况:
外部设备或者内存映射 I/O 等需要访问硬件的场景。这些变量的值可能会被外部设备或者其他程序改变,因此编译器不能对其进行优化。
多线程编程中,多个线程共享同一个变量时,需要使用 volatile 来确保变量的可见性。volatile 可以保证线程读取该变量的值时,不会使用缓存中的旧值,而是从内存中读取最新的值。
需要注意的是,volatile 只能保证变量的原子性,不能保证线程安全。如果需要保证线程安全,需要使用其他同步机制,例如互斥锁、原子操作等。
同时,由于 volatile 可能会影响编译器的优化,因此在一些情况下,使用 volatile 可能会导致代码变慢,因此需要谨慎使用。

108 std::move函数
std::move 是 C++11 中引入的一个函数,用于将对象的所有权从一个对象转移到另一个对象。
具体来说,std::move 接受一个左值引用,并将其转换为一个右值引用。右值引用可以绑定到临时对象或者将要被销毁的对象,因此可以用于将对象的所有权转移给其他对象。
std::move 主要用于两种情况:
将一个对象的所有权转移给另一个对象,从而避免进行深拷贝操作,提高程序的效率。例如,可以将一个数组或者容器中的元素移动到另一个数组或者容器中。
在实现移动构造函数和移动赋值运算符时,可以使用 std::move 将成员变量的所有权转移给新对象,从而避免进行深拷贝操作,提高程序的效率。
需要注意的是,使用 std::move 转移对象的所有权后,原对象的状态变为未定义状态,因此不能再次使用原对象。同时,转移对象的所有权并不意味着对象的内容被移动或者删除,只是对象的所有权发生了改变。

109 虚函数实现动态多态的原理、虚函数与纯虚函数的区别
虚函数实现动态多态的原理:
在 C++ 中,虚函数是通过虚函数表(virtual table)来实现的。每个包含虚函数的类都有一个虚函数表,其中存放着该类中每个虚函数的地址。当一个对象调用虚函数时,程序会根据该对象的类型,在虚函数表中查找对应的虚函数地址,并调用该函数。
由于虚函数的调用是在运行时确定的,因此可以实现动态多态,即在运行时根据对象的实际类型来调用相应的函数。
虚函数与纯虚函数的区别:
虚函数是一种可以在基类和派生类之间进行重载的函数,它可以被派生类覆盖,也可以被基类中的其他函数覆盖。
而纯虚函数是一种没有实现的虚函数,它必须在派生类中被实现。纯虚函数在基类中可以被定义为“纯虚函数”(pure virtual function),即在函数声明后加上“=0”的标识符,表示该函数没有实现。
在使用纯虚函数时,必须派生一个类,并在派生类中实现该函数,否则派生类也会成为抽象类,无法实例化。
因此,虚函数和纯虚函数的主要区别在于是否有实现,虚函数可以有实现,也可以没有实现,而纯虚函数必须在派生类中有实现。

110 继承时,父类的析构函数是否为虚函数?构造函数能不能为虚函数?为什么?
在 C++ 中,如果一个类被设计为作为基类使用,那么它的析构函数应该声明为虚函数。这是因为当一个对象被删除时,如果它有一个指向派生类的基类指针,那么如果基类的析构函数不是虚函数,就会发生未定义行为。因为如果一个指针调用一个非虚函数,那么编译器会根据指针类型确定要调用的函数,而不是根据指针所指向的对象的类型。
构造函数不能声明为虚函数。这是因为在调用构造函数时,对象所属的类还没有被构造完成,因此不能使用虚函数表来调用构造函数。而且,如果一个类的构造函数是虚函数,那么在创建对象时,需要调用虚函数表来确定要调用的构造函数,这会导致一些问题。
总之,析构函数应该声明为虚函数,而构造函数不能声明为虚函数。这是因为析构函数在销毁对象时需要调用,而且可能会涉及到多态,而构造函数在创建对象时需要调用,不能使用虚函数表。

111 vector迭代器失效的情况
在使用 vector 容器时,以下操作会导致迭代器失效:
在 vector 容器中插入或删除元素,这会导致 vector 的大小发生变化,从而导致所有迭代器失效。
对 vector 容器进行排序,因为排序会改变元素的位置,从而导致所有迭代器失效。
使用 push_back() 函数将元素添加到 vector 中时,可能会导致 vector 容器重新分配内存,从而导致所有迭代器失效。
需要注意的是,在迭代器失效后,不能再通过失效的迭代器来访问 vector 中的元素,否则会导致未定义行为。如果需要继续访问 vector 中的元素,可以重新获取迭代器。
为了避免迭代器失效,可以使用 erase() 函数和 insert() 函数来删除插入元素,这两个函数会返回一个指向被删除或插入元素的迭代器,可以用来更新迭代器。同时,在对 vector 进行排序时,可以使用稳定排序算法,例如归并排序,以避免元素位置的改变。

112 set与unordered_set对比
set 和 unordered_set 都是 C++ STL 中的容器,它们都实现了相似的功能:存储一组唯一的元素,支持插入、删除和查找等操作。但是它们的底层实现方式不同,因此在一些方面有所不同。
底层实现方式:set 基于红黑树实现,unordered_set 基于哈希表实现。
有序性:set 中的元素是有序的,而 unordered_set 中的元素是无序的。
查找效率:set 中的元素是按照一定顺序排列的,因此在查找时可以使用二分查找等高效的算法。而 unordered_set 中的元素是无序的,因此只能使用哈希算法进行查找,平均查找复杂度为 O(1)。
插入和删除效率:set 在插入和删除元素时需要进行平衡操作,因此效率较低,时间复杂度为 O(logn)。而 unordered_set 只需要进行哈希计算和链表操作,因此插入和删除效率较高,时间复杂度为 O(1)。
综上所述,如果需要存储一组唯一的元素,并且需要按照一定顺序进行查找,那么可以选择使用 set。如果不需要有序性,而且需要高效的插入和删除操作,那么可以选择使用 unordered_set。

113 STL容器空间配置器
在 C++ STL 中,每个容器都有一个默认的空间配置器,用于在运行时分配和释放容器所需的内存。空间配置器的作用是为容器中的元素分配内存,以及为容器本身分配内存,例如 vector 中的数组和链表节点。
STL 提供了两种空间配置器:std::allocator 和 std::allocator_traits。std::allocator 是一个模板类,用于为单个对象分配内存。std::allocator_traits 是一个模板类,用于为容器分配内存。
std::allocator 的用法示例:

#include <memory>

int main()
{
    std::allocator<int> alloc;

    int* p = alloc.allocate(10); // 分配 10 个 int 类型的空间

    for (int i = 0; i < 10; i++)
    {
        p[i] = i;
    }

    for (int i = 0; i < 10; i++)
    {
        std::cout << p[i] << ";
    }

    alloc.deallocate(p, 10); // 释放空间

    return 0;
}

std::allocator_traits 的用法示例:

#include <vector>
#include <iostream>

int main()
{
    std::allocator<int> alloc;

    std::vector<int, std::allocator<int>> v(alloc); // 使用自定义的空间配置器

    for (int i = 0; i <10; i++)
    {
        v.push_back(i);
    }

    for (int i = 0; i < v.size(); i++)
    {
        std::cout << v[i] << " ";
    }

    return 0;
}

114.auto与decltype用法与区别
auto关键字主要用于在声明变量时让编译器自动根据变量的初始化表达式来推导其类型;
在范围for循环中,auto常被用来简化迭代变量的类型声明,使代码更简洁易读;
编译时推导:编译器在编译阶段根据初始化表达式的实际类型来确定auto声明变量的类型,推导一旦完成,变量的类型就固定了,后续不会再改变。
注重表达式结果类型:重点关注的是初始化表达式计算后得到的结果类型,一般遵循常规的类型转换和 decay(退化)规则,比如数组会退化为指针、函数会退化为函数指针等。
decltype用于获取一个表达式的类型,然后可以利用该类型去声明其他变量、函数返回值类型等.
在模板编程里,有时候需要根据传入模板参数的表达式类型来确定模板内部定义的其他变量或函数返回值等的类型,decltype就能很好地完成这个任务.
精确推导:能精确地反映出所传入表达式的类型,不会像auto那样存在类型退化等情况。例如对于数组类型,decltype会保持其数组类型,而auto通常会将数组退化为指针.
不进行求值:只是获取表达式的类型,不会对表达式进行求值操作,这点和auto不同,auto需要根据表达式求值结果来确定类型,而decltype重点在分析表达式本身呈现出的类型特征.
auto与decltype的区别
推导依据不同
auto依据变量初始化表达式的求值结果类型进行推导,通常伴随着求值过程以及类型的一些常规转换、退化等情况。
decltype依据表达式本身的类型特征来推导,不会对表达式进行求值,且往往能更精确地反映原始类型,不存在类型退化(除了在某些涉及括号等特定语法结构改变语义的情况下)。
应用场景侧重不同
auto更多用于简化变量声明,尤其是在类型名称比较复杂冗长(比如涉及到复杂的模板类型等)或者不太容易明确写出具体类型(如范围for循环中迭代元素类型不确定时)的场景下,让编译器去自动处理类型推导,提高代码简洁性和可读性。
decltype侧重于在需要精确获取某个表达式类型来定义其他相关类型的情况,比如模板编程中确定函数返回值类型要依据模板参数相关表达式的类型、或者想保持某些特殊类型(如数组类型)不被转换等场景下使用。
语法形式不同
auto用于变量声明时,直接放在变量名之前,后面紧跟初始化表达式,形式如“auto 变量名 = 初始化表达式;”。
decltype后面括号内放入要获取类型的表达式,然后可以用该推导出来的类型去定义变量、函数返回值等,形式如“decltype(表达式) 变量名;” 或者在函数声明中作为返回值类型推导的一部分等用法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值