# i、C++ 抽象类

# ii、什么是抽象类

在 C++ 中,抽象类是一个特殊的类,它包含了至少一个纯虚函数(pure virtual function)。纯虚函数是在函数声明后面加上 "= 0" 的虚函数,表示该函数没有实际的实现,而是由派生类来实现。

抽象类的主要特点是它不能被实例化,即无法创建抽象类的对象。这是因为抽象类中包含了纯虚函数,这些函数没有实际的实现,无法直接调用。因此,抽象类的作用是定义一组接口或协议,规定了派生类必须实现的纯虚函数。

抽象类用于建立类的继承关系和接口规范,它充当了一种抽象的模板,要求派生类必须实现其纯虚函数,从而才能创建对象并调用这些函数。

抽象类的声明示例:

1
2
3
4
5
6
7
8
9
class Shape {
public:
virtual void draw() const = 0; // 纯虚函数,没有实际实现
virtual double area() const = 0; // 另一个纯虚函数
// 可以包含普通的虚函数和非虚函数,也可以有数据成员
};

// 无法实例化抽象类,下面的代码会导致编译错误
// Shape shape;

派生类可以从抽象类继承,并必须实现抽象类中的所有纯虚函数才能成为非抽象类。

抽象类在面向对象编程中有重要的作用,它提供了一种接口和规范的定义方式,促使派生类遵循约定,并具有更好的可扩展性和可维护性。

# ii、C++ “接口”

提问:C++ 中,据我所知,有抽象类的概念,我想知道 C++ 中有 “接口” 的概念吗,它和抽象类是什么关系?

回答:
在 C++ 中,虽然没有像 Java 或 C# 中那样明确的 "接口"(interface)关键字,但是我们可以通过抽象类来模拟接口的概念。

在其他面向对象编程语言中,接口是一种特殊的抽象类,它只包含纯虚函数(没有数据成员和非虚函数),而且类中的所有成员函数都是纯虚函数。接口定义了一组公共的方法签名,用于规范一组类的行为,但不包含具体的实现。类可以通过继承接口来实现这些方法,从而遵循了接口的规范。

在 C++ 中,我们可以使用抽象类来模拟接口。通过将所有成员函数都声明为纯虚函数,我们可以使抽象类成为一个接口。类似于接口,抽象类也不能被实例化,而只能用作其他类的基类,要求派生类必须实现其纯虚函数。

# i、C++ 嵌套类

在 C++ 中,类内部嵌套类(Nested Class)是一种定义在另一个类的内部的类。嵌套类可以像普通类一样拥有成员变量、成员函数、构造函数和析构函数,但它们的作用范围限定在包含它们的外部类中。嵌套类可以分为两种类型:静态嵌套类非静态嵌套类

1. 静态嵌套类(Static Nested Class): 静态嵌套类是一个定义在外部类的作用域内的内部类。它的定义不依赖于外部类的对象,可以直接通过外部类名访问。静态嵌套类可以拥有自己的静态成员和非静态成员,但它们不会继承外部类的非静态成员。

1
2
3
4
5
6
7
8
9
class OuterClass {
public:
// OuterClass members

static class StaticNestedClass {
public:
// StaticNestedClass members
};
};

2. 非静态嵌套类(Inner Class): 非静态嵌套类是一个定义在外部类的作用域内的内部类,但它的每个对象都与外部类的特定对象相关联。非静态嵌套类可以访问外部类的所有成员,包括私有成员。非静态嵌套类的定义必须在外部类的作用域内,否则编译器无法理解它们。

1
2
3
4
5
6
7
class OuterClass {
public:
class InnerClass {
public:
// InnerClass members
};
};

嵌套类的一个常见用途是将相关的类定义组织在一起,以减少全局命名冲突,并提高代码的模块化性。此外,嵌套类可以访问外部类的私有成员,从而实现一些封装和数据隐藏的效果。

需要注意的是,嵌套类的定义和使用需要遵循 C++ 的语法规则,尤其是在访问嵌套类的成员时,需要使用正确的作用域限定符。

提问:嵌套类的作用是什么,和使用继承关系有什么区别?

嵌套类的作用在于将相关的类定义组织在一起,以提高代码的模块化性和可读性。它可以将具有紧密关联的功能和数据封装在一起,减少全局命名冲突,并在逻辑上将相关的类组织在一起。嵌套类还可以访问外部类的私有成员,从而实现一些封装和数据隐藏的效果。

