Linux 中 tr 命令用于转换或删除文件中的字符。
语法
1 | tr [OPTION] SET1 [SET2] |
选项
1 | -c, --complerment:反选设定字符。也就是符合 SET1 的部份不做处理,不符合的剩余部份才进行转换; |
Item 19: Teat class design as type design.
| 特征 | new | malloc |
|---|---|---|
| 类型 | 关键字 | 标准库函数 |
| 支持语言 | C/C++ |
只在 C++ |
| 申请内存的位置 | 自由存储区 free store |
堆 |
| 分配内存的大小 | 编译器根据类型信息自行计算 | 显示指定字节数 |
| 内存分配失败 | 抛出 bac_alloc 异常, 不会返回 NULL |
返回 NULL |
| 返回类型 | 返回对象类型的指针 | 返回void 指针,需要转换 |
| 是否调用构造函数/析构函数 | 调用 | 不调用 |
| 处理数组 | new[] |
手动指定数组的大小 |
| 是否支持重载 | 支持 | 不支持 |
| 是否支持内存扩充 | 不支持 | realloc |
| 内存释放方式 | new/delete, new[]/delete[] |
malloc/free |
Item 18: Make interfaces easy to use correctly and hard to use incorrectly.
Item 17: Store newed objects in smart pointers in standalone statements.
以单独的语句将 new 的对象放入智能指针内。这是为了防止由于其他表达式抛出异常而导致的资源泄漏。
举个栗子:
1 | processWidget(shared_ptr<Widget>(new Widget), priority()); |
上述代码中,在 processWidget 函数被调用之前参数会首先得到计算。可以认为包括三部分的过程:
new Widgetshared_ptr<Widget>priority()因为C++不同于其他语言,函数参数的计算顺序很大程度上决定于编译器,编译器认为顺序应当是1, 3, 2,即:
new Widgetpriority()shared_ptr<Widget>那么如果 priority抛出了异常,新的 Widget 便永远地找不回来了。虽然我们使用了智能指针,但资源还是泄漏了!
于是更加健壮的实现中,应当将创建资源和初始化智能指针的语句独立出来:
1 | shared_ptr<Widget> pw = shared_ptr<Widget>(new Widget); |
Item 16: Use the same form in corresponding uses of new and delete.
如果你用 new 申请了动态内存,请用 delete 来销毁;如果你用 new xx[] 申请了动态内存,请用 delete[] 来销毁:
举个栗子:
1 | std::string* stringPtrl = new std::string; |
上面很容易理解但需要注意typedef:
1 | typedef std::string AddressLines[4]; //每个人的地址有四行, |
由于 AddressLines 是个数组,如果这样使用 new:
1 | std::string *pal = new AddressLines; //注意. "new AddressLines" 返回 |
那就必须匹配 “数组形式“的 delete:
1 | delete pal; //行为未有定义! |
为避免诸如此类的错误,最好尽量不要对数组形式做 typedefs 动作。可以使用更加面向对象的vector、string等对象。
Item 15: Provide access to raw resources in resource-managing classes.
APIs 往往要求访问原始资源(raw resources),所以每一个RAII class 应该提供提供对原始资源访问的方法。获取资源的方式有两类:隐式地获取和显式地获取。 显式的资源获取会更安全,它最小化了无意中进行类型转换的机会。
shared_ptr 提供了 get 方法来得到资源。
1 | shared_ptr<Investment> pInv; |
为了让 pInv 表现地更像一个指针,shared_ptr还重载了解引用运算符(dereferencing operator) operator->和 operator*:
1 | class Investment{ |
我们封装了Font来管理资源:
1 | class Font{ |
通过get方法来访问FontHandle:
1 | Font f(getFont()); |
可以隐式类型转换运算符将 Font 转换为 FontHandle:
1 | class Font{ |
然而问题也随之出现:
1 | FontHandle h2 = f1; |
无意间 h2 并没有被资源管理起来,这将会引发意外的资源泄漏。所以隐式转换在提供便利的同时, 也引起了资源泄漏的风险。
Item 14: Think carefully about copying behavior in resource-managing classes.
设计一个 RAII 对象:
1 | class Lock { |
客户对Lock的使用:
1 | Mutex m; |
当一个 RAII 对象被复制,会发生什么事? 不确定?
1 | Lock ml1(&m); |
记住资源管理对象的拷贝行为取决于资源本身的拷贝行为,同时资源管理对象也可以根据业务需要来决定自己的拷贝行为。一般有如下四种方式:
禁止复制。参考若不想使用编译器自动生成的函数,就该明确拒绝。对Lock而言看起来是这样:
1 | class Lock : private Uncopyable { |
引用计数,采用 shared_ptr 的逻辑。shared_ptr 构造函数提供了第二个参数 deleter,当引用计数到 0 时被调用。 所以 Lock 可以通过聚合一个 shared_ptr 成员来实现引用计数:
1 | class Lock{ |
Lock 的析构会引起 mutexPtr 的析构,而 mutexPtr 计数到0时unlock(mutexPtr.get()) 会被调用。
拷贝底部资源。复制资源管理对象时,进行的是深拷贝。比如 string 的行为:内存存有指向对空间的指针,当它被复制时会复制那片空间。
转移底部资源的拥有权。auto_ptr 就是这样做的,把资源移交给另一个资源管理对象,自己的资源置空。
Item 13: Use objects to manage resources.
Item 12: Copy all parts of an object
正确拷贝函数实现:
1 | class Customer{ |
1 | class Customer{ |
这时 lastTransaction 便被你忽略了,编译器也不会给出任何警告(即使在最高警告级别)
1 | class PriorityCustomer: public Customer { |
正确写法:
1 | class PriorityCustomer: public Customer { |