2018-04-12 10:44:18 valada 阅读数 4881

内容简介

到目前为止,C++ 仍然是计算机编程领域的经典语言之一,C++ 17 标准在2017上半年已经讨论确定。本期我们汇集了编程专家——祁宇(《深入应用 C++ 11》作者,C++ 开源社区 purecpp.org 创始人)的多年经验总结,并详细介绍了 C++ 17 最新标准中值得开发者关注的新特性和基本用法。

本书内容

C++14 实现编译期反射

文/祁宇

本文将通过分析 magic _ get 源码来介绍 magic _ get 实现的关键技术,深入解析实现 pod 类型反射的原理。

pod 类型编译期反射

反射是一种根据元数据来获取类内部信息的机制,通过元数据就可以获取对象的字段和方法等信息。C# 和 Java 的反射机制都是通过获取对象的元数据来实现的。反射可以用于依赖注入、ORM 对象-实体映射、序列化和反序列化等与对象本身信息密切相关的领域。比如 Java 的 Spring 框架,其依赖注入的基础是建立在反射的基础之上的,可以根据元数据获取类型的信息并动态创建对象。ORM 对象-实体之间的映射也是通过反射实现的。Java 和 C# 都是基于中间运行时的语言,中间运行时提供了反射机制,所以反射对于运行时语言来说很容易,但是对于没有中间运行时的语言,要想实现反射是很困难的。

