作者 | 罗能(知乎 id:@netcan)
整理 | 编程语言 Lab

花了我 3 个晚上才搞定,结论是目前 C++ 的 Ranges 标准库 [1] 对于实现 复杂的程序还不够用 ,提供的 views 适配器组合子也仅仅限于简单的 filter/transform 等,还未提供标准的方式让用户去定义组合子(不过这个问题目前 C++23 已经有提案 P2387R0 [2] 在做了)。

完整可编译可运行的日历程序请见:https://godbolt.org/z/overc6q51,效果如下(2021 年):

       January               February               March                 AprilSu Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa1  2      1  2  3  4  5  6      1  2  3  4  5  6               1  2  33  4  5  6  7  8  9   7  8  9 10 11 12 13   7  8  9 10 11 12 13   4  5  6  7  8  9 1010 11 12 13 14 15 16  14 15 16 17 18 19 20  14 15 16 17 18 19 20  11 12 13 14 15 16 1717 18 19 20 21 22 23  21 22 23 24 25 26 27  21 22 23 24 25 26 27  18 19 20 21 22 23 2424 25 26 27 28 29 30  28                    28 29 30 31           25 26 27 28 29 3031May                   June                  July                 AugustSu Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa1         1  2  3  4  5               1  2  3   1  2  3  4  5  6  72  3  4  5  6  7  8   6  7  8  9 10 11 12   4  5  6  7  8  9 10   8  9 10 11 12 13 149 10 11 12 13 14 15  13 14 15 16 17 18 19  11 12 13 14 15 16 17  15 16 17 18 19 20 2116 17 18 19 20 21 22  20 21 22 23 24 25 26  18 19 20 21 22 23 24  22 23 24 25 26 27 2823 24 25 26 27 28 29  27 28 29 30           25 26 27 28 29 30 31  29 30 3130 31September              October               November              DecemberSu Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa1  2  3  4                  1  2      1  2  3  4  5  6            1  2  3  45  6  7  8  9 10 11   3  4  5  6  7  8  9   7  8  9 10 11 12 13   5  6  7  8  9 10 1112 13 14 15 16 17 18  10 11 12 13 14 15 16  14 15 16 17 18 19 20  12 13 14 15 16 17 1819 20 21 22 23 24 25  17 18 19 20 21 22 23  21 22 23 24 25 26 27  19 20 21 22 23 24 2526 27 28 29 30        24 25 26 27 28 29 30  28 29 30              26 27 28 29 30 3131

日历程序问题分解

