我看到很多程序员写的代码像这样一样:
pair<int, int> p;
vector<int> v;
// ...
p = make_pair(3, 4);
v.push_back(4); v.push_back(5);
而实际上你可以这样写:
pair<int, int> p;
vector<int> v;
// ...
p = {3, 4};
v = {4, 5};
1. 使用一对{}来给容器赋值
我看到很多程序员写的代码像这样一样:
pair<int, int> p;
// ...
p = make_pair(3, 4);
而实际上你可以这样写:
pair<int, int> p;
// ...
p = {3, 4};
甚至对于更复杂的pair
pair<int, pair<char, long long> > p;
// ...
p = {3, {'a', 8ll}};
那其他的容器呢,比如vector、deque、set和其他容器呢?
vector<int> v;
v = {1, 2, 5, 2};
for (auto i: v)
cout << i << ' ';
cout << '\n';
// 输出 "1 2 5 2"
deque<vector<pair<int, int>>> d;
d = {{{3, 4}, {5, 6}}, {{1, 2}, {3, 4}}};
for (auto i: d) {
for (auto j: i)
cout << j.first << ' ' << j.second << '\n';
cout << "-\n";
}
// 输出 "3 4
// 5 6
// -
// 1 2
// 3 4
// -"
set<int> s;
s = {4, 6, 2, 7, 4};
for (auto i: s)
cout << i << ' ';
cout << '\n';
// 输出 "2 4 6 7"
list<int> l;
l = {5, 6, 9, 1};
for (auto i: l)
cout << i << ' ';
cout << '\n';
// 输出 "5 6 9 1"
array<int, 4> a;
a = {5, 8, 9, 2};
for (auto i: a)
cout << i << ' ';
cout << '\n';
// 输出 "5 8 9 2"
tuple<int, int, char> t;
t = {3, 4, 'f'};
cout << get<2>(t) << '\n';
请注意,这在stack和queue上不适用。
2. 宏中的参数名称
你可以使用’#'符号来获取宏参数的确切名称:
#define what_is(x) cerr << #x << " is " << x << endl;
// ...
int a_variable = 376;
what_is(a_variable);
// 输出 "a_variable is 376"
what_is(a_variable * 2 + 1)
// 输出 "a_variable * 2 + 1 is 753"
3. 摆脱那些包含文件!
只需使用
#include <bits/stdc++.h>
这个库包含了我们在比赛中需要的许多库,如algorithm、iostream、vector等等。相信我,你不需要包含其他任何东西!
4. 隐藏函数(并不是真正隐藏,但不常用)
一)
__gcd(value1, value2)
你不需要为gcd函数编写欧几里德算法的代码,现在我们可以使用__gcd函数。这个函数返回两个数的最大公约数。
例如,__gcd(18, 27) = 9。
二)
__builtin_ffs(x)
这个函数返回x的最低有效位的索引加1。如果x == 0,则返回0。这里的x是int类型,带有后缀’l’的函数接受一个long类型的参数,带有后缀’ll’的函数接受一个long long类型的参数。
例如,__builtin_ffs(10) = 2,因为10在二进制中表示为’…10 1 0’,从右边开始第一个1位的索引是1(从0开始计数),函数返回1 + 索引。
三)
__builtin_clz(x)
这个函数返回x从最高有效位开始的前导0位数。x是无符号int类型,与前一个函数类似,带有后缀’l’的函数接受一个无符号long类型的参数,带有后缀’ll’的函数接受一个无符号long long类型的参数。如果x == 0,则返回一个未定义的值。
例如,__builtin_clz(16) = 27,因为16在二进制中表示为’ … 10000’。无符号int类型的位数是32,所以函数返回32 - 5 = 27。
四)
__builtin_ctz(x)
这个函数返回x从最低有效位开始的尾部0位数。x是无符号int类型,与前一个函数类似,带有后缀’l’的函数接受一个无符号long类型的参数,带有后缀’ll’的函数接受一个无符号long long类型的参数。如果x == 0,则返回一个未定义的值。
例如,__builtin_ctz(16) = 4,因为16在二进制中表示为’…1 0000 '。尾部0位的个数是4。
五)
__builtin_popcount(x)
这个函数返回x的1位数。x是无符号int类型,与前一个函数类似,带有后缀’l’的函数接受一个无符号long类型的参数,带有后缀’ll’的函数接受一个无符号long long类型的参数。如果x == 0,则返回一个未定义的值。
例如,__builtin_popcount(14) = 3,因为14在二进制中表示为’… 111 0’,有三个1位。
注意:还有其他__builtin函数,但它们没有这些函数有用。
注意:其他函数不是未知的,我没有在这里列出它们,但如果你有兴趣使用它们,我建议你查看这个网站。
5. 可变参数函数和宏
我们可以有一个可变参数函数。我想写一个sum函数,它接受多个int参数,并返回它们的和。看看下面的代码:
int sum() { return 0; }
template<typename... Args>
int sum(int a, Args... args) { return a + sum(args...); }
int main() { cout << sum(5, 7, 2, 2) + sum(3, 4); /* 输出 "23" */ }
在上面的代码中,我使用了一个模板。sum(5, 7, 2, 2)变成了5 + sum(7, 2, 2),然后sum(7, 2, 2)本身又变成了7 + sum(2, 2),依此类推…我还声明了另一个不带参数的sum函数,返回0。
我甚至可以定义一个任意类型的sum函数:
int sum() { return 0; }
template<typename T, typename... Args>
T sum(T a, Args... args) { return a + sum(args...); }
int main() { cout << sum(5, 7, 2, 2) + sum(3.14, 4.89); /* 输出 "24.03" */ }
在这里,我只是将int改为T,并在模板中添加了typename T。
在C++14中,你还可以使用auto sum(T a, Args… args)来获得不同类型的参数的和。
我们还可以使用可变参数宏:
#define a_macro(args...) sum(args)
int sum() { return 0; }
template<typename T, typename... Args>
auto sum(T a, Args... args) { return a + sum(args...); }
int main() { cout << a_macro(5, 7, 2, 2) + a_macro(3.14, 4.89); /* 输出 "24.03" */ }
使用这两个,我们可以有一个很棒的调试函数:
#include <bits/stdc++.h>
using namespace std;
#define error(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); stringstream _ss(_s); istream_iterator<string> _it(_ss); err(_it, args); }
void err(istream_iterator<string> it) {}
template<typename T, typename... Args>
void err(istream_iterator<string> it, T a, Args... args) {
cerr << *it << " = " << a << endl;
err(++it, args...);
}
int main() {
int a = 4, b = 8, c = 9;
error(a, b, c);
}
输出:
a = 4
b = 8
c = 9
这个函数在调试中非常有用。
6. 这里是CF中的C++0x,为什么还是C++?
可变参数函数也属于C++11或C++0x,在这一部分,我想向你展示一些C++11的伟大特性。
一)基于范围的for循环
这是一段旧代码:
set<int> s = {8, 2, 3, 1};
for (set<int>::iterator it = s.begin(); it != s.end(); ++it)
cout << *it << ' ';
// 输出 "1 2 3 8"
相信我,那是很多代码,只需使用这个:
set<int> s = {8, 2, 3, 1};
for (auto it: s)
cout << it << ' ';
// 输出 "1 2 3 8"
我们还可以改变值,只需将auto更改为auto &:
vector<int> v = {8, 2, 3, 1};
for (auto &it: v)
it *= 2;
for (auto it: v)
cout << it << ' ';
// 输出 "16 4 6 2"
二)auto的威力
你不需要为要使用的类型命名,C++11可以为你推断类型。如果你需要遍历一个set<pair<int, pair<int, int> > >的迭代器,从begin到end,你需要为我来说,set<pair<int, pair<int, int> > >::iterator这个类型名太痛苦了!只需使用auto it = s.begin()
现在x.begin()和x.end()可以使用begin(x)和end(x)来访问。
这段代码:
for(i = 1; i <= n; i++) {
for(j = 1; j <= m; j++)
cout << a[i][j] << " ";
cout << "\n";
}
等价于:
for(i = 1; i <= n; i++)
for(j = 1; j <= m; j++)
cout << a[i][j] << " \n"[j == m];
原因在于:" \n"是一个char*," \n"[0]是空格," \n"[1]是换行符。
使用tie和emplace_back:
#define mt make_tuple
#define eb emplace_back
typedef tuple<int,int,int> State; // operator< defined
int main(){
int a,b,c;
tie(a,b,c) = mt(1,2,3); // 赋值
tie(a,b) = mt(b,a); // 交换(a,b)
vector<pair<int,int>> v;
v.eb(a,b); // 比pb(mp(a,b))更短更快
// Dijkstra
priority_queue<State> q;
q.emplace(0,src,-1);
while(q.size()){
int dist, node, prev;
tie(dist, ode, prev) = q.top(); q.pop();
dist = -dist;
// 寻找下一个状态
q.emplace(-new_dist, new_node, node);
}
}
这就是为什么emplace_back更快:emplace_back比push_back更快,因为它只在向量末尾构造值,而不是在其他地方构造值,然后将其移动到向量中。
也可以看到tie(args…)的工作原理。你还可以在tie中使用ignore关键字来忽略一个值:
tuple<int, int, int, char> t (3, 4, 5, 'g');
int a, b;
tie(b, ignore, a, ignore) = t;
cout << a << ' ' << b << '\n';
输出:5 3
我使用这个宏,我很喜欢它:
#define rep(i, begin, end) for (__typeof(end) i = (begin) - ((begin) > (end)); i != (end) - ((begin) > (end)); i += 1 - 2 * ((begin) > (end)))
首先,你不需要为要使用的类型命名。其次,它根据(begin > end)条件向前或向后移动。例如,rep(i, 1, 10)是1、2、…、8、9,rep(i, 10, 1)是9、8、…、2、1
它与不同类型很好地配合使用,例如:
vector<int> v = {4, 5, 6, 4, 8};
rep(it, end(v), begin(v))
cout << *it << ' ';
// 输出 "8 4 6 5 4"
还有C++11的另一个伟大特性,lambda函数!
Lambdas是像其他语言的闭包一样。它的定义如下:
[capture list](parameters) -> return value { body }
一)捕获列表:简单!我们在这里不需要,所以只需放一个空的[]。
二)参数:简单!例如int x、string s。
三)返回值:同样简单!例如pair<int, int>,在大多数情况下可以省略(感谢Jacob)。
四)函数体:包含函数体,并返回返回值。
例如:
auto f = [] (int a, int b) -> int { return a + b; };
cout << f(1, 2); // 输出 "3"
你可以在for_each、sort和许多其他STL函数中使用lambda函数:
vector<int> v = {3, 1, 2, 1, 8};
sort(begin(v), end(v), [] (int a, int b) { return a > b; });
for (auto i: v) cout << i << ' ';
输出:8 3 2 1 1
使用move:
当你使用vector等STL容器时,你可以使用move函数来移动容器,而不是复制整个容器。
vector<int> v = {1, 2, 3, 4};
vector<int> w = move(v);
cout << "v: ";
for (auto i: v)
cout << i << ' ';
cout << "\nw: ";
for (auto i: w)
cout << i << ' ';
输出:
v:
w: 1 2 3 4
正如你所看到的,v移动到了w而不是复制。
7. C++0x字符串
一)原始字符串(来自IvayloS的评论)
你可以有UTF-8字符串、原始字符串等等。这里我想展示原始字符串。我们将定义一个原始字符串如下:
string s = R"(Hello, World!)"; // 存储为 "Hello, World!"
原始字符串跳过所有的转义字符如\n或"。例如:
string str = "Hello\tWorld\n";
string r_str = R"(Hello\tWorld\n)";
cout << str << r_str;
输出:
Hello World
Hello\tWorld\n
你还可以有多行的原始字符串:
string r_str =
R"(Dear Programmers,
I'm using C++11
Regards, Swift!)";
cout << r_str;
输出:
Dear Programmer,
I’m using C++11
Regards, Swift!
二)正则表达式(regex)
正则表达式在编程中非常有用,我们可以使用regex来定义正则表达式。我们将使用原始字符串,因为有时正则表达式中会有\和其他字符。看看下面的例子:
regex email_pattern(R"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"); // 这个邮箱模式不完全正确!对于大多数邮箱是正确的。
string
valid_email("swift@codeforces.com"),
invalid_email("hello world");
if (regex_match(valid_email, email_pattern))
cout << valid_email << " is valid\n";
else
cout << valid_email << " is invalid\n";
if (regex_match(invalid_email, email_pattern))
cout << invalid_email << " is valid\n";
else
cout << invalid_email << " is invalid\n";
输出:
swift@codeforces.com is valid
hello world is invalid
注意:你可以在这个网站上学习正则表达式。
三)用户自定义字面量
你已经知道C++中的字面量,比如0xA、1000ll、3.14f等等…
现在你可以有自己定义的自定义字面量!听起来很棒 😃 所以让我们看一个例子:
long long operator "" _m(unsigned long long literal) {
return literal;
}
long double operator "" _cm(unsigned long long literal) {
return literal / 100.0;
}
long long operator "" _km(unsigned long long literal) {
return literal * 1000;
}
int main() {
// 以米为单位查看结果:
cout << 250_m << " meters \n"; // 输出 250 meters
cout << 12_km << " meters \n"; // 输出 12000 meters
cout << 421_cm << " meters \n"; // 输出 4.21 meters
}
注意,字面量应以下划线(_)开头。我们通过以下模式声明一个新的字面量:
[返回类型] operator "" _[名称]([参数]) { [函数体] }
请注意,参数只能是以下之一:
(const char *)
(unsigned long long int)
(long double)
(char)
(wchar_t)
(char16_t)
(char32_t)
(const char *,size_t)
(const wchar_t *,size_t)
(const char16_t *,size_t)
(const char32_t *,size_t)
字面量也可以与模板一起使用。