在2016年的 CppCon 技术大会上,Antony Polukhin 做了一个关于 C++ 反射的演讲,他提出了一个实现反射的新思路,即无需使用宏、标记和额外的工具即可实现反射。看起来似乎是一件不可能完成的任务,因为 C++ 是没有反射机制的,无法直接获取对象的元信息。但是 Antony Polukhin 发现对 pod 类型使用 Modern C++ 的模版元技巧可以实现这样的编译期反射。他开源了一个 pod 类型的编译期反射库 magic _ get(https://github.com/apolukhin/magic_get),这个库也准备进入 boost。我们来看看 magic _ get 的使用示例。

#include <boost pfr="" core.hpp="">struct foo {    int some_integer;    char c;};foo f {777, '!'};auto& r1 = boost::pfr::flat_get<0>(f); //通过索引来访问对象foo的第1个字段auto& r2 = boost::pfr::flat_get<1>(f); //通过索引来访问对象foo的第2个字段</boost>

通过这个示例可以看到,magic _ get 确实实现了非侵入式访问 foo 对象的字段,不需要写任何宏、额外的代码以及专门的工具,直接在编译期就可以访问 pod 对象的字段,没有运行期负担,确实有点 magic。

本文将通过分析 magic _ get 源码来介绍 magic _ get 实现的关键技术,深入解析实现 pod 类型反射的原理。

关键技术

实现 pod 类型反射的思路是这样的:先将 pod 类型转换为对应的 tuple 类型,接下来将 pod 类型的值赋给 tuple,然后就可以通过索引去访问 tuple 中的元素了。所以实现 pod 反射的关键就是如何将 pod 类型转换为对应的 tuple 类型和 pod 值赋值给 tuple。

pod 类型转换为 tuple 类型

pod 类型对应的 tuple 类型是什么样的呢?以上面的 foo 为例,foo 对应的 tuple 应该是 tuple<int, char>,即 tuple 中的元素类型和顺序和 pod 类型中的字段完全一一对应。

根据结构体生成一个 tuple 的基本思路是,按顺序将结构体中每个字段的类型萃取出来并保存起来,后面再取出来生成对应的 tuple 类型。然而字段的类型是不同的,C++ 也没有一个能直接保存不同类型的容器,因此需要一个变通的方法,用一个间接的方法来保存萃取出来的字段类型,即将类型转换为一个 size _ t 类型的 id,将这个 id 保存到一个 array<size_t, N> 中,后面根据这个 id 来获取实际的 type 并生成对应的 tuple 类型。

这里需要解决的一个问题是如何实现类型和 id 的相互转换。

type 和 id 在编译期相互转换

先借助一个空的模版类用来保存实际的类型,再借助 C++ 14 的 constexpr 特性,在编译期返回某个类型对应的编译期 id,就可以实现 type 转换为 id 了。具体代码如下:

http://ipad-cms.csdn.net/cms/article/code/3445

上面的代码在编译期将类型 int 和 char 做了一个编码,将类型转换为一个具体的编译期常量,后面就可以根据这些编译期常量来获取对应的具体类型。

编译期根据 id 获取 type 的代码如下:

constexpr auto id_to_type( std::integral_constant<std::size_t, 6=""> ) noexcept { int res{}; return res; }constexpr auto id_to_type( std::integral_constant<std::size_t, 9=""> ) noexcept { char res{}; return res; }</std::size_t,></std::size_t,>

上面的代码中 id _ to _ type 返回的是 id 对应的类型的实例,如果要获取 id 对应的类型还需要通过 decltype 推导出来。magic _ get 通过一个宏将 pod 基本类型都做了一个编码,以实现 type 和 id 在编译期的相互转换。

#define REGISTER_TYPE(Type, Index)                                              \    constexpr std::size_t type_to_id(identity<type>) noexcept { return Index; } \    constexpr auto id_to_type( std::integral_constant<std::size_t, index=""> ) noexcept { Type res{}; return res; }  \// Register all base types here    REGISTER_TYPE(unsigned short    , 1)    REGISTER_TYPE(unsigned int      , 2)    REGISTER_TYPE(unsigned long long , 3)    REGISTER_TYPE(signed char       , 4)    REGISTER_TYPE(short             , 5)    REGISTER_TYPE(int               , 6)    REGISTER_TYPE(long long         , 7)    REGISTER_TYPE(unsigned char     , 8)    REGISTER_TYPE(char              , 9)    REGISTER_TYPE(wchar_t          , 10)    REGISTER_TYPE(long             , 11)    REGISTER_TYPE(unsigned long    , 12)    REGISTER_TYPE(void*            , 13)    REGISTER_TYPE(const void*      , 14)    REGISTER_TYPE(char16_t         , 15)    REGISTER_TYPE(char32_t         , 16)    REGISTER_TYPE(float             , 17)    REGISTER_TYPE(double           , 18)    REGISTER_TYPE(long double      , 19)</std::size_t,></type>

将类型编码之后,保存在哪里以及如何取出来是接着要解决的问题。magic _ get 通过定义一个 array 来保存结构体字段类型 id。

template <class t,="" std::size_t="" n="">   struct array {       typedef T type;       T data[N];       static constexpr std::size_t size() noexcept { return N; }   };</class>

array 中的定长数组 data 中保存字段类型对应的 id,数组下标就是字段在结构体中的位置索引。

萃取 pod 结构体字段

前面介绍了如何实现字段类型的保存和获取,那么这个字段类型是如何从 pod 结构体中萃取出来的呢?具体的做法分为三步:

  • 定义一个保存字段类型 id 的 array;
  • 将 pod 的字段类型转换为对应的 id,按顺序保存到 array 中;
  • 筛除 array 中多余的部分。

下面是具体实现代码:

template <class t="">constexpr auto fields_count_and_type_ids_with_zeros() noexcept {    static_assert(std::is_trivial<t>::value, "Not applyable");    array<std::size_t, sizeof(t)=""> types{};    detect_fields_count_and_type_ids<t>(types.data, std::make_index_sequence<sizeof(t)>{});    return types;}template <class t="">constexpr auto array_of_type_ids() noexcept {    constexpr auto types = fields_count_and_type_ids_with_zeros<t>();    constexpr std::size_t count = count_nonzeros(types);    array<std::size_t, count=""> res{};    for (std::size_t i = 0; i < count; ++i) {        res.data[i] = types.data[i];    }        return res;    }</std::size_t,></t></class></sizeof(t)></t></std::size_t,></t></class>

定义 array 时需要定义一个固定的数组长度,长度为多少合适呢?应按结构体最多的字段数来确定。因为结构体的字段数最多为 sizeof(T),所以 array 的长度设置为 sizeof(T)。array 中的元素全部初始化为0。一般情况下,结构体字段数一般不会超过 array 的长度,那么 array 中就就会出现多余的元素,所以还需要将 array 中多余的字段移除,只保存有效的字段类型 id。具体的做法是计算出 array 中非零的元素有多少,接着再把非零的元素赋给一个新的 array。下面是计算 array 非零元素个数,同样是借助 constexpr 实现编译期计算。

template <class array="">constexpr auto count_nonzeros(Array a) noexcept {    std::size_t count = 0;    for (std::size_t i = 0; i < Array::size() && a.data[i]; ++i)        ++ count;    return count;}</class>

由于字段是按顺序保存到 array 中的,所以在元素值为0时的 count 就是有效的元素个数。接下来我们来看看 detect _ fields _ count _ and _ type _ ids 的实现,这个 constexpr 函数将结构体中的字段类型 id 保存到 array 的 data 中。

detect_fields_count_and_type_ids<t>(types.data, std::make_index_sequence<sizeof(t)>{});</sizeof(t)></t>

detect _ fields _ count _ and _ type _ ids 的第一个参数为定长数组 array <std::size _ t, sizeof(T)> 的 data,第二个参数是一个 std::index _ sequence 整形序列。detect _ fields _ count _ and _ type _ ids 具体实现代码如下:

template <class t,="" std::size_t="" i0,="" std::size_t...="" i="">constexpr auto detect_fields_count_and_type_ids(std::size_t* types, std::index_sequence<i0, i...="">) noexcept-> decltype( type_to_array_of_type_ids<t, i0,="" i...="">(types) ){    return type_to_array_of_type_ids<t, i0,="" i...="">(types);}template <class t,="" std::size_t...="" i="">constexpr T detect_fields_count_and_type_ids(std::size_t* types, std::index_sequence<i...>) noexcept {    return detect_fields_count_and_type_ids<t>(types, std::make_index_sequence<sizeof...(i) -="" 1="">{});}template <class t="">constexpr T detect_fields_count_and_type_ids(std::size_t*, std::index_sequence<>) noexcept {    static_assert(!!sizeof(T), "Failed for unknown reason");    return T{};}</class></sizeof...(i)></t></i...></class></t,></t,></i0,></class>

上面的代码是为了将 index _ sequence 展开为 0,1,2..., sizeof(T) 序列,得到这个序列之后,再调用 type _ to _ array _ of _ type _ ids 函数实现结构体中的字段类型 id 保存到 array 中。

在讲 type _ to _ array _ of _ type _ ids 函数之前我们先看一下辅助结构体 ubiq。保存 pod 字段类型 id 实际上是由辅助结构体 ubiq 实现的,它的实现如下:

template <std::size_t i="">struct ubiq {    std::size_t* ref_;    template <class type="">    constexpr operator Type() const noexcept {        ref_[I] = type_to_id(identity<type>{});        return Type{};    }};</type></class></std::size_t>

这个结构体比较特殊,我们先把它简化一下。

struct ubiq {    template <class type="">    constexpr operator Type() const {        return Type{};    };};</class>

这个结构体的特殊之处在于它可以用来构造任意 pod 类型,比如 int、char、double 等类型。

int i = ubiq{};double d = ubiq{};char c = ubiq{};

因为 ubiq 构造函数所需要的类型由编译器自动推断出来,所以它能构造任意 pod 类型。通过 ubiq 结构体获取了需要构造的类型之后,我们还需要将这个类型转换为 id 按顺序保存到定长数组中。

template <std::size_t i="">struct ubiq {    std::size_t* ref_;    template <class type="">    constexpr operator Type() const noexcept {        ref_[I] = type_to_id(identity<type>{});        return Type{};    }};</type></class></std::size_t>

上面的代码中先将编译器推导出来的类型转换为 id,然后保存到数组下标为 I 的位置。

再回头看 type _ to _ array _ of _ type _ ids 函数。

template <class t,="" std::size_t...="" i="">constexpr auto type_to_array_of_type_ids(std::size_t* types) noexcept -> decltype(T{ ubiq<i>{types}... }) {    return T{ ubiq<i>{types}... };}</i></i></class>

type _ to _ array _ of _ type _ ids 有两个模版参数,第一个 T 是 pod 结构体的类型,第二个 size _ t...为0到 sizeof(T) 的整形序列,函数的入参为 size _ t*,它实际上是 array<std::size_t, sizeof(T)> 的 data,用来保存 pod 字段类型 id。

保存字段类型的关键代码是这一行:T{ ubiq〈I〉{types}... },这里利用了 pod 类型的构造函数,通过 initializer _ list 构造,编译器会将 T 的字段类型推导出来,并借助 ubiq 将字段类型转换为 id 保存到数组中。这个就是 magic _ get 中的 magic。

将 pod 结构体字段 id 保存到数组中之后,接下来就需要将数组中的 id 列表转换为 tuple 了。

pod 字段 id 序列转换为 tuple

pod 字段 id 序列转换为 tuple 的具体做法分为两步:

  • 将 array 中保存的字段类型 id 放入整形序列 std::index _ sequence;
  • 将 index _ sequence 中的类型 id 转换为对应的类型组成 tuple。

下面是具体的实现代码:

template <std::size_t i,="" class="" t,="" std::size_t="" n="">constexpr const T& get(const array<t,n>& a) noexcept {    return a.data[I];}template <class t,="" std::size_t...="" i="">constexpr auto array_of_type_ids_to_index_sequence(std::index_sequence<i...>) noexcept {    constexpr auto a = array_of_type_ids<t>();    return std::index_sequence< get<i>(a)...>{};}</i></t></i...></class></t,n></std::size_t>

get 是返回数组中某个索引位置的元素值,即类型 id,返回的 id 放入 std::index _ sequence 中,接着就是通过 index _ sequence 将 index _ sequence 中的 id 转换为 type,组成一个 tuple。

template <std::size_t... i="">constexpr auto as_tuple_impl(std::index_sequence<i...>) noexcept {    return std::tuple< decltype( id_to_type(std::integral_constant<std::size_t, i="">{}) )... >{};}template <class t="">constexpr auto as_tuple() noexcept {    static_assert(std::is_pod<t>::value, "Not applyable");    constexpr auto res = as_tuple_impl(            array_of_type_ids_to_index_sequence<t>(                    std::make_index_sequence< decltype(array_of_type_ids<t>())::size() >()            )    );    static_assert(sizeof(res) == sizeof(T), "sizes check failed");    static_assert(            std::alignment_of<decltype(res)>::value == std::alignment_of<t>::value,            "alignment check failed"    );    return res;}</t></decltype(res)></t></t></t></class></std::size_t,></i...></std::size_t...>

id _ to _ type 返回的是某个 id 对应的类型实例,所以还需要 decltype 来推导类型。这样我们就可以根据 T 来获取一个 tuple 类型了,接下来是要将 T 的值赋给 tuple,然后就可以根据索引来访问 T 的字段了。

pod 赋值给 tuple

对于 clang 编译器,pod 结构体是可以直接转换为 std::tuple 的,所以对于 clang 编译器来说,到这一步就结束了。

template <std::size_t i,="" class="" t="">decltype(auto) get(const T& val) noexcept {    auto t = reinterpret_cast<const decltype(detail::as_tuple<t="">())*>( std::addressof(val) );    return get<i>(*t);}</i></const></std::size_t>

然而,对于其他编译器,如 msvc 或者 gcc,tuple 的内存并不是连续的,不能直接将 T 转换为 tuple,所以更通用的做法是先做一个内存连续的 tuple,然后就可以将 T 直接转换为 tuple 了。

内存连续的 tuple

下面是实现内存连续的 tuple 代码:

template <std::size_t n,="" class="" t="">struct base_from_member {    T value;};template <class i,="" class="" ...tail="">struct tuple_base;template <std::size_t... i,="" class="" ...tail="">struct tuple_base< std::index_sequence<i...>, Tail... >        : base_from_member<i ,="" tail="">...{    static constexpr std::size_t size_v = sizeof...(I);    constexpr tuple_base() noexcept = default;    constexpr tuple_base(tuple_base&&) noexcept = default;    constexpr tuple_base(const tuple_base&) noexcept = default;    constexpr tuple_base(Tail... v) noexcept            : base_from_member<i, tail="">{ v }...    {}};template <>struct tuple_base<std::index_sequence<> > {    static constexpr std::size_t size_v = 0;};template <class ...values="">struct tuple: tuple_base<        std::make_index_sequence<sizeof...(values)>,        Values...>{    using tuple_base<            std::make_index_sequence<sizeof...(values)>,            Values...    >::tuple_base;};</sizeof...(values)></sizeof...(values)></class></std::index_sequence<></i,></i></i...></std::size_t...></class></std::size_t>

base _ from _ member 用来保存 tuple 元素的索引和值,tuple _ base 派生于 base _ from _ member,自动生成 tuple 中每一个类型的 base _ from _ member,tuple 派生于 tuple _ base 用来简化 tuple _ base 的定义。再给 tuple 增加一个根据索引获取元素的辅助方法。

template <std::size_t n,="" class="" t="">constexpr const T& get_impl(const base_from_member<n, t="">& t) noexcept {    return t.value;}template <std::size_t n,="" class="" ...t="">constexpr decltype(auto) get(const tuple<t...>& t) noexcept {    static_assert(N < tuple<t...>::size_v, "Tuple index out of bounds");    return get_impl<n>(t);}</n></t...></t...></std::size_t></n,></std::size_t>

这样就可以通过 get 就可以获取 tuple 中的元素了。

到此,magic _ get 的核心代码分析完了。由于实际的代码会更复杂,为了让读者能更容易看懂,我选取的是简化版的代码,完整的代码可以参考 GitHub 上的 magicget 或者简化版的代码https://github.com/qicosmos/cosmos/blob/master/podreflection.hpp

总结

magic _ get 实现了对 pod 类型的反射,可以直接通过索引来访问 pod 结构体的字段,而不需要任何额外的宏、标记或工具,确实很 magic。magic _ get 主要是通过 C++11/14 的可变模版参数、constexpr、index _ sequence、pod 构造函数以及很多模版元技巧实现的。那么 magic _ get 可以用来做些什么呢?根据 magic _ get 无需额外的负担和代码就可以实现编译期反射的特点,很适合做 ORM 数据库访问引擎和通用的序列化/反序列化库,我相信还有更多潜力和应用等待我们去发掘。

Modern C++ 的一些看似平淡无奇的特性组合在一起就能产生神奇的魔力,让人不禁赞叹 Modern C++ 蕴藏了无限的可能性与神奇。

C++17 中那些值得关注的特性(上)
C++17 中那些值得关注的特性(中)
C++17 中那些值得关注的特性 (下)

阅读全文: http://gitbook.cn/gitchat/geekbook/5a7a81b85a8e2a1cf2e2a993

2017-12-10 19:21:41 syyyy712 阅读数 8254

C++矩阵转置

 看了很多网山有关矩阵转置的代码,大部分还用了中间变量,本人亲测矩阵转置代码无误,望对广大C++初学者有所帮助!

题目如下:
写一个函数,使给定的一个二维数组(3x3)转置,即行列互换。

Input

一个3×3的矩阵

Output

转置后的矩阵(每两个数字之间均有一个空格)

Sample Input

1 2 3
4 5 6
7 8 9
Sample Output

1 4 7
2 5 8
3 6 9

代码如下:

#include <iostream>
#include <string>
#include <iomanip>
#include <vector>
#include <array>
#include <algorithm>
using namespace std;
//int a[3][3] = { {1,2,3}, {4,5,6}, {7,8,9} };
int a[3][3];
//int temp;
void main() {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            cin >> a[i][j];
            cout << " ";
        }
        cout << endl;
    }
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            cout << a[j][i]<<" ";   
        }
        cout << endl;
    }
}
先定义一个int 类型的3x3的矩阵a,然后用cin输入,cout输出,输入的时候是按照a[i][j]输入,输出的时候两个for循环还是位置不变,只要将a[i][j]变成a[j][i]输出即可,包含这么多头文件是因为习惯性先把可能用到的头文件尽可能都写进去,同时在输出的for循环内部for循环结束时用了一个cout << endl ,确保最后以矩阵的形式输出。