最终输出的结果是字符串,问题域如下:

  • 以年为单位,一年 355/356 天,如何将每天按月、按周分组?(提示:group_by
  • 每个月每行输出的是 7 天,每天占宽 3 个字节,加一个填充位,总共 3 * 7 + 1 = 22 字节
  • 每个月显示一行月份标题,一行星期标题,最多 6 周(6 行),不足 6 行的补齐空行到 6 行,因此一个月的字符矩阵为 (6+2) x 22
  • 如何一行显示 N 个月的字符串信息?(提示:Chunks 分块)
  • 假设一行显示 3 个月的字符串信息,一行总长度就是 22 x 3,如果以月为维度,那么矩阵大小是 3 x 8 x 22(提示:矩阵转置),最终的字符矩阵为 4 x 3 x 8 x 22
  • 上一步转置之后矩阵大小为 4 x 8 x 3 x 22,如何降维处理?(提示:join),得到 32 x 3 x 22
  • 如何对低维的 3 x 22 进行降维处理(字符串拼接),得到一行长度为 66 的字符串?(提示:transform/join
  • 如何输出最终的字符矩阵 32 x 66?(提示:std::cout

如果让你编写这个程序,如何编写?(似乎作为一个面试题不错)是不是涉及一大堆循环、分支代码?涉及一大堆迭代变量(状态)?

这就是常规的 命令式(面向过程)编程风格。

我正好找到了一个使用命令式编程风格的日历实现,https://github.com/karelzak/util-linux/blob/master/misc-utils/cal.c,里面的最大嵌套深度居然达到了 5 层(cal_vert_output_months函数),一个人每面对一层嵌套,理解难度成倍增长。分支与循环引入的迭代状态与过深的嵌套层数,漫无边际的细节与面条式代码,不仅容易出错且非常难读。

而Ranges标准库使用函数式编程风格,它的最大优势是无状态编程,如果一个程序的状态越少越容易推理正确性越容易证明( bug 越少)。另一个优势在于行为的灵活组合能力,每一步操作(组合子)都是原子与抽象可复用可组合的动作,对每一层的 Range 处理不会引入额外的嵌套,利用这些原子动作可以组合出任意强大的程序。

使用 Ranges 标准库实现

如下是我使用 C++20 标准重写的代码,欣赏一下。这份代码参考了 range-v3 库(Ranges 标准库的基础)中的例子,具体思想可参考原作者于 CppCon 2015 年的演讲[3]。

int main(int argc, char** argv) {auto formatedCalendar= datesBetween(2021, 2022)| by_month()| layout_months()| chunk(3)| transpose_months()| views::join| join_months();ranges::copy(formatedCalendar,std::ostream_iterator<std::string_view>(std::cout, "\n"));return 0;
}auto datesBetween(unsigned short start, unsigned short stop) {return views::iota(Date{start, 1, 1}, Date{stop, 1, 1});
}auto by_month() {return group_by([](Date a, Date b) { return a.month() == b.month(); });
}auto by_week() {return group_by([](Date a, Date b) { return (++a).week_number() == (++b).week_number(); });
}// TODO: c++20 std::format
std::string month_title(const Date& d) {std::string title(22, ' ');std::string_view name = d.monthName();ranges::copy(name, ranges::begin(title) + (22 - name.length()) / 2);return title;
}// TODO: c++20 std::format
std::string format_day(const Date& d) {char res[4];sprintf(res, "%3d", (int)d.day());return res;
}// in: range<range<Date>>
// out: range<std::string>
auto format_weeks() {return views::transform([](/*range<Date>*/auto&& week) {std::string weeks((*ranges::begin(week)).dayOfWeek() * 3, ' ');weeks += (week | views::transform(format_day) | join);while (weeks.length() < 22) weeks.push_back(' ');return weeks;});
}// in: range<range<Date>>
// out: range<range<std::string>>
auto layout_months() {return views::transform([](/*range<Date>*/ auto&& month) {auto week_count = ranges::distance(month | by_week());return concat(views::single(month_title(*ranges::begin(month))),views::single(std::string("Su Mo Tu We Th Fr Sa")),month | by_week() | format_weeks(),repeat_n(std::string(22, ' '), 6 - week_count));});
}// In:  range<range<range<string>>>
// Out: range<range<range<string>>>, transposing months.
auto transpose_months() {return views::transform([](/*range<range<string>>*/ auto&& rng) {return rng | transpose;});
}// In:  range<range<string>>
// Out: range<string>, joining the strings of the inner ranges
auto join_months()
{return views::transform([](/*range<string>*/ auto rng) { return join(rng); });
}

正因为 Ranges 标准库的实现使用了元编程技术,性能并不比命令式的编程风格差。参考最终的汇编代码生成部分,只有少数几行代码涉及生成,其他代码 都被优化掉 了(开启 O2 优化,高亮部分为参与代码生成的部分)。

另一个优势在于 Range 的实现是延迟计算的,并且多次组合的背后可能仅仅迭代一次 Range。

生成器(generator)作为协程(coroutine)的一种特殊场景,也能够和 Ranges 进行组合。

最终的二进制大小仅仅为 52KB,系统自带的日历程序大小为40KB。

遇到的一些问题

遇到比较麻烦的问题是编译错误信息,组合后的类型相当长,一旦出错,告警提示的类型将淹没你所需要关注的点。这可能是静态类型语言的弱势。

例如表达式 iota(1, 100) | views::transform([](auto x){ return x * 2; }); 的类型为 std::ranges::transform_view<std::ranges::iota_view<int, int>, main(int, char**)::<lambda(auto)> >,一旦报错显示的类型相当长。

如下是我在编译过程中遇到类型爆炸的信息,简洁的背后是有一定代价的。

更多实现细节

日期 Date 类的实现比较 tricky,我的实现细节如下。

// Date
struct Date {using difference_type = std::ptrdiff_t;Date() = default;Date(uint16_t year, uint16_t month, uint16_t day):days(dayNumber(year, month, day)) { }friend bool operator==(const Date&, const Date&) = default;Date& operator++()            { ++days; return *this;}Date operator++(int)          { Date tmp(*this); ++*this; return tmp; }uint16_t day() const          { return fromDayNumber().day;           }uint16_t month() const        { return fromDayNumber().month;         }const char* monthName() const { return MONTH_NAME[month()];           }uint16_t year() const         { return fromDayNumber().year;          }uint16_t week_number() const {auto beginDays = dayNumber(year(), 1, 1);unsigned long day = (beginDays + 3) % 7;unsigned long week = (days + day - beginDays + 4)/7;if ((week>= 1) && (week <= 52)) { return static_cast<int>(week); }if (week == 53) {if((day==6) ||(day == 5 && isLeapYear(year()))) {return week; //under these circumstances week == 53.} else {return 1; //monday - wednesday is in week 1 of next year}}//if the week is not in current year recalculate using the previous year as the beginning yearelse if (week == 0) {beginDays = dayNumber(year()-1,1,1);day = (beginDays + 3) % 7;week = (days + day - beginDays + 4)/7;return week;}return week;  //not reachable -- well except if day == 5 and is_leap_year != true}uint16_t dayOfWeek() const {uint16_t a = static_cast<uint16_t>((14-month())/12);uint16_t y = static_cast<uint16_t>(year() - a);uint16_t m = static_cast<uint16_t>(month() + 12*a - 2);uint16_t d = static_cast<uint16_t>((day() + y + (y/4) - (y/100) + (y/400) + (31*m)/12) % 7);return d;}private:constexpr bool isLeapYear(uint16_t year) const {return (!(year % 4))  && ((year % 100) || (!(year % 400)));}struct ymd {uint16_t year;uint16_t month;uint16_t day;};constexpr long dayNumber(uint16_t year, uint16_t month, uint16_t day) const {uint16_t a = static_cast<uint16_t>((14-month)/12);uint16_t y = static_cast<uint16_t>(year + 4800 - a);uint16_t m = static_cast<uint16_t>(month + 12*a - 3);return day + ((153*m + 2)/5) + 365*y + (y/4) - (y/100) + (y/400) - 32045;}constexpr ymd fromDayNumber() const {uint32_t a = days + 32044;uint32_t b = (4*a + 3)/146097;uint32_t c = a-((146097*b)/4);uint32_t d = (4*c + 3)/1461;uint32_t e = c - (1461*d)/4;uint32_t m = (5*e + 2)/153;uint16_t day = static_cast<uint16_t>(e - ((153*m + 2)/5) + 1);uint16_t month = static_cast<uint16_t>(m + 3 - 12 * (m/10));uint16_t year = static_cast<uint16_t>(100*b + d - 4800 + (m/10));return {year,month,day};}static constexpr const char* MONTH_NAME[] = {"","January","February","March","April", "May", "June", "July","August", "September", "October","November", "December",};long days;
};

由于 Ranges 标准库支持的 views 适配器组合子比较少,最后附上自己实现的几个组合子,目前没有标准方式去定义组合子,所以实现比较 tricky,感兴趣的可以看看。

///
// group_by_view
template<ranges::viewable_range Rng, typename Pred>
struct group_by_view: ranges::view_interface<group_by_view<Rng, Pred>> {group_by_view() = default;group_by_view(Rng r, Pred p): r(std::move(r)), p(std::move(p)) {}private:struct GroupIterator {using difference_type = std::ptrdiff_t;using value_type = ranges::subrange<ranges::iterator_t<Rng>>;GroupIterator& operator++() {cur = next_cur;if (cur != last) {next_cur = ranges::find_if_not(ranges::next(cur), last, [&](auto&& elem){return p(*cur, elem); });}return *this;}GroupIterator operator++(int) {GroupIterator tmp(*this);++*this;return tmp;}value_type operator*() const { return {cur, next_cur}; }friend bool operator==(const GroupIterator& iter, std::default_sentinel_t){return iter.cur == iter.last;}// C++20 defaulted comparison operatorsfriend bool operator==(const GroupIterator& lhs, const GroupIterator& rhs) = default;Pred p;ranges::iterator_t<Rng> cur{};ranges::iterator_t<Rng> next_cur{};ranges::sentinel_t<Rng> last{};};
public:GroupIterator begin() {auto beg = ranges::begin(r);auto end = ranges::end(r);return {p, beg,ranges::find_if_not(ranges::next(beg), end,[&](auto&& elem) { return p(*beg, elem); }),end};}std::default_sentinel_t end() { return {}; }Rng r;Pred p;
};#if __GNUC__ > 10
struct _Group_by: views::__adaptor::_RangeAdaptor<_Group_by> {template<ranges::viewable_range Rng, typename Pred>constexpr auto operator()(Rng&& r, Pred&& p) const {return group_by_view{std::forward<Rng>(r), std::forward<Pred>(p)};}static constexpr int _S_arity = 2;using views::__adaptor::_RangeAdaptor<_Group_by>::operator();
};
inline constexpr _Group_by group_by;
#else
inline constexpr views::__adaptor::_RangeAdaptor group_by= [] <ranges::viewable_range Rng, typename Pred> (Rng&& r, Pred&& p) {return group_by_view{std::forward<Rng>(r), std::forward<Pred>(p)};
};
#endif///
// concat_view
template<ranges::viewable_range... Rngs>
requires (sizeof...(Rngs) > 1)
struct concat_view {static constexpr size_t nRngs = sizeof...(Rngs);using RNGs = std::tuple<Rngs...>;concat_view() = default;explicit concat_view(Rngs... rngs): rngs{std::move(rngs)...}{ }struct ConcatIterator {using difference_type = std::ptrdiff_t;using value_type = std::common_type_t<ranges::range_reference_t<Rngs>...>;ConcatIterator() = default;ConcatIterator(RNGs* rngs): rngs(rngs){its.template emplace<0>(ranges::begin(std::get<0>(*rngs))); }ConcatIterator& operator++() {// TODO: check empty range, skip itstd::visit([&](auto&& iter) {constexpr size_t idx = iter.value;if ((iter.iterator = ranges::next(iter.iterator))== ranges::end(std::get<idx>(*rngs)) ) {if constexpr (idx + 1 < nRngs) {its.template emplace<idx + 1>(ranges::begin(std::get<idx + 1>(*rngs)));}}}, its);return *this;}ConcatIterator operator++(int) {ConcatIterator tmp(*this);++*this;return tmp;}using reference = std::common_reference_t<ranges::range_reference_t<Rngs>...>;reference operator*() const {return std::visit([](auto&& iter) -> reference {return *iter.iterator;}, its);}friend bool operator==(const ConcatIterator& iter, std::default_sentinel_t) {return iter.its.index() == nRngs - 1 &&(std::get<nRngs - 1>(iter.its).iterator ==ranges::end(std::get<nRngs - 1>(*iter.rngs)));}friend bool operator==(const ConcatIterator& lhs, const ConcatIterator& rhs) = default;private:template<size_t N, typename Rng>struct IteratorWithIndex: std::integral_constant<size_t, N> {IteratorWithIndex() = default;IteratorWithIndex(ranges::iterator_t<Rng> iterator):iterator(std::move(iterator)) {}ranges::iterator_t<Rng> iterator;friend bool operator==(const IteratorWithIndex& lhs, const IteratorWithIndex& rhs) = default;};template<size_t ...Is>static constexpr auto iteratorVariantGenerator(std::index_sequence<Is...>)-> std::variant<IteratorWithIndex<Is, std::tuple_element_t<Is, RNGs>>...>;decltype(iteratorVariantGenerator(std::make_index_sequence<nRngs>{})) its;RNGs* rngs {};};ConcatIterator begin() { return {&this->rngs}; }std::default_sentinel_t end() { return {}; }private:RNGs rngs;
};#if __GNUC__ > 10
struct _Concat: views::__adaptor::_RangeAdaptor<_Concat> {template<ranges::viewable_range... Rngs>constexpr auto operator()(Rngs&&... rngs) const {return concat_view{std::forward<Rngs>(rngs)...};}using views::__adaptor::_RangeAdaptor<_Concat>::operator();
};inline constexpr _Concat concat;
#else
inline constexpr views::__adaptor::_RangeAdaptor concat= [] <ranges::viewable_range... Rngs> (Rngs&&... rngs) {return concat_view{std::forward<Rngs>(rngs)...};
};
#endif
///
// repeat_n
template<typename Value>
struct repeat_n_view: ranges::view_interface<repeat_n_view<Value>> {repeat_n_view() = default;repeat_n_view(size_t n, Value value): n(n), value(std::move(value)) { }private:struct RepeatIterator {using difference_type = std::ptrdiff_t;using value_type = Value;RepeatIterator& operator++() { --n; return *this;}RepeatIterator operator++(int) {RepeatIterator tmp(*this);++*this;return tmp;}decltype(auto) operator*() const { return *value; }friend bool operator==(const RepeatIterator& iter, std::default_sentinel_t){return iter.n == 0;}friend bool operator==(const RepeatIterator& lhs, const RepeatIterator& rhs) = default;size_t n{};Value* value{};};public:RepeatIterator begin() { return {n, &value}; }std::default_sentinel_t end() { return {}; }private:size_t n;Value value;
};inline constexpr auto repeat_n = []<typename Value>(Value&& value, size_t n) {return repeat_n_view{n, std::forward<Value>(value)};
};///
// join
#if __GNUC__ > 10
struct _Join: views::__adaptor::_RangeAdaptorClosure {template<ranges::viewable_range Rng>constexpr auto operator()(Rng&& rng) const {using RangeValue = ranges::range_value_t<Rng>;using JoinResult = std::conditional_t<ranges::range<RangeValue> && !ranges::view<RangeValue>, // is container?RangeValue,std::vector<ranges::range_value_t<RangeValue>>>;JoinResult res;for (auto&& r: rng) {res.insert(ranges::end(res),ranges::begin(r),ranges::end(r));}return res;}
};
inline constexpr _Join join;
#else
inline constexpr views::__adaptor::_RangeAdaptorClosure join= [] <ranges::viewable_range Rng> (Rng&& rng) {using RangeValue = ranges::range_value_t<Rng>;using JoinResult = std::conditional_t<ranges::range<RangeValue> && !ranges::view<RangeValue>, // is container?RangeValue,std::vector<ranges::range_value_t<RangeValue>>>;JoinResult res;for (auto&& r: rng) {res.insert(ranges::end(res),ranges::begin(r),ranges::end(r));}return res;
};
#endif///
// chunk_view
template<ranges::viewable_range Rng>
struct chunk_view: ranges::view_interface<chunk_view<Rng>> {chunk_view() = default;chunk_view(Rng r, size_t n): r(std::move(r)), n(n) {}private:struct ChunkIterator {using difference_type = std::ptrdiff_t;using value_type = ranges::subrange<ranges::iterator_t<Rng>>;ChunkIterator& operator++() {cur = next_cur;if (cur != last) {next_cur = ranges::next(cur, n, last);}return *this;}ChunkIterator operator++(int) {ChunkIterator tmp(*this);++*this;return tmp;}value_type operator*() const { return {cur, next_cur}; }friend bool operator==(const ChunkIterator& iter, std::default_sentinel_t){return iter.cur == iter.last;}friend bool operator==(const ChunkIterator& lhs, const ChunkIterator& rhs) {return lhs.cur == rhs.cur;}size_t n{};ranges::iterator_t<Rng> cur{};ranges::iterator_t<Rng> next_cur{};ranges::sentinel_t<Rng> last{};};
public:ChunkIterator begin() {auto beg = ranges::begin(r);auto end = ranges::end(r);return {n, beg, ranges::next(beg, n, end), end};}std::default_sentinel_t end() { return {}; }Rng r;size_t n;
};#if __GNUC__ > 10
struct _Chunk: views::__adaptor::_RangeAdaptor<_Chunk> {template<ranges::viewable_range Rng>constexpr auto operator()(Rng&& r, size_t n) const {return chunk_view{std::forward<Rng>(r), n};}static constexpr int _S_arity = 2;using views::__adaptor::_RangeAdaptor<_Chunk>::operator();
};
inline constexpr _Chunk chunk;
#else
inline constexpr views::__adaptor::_RangeAdaptor chunk= [] <ranges::viewable_range Rng> (Rng&& r, size_t n) {return chunk_view{std::forward<Rng>(r), n};
};
#endif///
// interleave_view
template<ranges::viewable_range Rng>
struct interleave_view: ranges::view_interface<interleave_view<Rng>> {using RNGs = std::vector<ranges::range_value_t<Rng>>;interleave_view() = default;interleave_view(Rng rng) {for(auto&& r: rng) { rngs.emplace_back(r); }}private:struct InterleaveInterator {using difference_type = std::ptrdiff_t;using value_type = ranges::range_value_t<ranges::range_value_t<Rng>>;InterleaveInterator() = default;InterleaveInterator(RNGs* rngs): rngs(rngs) {for (auto&& r: *rngs) {its.emplace_back(ranges::begin(r));}}InterleaveInterator& operator++() {if (((++n) %= its.size()) == 0) {ranges::for_each(its, [](auto&& iter) {iter = ranges::next(iter);});}return *this;}InterleaveInterator operator++(int) {InterleaveInterator tmp(*this);++*this;return tmp;}decltype(auto) operator*() const { return *its[n]; }friend bool operator==(const InterleaveInterator& iter, std::default_sentinel_t) {if (iter.n != 0) return false;auto ends = *iter.rngs | views::transform(ranges::end);return ranges::mismatch(iter.its, ends, std::equal_to<>{}).in1 == iter.its.end();}friend bool operator==(const InterleaveInterator& lhs, const InterleaveInterator& rhs) = default;size_t n{};std::vector<ranges::iterator_t<ranges::range_value_t<Rng>>> its;RNGs* rngs{};};
public:InterleaveInterator begin() { return {&rngs}; }std::default_sentinel_t end() { return {}; }RNGs rngs;
};#if __GNUC__ > 10
struct _Interleave: views::__adaptor::_RangeAdaptorClosure {template<ranges::viewable_range Rng>constexpr auto operator()(Rng&& r) const {return interleave_view{std::forward<Rng>(r)};}
};
inline constexpr _Interleave interleave;
#else
inline constexpr views::__adaptor::_RangeAdaptorClosure interleave= [] <ranges::viewable_range Rng> (Rng&& r) {return interleave_view{std::forward<Rng>(r)};
};
#endif///
// transpose
#if __GNUC__ > 10
struct _Transpose: views::__adaptor::_RangeAdaptorClosure {template<ranges::viewable_range Rng>constexpr auto operator()(Rng&& r) const {return r | interleave | chunk(ranges::distance(r));}
};
inline constexpr _Transpose transpose;
#else
inline constexpr views::__adaptor::_RangeAdaptorClosure transpose= [] <ranges::viewable_range Rng> (Rng&& r) {return r | interleave | chunk(ranges::distance(r));
};
#endif

参考

[1] C++20 Ranges library https://en.cppreference.com/w/cpp/ranges
[2] C++23 Proposal P2387R0: Pipe support for user-defined range adaptors http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2387r0.html
[3] CppCon 2015: Eric Niebler “Ranges for the Standard Library” https://www.youtube.com/watch?v=mFUXNMfaciE

技术分享 | 使用 C++20 Ranges 标准库实现日历程序相关推荐

  1. 基于STM32标准库的MS5837程序移植

    基于STM32标准库的MS5837程序移植 一.准备工作 1. 硬件电路 2. 新建工程 二.开始移植 1. IIC底层模拟 2. MS5837移植 3. 主函数编写 4. 代码调试结果 三.源代码下 ...

  2. 【博学谷学习记录】超强总结,用心分享 | 人工智能编程语言Python常用标准库(上)

    Python常用标准库 上 sys库 time库 random库 math库 os库 shutil库 Python语言的急速发展很大程度上得益于其开放共享的特点和良好的社区支持和计算生态,拥有超过十几 ...

  3. C++各大有名库的介绍之C++标准库

    C++各大有名库的介绍之C++标准库 标准库中提供了C++程序的基本设施.虽然C++标准库随着C++标准折腾了许多年,直到标准的出台才正式定型,但是在标准库的实现上却很令人欣慰得看到多种实现,并且已被 ...

  4. C++库介绍-标准库、类库

    目录: 1.标准库 2.GUI库 3.网络通信 4.XML 5.科学计算 6.游戏开发 7.线程 8.序列化 9.字符串 10.综合 11.其他 12.C++重要人物网站 C++类库介绍 再次体现了C ...

  5. C++著名类库和C++标准库介绍

    C++著名类库 1.C++各大有名库的介绍--C++标准库  2.C++各大有名库的介绍--准标准库Boost  3.C++各大有名库的介绍--GUI  4.C++各大有名库的介绍--网络通信  5. ...

  6. 【C++深度剖析教程9】初探C++标准库

    在这之前,我写的C++程序不能叫做标准的C++程序,因为里面写的大多数还带有C语言的影子.今天我们来学习C++标准库. 首先看一下例子:操作符<<的原生意义是按位左移.那么我们重载这个操作 ...

  7. C/C++标准库到底是什么?

    C/C++ 标准库 在学习 C/C++ 的日子里,我们经常会有一个困惑:我们在代码里使用的标准库函数和类都是哪里来的?谁实现了它们?它们是打包好在操作系统里了吗?有没有官方的 C/C++ 手册呢? 在 ...

  8. c运行库、c标准库、windows API的区别和联系

    c运行库.c标准库.windows API的区别和联系 C运行时库函数 C运行时库函数是指C语言本身支持的一些基本函数,通常是汇编直接实现的.    API函数 API函数是操作系统为方便用户设计应用 ...

  9. c运行库、c标准库、windows API都是什么玩意

    c运行库.c标准库.windows API都是什么玩意 2012-11-28 14:37 768人阅读 评论(2) 收藏 举报 C运行库和C标准库的关系 C标准库,顾名思义既然是标准,就是由标准组织制 ...

最新文章

  1. base64格式的图片数据如何转成图片
  2. 另辟蹊径,中科院自动化所等首次用图卷积网络解决语义分割难题
  3. 为什么 Java 线程没有 Running 状态?
  4. MOVE-CORRESPONDING 应该注意的语法特点
  5. 东京战纪服务器维护中,东京战纪7月21维护公告 当前测试进度介绍
  6. 代码整理工具_整理了 11 个好用的代码质量审核和管理工具
  7. 贾跃亭致信债权人:将努力打工还债,请相信我!
  8. 关于javascript的原型和原型链,看我就够了(一)
  9. mysql gis 高德_基于高德自定义地图数据的GIS矢量地图制作
  10. 既能被2又能被5整C语言,2012年国研究生统一考试心理学专业试题与答案
  11. 斐那契波黄金数列MATLAB,广义斐波那契数列的性质及推广
  12. python 验证码识别
  13. python批量修改照片的大小
  14. 自制PMW3901光流模块
  15. Linux网卡模块,裁剪Linux并实现网卡模块的安装(附有命令移植的脚本)
  16. 仓库温控管理系统(一)系统概述
  17. 解决HTML5页面在手机浏览器测试中发现 横向滚动条,尽管页面没有内容也是照常出现。
  18. 使用 Python 自动识别防疫二维码
  19. 使用KVM虚拟机安装Linux
  20. 软件模块化定制将造成传统软件消失?

热门文章

  1. 马化腾凌晨网上发问,他的问题事关未来十年大事!
  2. VSCode集成Git
  3. 设计图像素和开发像素_游戏开发的像素艺术设计
  4. 植物大战僵尸中文版 for Mac v1.2 好玩的怀旧游戏
  5. 如何用服务器文件管理系统记录共享文件的访问日志、记录共享文件复制、修改、删除和剪切等操作?...
  6. 解决从微信公众号复制图片显示“此图片来自微信公众平台未经允许不可引用”的问题...
  7. 古诗词成语故事权重站打造网站模板
  8. 使用vue+textarea的属性maxlength制作一个文本框字数限制
  9. 浪潮服务器linux下升级固件,补丁包下载
  10. 文件管理-Linux系统压缩打包