如何有效地将一个大std::字符串的一部分转换为float


void OBJLoader::load_vec(const char* line_prefix) {
size_t jump = 0;
size_t strpos = 0;
if (line_prefix == "v ") {
while ((strpos = buffer.find(line_prefix, strpos)) != string::npos) {
strpos++;
vcoord_vec.emplace_back(stof(&buffer.at(strpos), &jump));
strpos += jump;
vcoord_vec.emplace_back(stof(&buffer.at(strpos), &jump));
strpos += jump;
vcoord_vec.emplace_back(stof(&buffer.at(strpos), &jump));
strpos += jump;
}
return;
}
//(...)

这是我用来将buffer字符串中的文本转换为float并将其放入向量中的代码。字符串本身的重要部分具有这样的结构:

v -0.893339 0.784809 0.891470
v -0.893339 -0.784809 0.891470
v -0.692655 -0.634043 0.017402
v 0.692655 0.586786 -0.017402
v -0.710057 0.651445 0.000000
v 0.710057 -0.604188 0.000000
v 0.017402 -0.571364 -0.674429
v -0.017402 0.618621 0.674429
v 0.000000 -0.636023 0.691831

使用vcoord_vec.emplace_back(stof(&buffer.at(strpos), &jump)),将单个值加载到向量中大约需要200ms,而这:

void OBJLoader::load_vec(const char* line_prefix) {
char* cbuffer = const_cast<char*>(buffer.c_str());
if (line_prefix == "v ") {
while (cbuffer = strstr(cbuffer, line_prefix)) {
cbuffer++;
vcoord_vec.emplace_back(strtof(cbuffer, &cbuffer));
vcoord_vec.emplace_back(strtof(cbuffer, &cbuffer));
vcoord_vec.emplace_back(strtof(cbuffer, &cbuffer));
}
return;
}
//(...)

在489ms内加载4 718 598个。将已知索引处的字符串的一部分转换为具有类似效率的任何类型的数字的"C++方法"是什么?

您使用std::stof错误。当它期望const std::string&时,您正在向它传递char*

其结果是在对std::stof的每次调用中从char*std::string的隐式转换,将从strpos开始到其空终止符的buffer的全部内容复制到函数参数转换创建的临时内容中。

如果可用(C++17(,我更喜欢std::from_chars,否则我建议使用std::strtof进行转换(这并不意味着你必须继续使用strstr(。CCD_ 14被指定为无论如何都调用CCD_。

对于像您正在使用的具有空格分隔值的真正基本格式,您可以使用C++流:

#include <sstream>
/*...*/
char id; 
float v[3];
std::istringstream iss("v 123.4 12.5 0262.2");
iss >> id;
if (id == 'v') {
iss >> v[0] >> v[1] >> v[2];
}

这是我在3D图形引擎中使用的一个实用程序类。为了与我的示例一起使用,您需要有一个可用的GLM库版本,否则您可以注释掉所有与GLM相关的内容,以便对单个值进行简单的基本转换。然而,我在这里展示它是为了演示如何从字符串转换为不同的数字类型,包括用户定义的类型。

不能构造此Utility类对象的实例,因为它的构造函数已删除,并且没有成员变量。它只是相似或相关函数的集合,主要是string操作方法。

如果您花时间通读这个类,您会发现我正在使用静态函数将字符串转换为所需的类型。

该类确实有2个私有函数模板,string_to_value在类外部的标头中定义,get_value在cpp文件中定义,因为基于返回类型有3个重载或template specialization函数。它们分别是intunsignedfloat。为了便于打印,我还为所有glm矢量类型包含了重载的operator<<

get_value函数中,我使用了std::stofstd::stoistd::stoul。如果您想添加转换为double的功能,那么将其添加到此类应该不会那么困难。

我的课比这个大得多,但为了简短起见,我至少省略了一半


现在是代码。。。

应用程序和输出

main.cpp

#include <iostream>
#include "Utility.h"
int main() {
using namespace util;
try {
std::string str1("3,4,5");
std::string str2("-4,-2,7");
std::string str3("2.4,7.8,9.2");      
glm::uvec3 uvec3 = Utility::convert_to_uvec3(str1);
glm::ivec3 ivec3 = Utility::convert_to_ivec3(str2);
glm::vec3   vec3 = Utility::convert_to_vec3(str3);
std::cout << uvec3 << 'n';
std::cout << ivec3 << 'n';       
std::cout <<  vec3 << 'n';
// test an exception case:
glm::vec4 v4 = Utility::convert_to_vec4(str2);
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

输出

(3,4,5)
(-4,-2,7)
(2.4,7.8,9.2)
util::Utility::convert_to_vec4 Bad conversion of [-4,-2,7] to vec4

公用事业类别

实用程序.h

#ifndef UTILITY_H
#define UTILITY_H
#include <string>
#include <algorithm>
#include <glm/glm.hpp>
namespace util {
class Utility {
public:
static std::string to_upper(const std::string& str);
static std::string to_lower(const std::string& str);
static std::string trim(const std::string& str, const std::string elements_to_trim = " tnr");
static unsigned     convert_to_unsigned(const std::string& str);
static int          convert_to_int(const std::string& str);
static float        convert_to_float(const std::string& str);
static glm::vec2    convert_to_vec2(const std::string& str);
static glm::vec3    convert_to_vec3(const std::string& str);
static glm::vec4    convert_to_vec4(const std::string& str);
static glm::ivec2   convert_to_ivec2(const std::string& str);
static glm::ivec3   convert_to_ivec3(const std::string& str);
static glm::ivec4   convert_to_ivec4(const std::string& str);
static glm::uvec2   convert_to_uvec2(const std::string& str);
static glm::uvec3   convert_to_uvec3(const std::string& str);
static glm::uvec4   convert_to_uvec4(const std::string& str);
private:
Utility() = delete;
Utility(const Utility& c) = delete;
Utility& operator=(const Utility& c) = delete;
template<typename T>
static bool string_to_value(const std::string& str, T* value, unsigned num_values);
template<typename T>
static T get_value(const std::string& str, std::size_t& remainder);
};
template<typename T>
static bool Utility::string_to_value(const std::string& str, T* value, unsigned num_values) {
int num_commas = std::count(str.begin(), str.end(), ',');
if (num_commas != num_values - 1) return false;
std::size_t remainder;
value[0] = get_value<T>(str, remainder);
if (num_values == 1) {
if (str.size() != remainder) {
return false;
}
} else {
std::size_t offset = remainder;
if (str.at(offset) != ',') {
return false;
}
unsigned last_indx = num_values - 1;
for (unsigned u = 1; u < num_values; ++u) {
value[u] = get_value<T>(str.substr(++offset), remainder);
offset += remainder;
if ((u < last_indx && str.at(offset) != ',') ||
(u == last_indx && offset != str.size())) {
return false;
}
}
}
return true;
}
} // namespace util
std::ostream& operator<<(std::ostream& out, const glm::ivec2& v2Value);
std::ostream& operator<<(std::ostream& out, const glm::ivec3& v3Value);
std::ostream& operator<<(std::ostream& out, const glm::ivec4& v4Value);
std::ostream& operator<<(std::ostream& out, const glm::vec2& v2Value);
std::ostream& operator<<(std::ostream& out, const glm::vec3& v3Value);
std::ostream& operator<<(std::ostream& out, const glm::vec4& v4Value);
std::ostream& operator<<(std::ostream& out, const glm::uvec2& v2Value);
std::ostream& operator<<(std::ostream& out, const glm::uvec3& v3Value);
std::ostream& operator<<(std::ostream& out, const glm::uvec4& v4Value);
#endif // UTILITY_H

实用程序.cpp

#include "Utility.h"
#include <exception>
#include <sstream>
namespace util {
std::string Utility::to_upper(const std::string& str) {
std::string result = str;
std::transform(str.begin(), str.end(), result.begin(), ::toupper);
return result;
}
std::string Utility::to_lower(const std::string& str) {
std::string result = str;
std::transform(str.begin(), str.end(), result.begin(), ::tolower);
return result;
}
std::string Utility::trim(const std::string& str, const std::string elements_to_trim) {
std::basic_string<char>::size_type first_index = str.find_first_not_of(elements_to_trim);
if (first_index == std::string::npos) {
return std::string(); // nothing left
}
std::basic_string<char>::size_type last_index = str.find_last_not_of(elements_to_trim);
return str.substr(first_index, last_index - first_index + 1);
}
template<>
float Utility::get_value(const std::string& str, std::size_t& remainder) {
return std::stof(str, &remainder);
} // getValue<float>
template<>
int Utility::get_value(const std::string& str, std::size_t& remainder) {
return std::stoi(str, &remainder);
} // getValue<int>
template<>
unsigned Utility::get_value(const std::string& str, std::size_t& remainder) {
return std::stoul(str, &remainder);
} // getValue<unsigned>
unsigned Utility::convert_to_unsigned(const std::string& str) {
unsigned u = 0;
if (!string_to_value(str, &u, 1)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to unsigned";
throw std::exception(stream.str().c_str());
}
return u;
}
int Utility::convert_to_int(const std::string& str) {
int i = 0;
if (!string_to_value(str, &i, 1)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to int";
throw std::exception(stream.str().c_str());
}
return i;
}
float Utility::convert_to_float(const std::string& str) {
float f = 0;
if (!string_to_value(str, &f, 1)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to float";
throw std::exception(stream.str().c_str());
}
return f;
}
glm::vec2 Utility::convert_to_vec2(const std::string& str) {
glm::vec2 v2;
if (!string_to_value(str, &v2[0], 2)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to vec2";
throw std::exception(stream.str().c_str());
}
return v2;
}
glm::vec3 Utility::convert_to_vec3(const std::string& str) {
glm::vec3 v3;
if (!string_to_value(str, &v3[0], 3)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to vec3";
throw std::exception(stream.str().c_str());
}
return v3;
}
glm::vec4 Utility::convert_to_vec4(const std::string& str) {
glm::vec4 v4;
if (!string_to_value(str, &v4[0], 4)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to vec4";
throw std::exception(stream.str().c_str());
}
return v4;
}
glm::ivec2 Utility::convert_to_ivec2(const std::string& str) {
glm::ivec2 v2;
if (!string_to_value(str, &v2[0], 2)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to ivec2";
throw std::exception(stream.str().c_str());
}
return v2;
}
glm::ivec3 Utility::convert_to_ivec3(const std::string& str) {
glm::ivec3 v3;
if (!string_to_value(str, &v3[0], 3)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to ivec3";
throw std::exception(stream.str().c_str());
}
return v3;
}
glm::ivec4 Utility::convert_to_ivec4(const std::string& str) {
glm::ivec4 v4;
if (!string_to_value(str, &v4[0], 4)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to ivec4";
throw std::exception(stream.str().c_str());
}
return v4;
}
glm::uvec2 Utility::convert_to_uvec2(const std::string& str) {
glm::uvec2 v2;
if (!string_to_value(str, &v2[0], 2)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to uvec2";
throw std::exception(stream.str().c_str());
}
return v2;
}
glm::uvec3 Utility::convert_to_uvec3(const std::string& str) {
glm::uvec3 v3;
if (!string_to_value(str, &v3[0], 3)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to uvec3";
throw std::exception(stream.str().c_str());
}
return v3;
}
glm::uvec4 Utility::convert_to_uvec4(const std::string& str) {
glm::uvec4 v4;
if (!string_to_value(str, &v4[0], 4)) {
std::ostringstream stream;
// __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
stream << __FUNCTION__ << " Bad conversion of [" << str << "] to uvec4";
throw std::exception(stream.str().c_str());
}
return v4;
}
} // namespace util
std::ostream& operator<<(std::ostream& out, const glm::ivec2& v2Value) {
out << "("
<< v2Value.x << ","
<< v2Value.y << ")";
return out;
}
std::ostream& operator<<(std::ostream& out, const glm::ivec3& v3Value) {
out << "("
<< v3Value.x << ","
<< v3Value.y << ","
<< v3Value.z << ")";
return out;
}
std::ostream& operator<<(std::ostream& out, const glm::ivec4& v4Value) {
out << "("
<< v4Value.x << ","
<< v4Value.y << ","
<< v4Value.z << ","
<< v4Value.w << ")";
return out;
}
std::ostream& operator<<(std::ostream& out, const glm::vec2& v2Value) {
out << "("
<< v2Value.x << ","
<< v2Value.y << ")";
return out;
}
std::ostream& operator<<(std::ostream& out, const glm::vec3& v3Value) {
out << "("
<< v3Value.x << ","
<< v3Value.y << ","
<< v3Value.z << ")";
return out;
}
std::ostream& operator<<(std::ostream& out, const glm::vec4& v4Value) {
out << "("
<< v4Value.x << ","
<< v4Value.y << ","
<< v4Value.z << ","
<< v4Value.w << ")";
return out;
}
std::ostream& operator<<(std::ostream& out, const glm::uvec2& v2Value) {
out << "("
<< v2Value.x << ","
<< v2Value.y << ")";
return out;
} 
std::ostream& operator<<(std::ostream& out, const glm::uvec3& v3Value) {
out << "("
<< v3Value.x << ","
<< v3Value.y << ","
<< v3Value.z << ")";
return out;
} 
std::ostream& operator<<(std::ostream& out, const glm::uvec4& v4Value) {
out << "("
<< v4Value.x << ","
<< v4Value.y << ","
<< v4Value.z << ","
<< v4Value.w << ")";
return out;
}

注意:

string_to_value函数中,我使用逗号作为分隔符或字符串分隔符。你可以很容易地将其更改为一个空格或其他字符,它应该会给你同样的功能。。。

最新更新