Antkillerfarm Hacking V7.0

C/C++编程心得(四)

2019-07-23

虚函数

class Shape {
public:
    virtual void Draw() const = 0;    // 1) 纯虚函数
    virtual void Error(const string& msg);  // 2) 普通虚函数
    int ObjectID() const;  // 3) 非虚函数
};

class Rectangle: public Shape { ... };
  • 非虚函数:

基类和派生类都有该函数:

Rectangel rc; // rc is an object of type Rectangle
Shape *pB = &rc; // get pointer to rc
Rectangle *pD = &rc; // get pointer to rc

pB->ObjectID(); // calls Shape::ObjectID()
pD->ObjectID(); // calls Rectagle::ObjectID()

如果派生类没有该函数,则无论pB->ObjectID();还是pD->ObjectID();都调用Shape::ObjectID()。

  • 普通虚函数:

基类和派生类都有该函数:

Rectangel rc; // rc is an object of type Rectangle
Shape *pB = &rc; // get pointer to rc
Rectangle *pD = &rc; // get pointer to rc

pB->ObjectID(); // calls Rectagle::ObjectID()
pD->ObjectID(); // calls Rectagle::ObjectID()

如果派生类没有该函数,则无论pB->ObjectID();还是pD->ObjectID();都调用Shape::ObjectID()。

  • 纯虚函数虽然要求派生类必须实现该函数,但并不意味着基类就不可以实现。但需要用特殊的语法才能调用基类实现。
ps1->Shape::Draw(); // calls Shape::draw

基类和派生类都有该函数的情况与普通虚函数相同。


这是override的4种错误情况:

class Base {
public:
    virtual void mf1() const;
    virtual void mf2(int x);
    virtual void mf3() &;
    void mf4() const;// is not declared virtual in Base
};

class Derived: public Base {
public:
    virtual void mf1();// declared const in Base, but not in Derived.
    virtual void mf2(unsigned int x);// takes an int in Base, but an unsigned int in Derived
    virtual void mf3() &&;// is lvalue-qualified in Base, but rvalue-qualified in Derived.
    void mf4() const;
};

由于声明上的差异,这4种情况均无法override,且不易查找。

class Derived: public Base {
public:
    virtual void mf1() const override;// adding "virtual" is OK, but not necessary
    virtual void mf2(int x) override;
    void mf3() & override;
    void mf4() const override; 
};

添加override关键字之后,就会去基类中查找是否有匹配成员函数,否则直接报错。


‘vtable for xxxx’未定义的引用:

必须吐槽一下C++的错误提示,这个错误如果改成没有实现XXX虚函数,就直观的多了。

参考:

https://www.cnblogs.com/xinxue/p/5471708.html

C++11之override

typedef struct

在C中:

typedef struct Student
{
  int a;
}Stu;

于是在声明变量的时候就可:Stu stu1;

如果没有typedef就必须用struct Student stu1;来声明。

这里的Stu实际上就是struct Student的别名。

但是在C++中:

struct Student
{
  int a;
};

声明变量时可直接Student stu2;

所以当给C++的库提供C的接口时,经常可以看见如下用法:

typedef struct Student Student;

这样就可以抹平两者的差异了。

参考:

https://www.cnblogs.com/qyaizs/articles/2039101.html

struct和typedef struct彻底明白了

cout格式化输出

cout<<hex<<i<<endl; //输出十六进制数
cout<<oct<<i<<endl; //输出八进制数
cout<<dec<<i<<endl; //输出十进制数

小细节

对于std::vector来说,begin()/end()返回iterator,而front()/back()返回的是element。此外,还有cbegin()/cend()返回const iterator。

template< class InputIt >
vector( InputIt first, InputIt last,
        const Allocator& alloc = Allocator() );

这个构造函数很有意思,可以用于复制一个[first, last)区间的数据。


除了for循环遍历之外,C++还可以用std::for_each/std::find_if进行循环遍历,尤其是后者可以用于条件筛选。

for(auto& item: list)
{
  //do sth
}

还有一些方法可用于统计:

std::all_of, std::any_of, std::none_of:全都是/至少有一个是/全都不是XXX。

std::count, std::count_if:计数。


map:存储key-value pair。iterator->first指向key,而iterator->second指向value。

set:只有value。set也有unordered_set这样的变种。

map和set都不允许重复值,如果需要重复的话,可以使用multiset和multimap。


template<typename T>
struct S {
    template<typename U> void foo(){}
};
 
template<typename T>
void bar()
{
    S<T> s;
    s.foo<T>(); // error: < parsed as less than operator
    s.template foo<T>(); // OK
}

上面是template的一种特殊用法,本来s.foo<T>()也就行了,然而编译器会把它当作s.foo<T,所以需要加上template以示区分。


数字转字符串:

double a = 123.32;
string res;
stringstream ss;
ss << a;
ss >> res;//Or res = ss.str();

字符串转数字:

string a = "123.32";
double res;
stringstream ss;
ss << a;
ss >> res;