运行结果:
这里写图片描述

2015-09-28 09:58:54 a2008301610258 阅读数 4615

c++ OOP的一些总结

1 面向对象特点:

封装,继承,多态

2 抽象类

抽象类是特殊的类,只是不能被实例化(将定义了纯虚函数的类称为抽象类);除此以外,具有类的其他特性;抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须覆盖它们。另外,抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖,如果不覆盖,则其派生类必须覆盖它们。虽然不能定义抽象类的实例,但是可以定义它的指针,这正是用抽象类实现接口的重点所在。

class animal  //抽象类(有了纯虚函数才能成为抽象类)
{
public:
      virtual ~animal() = 0; //纯虚析构函数
      animal (int i = 0){x = i} //构造函数可有可无
      virtual void count(float a) = 0;  //纯虚函数
protected:
int x;
}
//纯虚函数不能实例化,只有在子类中才能实例化


总结:如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,就不要定义虚析构函数了,因为它会增加一个虚函数表,使得对象的体积翻倍,还有可能降低其可移值性。

3 虚函数

C++实现运行中的多态性是通过虚函数实现的,而虚函数必须存在于继承环境下。
 虚函数是重载的一种表现形式,是一种动态的重载方式。
只有类的普通成员函数可以定义为虚函数,全局函数及静态成员函数(类拥有)不能声明为虚函数。


