C++ std::pair / std :: tuple / std::optional 的用法

  • Post author:
  • Post category:其他


leetcode —— Two Sum

刷题刷到std::pair和make_pair()函数,记录下用法,做一个?上人。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

using namespace std;

class Solution
{
    // 新思路,两根指针分别指向vector首部以及尾部
    // 左侧指针向右移,右侧指针向左移
    // 两指针指向的内存数据相加与target比较
    // 左侧指针只在相加结果<target时右移,
    // 右侧指针只在相加结果>target时左移。
    // 左侧指针与右侧指针相遇时结束。

public:
vector<int> twoSum(vector<int> &nums, int target)
{
	vector<int> ret;
	// 对nums排序并保存下标
	std::vector< std::pair<int, int> > nums_;

	int nums_size = nums.size();
	for (int i = 0; i < nums_size; ++i) { nums_.push_back(make_pair(nums[i], i)); }
	std::sort(nums_.begin(), nums_.end());

	// 下标声明
	int l = 0, r = nums_.size() - 1;
	while (l < r)
	{
		if (nums_[l].first + nums_[r].first == target)
		{
			ret.push_back(nums_[l].second);
			ret.push_back(nums_[r].second);
			break;
		}
		else if (nums_[l].first + nums_[r].first > target)
		{
			r--;
		}
		else l++;
	}

	return ret;
}

};

int main()
{
	//int arr[100] = {0};
	//cout << arr[0] << " " << arr[1]<<" "<<arr[100];
	Solution m_sol;
	vector<int> nums{ 2,7,11,15 };
	vector<int> m_return;
	m_return=m_sol.twoSum(nums , 13);
	for (auto i : m_return)
		cout << i << " ";

	return 0;
}

使用vector配合std::pair成为简易的unordered_map;

背景

C/C++ 语言中的函数只能返回一个值。那么我们如何达到返回多个值的目的?

  1. 使用指针指向一个容纳多个非对称类型的值的结构,将指针返回到该数据类型的数组。
  2. 使用元组tuple(用于返回多个值)和成对pair(用于两个值)。

std::pair


1 pair的应用

pair是将2个数据组合成一个数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。 pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。


2 make_pair函数

template pair make_pair(T1 a, T2 b) { return pair(a, b); }

很明显,我们可以使用pair的构造函数也可以使用make_pair来生成我们需要的pair。 一般make_pair都使用在需要pair做参数的位置,可以直接调用make_pair生成pair对象很方便,代码也很清晰。 另一个使用的方面就是pair可以接受隐式的类型转换,这样可以获得更高的灵活度。灵活度也带来了一些问题如:

std::pair<int, float>(1, 1.1);

std::make_pair(1, 1.1);

是不同的,第一个就是float,而第2个会自己匹配成double。

类模板:template <class T1, class T2> struct pair

参数:T1是第一个值的数据类型,T2是第二个值的数据类型。

功能:pair将一对值组合成一个值,这一对值可以具有不同的数据类型(T1和T2),两个值可以分别用pair的两个公有函数first和second访问。

具体用法:

1.定义(构造):

1     pair<int, double> p1;  //使用默认构造函数
2     pair<int, double> p2(1, 2.4);  //用给定值初始化
3     pair<int, double> p3(p2);  //拷贝构造函数

2.访问两个元素(通过first和second):

1     pair<int, double> p1;  //使用默认构造函数
2     p1.first = 1;
3     p1.second = 2.5;
4     cout << p1.first << ' ' << p1.second << endl;

输出结果:1 2.5

std::tuple

pair 可以理解为一种特殊的 tuple(只有 2 个元素的 tuple),tuple是一个固定大小的不同类型值的集合,是泛化的std::pair。我们也可以把他当做一个通用的结构体来用,不需要创建结构体又获取结构体的特征,在某些情况下可以取代结构体使程序更简洁,直观。std::tuple理论上可以有无数个任意类型的成员变量,而std::pair只能是2个成员,因此在需要保存3个及以上的数据时就需要使用tuple元组了。

#include<iostream>
#include<bits/stdc++.h> 
using namespace std; 
  
// A Method that returns multiple values using 
// tuple in C++. 
tuple<int, int, char> foo(int n1, int n2) 
{ 
    // Packing values to return a tuple 
    return make_tuple(n2, n1, 'a');              
} 
  
// A Method returns a pair of values using pair 
std::pair<int, int> foo1(int num1, int num2) 
{ 
    // Packing two values to return a pair  
    return std::make_pair(num2, num1);             
} 
  
int main() 
{ 
    int a,b; 
    char cc; 
      
    // Unpack the elements returned by foo 
    std::tie(a, b, cc) = foo(5, 10);              //创建到其参数或 std::ignore 实例的左值引用的 tuple 。  
      
    // Storing  returned values in a pair  
    std::pair<int, int> p = foo1(5,2);   
      
    cout << "Values returned by tuple: "; 
    cout << a << " " << b << " " << cc << endl; 
      
    cout << "Values returned by Pair: "; 
    cout << p.first << " " << p.second; 
    return 0; 
} 

std::tie


std::tie

能用于引入字典序比较到结构体,或解包 tuple 。通过tie解包将会把这4个元素的值分别赋值给tie提供的4个变量中。

