C++技巧

参考文章

我看到很多程序员写的代码像这样一样:

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)

字面量也可以与模板一起使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值