只有用virtual声明类的成员函数,使之成为虚函数,不能将类外的普通函数声明为虚函数。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义。所以虚函数只能用于类的继承层次结构中。


根据什么考虑是否把一个成员函数声明为虚函数?
       ① 看成员函数所在的类是否会作为基类
       ② 看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。

4 纯虚函数

 virtual 函数类型 函数名(形参表列)=0;

抽象类中定义的,为了派生类中的使用而声明定义的,其在基类中没有任何意义

5 虚析构函数 纯虚析构函数

如果将基类的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不相同。
virtual ~A(void);  //虚析构函数
virtual ~A(void) = 0;  //纯虚析构函数 ,所在的类就为抽象类了

如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,就不要定义虚析构函数了,因为它会增加一个虚函数表,使得对象的体积翻倍,还有可能降低其可移值性。

定义一个函数为虚函数,不代表该函数未被实现,只是为了来实现多态。

定义一个函数为纯虚函数,才表示函数未被实现 ,定义它是为了实现一个接口,起一个规范作用。继承抽象类的派生类要实现这个函数…


6 接口

时候,我们得提供一些接口给别人使用。接口的作用,就是提供一个与其他系统交互的方法。其他系统无需了解你内部细节,并且也无法了解内部细节,只能通过你提供给外部的接口来与你进行通信。根据c++的特点,我们可以采用纯虚函数的方式来实现。这样做的好处是能够实现封装和多态。
//提供给外面使用的接口一般采用纯虚函数
实现接口是通过继承接口的子类来实现的,不同的子类可以实现不同效果,即所谓多态。

