C++八股文面经

1.介绍一下你对面向对象的理解,

面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,它将数据和操作数据的方法组合成一个对象,以此来描述现实世界中的事物和概念。在面向对象编程中,程序被组织成一个个对象,每个对象都有自己的属性和方法,对象之间通过消息传递来进行通信和协作。

与面向过程编程相比,面向对象编程更加注重数据的封装和抽象,使得程序更加易于维护和扩展。面向过程编程则更加注重流程和算法的设计,程序的执行过程是由一系列的函数调用和数据传递组成的。

总的来说,面向对象编程更加适合大型复杂的软件系统的开发,而面向过程编程则更加适合简单的、流程性的程序设计。

面向对象的三大特性

面向对象编程(Object-Oriented Programming,简称OOP)的三大特性是封装、继承和多态。

1. 封装(Encapsulation):封装是指将数据和对数据的操作封装在一个类中,通过定义类的属性和方法来控制对数据的访问。封装可以隐藏内部实现细节,只暴露必要的接口给外部使用,提高了代码的可维护性和安全性。

2. 继承(Inheritance):继承是指一个类可以继承另一个类的属性和方法。通过继承,子类可以重用父类的代码,并且可以在不修改父类的情况下扩展或修改其功能。继承可以建立类之间的层次关系,提高代码的可重用性和扩展性。

3. 多态(Polymorphism):多态是指同一个方法可以根据不同的对象调用出不同的行为。多态通过方法的重写和方法的重载实现。方法的重写(Override)是指子类可以重写父类的方法,以实现自己的特定行为。方法的重载(Overload)是指在一个类中可以定义多个同名但参数列表不同的方法,根据传入的参数类型和个数来决定调用哪个方法。多态提高了代码的灵活性和可扩展性,使得程序可以根据实际情况做出不同的处理。

介绍一下c++面向对象中的封装

在C++中,封装是面向对象编程的一个重要概念。它指的是将数据和操作数据的函数(即方法)封装在一个单独的实体中,这个实体被称为类。封装的目的是将数据和方法组合在一起,形成一个独立的、可复用的模块,同时隐藏内部的实现细节,只暴露必要的接口给外部使用。

封装通过访问控制符来实现对类的成员的访问限制。C++中有三种访问控制符:public、private和protected。

- public:公有成员可以在类的内部和外部被访问。它们可以被类的对象直接访问,也可以被类的成员函数访问。
- private:私有成员只能在类的内部被访问。它们不能被类的对象直接访问,只能通过类的公有成员函数来访问。
- protected:受保护成员类似于私有成员,但可以在派生类中被访问。

封装的优点有:

1. 数据隐藏:封装可以隐藏类的内部实现细节,只暴露必要的接口给外部使用,提高了代码的安全性和可维护性。
2. 代码复用:封装将数据和方法组合在一起形成一个独立的模块,可以在不同的地方重复使用,提高了代码的复用性。
3. 简化接口:封装可以将复杂的内部实现封装起来,对外部提供简单的接口,降低了使用者的使用难度。
 

介绍一下c++面向对象中的继承

C++面向对象中的继承是指一个类可以从另一个类中继承属性和方法。被继承的类称为基类或父类,继承的类称为派生类或子类。继承可以使代码重用更加方便,同时也可以使代码更加易于维护和扩展。

在C++中,继承有三种类型:公有继承、私有继承和保护继承。公有继承是最常用的一种继承方式,它使得派生类可以访问基类的公有成员和方法,但不能访问基类的私有成员和方法。私有继承和保护继承则分别限制了派生类对基类成员的访问权限。

父类中所有非静态成员属性都会被子类继承下去

父类中私有属性 是被编译器隐藏了 因此访问不到 但是的确继承下去了

菱形继承:一个类被俩个类继承 这俩个又被一个类继承

继承的类型有哪些

C++的继承分为三种:
公有继承
保护继承
私有继承

C++多态怎么理解?

C++多态是指在面向对象编程中,同一个函数名可以有多种不同的实现方式,这些实现方式可以根据不同的对象类型进行调用,从而实现不同的行为。

C++多态的实现方式主要有两种:虚函数和模板函数。