与继承关系相比,嵌套类具有以下区别和优势:
1. 组织性和可读性: 嵌套类更适合在代码中组织相关的类定义,使代码更易读、易理解。继承关系更多用于实现类之间的层次关系。
2. 权限控制: 嵌套类可以访问外部类的私有成员,但继承关系只能通过继承来获得基类的受保护和公有成员。
3. 独立性: 嵌套类是独立于外部类的,每个嵌套类的对象与外部类对象无关。而继承关系会导致子类和父类之间的紧密耦合。
4. 名称空间: 嵌套类可以有更短的名称,因为它们的名称是相对于外部类的。继承关系中的子类名称需要保留父类的名称。

总之,嵌套类适用于将相关的类定义组织在一起,实现更模块化的代码结构,而继承关系适用于实现类之间的层次关系和共享行为。

提问:嵌套类只能在外部类中实例化吗,还是在整个程序中都可以使用?

嵌套类并不仅限于在外部类中实例化,它们可以在整个程序中使用,就像独立的类一样。嵌套类的可见性和使用范围取决于其访问修饰符(public、protected、private)以及外部类的访问修饰符。

在外部类的作用域中,嵌套类的访问权限和普通类的访问权限一样。例如,如果嵌套类声明为 public ,则它可以在外部类的作用域外被其他代码访问和实例化。如果嵌套类声明为 private ,则只有外部类的成员函数内部才能实例化和访问嵌套类。

而在外部类的作用域外,要使用嵌套类,需要通过外部类名来限定。嵌套类的名称是相对于外部类的,因此需要使用 外部类名::嵌套类名 的方式来访问。

以下是一个示例,演示了如何在外部类作用域外使用嵌套类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Outer {
public:
class Nested {
public:
void NestedFunction() {
// 嵌套类的成员函数
}
};
};

int main() {
Outer::Nested nestedObj; // 在外部类作用域外实例化嵌套类对象
nestedObj.NestedFunction(); // 调用嵌套类的成员函数

return 0;
}

在上述示例中, NestedOuter 的嵌套类,但它可以在 main 函数中实例化和使用。需要注意的是,嵌套类的可见性和使用范围仍然受到外部类的访问修饰符的影响。

提问:嵌套类可以任意访问外部类的所有成员数据吗?

嵌套类可以访问外部类的所有成员数据,包括私有成员数据。这是因为嵌套类在外部类的作用域内,被视为外部类的一个成员,所以可以访问外部类的所有成员,不论其是公有、保护还是私有。

以下是一个示例,演示了嵌套类如何访问外部类的成员数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Outer {
private:
int privateMember;

public:
class Nested {
public:
void AccessOuterPrivate(Outer& outerObj) {
// 嵌套类可以访问外部类的私有成员
int data = outerObj.privateMember;
// 在这里使用外部类的数据进行操作
}
};
};

在上述示例中, Nested 嵌套类可以通过外部类对象的引用来访问外部类的私有成员变量 privateMember

需要注意的是,虽然嵌套类可以访问外部类的所有成员数据,但这也增加了代码的紧密性和依赖性。嵌套类和外部类之间的耦合度会增加,可能会影响代码的维护和扩展。因此,在使用嵌套类时,需要谨慎考虑类之间的关系,避免不必要的耦合。

提问:嵌套类和组合关系有什么区别?

嵌套类和组合关系是两种不同的概念,它们在面向对象编程中具有不同的含义和用途。

  1. 嵌套类(Nested Class)
    • 嵌套类是在一个类的内部定义的类,也称为内部类。
    • 嵌套类可以访问外部类的成员,包括私有成员。
    • 嵌套类可以在外部类的作用域内创建实例。
    • 嵌套类可以用来实现一种逻辑上的内部封装,将与外部类相关的功能封装在一起,但也增加了类之间的紧密性。
    • 嵌套类通常用于辅助外部类,但并不意味着嵌套类是外部类的组成部分。
  2. 组合关系
    • 组合是一种对象关系,它描述了一个类(整体)由其他类(部分)组成的关系。
    • 在组合关系中,一个类的对象包含了其他类的对象作为其成员变量。
    • 组合关系强调了整体与部分之间的关系,整体对象负责管理和控制部分对象的生命周期。
    • 组合关系通常用于表示更复杂的对象结构,通过将不同的对象组合在一起实现更高层次的功能。

总的来说,嵌套类是一个类的内部定义的类,用于实现逻辑上的封装,而组合关系描述了整体与部分之间的关系,用于构建更复杂的对象结构。在选择使用嵌套类还是组合关系时,需要根据具体的需求和设计来决定。

提问:但嵌套类不也是描述了一个类作为其他类的组成成分吗?

嵌套类确实也可以看作是一种类的组成关系,但是与常规的组合关系还是有一些区别。

