C++基础概念复习
前言
本篇文章作基础复习用,主要是在C++学习中遇到的概念总结,后续会继续补充。如有不足,请前辈指出,万分感谢。
1、什么是封装,有何优点,在C++中如何体现封装这一特性?
封装是面向对象编程(OOP)中的一个重要概念。 它将数据(成员变量)和操作这些数据的函数(成员函数)捆绑在一起,形成一个类。并且对外部隐藏了类的内部实现细节,只提供一些公共的接口来访问和操作类中的数据。
例如,把汽车看成一个类,驾驶员不需要知道发动机是如何工作的(内部细节),只需要知道通过操作方向盘、油门、刹车等接口来驾驶汽车。
优点: 数据隐藏和安全性,代码的可维护性和可修改性,提高代码的复用性。
在C++中的实现: C++ 通过访问修饰符来实现封装。
主要有public(公共的)、private(私有的)和protected(受保护的)三种。
(补充:继承具有传递性,继承不具有对称性)
2、什么是虚基类?有何作用?
语法上,在继承方式前加上virtual关键字
来声明虚基类
例如,假设有类A,类B和类C都虚继承自A,然后类D又同时继承自B和C。此时类A就是类B和类C 的虚基类。
作用: 多重继承会出现菱形继承问题会导致二义性
,如果A不是虚基类,那么在D中会有两份A的副本。使用虚基类可以避免这种情况,确保基类A在派生类B、C中只有一个实例。
3、什么是友元函数?友元函数的作用是什么?它是否破坏了封装性?
友元函数的定义:
在 C++ 中,友元函数是一种定义在类外部,但可以访问类的私有和受保护成员的函数。
通过在类中使用friend关键字
来声明友元函数,并且友元函数在类中声明时的属性是public
。
作用:
增强灵活性
(有时候,我们可能需要一个外部函数来访问类的私有成员
,以实现一些特定的功能。例如,在操作符重载中
,当我们想让某个操作符能够直接访问类的私有成员进行运算时,友元函数就很有用。)
方便实现不同类之间的交互
(当多个类之间需要紧密协作,并且需要互相访问对方的私有数据来完成某个功能时,友元函数可以提供一种方便的途径。)
是否破坏封装性:
友元打破了类的封装性,使得类的非成员函数可以访问类的私有成员,但也为实现一些复杂逻辑提供了便利。
4、什么是模板?C++中模板分为哪两种类型?它们的作用是什么?
模板的定义:
模板就像是一个模具,根据不同的参数(数据类型等)可以生成不同具体类型的代码。
它允许程序员编写能够处理多种不同数据类型的代码,而不必为每种数据类型都重复编写相似的代码。
两种类型:
函数模板template <typename T >
类模板template <typename T1> class class_name{ };//其中template T1表示模板参数表,由类型参数和非类型参数组成。
作用是什么:
函数模板: 函数模板是用于创建通用函数的模板。它可以根据调用时提供的参数类型自动生成相应的函数版本。
类模板:可以使用类模板为类定义一种模式,使得类中的一些数据成员、成员函数的参数、以及返回值可以取任意类型。
(由于类模板需要一个或多个类型的参数,所以类模板也称为参数化类,类模板可以看成是类的抽象。)
5、什么是const成员函数?它的作用是什么?如何声明和调用const成员函数?
在类中使用const关键字修饰的函数,被称为常成员函数
作用:
确保数据的完整性和一致性:当一个对象的状态不应该被改变时,使用 const 成员函数可以防止意外地修改对象的数据。
(例如在函数调用过程中只需要获取对象的某些属性或者进行一些不改变对象状态的计算,)
与 const 对象配合使用:如果一个对象被声明为 const,那么它只能调用 const 成员函数。确保了 const 对象的状态不会被改变。
声明格式:
类型说明 成员函数名(参数表)const;(也就是在一般的函数()后加一个const)
调用方式: 调用常成员函数的方式与普通成员函数相同,可以通过常对象和非常对象来调用。实例对象.const成员函数。
6、什么是消息?什么是消息映射?
消息的定义:
在 C++ 编程的某些特定场景下(如在图形用户界面(GUI)编程或者事件驱动编程的环境中),消息是对象之间通信的一种方式
。它表示一个事件或者请求,从一个对象发送到另一个对象或者一组对象。
消息映射:
消息映射是一种将消息与对应的处理函数关联起来的机制。
在面向对象编程,特别是在处理事件驱动的应用程序(如 Windows 下的 MFC 或者 Qt 框架)中广泛使用。
• 例如,在 MFC(Microsoft Foundation Classes)中,消息映射用于将 Windows
系统发送的各种消息(如鼠标移动、键盘按键等消息)和类中的成员函数(消息处理函数)联系起来。当一个消息到达时,系统通过消息映射找到对应的处理函数并执行它。
• 消息映射通常是通过一些宏或者特定的代码结构来实现的。
7、什么是异常处理?C++中如何使用try、catch和throw进行异常处理?
异常处理:
异常处理是一种程序设计机制,用于处理程序执行过程中出现的意外或错误情况。
在一个复杂的程序中,可能会遇到各种运行时错误,例如文件不存在、网络连接中断、内存不足、算术运算错误(如除数为 0)等。异常处理允许程序在遇到这些错误时,以一种可控的方式做出响应,而不是直接崩溃。
它提供了一种将正常的程序流程与错误处理流程分离的方法,使得代码的主逻辑更加清晰,同时也提高了程序的健壮性和可靠性。
处理方式
需要包含标准异常类的头文件#include < stdexcept >
throw表达式用于抛出一个异常对象。
这个对象可以是基本数据类型(如int、char*等),也可以是用户自定义的类类型。当程序执行到throw语句时,当前函数的执行会立即停止,并开始在调用栈中查找能够处理这个异常的catch块。
8、什么是虚析构函数?为什么基类的析构函数通常应该声明为虚函数?
虚析构函数
析构函数通常用于释放对象占用的资源,并在对象生命周期结束时执行清理操作。
当在析构函数前加上virtual关键字
,它就被称为虚析构函数。
原因
首先虚函数的目的是为了基类可以访问子类(因为通常情况基类是无法访问子类的成员对象的),如果是将基类的析构函数声明为虚析构函数,可以通过基类的指针调用子类的析构函数,保证子类的资源可以被正常释放。
补充:(当我们使用基类指针来删除派生类对象时,如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类特有的资源没有被正确释放,从而造成内存泄漏或其他资源泄露问题。)
9、什么是拷贝构造函数?什么情况下会调用拷贝构造函数?【学习通】
拷贝构造函数:
拷贝构造函数是一个特殊的构造函数,用于创建一个新的对象,并将其初始化为另一个同类型对象的副本。
ClassName(const ClassName& other); other 是对同类型对象的常量引用,它是要被拷贝的对象。
调用拷贝构造函数
1、 比如通过值传递对象给函数
Void func(class_name obj) {}
2、 从对象初始化另一个对象
Clss_name obj1;
Obj2 =obj1;
10、什么是静态联编,什么是动态联编,C++中的多态性体现在哪些方面?
静态联编: 在编译阶段将函数实现和函数调用进行关联,即在程序运行之前就确定好调用的函数。
(速度快效率高,但是不够灵活,不过在函数重载的情况下,编译器会根据函数名和参数类型来确定要调用的函数,这个过程需要在编译阶段进行,此时属于静态联编)
动态联编: 指的是在程序运行时才确定函数的调用关系,也就是在程序执行时才将函数实现和函数关联起来(灵活性强,可以实现多态性)
。动态联编通常是通过虚函数
实现的(动态联编实现的条件:成员函数必须是virtual;存在父子关系的类,并且自雷重写了父类的虚函数;使用指针或者引用指向派生类对象,并通过基类指针或引用调用虚函数)
什么是多态性
多态性指的是同样的消息被不同类型的对象接受时导致不同的行为。
在C++中,多态性允许基类通过指针或引用来调用怕派生类中的重写函数,从而实现不同的行为。比如函数重载、运算符重载、虚函数的使用
都可以体现多态性的机制。(多态性是比较复杂的一个机制,那么在后面的文章会继续探讨这个特点)
可以提高代码的可维护性、可扩展性以及灵活性。
11、 何时应该将成员函数声明为虚函数?声明的虚函数该如何调用?
声明虚函数的情景
当使用基类的指针或引用来调用派生类中的重写函数时,需要将基类中成员函数声明为虚函数。
调用方式
使用基类的指针进行调用,比如A是基类,B是其派生类,fun()函数是A类的虚函数
B obj;A *obj1 = &obj;obj1->fun();
或者A *obj1 = new B; obj1->fun(); delete obj1;
使用父类引用绑定子类对象的方式进行调用
B obj; A &obj1 = obj; obj1.fun();
#include <iostream>
using namespace std;
class Animal
{
public :
virtual void speak()
{
cout<<"Animal speak"<<endl;
}
virtual void run()
{
cout<<"Animal runs"<<endl;
}
};
class Dog : public Animal
{
public :
void speak()
{
cout<<"Dog bark"<<endl;
}
void run()
{
cout<<"Dog runs"<<endl;
}
};
class Cat : public Animal
{
public :
void speak()
{
cout<<"Cat meows"<<endl;
}
void run()
{
cout<<"Cat runs"<<endl;
}
};
int main()
{
// Dog d;
// Cat c;
// d.Dog::speak();
Dog d;
//以下是多态
//父类指针指向子类对象
cout<<"父类指针指向子类对象"<<endl;
Animal *p = &d;
p->speak();
cout<<endl;
//使用new关键字
cout<<"使用new关键字"<<endl;
Animal *p1 = new Dog;
p1->speak();
delete p1;
cout<<endl;
//父类引用绑定(指向)子类对象
cout<<"父类引用绑定(指向)子类对象"<<endl;
Dog d1;
Animal &p2 = d1;
p2.speak();
cout<<endl;
// cout<<"使用数组输出"<<endl;
// Animal *a[2];
// a[0] = &d;
// a[1] = &c;
// for(int i= 0;i<2;i++){
// a[i]->speak();
// a[i]->run();
// }
return 0;
}
12、简述操作符重载的注意事项有哪些。
1、
只能重载C++中已有的运算符
2、不能改变运算符原有的优先级、结合性以及操作数。
3、一般运算符重载为类的成员函数时,函数参数个数要比该运算符需要的操作数个数少一个。
双目运算符重载函数只需要一个参数,单目运算符重载函数不需要参数。
4、如果是在类外运算符重载为类的友元函数,友元函数所需参数一般和该运算符所需参数个数一致。
5、重载运算符函数的返回类型一般是自定义或与操作数兼容的类型。
13、简述构造函数的概念、作用及其特征。
构造函数的概念
构造函数是一种特殊的成员函数,在创建对象时自动调用,用于初始化对象。
类名(参数表){构造函数体}
类名(参数表):成员初始化表 {构造函数体}
构造函数的作用
类的构造函数的作用是在对象创建后,对对象的非静态数据成员初始化
对于需要动态分配资源的对象,构造函数可以负责分配这些资源。
构造函数的特征
1、
构造函数的名称必须与类名完全相同,并且没有返回类型(包括void)。
2、构造函数在创建完成类的对象时会被自动调用
3、构造函数可以有多个,只要它们的参数列表不同即可(也成为函数的重载)
4、当一个类中没有定义任何构造函数时,编译器会在类的实例对象创建完成后自动调用类的默认构造函数。
5、每个通过构造函数创建的对象,在其生命周期结束时,都会调用相应的析构函数来释放资源。
14、什么是赋值兼容原则?该原则一般应用在何处?
赋值兼容原则
是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。
通过公有继承,派生类不仅继承了基类的成员(除了构造函数和析构函数),而且继承了基类成员的访问控制属性。公有派生类实际上具备了基类的所有功能。赋值兼容原则常用于实现多态性
应用场景
对象赋值
:派生类的对象可以直接赋值给基类对象。但请注意,这种赋值后,只能使用从基类继承的成员。
引用初始化
:派生类的对象可以直接初始化基类的引用。
指针指向
:派生类对象的地址可以赋给指向基类的指针。
15 、在含有组合对象的派生类中,构造函数执行的次序是怎样的?
调用次序
当派生类的对象被创建完成后,会先调用基类的构造函数
(如果基类本身也是一种继承,那么将顺次先调用基类的基类的构造函数
);
然后是派生类中的成员对象的构造函数
,调用完成基类的构造函数后,在调用本类的构造函数之前,会先根据派生类中所有成员对象的构造函数在类中的声明顺序进行调用
;
最后是派生类本类的构造函数
。
16、比较继承与组合的异同。
继承与组合的相同点
代码的复用
,开发者可以基于已有的类来构建新的类,减少代码冗余。
继承与组合的不同点
多态性:
继承支持多态性,
可以通过基类的指针指向派生类的对象;
组合本身不直接支持多态性,不过可以通过成员对象的多态性来实现较为复杂的行为
灵活性:
继承在提供代码复用的同时,会限制子类的灵活性;
继承可以更方便的创建更多新的子类;组合提供了更高的灵活性,可以无需改变类的定义,直接通过组合不同的对象来创建复杂的行为 组合可以更灵活的在已有类中添加新的成员对象
耦合性:
继承通常具有较高的耦合性,子类的创建需要依赖父类的创建,父类的 变化可能会影响子类;
组合有较低的耦合度,组合对象之间的依赖关系相对较弱,一个对象的变化通常不会影响另一个对象。
17、比较类的普通成员函数、普通成员变量、静态成员变量存储方式的异同。
不同点:
普通成员函数:
在编译时被存储在代码段中,不直接存储在对象实例中,函数体代码在内存中只有一份,供所有对象共享
;调用成员函数时,函数代码通过对象或指针this指针
来访问对象的成员变量。普通成员函数的生命周期与程序的生命中周期相同。
函数代码在程序加载时被加载到内存中,在程序结束时被卸载。
普通成员变量:
普通成员变量存储在对象的实例中,每个类的实例对象在内存中都有自己独立的一块空间用于存储普通成员变量。
对于局部对象所在的存储空间位于栈,对于动态分配的对象位于堆(new关键字)。
普通成员变量的生命周期与对象的生命周期相同,对象创建时,成员变量被分配内存并进行初始化;对象销毁时,成员变量所占用的内存被释放。
静态成员变量:
静态成员变量存储在全局数据区(静态存储区)(它不属于任何一个具体的对象,而是被类的所有对象共享。)静态成员变量可以通过类名直接访问,也可以通过对象通过对象的成员访问运算符(. 或 ->)访问。
(不过要注意,静态成员变量在类内声明,在类外初始化)
相同点:
静态成员变量和普通成员变量都定义在类的范围内
,但是静态成员变量不属于任何对象实例;普通成员函数和静态成员函数都定义在类的范围内
,但静态成员函数不依赖于任何对象实例。
18、C++中有哪三种继承方式,其区别是什么?
继承方式:
公有继承(public),私有继承(private),保护继承(protected)
区别:
公有继承中,
基类的公有成员在派生类中仍然是公有成员,基类的保护成员在派生类中仍然是保护成员,基类的私有成员在派生类中不可访问。
保护继承中,
基类的公有成员和保护成员在派生类中都变成保护成员。这意味着基类的公有成员在派生类中的访问权限变窄了。main函数中创建的派生类的对象不能直接访问基类的公有成员(和公有继承不同),但是派生类的成员函数可以访问这些变成保护成员的原基类成员。
私有继承中,
基类的公有成员和保护成员在派生类中都变成私有成员。派生类的对象不能访问基类的公有成员,派生类的成员函数可以访问这些变成私有成员的原基类成员,
19、什么是静态成员变量和静态成员函数?它们的特点是什么?
静态成员变量
在类的成员变量前加上static关键字,并在类外定义并初始化静态成员变量。
静态成员变量是类级别的变量,而非对象级别的变量。静态成员变量在所有对象之间可共享,并且只有一个副本存于内存当中,无论创建多少对象。
特点
共享性:静态成员变量属于类,所有对象之间可共享静态成员变量;
访问方式:可以通过类名直接访问静态成员变量,也可以通过对象访问。
存储位置:静态成员变量存储在全局数据区,而不是堆或栈。
初始化:必须在类外部定义或初始化。
静态成员函数
在类的普通成员函数前面加上static关键字,在类内进行定义或初始化,
即可将普通成员函数定义为静态成员函数。静态成员函数是类级别的函数,不依赖于类的任何特定对象。
特点
访问限制:只能访问静态成员变量和其他静态成员函数,不能访问非静态成员变量或非静态成员函数
无this指针:相较于普通成员函数,静态成员函数没有this指针,因为它们是独立于对象的。
20、什么是多态性?C++中如何实现多态性?
什么是多态性
多态性指的是同样的消息被不同类型的对象接受时导致不同的行为。
提高代码的可维护性、可扩展性以及灵活性。
在C++中,多态性允许基类通过指针或引用来调用怕派生类中的重写函数,从而实现不同的行为。
函数重载
• 函数重载是指在同一个作用域内,可以有多个函数具有相同的函数名,但参数列表不同
(参数类型、参数个数、参数顺序至少有一个不同)。
• 编译器根据函数调用时的参数类型和数量来决定调用哪个函数。
• 函数重载实现了静态多态性(编译时多态性)。
运算符重载
• 运算符重载是指可以为已有的运算符赋予新的含义,使其能够用于特定的类类型。
• 通过运算符重载,可以使系统预定义的运算符可操作于类对象。
• 运算符重载同样实现了静态多态性。
虚函数和纯虚函数的使用
• 虚函数是指在基类中声明的函数,并在派生类中可以被重写(Override),从而实现不同的行为。
• 当使用基类指针或引用调用虚函数时,实际调用的是派生类中重写的函数,而不是基类中的函数。
• 虚函数实现了动态多态性(运行时多态性),即在程序运行时才确定调用哪个函数。
• 虚函数是C++中实现多态性的关键机制。
总结
以上就是本篇文章介绍的有关C++中常见的概念,C++中的特性远不止于此,后续会持续更新。