虚函数是指在基类中声明一个函数为虚函数,在派生类中重写该函数,通过基类指针或引用调用该函数时,会根据实际指向的对象类型来调用相应的函数实现。

模板函数是指在函数定义时使用模板参数,可以根据不同的参数类型生成不同的函数实现,从而实现多态。

C++多态的优点是可以提高代码的可扩展性和可维护性,使得代码更加灵活和易于扩展。

类和对象的区别和联系

类是一种抽象的概念,它是对一类具有相同属性和行为的对象的描述。类定义了对象的属性和方法,它是创建对象的模板。类可以看作是一个蓝图或者是一个定义,它描述了对象应该具有的特征和行为。

对象是类的实例化,它是类的具体实体。对象是根据类的定义创建的,它具有类定义的属性和方法。每个对象都是独立的,它们可以有自己的状态和行为。

类和对象之间有以下联系和区别:

1. 关系:类是对象的抽象,对象是类的具体实例。类是对象的模板,而对象是类的具体化。

2. 属性和方法:类定义了对象的属性和方法,而对象具有类定义的属性和方法。类中的属性和方法是共享的,而对象的属性和方法是独立的。

3. 创建:类是在编码阶段定义的,而对象是在运行时创建的。

4. 多个对象:一个类可以创建多个对象,每个对象都是独立的,它们可以有自己的状态和行为。

5. 继承:类可以通过继承创建子类,子类继承了父类的属性和方法,并可以添加自己的属性和方法。

总结来说,类是对一类对象的抽象描述,而对象是类的具体实例。类定义了对象的属性和方法,而对象具有类定义的属性和方法。类和对象之间是一种抽象和具体的关系。

抽象类和接口的区别和联系如下:

1.定义方式:
   -抽象类: 使用`@abstract`注解声明,可以包含抽象方法和非抽象方法。
   -接口: 使用`@interface`注解声明,只包含抽象方法和常量。

2.使用场景:
   -抽象类: 用于描述一类对象的共同属性和方法,提供默认实现。可以用于单继承,也可以用于多继承。
   -接口: 用于描述对象的行为,只关注方法的定义,不关注实现。可以用于多继承。

3.成员变量:
   -抽象类: 可以包含成员变量。
   -接口: 只能包含常量。

4.方法:
   -抽象类: 可以包含抽象方法和非抽象方法。
   -接口: 只能包含抽象方法。

5.继承:
   -抽象类: 可以被继承。
   -接口: 只能被实现,不能被继承。

6.实现:
   -抽象类: 可以实现多个接口。
   -接口: 可以实现多个接口。

7.构造方法:
   -抽象类: 可以有构造方法。
   -接口: 不允许有构造方法。

8.成员的可见性:
   -抽象类: 成员可以是`public`、`protected`或`private`。
   -接口: 成员默认为`public`。

9.其他:
   -抽象类: 可以有静态方法和静态变量。
   -接口: 不允许有静态方法和静态变量。

总之,抽象类和接口都是用于实现抽象化的机制,但它们的设计目的和使用场景有所不同。抽象类更关注于类的实现,而接口更关注于行为的描述。

抽象类和接口是面向对象编程中的两个重要概念,它们都用于实现类的继承和多态性。下面是它们的区别和联系:

区别:
1. 定义方式:抽象类使用关键字"abstract"来定义,可以包含抽象方法和非抽象方法;接口使用关键字"interface"来定义,只能包含抽象方法和常量。
2. 实现方式:一个类只能继承一个抽象类,但可以实现多个接口。
3. 构造函数:抽象类可以有构造函数,而接口不能有构造函数。
4. 成员变量:抽象类可以有成员变量,而接口只能有常量。
5. 方法实现:抽象类中的抽象方法可以有实现,而接口中的抽象方法必须在实现类中实现。

联系:
1. 都可以用于实现类的继承和多态性。
2. 都不能被实例化,只能被继承或实现。抽象类的主要作用是为其子类提供一个通用的模板,子类需要实现抽象类中的抽象方法,才能被实例化。因此,抽象类只能被用作父类,不能直接被实例化。如果我们需要使用抽象类中的方法,可以通过创建一个实现了抽象类中所有抽象方法的子类来实现。这个子类可以被实例化,从而可以使用抽象类中的方法。
3. 都可以被用作类型,即可以作为方法的参数或返回值类型。

