1. 类与对象
类 是定义对象属性和行为的模板。它是一种抽象的数据类型,用于创建具体的实例(对象)。
对象 是类的实例,通过类定义创建。对象拥有类中定义的属性和方法。
示例代码:
#include <iostream>
using namespace std;
// 定义一个类
class Car {
public:
string brand;
string model;
int year;
// 类的方法
void start() {
cout << "Car is starting" << endl;
}
};
int main() {
// 创建一个对象
Car myCar;
myCar.brand = "Toyota";
myCar.model = "Corolla";
myCar.year = 2015;
// 调用对象的方法
myCar.start();
return 0;
}
2. 继承
继承 允许一个类(子类)继承另一个类(父类)的特性(属性和方法)。
支持代码重用,并实现层次模型。
示例代码:
class Vehicle {
public:
string brand = "Ford";
void honk() {
cout << "Tuut, tuut!" << endl;
}
};
// 派生类
class Car: public Vehicle {
public:
string model = "Mustang";
};
int main() {
Car myCar;
myCar.honk();
cout << myCar.brand + " " + myCar.model;
return 0;
}
3. 多态
多态 允许我们使用统一的接口访问不同的基础形态(类)。
通常通过继承和虚函数实现。
示例代码:
class Animal {
public:
// 虚函数
virtual void makeSound() {
cout << "Some sound" << endl;
}
};
class Pig : public Animal {
public:
void makeSound() override {
cout << "Oink oink" << endl;
}
};
class Dog : public Animal {
public:
void makeSound() override {
cout << "Bark bark" << endl;
}
};
int main() {
Animal* myAnimal = new Pig();
myAnimal->makeSound();
myAnimal = new Dog();
myAnimal->makeSound();
delete myAnimal;
return 0;
}
4. 封装
封装 指将对象的数据(属性)和方法(行为)捆绑在一起,隐藏内部实现细节,只暴露必要的接口。
通过访问修饰符(如 public, private, protected)实现。
示例代码:
class BankAccount {
private:
double balance; // 私有属性
public:
BankAccount(double initialBalance) : balance(initialBalance) {}
// 公有方法来访问私有属性
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
}
}
double getBalance() {
return balance;
}
};
int main() {
BankAccount account(100.0);
account.deposit(50.0);
account.withdraw(25.0);
cout << "Current balance: $" << account.getBalance();
return 0;
}
这些是面向对象编程的四个核心概念。它们构成了 OOP 的基础。
5.深拷贝和浅拷贝
浅拷贝示例:
#include <iostream>
using namespace std;
class ShallowCopy {
public:
int *data;
ShallowCopy(int d) {
data = new int(d);
}
// 浅拷贝构造函数
ShallowCopy(const ShallowCopy &source) : data(source.data) {}
~ShallowCopy() {
delete data;
}
};
int main() {
ShallowCopy obj1(100);
ShallowCopy obj2 = obj1; // 浅拷贝发生
cout << *obj1.data << endl; // 输出 100
cout << *obj2.data << endl; // 输出 100,但这是相同的内存地址
return 0;
}
深拷贝示例:
#include <iostream>
using namespace std;
class DeepCopy {
public:
int *data;
DeepCopy(int d) {
data = new int(d);
}
// 深拷贝构造函数
DeepCopy(const DeepCopy &source) {
data = new int(*source.data);
}
~DeepCopy() {
delete data;
}
};
int main() {
DeepCopy obj1(100);
DeepCopy obj2 = obj1; // 深拷贝发生
cout << *obj1.data << endl; // 输出 100
cout << *obj2.data << endl; // 输出 100,这是不同的内存地址
return 0;
}
在例子中,ShallowCopy 类包含一个指向 int 的指针。当进行浅拷贝时,指针本身被复制,但指针指向的内存地址不变。这意味着新对象 obj2 和原对象 obj1 的 data 成员指向同一块内存区域。这种情况下,如果一个对象被销毁(比如离开作用域时),它会释放 data 指针指向的内存。如果另一个对象尝试访问同一块内存,就会出现问题,如悬垂指针或程序崩溃,因为内存已经被释放。
在实际应用中,这就是为什么深拷贝通常比浅拷贝更安全,尤其是在涉及指针和动态内存分配时。通过深拷贝,每个对象都会拥有指向内容的独立副本,从而避免了上述问题。在实现深拷贝时,需要在拷贝构造函数中显式地为指针成员分配新的内存,并复制原始对象所指向的内容。
这里还涉及拷贝构造函数的知识点:
- 拷贝构造函数用于创建一个新对象作为另一个现有对象的副本;
- 它通常有一个参数,即对另一对象的引用;
- 如果没有显式定义拷贝构造函数,编译器会提供一个默认的,默认的拷贝构造函数为浅拷贝。
拷贝构造函数示例:
#include <iostream>
using namespace std;
class MyClass {
public:
int a;
// 默认构造函数
MyClass() : a(0) {}
// 参数化构造函数
MyClass(int x) : a(x) {}
// 拷贝构造函数
MyClass(const MyClass &obj) {
a = obj.a;
}
void display() {
cout << "Value of a: " << a << endl;
}
};
int main() {
MyClass obj1(10); // 使用参数化构造函数
MyClass obj2 = obj1; // 使用拷贝构造函数
obj1.display(); // 输出: Value of a: 10
obj2.display(); // 输出: Value of a: 10
return 0;
}
6.什么是友元函数?它如何影响封装?
首先需要知道为什么要设计友元函数。
- 解决的问题
提供访问权限:有时,你可能需要让某个函数能够访问类的私有成员,但又不想将这些成员设置为公共(public)。
操作符重载:在实现操作符重载时,经常需要访问类的私有数据。 - 使用场景
当函数不是类的逻辑成员,但需要访问类的私有或保护成员时。
在操作符重载中,如重载输入输出操作符(<< 和 >>)。
其次什么是友元函数。友元函数是定义在类外部,但有权访问类的所有私有和保护成员的函数。它通常用于实现类与非成员函数之间的操作符重载等。虽然友元函数有助于提高灵活性,但它也破坏了类的封装性,因为它允许外部函数访问私有成员。
class MyClass {
private:
int value;
public:
MyClass(int v) : value(v) {}
friend void displayValue(const MyClass &obj);
};
void displayValue(const MyClass &obj) {
cout << obj.value << endl;
}
int main() {
MyClass obj(10);
displayValue(obj); // 可以访问 MyClass 的私有成员
return 0;
}
示例 :友元函数用于操作符重载
在下面的例子中,我们将使用友元函数来重载 << 操作符,以便能够直接打印 Point 类的对象。
#include <iostream>
using namespace std;
class Point {
private:
int x, y;
public:
Point(int x, int y) : x(x), y(y) {}
// 友元函数声明
friend ostream& operator<<(ostream& os, const Point& pt);
};
// 友元函数定义,重载 << 操作符
ostream& operator<<(ostream& os, const Point& pt) {
os << "(" << pt.x << ", " << pt.y << ")";
return os;
}
int main() {
Point p1(2, 3);
cout << p1 << endl; // 输出: (2, 3)
return 0;
}
示例 2: 多个类互为友元
在这个例子中,我们有两个类 ClassA 和 ClassB,它们互相需要访问对方的私有成员。
#include <iostream>
using namespace std;
class ClassB; // 前向声明
class ClassA {
private:
int valueA;
public:
ClassA() : valueA(5) {}
// ClassB 是 ClassA 的友元
friend void showData(ClassA&, ClassB&);
};
class ClassB {
private:
int valueB;
public:
ClassB() : valueB(10) {}
// ClassA 是 ClassB 的友元
friend void showData(ClassA&, ClassB&);
};
// 友元函数定义
void showData(ClassA& a, ClassB& b) {
cout << "Value of ClassA: " << a.valueA << endl;
cout << "Value of ClassB: " << b.valueB << endl;
}
int main() {
ClassA objA;
ClassB objB;
showData(objA, objB);
return 0;
}
示例 3: 友元类
一个类可以将另一个类声明为其友元,这允许友元类访问该类的所有私有和保护成员。
#include <iostream>
using namespace std;
class ClassB; // 前向声明
class ClassA {
private:
int data;
public:
ClassA() : data(100) {}
friend class ClassB; // 声明 ClassB 为友元类
};
class ClassB {
public:
void showData(ClassA& a) {
cout << "ClassA data: " << a.data << endl;
}
};
int main() {
ClassA objA;
ClassB objB;
objB.showData(objA);
return 0;
}
为什么构造函数不能是虚函数?以及什么是虚表?
构造函数不可以是虚函数,因为当构造函数被调用时,虚表(用于支持虚函数)可能还没有建立。虚函数依赖于虚表来正确工作,因此在构造时无法使用虚函数。
虚表(Virtual Table)的详细解释
在 C++ 中,虚表是实现运行时多态的一种机制。当类中包含虚函数时,编译器会为这个类创建一个虚表。
- 虚表的概念
虚表是一个存储类的虚函数地址的表格。
每个包含虚函数的类都有其自己的虚表。
类的每个对象都包含一个指向相应虚表的指针,称为虚指针(vptr)。
当虚函数被调用时,程序会使用虚指针和虚表来确定应该调用哪个函数。 - 虚表的作用
虚表允许运行时多态,即在运行时确定调用哪个函数。
它支持动态绑定,即基于对象的实际类型而非其声明类型来调用方法。 - 虚表如何工作
编译时,对于包含虚函数的类,编译器会创建一个虚表。
类的每个对象都会包含一个指向虚表的指针。
当调用虚函数时,程序会查找虚指针,进而查找虚表,最终通过虚表找到正确的函数地址。 - 虚表的例子
class Base {
public:
virtual void func1() {
cout << "Base func1" << endl;
}
virtual void func2() {
cout << "Base func2" << endl;
}
};
class Derived : public Base {
public:
void func1() override {
cout << "Derived func1" << endl;
}
};
在这个例子中,Base 和 Derived 类都有各自的虚表。当通过 Base 类型的指针或引用调用 func1 时,程序会检查虚表以确定是否调用 Base 的 func1 还是 Derived 的 func1。
虚表的工作原理
初始化:当一个对象被创建时,如果它属于包含虚函数的类或其派生类,则会在内存中为它分配一个虚指针(vptr)。这个指针指向相关类的虚表。
虚表结构:虚表是一个数组,每个元素都是一个函数指针,指向类的一个虚函数。这些函数指针按照它们在类中声明的顺序排列。
动态绑定:当调用一个虚函数时,编译器会通过对象的虚指针查找虚表,从而找到对应的函数实现来执行。
为何使用虚表
支持多态:虚表允许在运行时而非编译时确定应调用哪个函数。这是实现多态的关键。
灵活性:派生类可以重写基类的虚函数。通过虚表,可以确保调用的是对象的实际类型对应的函数实现。
示例
考虑一个包含虚函数的基类和派生类。当创建派生类对象并通过基类指针调用虚函数时,虚表机制确保调用的是派生类中重写的版本。
7.描述 C++ 中的静态成员变量和静态成员函数
静态成员变量是属于类的,而不是属于类的某个特定对象的变量。
静态成员函数也是类的一部分,它可以访问类的静态成员变量,但不能访问普通成员变量或函数。
静态成员在所有对象之间共享,可以在没有任何对象实例的情况下访问。
示例:
#include <iostream>
using namespace std;
class MyClass {
public:
static int staticValue; // 静态成员变量
static void staticMethod() { // 静态成员函数
cout << "Static Method. Static Value is " << staticValue << endl;
}
};
int MyClass::staticValue = 100; // 初始化静态成员变量
int main() {
MyClass::staticValue = 200;
MyClass::staticMethod(); // 调用静态方法
return 0;
}