C++
1. C++中类成员的访问权限
访问权限总结表
访问权限 | 当前类内部 | 派生类内部 | 类外部 |
---|---|---|---|
public |
✔️ | ✔️ | ✔️ |
protected |
✔️ | ✔️ | ❌ |
private |
✔️ | ❌ | ❌ |
继承与访问权限
基类成员权限 | 公有继承 | 保护继承 | 私有继承 |
---|---|---|---|
public |
仍为 public |
变为 protected |
变为 private |
protected |
仍为 protected |
仍为 protected |
变为 private |
private |
无法访问 | 无法访问 | 无法访问 |
2. 什么是构造函数?
构造函数是 C++ 中的一种特殊函数,用于在创建对象时对类的成员变量进行初始化。它与类名相同,且没有返回值(包括 void
),在对象被创建时自动调用。
- 构造函数的核心作用是初始化类对象的成员变量,提供默认、参数化、拷贝等多种初始化方式。
- 构造函数的类型包括默认构造函数、参数化构造函数、拷贝构造函数、委托构造函数等。
构造函数与析构函数的对比
特性 | 构造函数 | 析构函数 |
---|---|---|
作用 | 初始化对象 | 销毁对象、释放资源 |
名称 | 与类名相同 | 在类名前加 ~ |
参数 | 可以有参数,可重载 | 不能有参数,不可重载 |
调用时间 | 对象创建时自动调用 | 对象销毁时自动调用 |
手动调用 | 可以显式调用(但不推荐) | 不可显式调用 |
3. 构造函数的调用规则
C++编译器至少给一个类添加3个函数:默认构造函数、默认析构函数、默认拷贝构造函数
如果用户定义了有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造;如果用户定义拷贝构造函数,C++不会再提供其他构造函数。
4. 函数重载
函数重载(Function Overloading)是 C++ 中的一种特性,允许在同一个作用域内定义多个函数名称相同,但参数列表不同的函数。编译器会根据调用时的参数个数和参数类型来选择调用合适的函数。
5. 什么是深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作,指针成员共享相同内存地址。
深拷贝:在堆区重新申请空间,指针成员独立
浅拷贝会导致多个对象共享同一块动态内存,一个对象销毁时释放了内存,另一个对象的指针变成悬空指针。解决方法:使用深拷贝
6. 静态成员归纳
- 静态成员变量: 所有对象共享同一份数据; 在编译阶段分配内存;类内声明,类外初始化
- 静态成员函数:所有对象共享同一个函数;静态成员函数只能访问静态成员变量
- 关于两者内存:如果只声明了类而未定义对象,则类的一般成员变量是不占用内存空间的,只有在定义对象的时候,才为对象的成员变量分配空间。 静态成员不占用类内空间;静态成员函数在类内声明,类外初始化。
7. 继承
基类成员权限 | 公有继承 | 保护继承 | 私有继承 |
---|---|---|---|
public |
作为 public 成员 |
作为 protected 成员 |
作为 private 成员 |
protected |
作为 protected 成员 |
作为 protected 成员 |
作为 private 成员 |
private |
子类不可访问 | 子类不可访问 | 子类不可访问 |
- 继承方式:
- 公有继承、保护继承、私有继承,分别控制子类对基类成员的访问权限。
- 构造与析构:
- 构造函数从基类到派生类依次调用。
- 析构函数从派生类到基类依次调用。
- 多继承:
- 支持多继承,但需处理命名冲突和菱形继承问题(用虚继承解决)。
8. 菱形继承
两个派生类继承同一个基类;又有某个类同时继承了两个派生类;这种继承称为菱形继承。
羊继承了动物数据;牛继承了动物数据;牛马继承了羊和马的数据,则动物数据被继承了两份。
采用虚继承的方法解决该问题。
9. 虚函数是什么
虚函数:是用关键字 virtual
修饰的成员函数,通常在基类中定义,允许在派生类中重写(覆盖)。通过基类指针或引用调用时,虚函数实现动态绑定,从而运行时决定调用哪一个函数版本。
静态函数:静态函数是使用 static
修饰的类成员函数,与类本身关联,而不与类的对象关联。静态函数不依赖具体对象,也不能访问类的非静态成员。
详细对比:静态函数与虚函数
-
动态绑定与静态绑定:虚函数在调用时依赖对象的动态类型,因此实现动态绑定(运行时确定调用函数)。而静态函数的调用在编译阶段就已确定,属于静态绑定。
-
访问非静态成员:静态函数由于没有
this
指针,无法访问类的非静态成员变量。而虚函数是通过对象调用的,可以访问类的非静态成员。 -
虚函数不能是静态函数:虚函数依赖
this
指针的存在,而静态函数没有this
指针,因此静态函数不能声明为虚函数。
10. 多态
多态(Polymorphism)是面向对象编程(OOP)的核心特性之一,指同一个接口可以在不同场景下表现出不同的行为。C++ 中的多态主要分为两种类型:
(1)编译时多态(静态多态 / 静态绑定 / 早绑定)
- 编译时多态是通过函数重载和运算符重载实现的。
- 在编译阶段,编译器根据函数的参数列表或上下文选择调用合适的函数。
(2)运行时多态(动态多态 / 动态绑定 / 晚绑定)
- 运行时多态是通过虚函数和继承实现的。
- 在运行时,根据对象的实际类型,决定调用哪个函数。
11. 纯虚函数
- 纯虚函数是没有函数体的虚函数,在基类中仅声明而不实现,通常用来为派生类提供统一的接口。
- 在函数声明后加上
= 0
,表示该函数是纯虚函数。
纯虚函数 vs 虚函数
特性 | 虚函数 | 纯虚函数 |
---|---|---|
定义 | 用 virtual 声明,有函数体(可选)。 |
用 virtual 声明,并以 = 0 结束,无函数体。 |
实现 | 可以在基类或派生类中实现。 | 必须在派生类中实现。 |
基类实例化 | 基类可以实例化(如果未包含纯虚函数)。 | 基类不能实例化,抽象类不能创建对象。 |
目的 | 提供默认实现,可被派生类重写。 | 强制派生类必须实现纯虚函数。 |
12. 重载和覆盖
特性 | 重载(Overloading) | 覆盖(Overriding) |
---|---|---|
定义 | 在同一作用域中,函数名称相同,但参数列表不同。 | 在派生类中,重定义基类的虚函数,函数名称和参数列表完全相同。 |
**是否依赖继承 ** | 无需继承。 | 依赖继承,必须在派生类中重写基类的虚函数。 |
参数列表 | 必须不同(参数个数、类型或顺序)。 | 必须相同(函数名、参数列表完全一致)。 |
绑定方式 | 静态绑定(编译时确定)。 | 动态绑定(运行时确定)。 |
实现机制 | 编译器通过参数列表区分不同的函数。 | 虚函数表(vtable)和虚函数指针(vptr)。 |
13. 析构函数可以为 virtual 型,构造函数则不能,为什么
析构函数可以为 virtual
,是为了在多态情况下通过基类指针删除派生类对象时,确保调用派生类的析构函数,从而正确释放派生类的资源。
构造函数不能是 virtual
,因为对象的构造过程是从基类到派生类逐步完成的,虚函数表尚未建立,动态绑定机制无法工作,因此构造函数无法实现虚函数的功能。
14. 结构体和类的区别
(1)成员的默认访问级别
在结构体中,成员默认是公开的(public),而在类中,默认是私有的(private)。这意味着在结构体中,所有成员都可以直接访问,而在类中,只有类的方法才能访问私有成员。
(2)继承和多态
类支持继承和多态,这意味着一个类可以从另一个类继承属性和方法,并且可以使用多态性来实现不同形式的方法。而结构体不支持继承和多态。
(3)内存布局:
在一些编程语言中,结构体的实例通常是在栈上分配的,而类的实例通常是在堆上分配的。这意味着当你创建一个结构体的实例时,它会在函数调用栈上分配内存,并且当函数返回时,内存就会被释放。而类的实例则可以在堆上动态分配,并且可以手动释放内存。
(4)使用场景
通常来说,结构体更适合用于轻量级的数据传输和封装,而类则更适合用于复杂的对象建模和面向对象的编程。例如,当你需要表示一个点的坐标时,可以使用结构体,而当你需要表示一个人的信息时,可以使用类。