计算机 · 2021年12月4日 0

C++拾遗

重载

运算符重载

运算符重载的一边形式:

type operator sign (parameters) { /*... body ...*/ }

异常

https://en.cppreference.com/w/cpp/error/exception

类名 std::exception
头文件 <exception>

重要成员函数:

virtual const char* what() const throw(); 	// until C++11
virtual const char* what() const noexcept; 	// since C++11

throw

  • throw expression
    抛出一个异常。但是这里抛出的对象可以是任何类型,不限于exception,只要catch的时候类型匹配即可。
  • throw
    再次抛出当前捕获的异常

常用pattern:

try {
} catch(...) {
}

try {
} catch(std::exception &e) {
}

这个throw关键字可以抛任何类型的对象,只要catch语句中能够接住这个对象。比如你甚至可以throw 0https://stackoverflow.com/questions/2628677/what-does-throw-0-do-mean-is-it-bad 。

强制类型转换操作符

https://blog.csdn.net/bian_qing_quan11/article/details/70788312

dynamic_cast

https://www.cnblogs.com/xiangtingshen/p/10851851.html

用于将基类指针/引用转换为子类指针/引用,如果可以基类指针/引用指向的是对应的子类对象,则正常返回指针,否则返回空指针/抛出异常(因为引用没有空值)。

原理:dynamic_cast使用RTTI来检查指针指向的对象能否合法转换成指定的类型,因此如果基类没有虚函数,那么指向的对象就没有虚表,dynamic_cast就会无法工作,在编译时就会报错。

static_cast

https://www.cnblogs.com/xiangtingshen/p/10851349.html

完成编译器认可的隐式转换,告诉编译器这些转换是自己想要的,不用报warning了。

const_cast

https://www.cnblogs.com/ider/archive/2011/07/22/cpp_cast_operator_part2.html

去除变量的cv限定符,用于将带const的对象变为不带const的对象,以作为某些函数的不带const限定符的参数,而不是为了去修改原来就是const的对象。

reinterpret_cast

强制转换,爱咋咋地。

其他

return语句中临时对象的生成时间在局部变量的析构函数执行之前。

输入输出流

忽略无效输入

cin.ignore(numeric_limits<streamsize>::max(), '\n');

stringstream

使用peek函数和ignore函数:

stringstream ss(str);
int a;
vector<int> vec;

while (ss >> a){
    vec.push_back(a);

    if (ss.peek() == ','){
        ss.ignore();
    }
}

return vec;

或者判断每次读入是否成功:

    stringstream ss(str);
    int val;
    char tmp;
    vector<int> res;
    ss >> val;
    res.push_back(val);
    while (ss >> tmp) {
        ss >> val;
        res.push_back(val);
    }
    return res;

输入输出流有重载bool函数以表示当前输入输出流是否发生错误

// until c++11
operator void*() const;
// since c++11
explicit operator bool() const;

输入输出的格式化

和输入输出格式化相关的函数:

  • copyfmt
    basic_ios& copyfmt(const basic_ios& other);
    从另一个输入输出流复制除rdstate、exception mask、rdbuf之外的所有东西;
  • fill
    CharT fill() const;
    返回当前使用的fill character;
    CharT fill( CharT ch );
    更改使用的fill character,并返回之前使用的fill character;
  • setfill
    template< class CharT > /*unspecified*/ setfill( CharT c );
    用在out << ... << setfill(c) << ...这种场景的时候就等价于out.fill(c),即改变流的fill character;
  • flags
    fmtflags flags() const;
    返回当前使用的格式化配置;
    fmtflags flags(fmtflags flags);
    更改格式化配置;
    有效的fmtflags:
ConstantExplanation
dec输出整数时使用10进制
oct输出整数时使用8进制
hex输出整数时使用16进制
basefield`dec
left输出时左对齐
right输出时右对齐
internal输出时把fill character填中间,而不是左、右对齐时填在右边、左边
scientific输出时使用科学计数法,默认有效数字里有6位小数;如果还指定了fixed,那么输出的会是一个hexfloat(居然从来没听说过这种格式。。。)
fixed输出浮点数时会补0成6位小数;如果还指定了fixed,那么输出的会是一个hexfloat
floatfield`scientific
boolalpha设置输出布尔类型时是输出true/false还是1/0
showbase设置输出数字时是不是要加上0x0这种东西来表明数字的进制
showpoint/noshowpoint可以通过设置noshowpoint把1.000这种浮点数显示成1
showpos设置是否要在非负数前面显示一个+
skipws设置读取输入时是否要跳过空白字符
unitbuf让每个输出都实时flush,否则可能就会等到输出换行这种操作时才会flush显示输出
uppercase输出字符时更换为大写字符
  • setf
    fmtflags setf( fmtflags flags );
    设置格式化的flag,等价于fl = fl | flags
    fmtflags setf( fmtflags flags, fmtflags mask );
    设置指定位上的flag,等价于fl = (fl & ~mask) | (flags & mask)
  • unsetf
    void unsetf( fmtflags flags );
    清除格式化的flag;
  • precision
    streamsize precision() const;
    获取当前会为浮点数输出多少位小数;
    streamsize precision( streamsize new_precision );
    设置为浮点数输出多少位小数;
  • setprecision
    /*unspecified*/ setprecision( int n );
  • width
    streamsize width() const;
    streamsize width( streamsize new_width );
    设置输出整数或者浮点数时的最小宽度,输入时的最大宽度。不够的用fill character补。
  • resetiosflags
    /*unspecified*/ resetiosflags( std::ios_base::fmtflags mask );
  • setiosflags
    /*unspecified*/ setiosflags( std::ios_base::fmtflags mask );

