面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,它将数据和操作数据的方法组合成一个对象,以此来描述现实世界中的事物和概念。在面向对象编程中,程序被组织成一个个对象,每个对象都有自己的属性和方法,对象之间通过消息传递来进行通信和协作。
与面向过程编程相比,面向对象编程更加注重数据的封装和抽象,使得程序更加易于维护和扩展。面向过程编程则更加注重流程和算法的设计,程序的执行过程是由一系列的函数调用和数据传递组成的。
总的来说,面向对象编程更加适合大型复杂的软件系统的开发,而面向过程编程则更加适合简单的、流程性的程序设计。
面向对象编程(Object-Oriented Programming,简称OOP)的三大特性是封装、继承和多态。
1. 封装(Encapsulation):封装是指将数据和对数据的操作封装在一个类中,通过定义类的属性和方法来控制对数据的访问。封装可以隐藏内部实现细节,只暴露必要的接口给外部使用,提高了代码的可维护性和安全性。
2. 继承(Inheritance):继承是指一个类可以继承另一个类的属性和方法。通过继承,子类可以重用父类的代码,并且可以在不修改父类的情况下扩展或修改其功能。继承可以建立类之间的层次关系,提高代码的可重用性和扩展性。
3. 多态(Polymorphism):多态是指同一个方法可以根据不同的对象调用出不同的行为。多态通过方法的重写和方法的重载实现。方法的重写(Override)是指子类可以重写父类的方法,以实现自己的特定行为。方法的重载(Overload)是指在一个类中可以定义多个同名但参数列表不同的方法,根据传入的参数类型和个数来决定调用哪个方法。多态提高了代码的灵活性和可扩展性,使得程序可以根据实际情况做出不同的处理。
在C++中,封装是面向对象编程的一个重要概念。它指的是将数据和操作数据的函数(即方法)封装在一个单独的实体中,这个实体被称为类。封装的目的是将数据和方法组合在一起,形成一个独立的、可复用的模块,同时隐藏内部的实现细节,只暴露必要的接口给外部使用。
封装通过访问控制符来实现对类的成员的访问限制。C++中有三种访问控制符:public、private和protected。
- public:公有成员可以在类的内部和外部被访问。它们可以被类的对象直接访问,也可以被类的成员函数访问。
- private:私有成员只能在类的内部被访问。它们不能被类的对象直接访问,只能通过类的公有成员函数来访问。
- protected:受保护成员类似于私有成员,但可以在派生类中被访问。
1. 数据隐藏:封装可以隐藏类的内部实现细节,只暴露必要的接口给外部使用,提高了代码的安全性和可维护性。
2. 代码复用:封装将数据和方法组合在一起形成一个独立的模块,可以在不同的地方重复使用,提高了代码的复用性。
3. 简化接口:封装可以将复杂的内部实现封装起来,对外部提供简单的接口,降低了使用者的使用难度。
C++面向对象中的继承是指一个类可以从另一个类中继承属性和方法。被继承的类称为基类或父类,继承的类称为派生类或子类。继承可以使代码重用更加方便,同时也可以使代码更加易于维护和扩展。
在C++中,继承有三种类型:公有继承、私有继承和保护继承。公有继承是最常用的一种继承方式,它使得派生类可以访问基类的公有成员和方法,但不能访问基类的私有成员和方法。私有继承和保护继承则分别限制了派生类对基类成员的访问权限。
父类中所有非静态成员属性都会被子类继承下去
父类中私有属性 是被编译器隐藏了 因此访问不到 但是的确继承下去了
菱形继承:一个类被俩个类继承 这俩个又被一个类继承
C++的继承分为三种:
公有继承
保护继承
私有继承
C++多态是指在面向对象编程中,同一个函数名可以有多种不同的实现方式,这些实现方式可以根据不同的对象类型进行调用,从而实现不同的行为。
C++多态的实现方式主要有两种:虚函数和模板函数。
虚函数是指在基类中声明一个函数为虚函数,在派生类中重写该函数,通过基类指针或引用调用该函数时,会根据实际指向的对象类型来调用相应的函数实现。
模板函数是指在函数定义时使用模板参数,可以根据不同的参数类型生成不同的函数实现,从而实现多态。
C++多态的优点是可以提高代码的可扩展性和可维护性,使得代码更加灵活和易于扩展。
2.重载,重写, 隐藏的区别
重载:作用域相同函数名相同
重写:父类的方法,子类重写,要求父类的该方法必须是虚函数或者纯虚函数virtual
隐藏:父类的方法,子类重写,要求父类的该方法不能被virtual修饰
explicit作用: 关闭函数的类型自动转换(防止隐式转换)
空类占用内存空间:1字节
概念:成员变量和成员函数前加一个static,称为静态成员
目的:为了实现一个类的不同对象之间的数据和函数共享。
• 所有对象共享同一份数据
• 在编译阶段分配内存
• 类内的声明,类外初始化
a.所有对象共享同一个函数
b.静态成员函数只能访问静态成员变量
在C++中,类内的成员变量和成员函数分开存储、只有非静态成员变量才属于类的对象上
this指针:当形参和成员变量同名时,可用this指针来区分(解决同名冲突)、在类的非静态成员函数中返回对象本身,可使用return * this
delete和delete[]都是用于释放动态分配的内存空间的操作符,但它们的使用场景和效果是不同的。
delete用于释放单个对象的内存空间,它会调用该对象的析构函数,然后释放该对象所占用的内存空间。
delete[]用于释放数组对象的内存空间,它会调用数组中每个元素的析构函数,然后释放整个数组所占用的内存空间。
如果使用delete释放数组对象的内存空间,会导致未定义行为,因为它只会释放数组中第一个元素所占用的内存空间,而不会释放整个数组所占用的内存空间。反之,如果使用delete[]释放单个对象的内存空间,也会导致未定义行为,因为它会调用一个不存在的析构函数。因此,使用delete和delete[]时要根据动态分配内存的方式来选择合适的操作符。
虚函数是用来实现多态性的一种机制。它允许在基类中声明一个虚函数,在派生类中进行重写,从而实现基类指针或引用调用派生类对象的函数时,能够根据实际对象的类型来调用相应的函数。
虚函数机制的实现通常是通过虚表(vtable)来实现的。每个包含虚函数的类都会有一个对应的虚表,虚表中存放着指向各个虚函数的函数指针。当对象被创建时,会在对象的内存布局中添加一个指向虚表的指针,通常称为虚表指针(vptr)。
虚表指针通常存放在对象的内存布局的最前面,即对象的起始位置。通过虚表指针,程序可以在运行时动态地确定对象的实际类型,并根据实际类型调用相应的虚函数。
C++中指针和引用都是用来处理内存地址的,但它们有不同的用途和特点。
指针是一个变量,它存储了一个内存地址,可以通过解引用操作符(*)来访问该地址上的值。指针可以被重新赋值,也可以被赋值为NULL,因此它具有更大的灵活性和可变性。指针还可以进行算术运算,比如指针加减操作,这在某些场景下非常有用。
引用是一个别名,它是已经存在的变量的别名,不占用额外的内存空间。引用不能被重新赋值,一旦被初始化,就一直指向同一个变量。引用通常用于函数参数传递和返回值,可以避免拷贝大量的数据,提高程序的效率。
因此,指针和引用各有其优点和适用场景,需要根据具体情况选择使用哪种方式。
Vector是C++ STL中的一个容器,它是一个动态数组,可以根据需要自动扩展或缩小。Vector的实现机制是通过一个连续的内存块来存储元素,当元素数量超过当前内存块的大小时,会自动申请更大的内存块,并将原有元素复制到新的内存块中,然后释放原有内存块。
Vector的内部实现主要包括以下几个方面:
1. 内存分配:Vector使用动态内存分配,当元素数量超过当前内存块的大小时,会自动申请更大的内存块,并将原有元素复制到新的内存块中,然后释放原有内存块。
2. 元素访问:Vector的元素可以通过下标访问,也可以通过迭代器访问。Vector的下标访问是通过指针偏移实现的,而迭代器访问是通过指针实现的。
3. 插入和删除:Vector的插入和删除操作会导致元素的移动,因此效率较低。在插入和删除元素时,Vector会将后面的元素向后移动或向前移动,以保证元素的连续性。
4. 内存管理:Vector的内存管理是由STL库自动完成的,用户不需要手动管理内存。Vector会自动分配和释放内存,以保证内存的正确使用。
C++迭代器是一种用于遍历容器中元素的对象,它提供了一种通用的方式来访问容器中的元素,而不需要了解容器的内部实现细节。迭代器可以被看作是一种指针,它指向容器中的某个元素,并提供了一些操作来访问和操作该元素。
迭代器可以分为五种类型:输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。这些迭代器类型的区别在于它们支持的操作不同。输入迭代器和输出迭代器只支持单向遍历,前向迭代器支持单向遍历和单个元素的插入和删除,双向迭代器支持双向遍历和单个元素的插入和删除,而随机访问迭代器则支持随机访问和所有元素的插入和删除。
使用迭代器可以方便地遍历容器中的元素,而不需要了解容器的内部实现细节。迭代器还可以用于算法中,例如排序、查找、拷贝等操作。在使用迭代器时,需要注意迭代器的有效性,避免使用无效的迭代器访问容器中的元素。
浅拷贝是指将一个对象的值复制到另一个对象,这两个对象将共享相同的内存地址。当其中一个对象修改了共享的数据时,另一个对象也会受到影响。这种拷贝方式适用于简单的数据类型,如整数、浮点数等。
深拷贝是指创建一个新的对象,并将原始对象的所有成员变量的值复制到新对象中。这意味着新对象拥有独立的内存空间,对新对象的修改不会影响原始对象。这种拷贝方式适用于包含动态分配内存的对象,如指针、数组、字符串等。
如果系统中只有10K内存,而你要分配12K,那么无法成功分配。因为内存分配需要满足连续的内存空间,而系统中只有10K的内存空间是不足以满足12K的需求的。
即使你能够分配到12K的内存空间,使用memset进行初始化也可能会失败。因为memset函数需要访问和修改内存空间,如果你超出了系统分配给你的内存空间,就会导致访问越界,可能会引发程序崩溃或其他不可预测的行为。
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 等新的容器和算法。
在 C++11 中,auto 关键字被引入用于自动类型推导。auto 关键字可以让编译器自动推导变量的类型,从而简化代码的书写。
auto 的实现原理是通过编译器在编译时进行类型推导,根据变量的初始化表达式来推导变量的类型。编译器会根据初始化表达式的类型来推导变量的类型,并将其替换为实际的类型。
智能指针了解吗
智能指针是一种 C++ 中的类模板,它可以自动管理动态分配的内存,避免内存泄漏和悬挂指针等问题。智能指针的主要作用是在对象生命周期结束时自动释放内存,从而避免手动释放内存的繁琐和容易出错的过程。
智能指针有多种类型,包括 unique_ptr、shared_ptr、weak_ptr 等。其中,unique_ptr 是独占式智能指针,它只能有一个指针指向同一块内存,当 unique_ptr 被销毁时,它所指向的内存也会被自动释放。shared_ptr 是共享式智能指针,它可以有多个指针指向同一块内存,当最后一个 shared_ptr 被销毁时,它所指向的内存才会被自动释放。weak_ptr 是一种弱引用智能指针,它可以指向 shared_ptr 所管理的内存,但不会增加内存的引用计数,因此不会影响内存的释放。
在 C++ 中,表达式可以分为左值和右值两种类型。左值是指可以取地址的表达式,即表达式的结果可以被赋值给一个变量或者指针。右值是指不能取地址的表达式,即表达式的结果不能被赋值给一个变量或者指针。
右值引用是 C++11 引入的一种新的引用类型,右值引用使用 && 符号表示,可以绑定到临时对象(右值)或将要销毁的对象,用于实现移动语义和完美转发。
右值引用的主要作用有两个: 1. 移动语义:右值引用可以绑定到临时对象,这些临时对象在表达式结束后将被销毁。通过使用移动构造函数和移动赋值运算符,可以将临时对象的资源(如堆内存)转移到新的对象上,避免了不必要的内存拷贝,提高了性能。 2. 完美转发:右值引用还可以用于实现完美转发,即在函数调用中将参数以原始的形式传递给其他函数,避免了多次拷贝和类型转换。通过使用模板和右值引用,可以实现通用的转发函数,将参数按照原始类型和值类别转发给其他函数。
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 可能会导致内存开销较大。
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时,需要注意避免循环引用的问题,否则会导致内存泄漏。
constexpr是C++11引入的关键字,用于声明函数或变量为编译时常量。它的作用是告诉编译器,在编译时就可以计算出函数或变量的值,而不需要在运行时进行计算。
对于constexpr函数,它必须满足以下条件:
1. 函数体内只能包含一条return语句。
2. 函数的参数和返回值类型必须是字面值类型(literal type)。
3. 函数体内只能包含能在编译时计算的语句,如赋值、条件语句等。
constexpr变量则是指在编译时就可以确定其值的常量。它的声明方式与普通变量相同,只需在声明前加上constexpr关键字。
与const的区别在于,const关键字用于声明运行时常量,而constexpr关键字用于声明编译时常量。const变量的值可以在运行时确定,而constexpr变量的值必须在编译时确定。constexpr函数和变量在编译时会被计算出结果,并在编译过程中被替换为其计算结果,从而提高程序的性能。
总结起来,constexpr用于声明编译时常量,而const用于声明运行时常量。constexpr函数和变量在编译时计算,而const变量在运行时计算。
Lambda表达式是一种匿名函数,它可以作为参数传递给其他函数或方法。Lambda表达式可以简化代码,使代码更加简洁和易于阅读。
lamada表达式中mutable 关键字有什么用
mutable关键字用于指定lambda表达式是否可以修改其捕获的变量。
当我们在lambda表达式中捕获一个变量时,默认情况下,该变量是只读的,即不能在lambda表达式内部修改它的值。这是因为lambda表达式默认是const的。
然而,有时我们可能需要在lambda表达式内部修改捕获的变量的值。这时就可以使用mutable关键字。通过在lambda表达式的参数列表后面加上mutable关键字,我们可以将捕获的变量声明为可修改的,在lambda表达式外部,变量的值仍然保持不变。mutable关键字只对捕获的变量有效,对于lambda表达式内部的局部变量是无效的,mutable 关键字可以在不修改对象的情况下,在表达式中创建新的可变对象。
特性:new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
参数:使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
返回类型:new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
分配失败: new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
特性:new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
参数:使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
C是面向过程的语言,而C++是面向对象的语言
• C只能写面向过程的代码,而C++既可以写面向过程的代码,也可以实现面向对象的代码
• C和强制类型转换上也不一样 const_cast static_cast reinterpret_cast dynamic_cast
• C和C++的输入输出方式也不一样
• C++引⼊入 new/delete 运算符,取代了了C中的 malloc/free 库函数;
• C++引⼊入引⽤用的概念
• C++引⼊入类的概念
• C++引⼊入函数重载的特性
后置++中tmp是一个临时对象,会造成一次构造函数和一次析构函数的额外开销
效率高:前置++,不产生临时对象
友元:让一个函数或者类,访问另一个类的私有成员(打破封装)
三种实现:
• 全局函数做友元
• 类做友元(友元类)
• 成员函数做友元
虚函数和纯虚函数是面向对象编程中的概念,用于实现多态性。
虚函数是在基类中声明的函数,可以被派生类重写。(通过运行阶段才能知道需要调用那个对象)当通过基类指针或引用调用虚函数时,实际调用的是派生类中的重写函数。虚函数通过在基类中使用关键字"virtual"来声明,派生类中可以选择是否重写虚函数。
纯虚函数是在基类中声明的没有实际实现的函数,它只是作为接口存在,要求派生类必须实现该函数。纯虚函数通过在基类中使用关键字"virtual"和"= 0"来声明,派生类必须实现纯虚函数,否则派生类也会成为抽象类。
区别如下:
1. 虚函数可以有实现,而纯虚函数没有实现。
2. 虚函数可以被派生类选择性地重写,而纯虚函数必须在派生类中实现。
3. 含有纯虚函数的类称为抽象类,不能实例化对象,只能作为基类使用。而含有虚函数的类可以实例化对象。
4. 如果一个类中包含了纯虚函数,那么它就是一个抽象类,派生类必须实现纯虚函数才能被实例化。
5. 虚函数可以有默认实现,而纯虚函数没有默认实现。
总结来说,虚函数是可以有实现的,而纯虚函数没有实现,必须在派生类中实现。虚函数可以选择性地重写,而纯虚函数必须在派生类中实现。
虚析构作用:使用父类指针释放子类对象时可以让子类的析构函数和父类的析构函数同时被调用到。
• 可以解决父类指针释放子类对象
• 都需要具体的函数实现
虚析构语法:virtual ~类名(){};
纯虚析构语法:virtual ~类名() = 0;
纯虚析构实现类名::~类名(){}