总结来说,抽象类更适合用于定义一些具有共同特征的类的基类,而接口更适合用于定义一些行为的规范,实现类可以根据需要实现多个接口。

重载,重写, 隐藏的区别

重载(Overloading)是指在同一个类中,可以定义多个同名的方法,但是这些方法的参数列表必须不同。重载的目的是为了提供更多的方法选择,以适应不同的参数类型或参数个数。

重写(Override,又叫覆盖)是指在子类中重新定义父类中已经存在的方法。重写的方法必须具有相同的方法名、参数列表和返回类型。重写(覆盖)是指派生类函数覆盖基类函数,通过重写,子类可以根据自己的需求来实现父类方法的不同行为。要求父类的该方法必须是虚函数或者纯虚函数virtual

重定义(Redefinition, 又叫隐藏)是指在子类中定义一个与父类中同名的方法,但是参数列表可以不同。重定义不同于重写,它是在子类中新增一个方法,与父类中的方法没有关联,实际上子类和父类中两个同名函数,不满足重写的条件,就是重定义。

(a)如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。(注意不要和重载混淆,重载也是参数不同的两个同名函数,但是重载是在一个类里面的)

(b)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字,此时,基类的函数被隐藏。(如果父类的成员和子类的成员属性名称相同,我们可以通过作用域操作符来显式的使用父类的成员,如果我们不使用作用域操作符,默认使用的是子类的成员属性。)

将派生类赋值给基类,包括将派生类对象赋值给基类对象(这种方法只会保留基类有的方法,派生类多出来的方法都会丢失)、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用)时,如果加上virtual变成虚函数(重写,覆盖),则运行子类的函数:如果不加上virtual(或者参数列表,返回值不同),则运行基类的函数:

#include <iostream>

class base {
public:
  virtual void test1()
{
    std::cout << "in base1" << std::endl;
  }
  void test2()
{
    std::cout << "in base2" << std::endl;
  }
};

class sub : public base {
public:
  void test1()
{
    std::cout << "in sub1" << std::endl;
  }
  void test2()
{
    std::cout << "in sub2" << std::endl;
  }
};

int main() {

  sub t2;
  base* t1 = &t2;
  std::cout << "virtual test1:" << std::endl;
  t1->test1();
  std::cout << "test2:" << std::endl;
  t1->test2();
}

// 输出
virtual test1:
in sub1
test2:
in base2

 当然如果没有向上转型,直接用的子类的指针,则一定都输出子类

int main() {

  sub t2;
  sub* t1 = &t2;
  std::cout << "virtual test1:" << std::endl;
  t1->test1();
  std::cout << "test2:" << std::endl;
  t1->test2();
}
// 输出
virtual test1:
in sub1
test2:
in sub2

重写和重载的区别
    (1)范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类中。

    (2)参数区别:重写与被重写的函数参数列表一定相同,重载和被重载的函数参数列表一定不同。

    (3)virtual的区别:重写的基类函数必须要有virtual修饰,重载函数和被重载函数可以被virtual修饰,也可以没有。

explicit作用: 关闭函数的类型自动转换(防止隐式转换)

空类占用内存空间:1字节

静态成员变量:

概念:成员变量和成员函数前加一个static,称为静态成员

目的:为了实现一个类的不同对象之间的数据和函数共享。

• 所有对象共享同一份数据

• 在编译阶段分配内存

• 类内的声明,类外初始化

静态成员函数:

a.所有对象共享同一个函数

b.静态成员函数只能访问静态成员变量

在C++中,类内的成员变量和成员函数分开存储、只有非静态成员变量才属于类的对象上

this指针:当形参和成员变量同名时,可用this指针来区分(解决同名冲突)、在类的非静态成员函数中返回对象本身,可使用return * this

2.内联函数

介绍一下内联函数

如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

    如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

    

内联函数的作用

引入内联函数的目的是为了解决程序中函数调用的效率问题,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。

内联函数的使用场合

首先使用inline函数可以完全取代表达式形式的宏定义。

    内联函数在C++类中应用最广的,应该是用来定义存取函数。我们定义的类中一般会把数据成员定义成私有的或者保护的,这样,外界就不能直接读写我们类成员的数据了。对于私有或者保护成员的读写就必须使用成员接口函数来进行。如果我们把这些读写成员函数定义成内联函数的话,将会获得比较好的效率。在类定义中的定义的函数都是内联函数,即使没有使用inline 说明符。