#define DispatchOnTensorType(tensor_type, function, ...)          \
  switch (tensor_type->AsPrimitiveDataType()->GetDataType()) {    \
    case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:              \
      function<float>(__VA_ARGS__);                               \
      break;                                                      \
    case ONNX_NAMESPACE::TensorProto_DataType_BOOL:               \
      function<bool>(__VA_ARGS__);                                \
      break;                                                      \
    case ONNX_NAMESPACE::TensorProto_DataType_DOUBLE:             \
      function<double>(__VA_ARGS__);                              \
      break;                                                      \

POD

POD,全称plain old data,plain代表它是一个普通类型,old代表它可以与c兼容,可以使用比如memcpy()这类c中最原始函数进行操作。

POD数据类型主要用来解决C++与C之间数据类型的兼容性,以实现C++程序与C函数的交互。

参考:

https://blog.csdn.net/fu_zk/article/details/17028669

C++的POD数据类型

https://www.cnblogs.com/DswCnblog/p/6371071.html

C++11 POD类型

命令行解析

https://github.com/jarro2783/cxxopts

https://github.com/tanakh/cmdline

https://github.com/gflags/gflags

前两个是轻量级的库,只要包含header即可。gflags是Google的产品,适合比较高端的用法。

此外,boost::program_options也是一个选择。

和命令行解析相对的,就是命令行输出了。

https://github.com/agauniyal/rang

这个项目可以设置终端输出的颜色等效果。

inl文件

inl文件是内联函数的源文件。内联函数通常在C++头文件中实现,但是当C++头文件中内联函数过多的情况下,我们想使头文件看起来简洁点,能不能像普通函数那样将内联函数声明和函数定义放在头文件和实现文件中呢?

当然答案是肯定的,具体做法将是:将内联函数的具体实现放在inl文件中,然后在该头文件末尾使用#include引入该inl文件。

参考:

https://www.cnblogs.com/findumars/p/4340936.html

C++中的INL

std::any & std::variant & std::optional

std::any:一个类型安全的容器,可以放置各种类型的数据。类似于void*

std::variant:类型安全的union。

std::optional:该类型是用来表示一个值是不是存在的。std::optional有两个状态,即有值和无值。通常我们将std::optional用于函数的返回值,当函数执行成功了返回有值的状态,当函数执行失败了返回无值的状态。

参考:

https://www.jianshu.com/p/b2ec40c1c7c1

C++17:std::any, std::variant 和 std::optional

std::transform

std::string s("Hello");
std::transform(s.begin(), s.end(), s.begin(),
    [](unsigned char c) { return std::toupper(c); });
std::cout << s << std::endl; // HELLO

std::string

std::string有很多实现方式,归纳起来有三类,而每类又有多种变化。

1、无特殊处理(eager copy),采用类似std::vector的数据结构。现在很少采用这种方式。

2、Copy-on-write(COW),g++的std::string一直采用这种方式,不过慢慢被SSO取代。

3、短字符串优化(SSO),利用string对象本身的空间来存储短字符串。Visual C++ 2010、clang libc、linux gnu5.x之后都采用的这种方式。

https://www.cnblogs.com/cthon/p/9181979.html

c++再探string之eager-copy、COW和SSO方案

数组定义时的赋值方法

struct mtd_partition {
	const char *name;		/* identifier string */
	uint64_t size;			/* partition size */
	uint64_t offset;		/* offset within the master MTD space */
};

static struct mtd_partition parts[] = {
      {name: "boot", offset: 0,           size:0x500000,},
      {name: "setting", offset: 0x500000, size:0x300000,},
      {name: "linux", offset: 0x800000,   size:0x500000,}, 
      {name: "config", offset: 0xd00000,   size:0x100000,},	
      {name: "rootfs", offset: 0xe00000,  size:0x3200000,},
      {name: "app", offset: 0x4e00000, size:0x800000,},

};

static struct resource pxa27x_resource_ohci[] = {
	[0] = {
		.start  = 0x4C000000,
		.end    = 0x4C00ff6f,
		.flags  = IORESOURCE_MEM,
	},
	[1] = {
		.start  = IRQ_USBH1,
		.end    = IRQ_USBH1,
		.flags  = IORESOURCE_IRQ,
	},
};

这些方法虽然不知道是C的哪个标准引入的,但是见到有代码这样用。

sanitizers

sanitizers是Google推出的调试工具。已集成到各主流编译器之中,无需安装。

代码:

https://github.com/google/sanitizers

参考:

https://zhuanlan.zhihu.com/p/257939964

编译器自带的调试神器sanitizers

常用libc实现

libc是C语言标准库的简称,它有多种实现。除了最常用的gcc自带的glibc之外,还有musl、uClibc、dietlibc等。

http://www.etalabs.net/compare_libcs.html

这个网址是以上4种libc实现的比较结果。从结果来看,musl比较有投资价值。实际上,最近(2015.5)的OpenWrt项目就已经将libc由uClibc改为musl。我也是因为这个原因,才知道musl的。

当然这个表并不完整,其他的libc可以参见:

https://en.wikipedia.org/wiki/C_standard_library

Fork me on GitHub