每周学点 C++

[toc]

std

std::numeric_limits

  • The std::numeric_limits class template provides a standardized way to query various properties of arithmetic types.
    • int 的最大值是 numeric_limits::max().
  • minlowest 都被用来获取某个类型的最小值。它们分别返回这个最小值可表示的最小正数 (min) 和最小负数 (lowest)。因此,当 T 是一个无符号类型的时候,min () 和 lowest () 返回的值是相同的。其次,lowest () 只对有符号类型有效,而 min () 对所有数据类型都有效。
++view raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <limits>

int main(){
int max_int = std::numeric_limits<int>::max();
int min_int = std::numeric_limits<int>::min();
int lowest_int = std::numeric_limits<int>::lowest();
std::cout << "max int is " << max_int << std::endl;
std::cout << "min int is " << min_int << std::endl;
std::cout << "lowest int is " << lowest_int << std::endl;

// output is
// max int is 2147483647
// min int is -2147483648
// lowest int is -2147483648

return 0;
}

std::sort

  • Sorts the elements in the range [first, last) in non-descending order.
    • 要求传入两个迭代器确定范围
  • 逆序排序 std::sort (vec.rbegin (), vec.rend ())

std::equal

  • Two ranges are considered equal if they have the same number of elements and, for every iterator i in the range [first1, last1), *i equals *(first2 + (i - first1)).
    • 要求按位置一致
  • std::equal should not be used to compare the ranges formed by the iterators from std::unordered_set/map

Programming grammar

string

std::string::substr

  • Returns a newly constructed string object with its value initialized to a copy of a substring of this object.
  • string::substr (pos, n) 返回从 pos 开始的 n 个字符的拷贝.

std::string::size

  • Returns the length of the string, in terms of bytes.
  • Returns size_t, an unsigned integral type.
    • 不能直接 min (size_t, int), 编译错误

std::string::operator[]

  • Returns a reference to the character at position pos in the string.

stack

std::stack::top

  • Returns a reference to the top element in the stack.
  • 如果栈为空,返回值未定义。

map

std::map::insert\

  • 插入元素类型是 pair, 可以是 insert ({key_x, value_y}), 或者是 insert (make_pair (key_x, value_y)).

const int * vs const int * const vs int const *

  • int const* is equivalent to const int* 指向常量 int 的指针

This means that the variable being declared is a pointer, pointing to a constant integer. Effectively, this implies that the pointer is pointing to a value that should not be changed. Const qualifier doesn’t affect the pointer in this scenario so the pointer is allowed to point to some other address.

  • int *const 指向 int 的常量指针

This means that the variable being declared is a constant pointer pointing to an integer. Effectively, this implies that the pointer shouldn’t point to some other address. Const qualifier doesn’t affect the value of integer in this scenario so the value being stored in the address is allowed to change.

  • const int* const is equivalent to int const* const 指向常量 int 的常量指针

This means that the variable being declared is a constant pointer pointing to a constant integer. Effectively, this implies that a constant pointer is pointing to a constant value. Hence, neither the pointer should point to a new address nor the value being pointed to should be changed.

  • Memory Map

One way to remember the syntax (according to Bjarne Stroustrup) is the spiral rule -
The rule says, start from the name of the variable and move clockwise to the next pointer or type. Repeat until expression ends.

Using this rule, even complex declarations can be decoded like, int ** const is a const pointer to pointer to an int.

以 * 为界, 在 * 前后的 const 可以和同侧的 data type 调换位置,不影响变量表达的含义。

std::numeric_limits::min() vs lowest()

根据 ppreference 定义:

  • lowest: Returns the lowest finite value representable by the numeric type T, that is, a finite value x such that there is no other finite value y where y < x.
  • min:For floating-point types with denormalization, min returns the minimum positive normalized value.

也就是说,

  1. 如果 T 属于 int 型,則 min 和 lowest 会返回一样的值,皆为该类型的最小值(负数或 0)。
  2. 如果 T 属于浮点型,min 返回 “最小正数”,lowest 返回最小值。

std::move 对象移动

在重新分配内存的过程中,从旧内存将元素拷贝到新内存是不必要的,最好的方式是移动元素;此外,IO 类和 unique_ptr 类可以移动但不能拷贝。

  • 标准库容器、string 和 shared_ptr 类既支持移动也支持拷贝。IO 类和 unique_ptr 类可以移动但不能拷贝。

变量是左值,我们不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不行。