class A
{
 private:
  int nTest;
public:
  int readTest()
{
    return nTest;
  }
  void setTest(int i);
};
 
inline void A::setTest(int i)
{
  nTest = i;
};

类A的成员函数readTest()和setTest()都是inline函数。readTest()函数的定义体被放在类声明之中,因而readTest()自动转换成inline函数;setTest()函数的定义体在类声明之外,因此要加上inline关键字。

内联函数与宏有什么区别

    (1)内联函数在编译时展开,宏在预编译时展开。

    (2)在编译的时候,内联函数可以直接被镶嵌到目标代码中,而宏只是一个简单的文本替换。

    (3)内联函数可以完成诸如类型检测、语句是否正确等编译功能,宏就不能具有这样的功能。

    (4)宏不是函数,inline函数是函数。

    (5)宏在定义时要小心处理宏参数(一般情况是把参数用括号括起来),否则容易出现二义性。而内联函数定义时不会出现二义性。

为什么不把所有的函数都定义成内联函数

内联是以代码膨胀(复制)为代价的,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

以下情况不宜使用内联:

    (1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

    (2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

3.delete []和delete的区别

delete和delete[]都是用于释放动态分配的内存空间的操作符,但它们的使用场景和效果是不同的。

delete用于释放单个对象的内存空间,它会调用该对象的析构函数,然后释放该对象所占用的内存空间。

delete[]用于释放数组对象的内存空间,它会调用数组中每个元素的析构函数,然后释放整个数组所占用的内存空间。

如果使用delete释放数组对象的内存空间,会导致未定义行为,因为它只会释放数组中第一个元素所占用的内存空间,而不会释放整个数组所占用的内存空间。反之,如果使用delete[]释放单个对象的内存空间,也会导致未定义行为,因为它会调用一个不存在的析构函数。因此,使用delete和delete[]时要根据动态分配内存的方式来选择合适的操作符。

4.虚函数是用来干嘛的?虚函数机制怎么实现的?虚表指针在内存中的存放位置?

虚函数是用来实现多态性的一种机制。它允许在基类中声明一个虚函数,在派生类中进行重写,从而实现基类指针或引用调用派生类对象的函数时,能够根据实际对象的类型来调用相应的函数。

虚函数机制的实现通常是通过虚表(vtable)来实现的。每个包含虚函数的类都会有一个对应的虚表,虚表中存放着指向各个虚函数的函数指针。当对象被创建时,会在对象的内存布局中添加一个指向虚表的指针,通常称为虚表指针(vptr)。

虚表指针通常存放在对象的内存布局的最前面,即对象的起始位置。通过虚表指针,程序可以在运行时动态地确定对象的实际类型,并根据实际类型调用相应的虚函数。

5.C++为什么有指针还要引用

C++中指针和引用都是用来处理内存地址的,但它们有不同的用途和特点。

指针是一个变量,它存储了一个内存地址,可以通过解引用操作符(*)来访问该地址上的值。指针可以被重新赋值,也可以被赋值为NULL,因此它具有更大的灵活性和可变性。指针还可以进行算术运算,比如指针加减操作,这在某些场景下非常有用。

引用是一个别名,它是已经存在的变量的别名,不占用额外的内存空间。引用不能被重新赋值,一旦被初始化,就一直指向同一个变量。引用通常用于函数参数传递和返回值,可以避免拷贝大量的数据,提高程序的效率。

因此,指针和引用各有其优点和适用场景,需要根据具体情况选择使用哪种方式。

6.vector实现的机制是怎么样的?

Vector是C++ STL中的一个容器,它是一个动态数组,可以根据需要自动扩展或缩小。Vector的实现机制是通过一个连续的内存块来存储元素,当元素数量超过当前内存块的大小时,会自动申请更大的内存块,并将原有元素复制到新的内存块中,然后释放原有内存块。

Vector的内部实现主要包括以下几个方面:

1. 内存分配:Vector使用动态内存分配,当元素数量超过当前内存块的大小时,会自动申请更大的内存块,并将原有元素复制到新的内存块中,然后释放原有内存块。

2. 元素访问:Vector的元素可以通过下标访问,也可以通过迭代器访问。Vector的下标访问是通过指针偏移实现的,而迭代器访问是通过指针实现的。

3. 插入和删除:Vector的插入和删除操作会导致元素的移动,因此效率较低。在插入和删除元素时,Vector会将后面的元素向后移动或向前移动,以保证元素的连续性。

4. 内存管理:Vector的内存管理是由STL库自动完成的,用户不需要手动管理内存。Vector会自动分配和释放内存,以保证内存的正确使用。

7.迭代器有了解吗?讲解一下你的理解

C++迭代器是一种用于遍历容器中元素的对象,它提供了一种通用的方式来访问容器中的元素,而不需要了解容器的内部实现细节。迭代器可以被看作是一种指针,它指向容器中的某个元素,并提供了一些操作来访问和操作该元素。

迭代器可以分为五种类型:输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。这些迭代器类型的区别在于它们支持的操作不同。输入迭代器和输出迭代器只支持单向遍历,前向迭代器支持单向遍历和单个元素的插入和删除,双向迭代器支持双向遍历和单个元素的插入和删除,而随机访问迭代器则支持随机访问和所有元素的插入和删除。

使用迭代器可以方便地遍历容器中的元素,而不需要了解容器的内部实现细节。迭代器还可以用于算法中,例如排序、查找、拷贝等操作。在使用迭代器时,需要注意迭代器的有效性,避免使用无效的迭代器访问容器中的元素。

8.介绍一下C++中深拷贝和浅拷贝的区别

浅拷贝是指将一个对象的值复制到另一个对象,这两个对象将共享相同的内存地址。当其中一个对象修改了共享的数据时,另一个对象也会受到影响。这种拷贝方式适用于简单的数据类型,如整数、浮点数等。

深拷贝是指创建一个新的对象,并将原始对象的所有成员变量的值复制到新对象中。这意味着新对象拥有独立的内存空间,对新对象的修改不会影响原始对象。这种拷贝方式适用于包含动态分配内存的对象,如指针、数组、字符串等。

9.如果系统中只有10K内存,而我要分配12K,能分配成功吗?如果能,那么将分配到的区域用memset进行初始化,会成功吗?   

如果系统中只有10K内存,而你要分配12K,那么无法成功分配。因为内存分配需要满足连续的内存空间,而系统中只有10K的内存空间是不足以满足12K的需求的。

即使你能够分配到12K的内存空间,使用memset进行初始化也可能会失败。因为memset函数需要访问和修改内存空间,如果你超出了系统分配给你的内存空间,就会导致访问越界,可能会引发程序崩溃或其他不可预测的行为。

10.C++11有哪些新特性

1. 自动类型推导:使用 auto 关键字可以让编译器自动推导变量的类型。

2. Lambda 表达式:Lambda 表达式是一种匿名函数,可以在代码中直接定义和使用。

3. 右值引用和移动语义:引入了右值引用和移动语义,可以实现高效的对象移动和转移。

4. 智能指针:引入了 unique_ptr、shared_ptr 和 weak_ptr 等智能指针,可以自动管理动态分配的内存。

5. 范围 for 循环:引入了范围 for 循环,可以方便地遍历容器和数组。

6. 初始化列表:引入了初始化列表,可以方便地初始化对象和容器。

7. constexpr 函数和变量:引入了 constexpr 关键字,可以在编译时计算常量表达式。

8. 线程支持库:引入了线程支持库,可以方便地创建和管理线程。

9. 新的容器和算法:引入了 unordered_map、unordered_set、array、tuple 等新的容器和算法。

11.c++中auto是怎么实现的

在 C++11 中,auto 关键字被引入用于自动类型推导。auto 关键字可以让编译器自动推导变量的类型,从而简化代码的书写。

auto 的实现原理是通过编译器在编译时进行类型推导,根据变量的初始化表达式来推导变量的类型。编译器会根据初始化表达式的类型来推导变量的类型,并将其替换为实际的类型。

智能指针了解吗

智能指针是一种 C++ 中的类模板,它可以自动管理动态分配的内存,避免内存泄漏和悬挂指针等问题。智能指针的主要作用是在对象生命周期结束时自动释放内存,从而避免手动释放内存的繁琐和容易出错的过程。

智能指针有多种类型,包括 unique_ptr、shared_ptr、weak_ptr 等。其中,unique_ptr 是独占式智能指针,它只能有一个指针指向同一块内存,当 unique_ptr 被销毁时,它所指向的内存也会被自动释放。shared_ptr 是共享式智能指针,它可以有多个指针指向同一块内存,当最后一个 shared_ptr 被销毁时,它所指向的内存才会被自动释放。weak_ptr 是一种弱引用智能指针,它可以指向 shared_ptr 所管理的内存,但不会增加内存的引用计数,因此不会影响内存的释放。

12.什么是右值,右值引用

在 C++ 中,表达式可以分为左值和右值两种类型。左值是指可以取地址的表达式,即表达式的结果可以被赋值给一个变量或者指针。右值是指不能取地址的表达式,即表达式的结果不能被赋值给一个变量或者指针。

右值引用是 C++11 引入的一种新的引用类型,右值引用使用 && 符号表示,可以绑定到临时对象(右值)或将要销毁的对象,用于实现移动语义和完美转发。

右值引用的主要作用有两个: 1. 移动语义:右值引用可以绑定到临时对象,这些临时对象在表达式结束后将被销毁。通过使用移动构造函数和移动赋值运算符,可以将临时对象的资源(如堆内存)转移到新的对象上,避免了不必要的内存拷贝,提高了性能。 2. 完美转发:右值引用还可以用于实现完美转发,即在函数调用中将参数以原始的形式传递给其他函数,避免了多次拷贝和类型转换。通过使用模板和右值引用,可以实现通用的转发函数,将参数按照原始类型和值类别转发给其他函数。

13.shared_ptr使用中可能存在的问题

1. 循环引用:当多个 shared_ptr 相互引用时,可能会导致循环引用的问题。这会导致内存泄漏,因为这些对象的引用计数永远不会降为零,无法释放内存。为了解决这个问题,可以使用 weak_ptr 来打破循环引用。

2. 线程安全性:shared_ptr 的引用计数是线程安全的,但是对象本身的访问并不是线程安全的。如果多个线程同时访问同一个 shared_ptr 所管理的对象,可能会导致竞争条件和数据不一致的问题。为了解决这个问题,可以使用互斥锁或原子操作来保护共享资源。

3. 不适用于部分场景:shared_ptr 适用于多个指针共享同一块内存的场景,但并不适用于所有情况。例如,当需要在多个线程中传递指针所有权时,shared_ptr 并不是最佳选择,因为它的引用计数需要进行原子操作,可能会影响性能。

4. 对象销毁时机不确定:由于 shared_ptr 是通过引用计数来管理内存的,当最后一个 shared_ptr 被销毁时,对象的析构函数会被调用,但具体的销毁时机是不确定的。这可能会导致一些资源的延迟释放问题,例如文件句柄、数据库连接等。

5. 对象的大小:shared_ptr 内部需要维护引用计数和指向对象的指针,因此会增加对象的大小。如果需要管理大量的小对象,使用 shared_ptr 可能会导致内存开销较大。

14.weak_ptr是用来干嘛的?怎么保证用weak_ptr不会崩溃?

weak_ptr是用来解决shared_ptr的循环引用问题的。当两个或多个对象相互引用时,如果使用shared_ptr,会导致它们之间形成循环引用,导致内存泄漏。而使用weak_ptr可以避免这种情况。

weak_ptr是一种弱引用,它不会增加引用计数,也不会阻止对象被销毁。当需要使用对象时,可以通过lock()方法获取一个shared_ptr,如果对象已经被销毁,则返回一个空的shared_ptr。

为了保证使用weak_ptr不会崩溃,需要注意以下几点:

1. 在使用weak_ptr之前,需要先判断它是否已经过期,即是否为空。

2. 在使用lock()方法获取shared_ptr时,需要先判断返回的shared_ptr是否为空,以避免访问已经被销毁的对象。

3. 在使用weak_ptr时,需要保证其对应的shared_ptr对象还存在,否则会导致程序崩溃。

4. 在使用weak_ptr时,需要注意避免循环引用的问题,否则会导致内存泄漏。
 

15.constexpr 函数和变量的作用,与const的区别

constexpr是C++11引入的关键字,用于声明函数或变量为编译时常量。它的作用是告诉编译器,在编译时就可以计算出函数或变量的值,而不需要在运行时进行计算。

对于constexpr函数,它必须满足以下条件:
1. 函数体内只能包含一条return语句。
2. 函数的参数和返回值类型必须是字面值类型(literal type)。
3. 函数体内只能包含能在编译时计算的语句,如赋值、条件语句等。

constexpr变量则是指在编译时就可以确定其值的常量。它的声明方式与普通变量相同,只需在声明前加上constexpr关键字。

与const的区别在于,const关键字用于声明运行时常量,而constexpr关键字用于声明编译时常量。const变量的值可以在运行时确定,而constexpr变量的值必须在编译时确定。constexpr函数和变量在编译时会被计算出结果,并在编译过程中被替换为其计算结果,从而提高程序的性能。

总结起来,constexpr用于声明编译时常量,而const用于声明运行时常量。constexpr函数和变量在编译时计算,而const变量在运行时计算。

15.介绍一下Lambda 表达式,他有什么用

Lambda表达式是一种匿名函数,它可以作为参数传递给其他函数或方法。Lambda表达式可以简化代码,使代码更加简洁和易于阅读。

lamada表达式中mutable 关键字有什么用

mutable关键字用于指定lambda表达式是否可以修改其捕获的变量。

当我们在lambda表达式中捕获一个变量时,默认情况下,该变量是只读的,即不能在lambda表达式内部修改它的值。这是因为lambda表达式默认是const的。

然而,有时我们可能需要在lambda表达式内部修改捕获的变量的值。这时就可以使用mutable关键字。通过在lambda表达式的参数列表后面加上mutable关键字,我们可以将捕获的变量声明为可修改的,在lambda表达式外部,变量的值仍然保持不变。mutable关键字只对捕获的变量有效,对于lambda表达式内部的局部变量是无效的,mutable 关键字可以在不修改对象的情况下,在表达式中创建新的可变对象。

16.当初始化列表时,被初始化的顺序是声明的顺序不是列表顺序。

17.new与malloc的区别


特性:new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
参数:使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。

返回类型:new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

分配失败: new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。

特性:new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。

参数:使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。

18.C++与C的区别


C是面向过程的语言,而C++是面向对象的语言

• C只能写面向过程的代码,而C++既可以写面向过程的代码,也可以实现面向对象的代码

• C和强制类型转换上也不一样 const_cast static_cast reinterpret_cast dynamic_cast

• C和C++的输入输出方式也不一样

• C++引⼊入 new/delete 运算符,取代了了C中的 malloc/free 库函数;

• C++引⼊入引⽤用的概念

• C++引⼊入类的概念

• C++引⼊入函数重载的特性

19.前置++与后置++区别

后置++中tmp是一个临时对象,会造成一次构造函数和一次析构函数的额外开销
效率高:前置++,不产生临时对象

20.友元 friend

友元:让一个函数或者类,访问另一个类的私有成员(打破封装)

三种实现:

• 全局函数做友元

• 类做友元(友元类)

• 成员函数做友元

21.虚函数与纯虚函数的区别

虚函数和纯虚函数是面向对象编程中的概念,用于实现多态性。

虚函数是在基类中声明的函数,可以被派生类重写。(通过运行阶段才能知道需要调用那个对象)当通过基类指针或引用调用虚函数时,实际调用的是派生类中的重写函数。虚函数通过在基类中使用关键字"virtual"来声明,派生类中可以选择是否重写虚函数。

纯虚函数是在基类中声明的没有实际实现的函数,它只是作为接口存在,要求派生类必须实现该函数。纯虚函数通过在基类中使用关键字"virtual"和"= 0"来声明,派生类必须实现纯虚函数,否则派生类也会成为抽象类。

区别如下:
1. 虚函数可以有实现,而纯虚函数没有实现。
2. 虚函数可以被派生类选择性地重写,而纯虚函数必须在派生类中实现。
3. 含有纯虚函数的类称为抽象类,不能实例化对象,只能作为基类使用。而含有虚函数的类可以实例化对象。
4. 如果一个类中包含了纯虚函数,那么它就是一个抽象类,派生类必须实现纯虚函数才能被实例化。
5. 虚函数可以有默认实现,而纯虚函数没有默认实现。

总结来说,虚函数是可以有实现的,而纯虚函数没有实现,必须在派生类中实现。虚函数可以选择性地重写,而纯虚函数必须在派生类中实现。

22.虚析构和纯虚析构

虚析构作用:使用父类指针释放子类对象时可以让子类的析构函数和父类的析构函数同时被调用到。

虚析构和纯虚析构共性:

• 可以解决父类指针释放子类对象

• 都需要具体的函数实现

实现

虚析构语法:virtual ~类名(){};

纯虚析构语法:virtual ~类名() = 0;

纯虚析构实现类名::~类名(){}

模板相关

C++模板技术是一种泛型编程技术,它允许程序员编写通用的代码,可以用于多种数据类型和数据结构。模板是一种代码生成机制,可以根据需要自动生成不同类型的代码。 C++模板技术包括函数模板和类模板。函数模板允许程序员编写一个通用的函数,可以用于多种数据类型,例如排序、查找等操作。类模板允许程序员编写一个通用的类,可以用于多种数据结构,例如链表、堆、树等。

C++模板类是一种通用的类,可以用于创建多种不同类型的对象。它们是一种代码重用的方式,可以让程序员编写一次代码,然后在不同的情况下使用它们来创建不同类型的对象。模板类可以在编译时根据不同的类型参数生成不同的类实例,从而实现代码的复用和泛化。模板类可以定义在头文件中,并且可以包含成员函数、静态成员变量和类型别名。模板类的定义通常以template关键字开始,后面跟着类的声明。在类的声明中,可以使用模板参数来表示类型或值。模板参数可以是类型参数,也可以是非类型参数。类型参数可以用来定义类的成员变量和成员函数的返回类型,非类型参数可以用来定义类的常量成员变量和成员函数的参数。在使用模板类时,需要提供实际的类型或值作为模板参数,这些参数将被用于实例化模板类,生成具体的类定义。

为什么使用模板类?


模板类允许你编写通用的数据结构和算法,可以适用于不同的数据类型。
它提高了代码的重用性,因为你可以使用相同的类定义来处理不同类型的数据。
C++ 标准库中的许多容器(例如 std::vector、std::list)和算法(例如 std::sort)都是使用模板类实现的。

模板类的声明和定义

  • 模板类的声明以 template 关键字开始,后跟一个模板参数列表,使用 < > 括起来,通常包括类型参数。

  • 类的定义使用模板参数来指定类的成员的类型。

  • 类的成员函数可以在类内部定义,也可以在类外部定义。通常,成员函数的定义需要在类的模板声明之后提供。

template <typename T>
class MyTemplateClass {
public:
    MyTemplateClass(T data);
    T getData();
private:
    T data_;
};

template <typename T>
MyTemplateClass<T>::MyTemplateClass(T data) : data_(data) {}

template <typename T>
T MyTemplateClass<T>::getData() {
    return data_;
}

模板类的使用

  • 使用模板类时,你需要提供具体的数据类型,以实例化模板类。

  • 这可以通过提供模板参数来实现。例如,MyTemplateClass<int> 实例化了一个处理整数的模板类。

  • 可以创建多个不同数据类型的模板类实例。

MyTemplateClass<int> intInstance(42);
MyTemplateClass<double> doubleInstance(3.14);

非类型模板参数

  • 除了类型参数,模板还支持非类型模板参数,这些参数可以是常数值、枚举或指针。

  • 非类型参数可以用于在编译时确定模板的一些属性。

template <int Size>
class FixedArray {
public:
    int GetSize() { return Size; }
    // ...
};

模板的特化和偏特化:

  • 可以对特定类型的参数创建特化版本的模板类。

  • 偏特化允许你对模板参数的某些属性进行特化,以满足不同情况的需求。

模板类的编译和实例化

  • C++ 中的模板类是在编译时实例化的。

  • 编译器根据使用模板类的上下文为特定类型生成实例化的类。

  • 这意味着只需提供一次模板定义,可以在不同地方和不同类型的数据上使用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值