7 抽象类与接口

抽象类和接口的区别:
      (1).类是对对象的抽象,可以把抽象类理解为把类当作对象,抽象成的类叫做抽象类.而接口只是一个行为的规范或规定,微软的自定义接口总是后带able字段,证明其是表述一类类“我能做。。。”.抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中. 
      (2).接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法;     
      (3).一个类一次可以实现若干个接口,但是只能扩展一个父类     
      (4).接口可以用于支持回调,而继承并不具备这个特点.     
      (5).抽象类不能被密封。   
      (6).抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的. 
      (7).(接口)与非抽象类类似,抽象类也必须为在该类的基类列表中列出的接口的所有成员提供它自己的实现。但是,允许抽象类将接口方法映射到抽象方法上。   
      (8).抽象类实现了oop中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的座位子类去实现。   
      (9).好的接口定义应该是具有专一功能性的,而不是多功能的,否则造成接口污染。如果一个类只是实现了这个接口的中一个功能,而不得不去实现接口中的其他方法,就叫接口污染。   
      (10).尽量避免使用继承来实现组建功能,而是使用黑箱复用,即对象组合。因为继承的层次增多,造成最直接的后果就是当你调用这个类群中某一类,就必须把他们全部加载到栈中!后果可想而知.(结合堆栈原理理解)。同时,有心的朋友可以留意到微软在构建一个类时,很多时候用到了对象组合的方法。比如asp.net中,Page类,有Server Request等属性,但其实他们都是某个类的对象。使用Page类的这个对象来调用另外的类的方法和属性,这个是非常基本的一个设计原则。   
      (11).如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法.

8.封装

封装就是通过权限来限制类中的代码外界无法看到更无法更改;
封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!
即 
1)公有( public )成员可以在类外访问。   
2)私有( private )成员只能被该类的成员函数访问。   
3)保护( protected )成员只能被该类的成员函数或派生类的成员函数访问。


 












15个C++项目列表

阅读数 31027

C++常用内置函数

阅读数 2894

C++实现简单二叉树

阅读数 5219

C++实现简单二叉树

博文 来自: qq_1844548689

C++项目经验总结

阅读数 5475

没有更多推荐了,返回首页