嵌套类确实是一个类作为另一个类的一部分的情况,但是它通常用于在一个类的内部定义一个辅助的、相对独立的类,以实现某种特定的功能。它并不是强调整体与部分之间的关系,而更强调内部的逻辑结构。例如,在外部类内部定义的嵌套类通常是为了实现外部类的某个方面的功能,它们的存在并不一定是整体与部分的强关系

组合关系则更强调整体与部分之间的紧密关系,其中一个类的实例包含了另一个类的实例。组合关系用于构建复杂的对象结构,强调整体对象和部分对象之间的依赖和合作关系,通常整体对象负责管理部分对象的生命周期。

虽然嵌套类和组合关系都涉及类与类之间的关系,但它们的重点和用途略有不同。嵌套类更侧重于内部逻辑结构和功能的划分,而组合关系更强调整体与部分之间的关系和依赖。所以,嵌套类虽然可以看作是一种类的组成关系,但在实际使用中,它的重点和目的与通常的组合关系有所不同。

提问:那么,外部类中定义了一个嵌套类,是否同时在外部类中定义 / 实例化了一个嵌套类的对象,还是说仅仅只是定义了嵌套类?

在外部类中定义了一个嵌套类,并不会自动同时在外部类中实例化一个嵌套类的对象。定义嵌套类只是在外部类的作用域中引入了一个内部类的定义,你需要显式地实例化嵌套类的对象才能在外部类中使用

嵌套类的定义只是为了在外部类内部创建一个命名空间,以便可以在外部类的作用域中引用嵌套类的名称。要在外部类中使用嵌套类,你需要通过外部类的实例来创建嵌套类的对象。这个对象并不是自动创建的,而是需要你显式地进行实例化。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class OuterClass {
public:
class NestedClass {
public:
void NestedMethod() {
// ...
}
};

void OuterMethod() {
NestedClass nestedObject; // 实例化嵌套类的对象
nestedObject.NestedMethod(); // 调用嵌套类的方法
}
};

int main() {
OuterClass outerObject;
outerObject.OuterMethod();

// 下面这行代码会报错,因为 NestedClass 不在 main 的作用域内
// NestedClass nestedObject;

return 0;
}

在这个示例中,虽然在 OuterClass 中定义了 NestedClass ,但要在 main 函数中使用 NestedClass ,你需要通过 OuterClass 的实例来创建 NestedClass 的对象。直接在 main 函数中创建 NestedClass 的对象会导致编译错误,因为 NestedClass 的作用域仅限于 OuterClass 内部。

# i、C++ 模板类

# ii、什么是模板类

C++ 的模板类(Template Class)是一种通用的类定义,可以用来生成特定类型的类。模板类在类的定义中使用了模板参数,使得类的行为和属性能够根据不同的类型进行定制。模板类可以使代码更加通用和可复用,因为它可以为不同的数据类型生成相似的类,而不需要重复编写相似的代码。

模板类的定义通常如下所示:

1
2
3
4
template <typename T>
class MyTemplateClass {
// 类的成员和方法定义,可以使用类型 T
};

在上面的例子中, typename T 是一个模板参数,代表一个通用的类型。在使用模板类时,你可以为 T 指定具体的类型,然后编译器会根据指定的类型生成相应的类。

# ii、模板类的继承

关于模板类的继承,模板类可以像普通类一样继承其他类,但有一些注意事项:

  1. 模板类可以继承普通类:可以创建一个继承自普通类的模板类。这样,模板类会继承基类的属性和方法,但模板参数仍然可以在子类中使用。
  2. 模板类可以继承模板类:模板类也可以继承其他模板类,但需要确保模板参数的匹配。即使基类和派生类的模板参数不同,它们仍然被认为是不同的类。

下面是一个简单的示例,展示了模板类的继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template <typename T>
class Base {
public:
T value;

Base(T val) : value(val) {}

void printValue() {
cout << "Value: " << value << endl;
}
};

template <typename T>
class Derived : public Base<T> {
public:
Derived(T val) : Base<T>(val) {}

void printDoubledValue() {
cout << "Doubled Value: " << this->value * 2 << endl;
}
};

int main() {
Derived<int> derivedObj(5);
derivedObj.printValue();
derivedObj.printDoubledValue();

return 0;
}

模板类继承模板类时,派生类的模板参数必须和基类的模板参数一致吗?

不一定,但派生类模板的模板参数受基类模板的模板参数影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename T>
class Base
{
public:
T _a;
public:
Base(T n):_a(n) {}
T get() const { return _a; }
};