针对上述问题,我们可以使用 move 的新标准库函数显式地将一个左值转换为对应的右值引用类型,来获得绑定到左值上的右值引用。

1
2
3
4
5
int &&rr1 = 42;  // 正确:字面常量是右值
int &&rr2 = rr1; // 错误:表达式rr1是左值。
int &&rr3 = std::move(rr1); // 正确。

调用move意味着承诺:除了rr1赋值或者销毁它外,我们将不再使用它。我们不能对移后源对象的值做任何假设。

我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值。

  • 与多数标准库名字的使用不同,对 move 我们不提供 using 声明,我们直接调用 std::move 而不是 move。

std::set

STL 中 set 底层实现方式?

  • set 底层实现方式为 RB 树(即红黑树)。
  • 红黑树与 hash table 最大的不同是,红黑树是有序结构,而 hash table 不是。
    • 如果只是判断 set 中的元素是否存在,那么 hash 显然更合适,因为 set 的访问操作时间复杂度是 log (N) 的,而使用 hash 底层实现的 hash_set 是近似 O (1) 的。
    • set 应该更加被强调理解为 “集合”,而集合所涉及的操作并、交、差等,即 STL 提供的如交集 set_intersection ()、并集 set_union ()、差集 set_difference () 和对称差集 set_symmetric_difference (),都需要进行大量的比较工作,那么使用底层是有序结构的红黑树就十分恰当了,这也是其相对 hash 结构的优势所在。
  • 在 STL 中,set 和 multiset 都是基于红黑树实现的。

什么时候使用 std::move

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

To answer the question in the title, use std::move on a return value when you want it to be moved and it would not get moved anyway. That is:

  • you want it to be moved, and
  • it is an lvalue, and
  • it is not eligible for copy elision, and
  • it is not the name of a by-value function parameter.

Reference to When should std::move be used on a function return value? [duplicate].

at vs operator[] in vector

at returns a reference to the element at position n in the vector, which automatically checks whether n is within the bounds of valid elements in the vector, throwing an out_of_range exception if it is not.

operator[] returns a reference to the element at position n in the vector container. Portable programs should never call this function with an argument n that is out of range, since this causes undefined behavior.

operator[] has the same behavior as at, except that vector::at is bound-checked and signals if the requested position is out of range by throwing an out_of_range exception.

在程序中推荐使用 at 函数,而不是 [] 操作符,更安全。

explicit 抑制构造函数定义的隐式转换

关键字 explicit 只对一个实参的构造函数有效。只能在类内声明构造函数时使用 explicit 关键字,在类外部定义时不应重复。
当我们用 explicit 关键字声明构造函数时,它将只能以直接初始化的形式使用。而且,编译器将不会在自动转换过程中使用该构造函数。

需要多个实参的构造函数不能用于隐式转换,所以无需将这些构造函数指定为 explicit 的。实测,即使指定为 explicit,程序编译不报错。

std::upper_bound vs std::lower_bound()

upper_bound

std:vector 和 std:string 的相互转换

  • string::assign

    • Assigns a new value to the string, replacing its current contents.
  • vector::assign

    • Assigns new contents to the vector, replacing its current contents, and modifying its size accordingly.
  • vector to string

1
2
3
4
// First method
std::vector<char> data = {'a', 'b', 'c'};
std::string res;
res.insert(res.begin(), data.begin(), data.end());
1
2
3
4
5
// Second method
std::vector<char> data = {'a', 'b', 'c'};
std::string str;
str.clear();
str.assign(data.begin(),data.end());
  • string to vector
1
2
3
4
string str = "what a nice day!";
std::vector <char> chars;
chars.resize(str.size());
chars.assign(str.begin(),str.end())

函数模板 (function template) 返回值

https://blog.csdn.net/u012515223/article/details/17003679

boost::optional()

  • 读取访问器 (read access) .get () 返回非常量引用 (non-const reference),所以你可以向其中写入。

It is sometimes helpful to think of optional as a value-and-pointer mixed together. There is a possibly null pointer to an owned buffer of memory that may, or may not hold a copy of the type.

你可以使用 * 或者 -> 来替代 get () 函数,以下三种写法是等价的。

1
2
3
*optional
optional->
optional.get()

Grammar Discriminate

构造函数初始化 (赋值和初始化)

我们一般习惯于构造函数初始化列表函数体内对类的成员变量初始化,两者的区别难道仅仅是表达方式和初始化位置不同吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 辨析两种初始化参数的方式 