#include <iostream>
#include <string>
#include <set>
#include <tuple>
 
struct S {
    int n;
    std::string s;
    float d;
    bool operator<(const S& rhs) const
    {
        // 比较 n 与 rhs.n,
        // 然后为 s 与 rhs.s,
        // 然后为 d 与 rhs.d
        return std::tie(n, s, d) < std::tie(rhs.n, rhs.s, rhs.d);
    }
};
 
int main()
{
    std::set<S> set_of_s; // S 为可比较小于 (LessThanComparable)
 
    S value{42, "Test", 3.14};
    std::set<S>::iterator iter;
    bool inserted;
 
    // 解包 insert 的返回值为 iter 与 inserted
    std::tie(iter, inserted) = set_of_s.insert(value);
 
    if (inserted)
        std::cout << "Value was inserted successfully\n";
}

std::optional

std::optional 是在 C++ 17 中引入到标准库中的,C++ 17 之前的版本可以通过 boost::optional 实现几乎相同的功能。

The class template

std::optional

manages an

optional

contained value, i.e. a value that may or may not be present.

A common use case for

optional

is the return value of a function that may fail. As opposed to other approaches, such as

std::pair

<T,bool>,

optional

handles expensive-to-construct objects well and is more readable, as the intent is expressed explicitly.

类模板

std::optional

管理一个

可选

的容纳值,即可以存在也可以不存在的值。

一种常见的

optional

使用情况是一个可能失败的函数的返回值。与其他手段,如

std::pair

<T,bool> 相比,

optional

良好地处理构造开销高昂的对象,并更加可读,因为它显式表达意图。

若一个

optional<T>

含值,则保证值作为

optional

对象所用空间的一部分分配,

即不会发生动态内存分配。

从而

optional

对象模拟一个对象,而非指针,尽管定义了

operator*()



operator->()

运算符。

当一个

optional<T>

对象被

隐式转换成 bool

时,若对象含值则转换返回 true ,若对象不含值则返回 false 。


optional

对象在下列条件下含值:

  • 对象被以

    T

    类型值或另一含值的

    optional

    初始化/赋值。

对象在下列条件下不含值:

  • 对象被默认初始化。
  • 对象被以

    std::nullopt_t

    类型值( 等同于{ } )或不含值的

    optional

    对象初始化/赋值。
  • 调用了成员函数

    reset()

如果程序

optional

使用引用类型实例化一个程序,则格式不正确。另外,类型

optional




std :: reference_wrapper




T

可以用来保存引用。

此外,如果程序

optional

使用(可能是cv限定的)标记类型


std :: nullopt_t





std :: in_place_t


实例化一个程序,则该程序格式将报错。


创建一个 optional 的方法:

// 空 optiolal
optional<int> oEmpty;
optional<float> oFloat = nullopt;

optional<int> oInt(10);
optional oIntDeduced(10);  // type deduction

// make_optional
auto oDouble = std::make_optional(3.0);
auto oComplex = make_optional<complex<double>>(3.0, 4.0);

// in_place
optional<complex<double>> o7{in_place, 3.0, 4.0};

// initializer list
optional<vector<int>> oVec(in_place, {1, 2, 3});

// 拷贝赋值
auto oIntCopy = oInt;


示例:

#include <iostream>
#include <string>
#include <functional>
#include <optional>

struct Out {
	std::string out1{ "" };
	std::string out2{ "" };
};

std::optional<Out> func(const std::string& in) {
	Out o;
	if (in.size() == 0)
		return std::nullopt;
	o.out1 = "hello";
	o.out2 = "world";
	return { o };
}

// optional can be used as the return type of a factory that may fail
std::optional<std::string> create(bool b) {
	if (b)
		return "Godzilla";
	return {};
}

// std::nullopt can be used to create any (empty) std::optional
auto create2(bool b) {
	return b ? std::optional<std::string>{"Godzilla"} : std::nullopt;
}

// std::reference_wrapper may be used to return a reference
auto create_ref(bool b) {
	static std::string value = "Godzilla";
	return b ? std::optional<std::reference_wrapper<std::string>>{value}: std::nullopt;
}

int main()
{
	if (auto ret = func("hi"); ret.has_value()) {
		std::cout << ret->out1 << std::endl;
		std::cout << ret->out2 << std::endl;
	}

	std::cout << "create(false) returned "
		<< create(false).value_or("empty") << '\n';

	// optional-returning factory functions are usable as conditions of while and if
	if (std::optional<std::string> str = create2(true)) {
		std::cout << "create2(true) returned " << *str << '\n';
	}

	if (auto str = create_ref(true)) {
		// using get() to access the reference_wrapper's value
		std::cout << "create_ref(true) returned " << str->get() << '\n';

		str->get() = "Mothra";
		std::cout << "modifying it changed it to " << str->get() << '\n';
	}
}


使用 std::optional 带来的好处:

  • 省去了运行状态的 bool 值的声明,让代码更简洁,更注重返回值本身的语意
  • 不用担心额外的动态内存分配,这一点会在后面的文章里详细展开


参考文章:



版权声明:本文为weixin_40539125原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。