template<typename T, typename U>
class Derive:public Base<U>
{
public:
U _b;
public:
Derive(T t, U u):Base<T>(t), _b(u) {}
U sum() const { return _b + U(Base::get()); }
};

参考链接:
https://www.54benniao.com/a/fleu4w.html

# i、模板特化

在 C++ 中,模板特化(Template Specialization)是一种机制,允许你为模板类或函数的特定参数类型提供特定的实现。特化的主要目的是让你可以根据不同的类型提供特定的行为,而不是使用通用的模板实现。

有两种主要类型的特化:类模板特化和函数模板特化。

1. 类模板特化
类模板特化用于为特定的数据类型提供特定的类定义。它通常用于为模板类的成员变量或成员函数提供特定的实现。例如,你可以为一个模板类提供特定于某种类型的成员函数实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename T>
class MyTemplateClass {
public:
void DoSomething() {
// 通用实现
}
};

template <>
class MyTemplateClass<int> {
public:
void DoSomething() {
// 针对 int 类型的特化实现
}
};

2. 函数模板特化
函数模板特化用于为特定的数据类型提供特定的函数实现。它通常用于为模板函数提供特定的行为。例如,你可以为一个模板函数提供特定于某种类型的实现。

1
2
3
4
5
6
7
8
9
template <typename T>
T Add(T a, T b) {
return a + b;
}

template <>
int Add<int>(int a, int b) {
return a * b; // 针对 int 类型的特化实现
}

模板特化的语法通常包括在模板声明之后使用 template <> 关键字,然后提供特化的参数类型,随后是特定的实现。特化可以用于为不同的数据类型提供不同的逻辑,以满足特定的需求。

需要注意的是,特化不仅可以用于提供不同的实现,还可以用于解决特定类型的边界条件和优化,以提高程序的性能和灵活性。特化是 C++ 中强大的工具,用于在泛型编程中处理不同数据类型的情况。

# i、函数模板

一般形式:

1
2
3
4
5
template <typename type>
return_type funcname(parameter list)
{
// 函数的主体
}

其中,模板参数可以出现在返回值或传参列表中,但不一定。

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename T>
inline T const& Max (T const& a, T const& b)
{
return a < b ? b:a;
}

template <class T>
void Swap(T & x, T & y)
{
T tmp = x;
x = y;
y = tmp;
}

另外,在调用模板函数时,网上的教程举的例子一般都是隐式转换,即传参时并没有显式声明模板参数的类型,让编译器自动推导。

但也可以直接显式声明(有些情况下编译器无法隐式推导)。此时调用格式为:

1
函数名<typename>(函数传参);

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
template <typename InjectInfo, typename Module>
void write_bin(const string & data_type)
{
for (auto &info : dict[data_type])
{
// 获取bin文件中的data_size和offset
char buf[info.data_size + 1];
parse.old_bin_file[data_type]->seekg(info.offset);
parse.old_bin_file[data_type]->read(buf, info.data_size);

// 组成inject格式
InjectInfo iInfo;
iInfo.info = shared_ptr<Module>(new Module());;
iInfo.info->ParseFromArray(buf, info.data_size);
iInfo.utc_tick = info.utc_tick;
iInfo.gtc_tick = info.gtc_tick;
std::shared_ptr<InjectInfo> _inject(new InjectInfo(iInfo));
vector<shared_ptr<InjectInfo> > _inject_v;
_inject_v.emplace_back(_inject);

// 用msgpack序列化然后写入新的bin文件
msgpack::sbuffer b;
msgpack::packer<msgpack::sbuffer> p(&b);
p.pack(_inject_v);
parse.new_bin_file[old_2_new_bin[data_type]]->write(b.data(), b.size());
}
}

void write_log()
{
string data_type;
data_type = "ObjectPerceptionObjectList";
write_bin<VehicleInjectInfo, perception::ObjectList>(data_type);
data_type = "roadmarkingfront";
write_bin<LaneInjectInfo, perception::Roadmarking>(data_type);
data_type = "radar";
write_bin<RadarInjectInfo, perception::Radar>(data_type);
data_type = "corner_radar";
write_bin<RadarInjectInfo, perception::Radar>(data_type);
data_type = "rcfusion";
write_bin<ResultInfo, perception::RCFusion>(data_type);
data_type = "rcfusion_hmi";
write_bin<ResultInfo, perception::RCFusion>(data_type);
data_type = "vehicle_signal";
write_bin<DriveInjectInfo, minieye::VehicleSignal>(data_type);
return ;
}

更新于

请我喝杯咖啡吧~

Rick 微信支付

微信支付

Rick 支付宝

支付宝