// Just show me the code!
// 构造函数初始化列表 (constructor initialize list)
ClassA::ClassA(const std::string& name_in):name_(name_in) {
// 只调用了默认构造函数,不会再调用拷贝构造函数。
}

// 赋值初始化
ClassA::ClassA(const std::string& name_in) {
// 对象的成员变量的初始化动作发生在进入构造函数体之前。
// name_不是初始化而是赋值。首先调用了这个类的默认构造函数,然后将name_in赋值到name_,即在调用拷贝构造函数。
name_ = name_in;
}

C++ 规定,当某个数据成员被构造函数初始化列表忽略时,它将以与合成默认构造函数相同的方式隐式初始化。即没有出现在构造函数初始化列表中的成员将通过相应的类内初始化(如果存在的话)初始化,或者执行默认初始化。

综上所述,相比函数体内初始化,使用成员初始化列表,程序的效率更高。

动态绑定

  • 当我们使用基类的引用或指针调用一个虚成员函数时,会执行动态绑定。所以,所有的虚函数都必须有定义。
  • 动态绑定只有当我们通过指针或引用调用虚函数时才会发生。因为当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定应该调用哪个版本的函数。

默认构造函数

  • 一旦我们定义了一些其他的构造函数,那么除非我们再定义一个默认的构造函数,否则类将没有默认构造函数。
  • = default : 声明默认构造函数,等同于之前使用的合成默认构造函数。

Vector

  • 当 vector 对象(或数组)销毁时,存储在其中的对象也会被销毁,也就是依次销毁 vector 中的每一个元素。
  • 很多使用动态内存的类应该使用 vector 对象或者 string 对象管理必要的存储空间;使用 vector 或者 string 的类能避免分配和释放内存带来的复杂性。

inline

  • 引入原因:为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。
  • inline 只适合涵数体内代码简单的涵数使用,不能包含复杂的结构控制语句,并且不能内联函数本身不能是直接递归函数(即,自己内部还调用自己的函数).
  • inline 函数仅仅是一个对编译器的建议,所以最后能否真正内联,由编译器决定。一个好的编译器将会根据函数的定义体,自动地取消不值得的内联
  • 定义在类中的成员函数默认都是内联的,如果在类定义时就在类内给出函数定义,那当然最好。如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上 inline,否则就认为不是内联的。
  • 内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。
    • 如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
  • inline 不应该出现在函数的声明中,原因如下:
    • 高质量 C++/C 程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。
    • 一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了 inline 不应该出现在函数的声明中)。
1
2
3
4
5
6
7
8
9
10
// 头文件
class A
{
public:
void Foo(int x, int y);
}

// 定义文件
// 关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用。
inline void A::Foo(int x, int y){}

Copy Constructor 拷贝构造函数

一个特殊的构造函数,一般只有一个参数,这个参数一般是用 const 修饰的,对自己类的一个引用 (reference)。

浅拷贝和深拷贝

浅拷贝:两个变量进行浅拷贝时,它们指向同一个地址,它们的值相同。这样会有问题,当其中的一个析构了那个地址,另外一个也没有了,有时候会发生错误,但浅拷贝比较廉价。

深拷贝:两个变量进行深拷贝时,第二变量会重新申请一块区域来存放跟第一个变量指向地址的值。两个东西完全是独立的,只是值相同。消耗比较大,因为要重新申请空间。

Code Style

class

1
2
3
// Setter

// Accessors

Friends

Initialization: =, (), and {}

  • For uniform initialization syntax,大括号初始化 (Brace Initialization) 的潜在问题:
    • “uniform” is a stretch: there are cases where ambiguity still exists.
    • This syntax is not exactly intuitive: no other common language uses something like it.
      • For uniform initialization syntax, we don’t believe in general that the benefits outweigh the drawbacks.

Best Practices for Initialization

  • Use assignment syntax when initializing directly with the intended literal value (for example: int, float, or std::string values), with smart pointers such as std::shared_ptr, std::unique_ptr, with containers (std::vector, std::map, etc), when performing struct initialization, or doing copy construction.
  • Use the traditional constructor syntax (with parentheses) when the initialization is performing some active logic, rather than simply composing values together.
  • Use {} initialization without the = only if the above options don’t compile.
  • Never mix {}s and auto.
    • For the language lawyers: prefer copy-initialization over direct-initialization when available, and use parentheses over curly braces when resorting to direct-initialization.