上面这些设置格式化的函数、参数很多也是适用于输入的,方便格式化输入。
使用时可以使用cout << showbase << scientific << ...这种方式来设置格式,也可以使用cout.setf(...)这种来设置,不过显然前面那种更简单简洁;

Hackerrank上面的一道习题

模板

首先,读一下我最喜欢的cppreference这个网站上的关于类模板的权威介绍,下面的内容基本上是以cppreference的介绍为主,自己翻译一部分,然后添加了一些自己觉得重要的东西。其次读一下C++ Primer一书第16章关于模板和范型编程的介绍。

定义类模板

template <parameter-list> class-declaration	(1)
export template <parameter-list> class-declaration (2) (until c++11)

只看第一种定义方式,第二种几乎没见人用过,看这个until c++11的标注,以后应该是要被废弃的。

class-declaration表示要定义的类模板的名字;parameter-list则是一个非空的、逗号分隔的模板参数列表,模板参数可以是非类型参数(non-type parameter)类型参数(type parameter)模板参数(template parameter)或者parameter pack。这几个术语需要解释一下:

类型参数(type parameter),这个是最容易被理解的,就是用一个类来作为这个类模板的参数。比如下面的例子:

template <class T>
class My_vector { /* ... */ };

或者

template <class T = void>
struct My_op_functor { /* ... */ };

非类型参数(non-type parameter),简单点说就是像普通函数那样接受指定类型的“值”作为这个类模板的参数。比如下面的例子:

template parameter

parameter pack

可变参数模板函数

题目:https://www.hackerrank.com/challenges/cpp-variadics/problem

对于可变参数模板函数,展开的方法有2种:

1. 通过递归函数展开;

通过递归展开也有两种方式,一个是通过普通的递归函数,每次取一个参数,参数包在不断地变小,另一个是通过tuple,索引在不断地变化,通过索引取遍每一个参数。通过tuple展开时,需要使用enable_if(参考https://blog.csdn.net/jeffasd/article/details/84667090#t2和https://en.cppreference.com/w/cpp/types/enable_if)。

#include <tuple>

// Enter your code for reversed_binary_value<bool...>()
template<std::size_t I = 0, typename Tuple>
typename std::enable_if<I == std::tuple_size<Tuple>::value, int>::type impl(Tuple t) {
    return 0;
}

template<std::size_t I = 0, typename Tuple>
typename  std::enable_if<I < std::tuple_size<Tuple>::value, int>::type impl(Tuple t) {
    return std::get<I>(t) + 2 * impl<I + 1>(t);
}

2. 通过逗号表达式和初始化列表方式展开参数包;

这里要用到一个c++11中的语法,叫参数包展开(parameter pack expansion),详见https://en.cppreference.com/w/cpp/language/parameter_pack中列出来的第5种情况,就是说pattern...这种写法,会把这个pattern应用到参数包中的每一个参数,然后用逗号连接起来。参考cppreference中列出来的例子,观察这个pattern是如何展开的:

template<class ...Us> void f(Us... pargs) {}
template<class ...Ts> void g(Ts... args) {
    f(&args...); // “&args...” is a pack expansion
                 // “&args” is its pattern
}
g(1, 0.2, "a"); // Ts... args expand to int E1, double E2, const char* E3
                // &args... expands to &E1, &E2, &E3
                // Us... pargs expand to int* E1, double* E2, const char** E3

由于时间关系来不及详细写,具体见《深入应用C++11 代码优化与工程级应用》这本书第3章第2节。

算法题

Attending Workshops

  • 如讨论区所说,这是一个interval scheduling的问题。采用贪心算法,把所有任务区间按照结束时间排序,然后从第一个区间开始贪心地选取不相交的区间即可。具体算法可以参看wikipediaGreedy polynomial solution一段。
  • 可怜自己写了一个动态规划,按照结束时间来进行递推。
  • 关于该问题的变种GISDP、GISMP。