跟我学C++中级篇——字符串的转化

240 篇文章 100 订阅

一、字符串和普通类型的转化

在实际的开发中,可能大家都经常遇到一个问题,那就是把字符串转化成基础类型或者逆向行为。这种情况下,C/C++的相对于其它语言的劣势一览无余。要么,看上去转化方式比较简单,但适应性低,对异常处理的不好;要么,使用复杂,不容易掌握。
同时,在c++中还存在着大量的不同平台定义的不同的字符串类型,特别是一些Unicode字符串的定义,在早期更是五花八门。本文重点是分析介绍标准库的std:string与其它基础类型的转化,Unicode相关字符串的转化以后再分析。

二、基础类型转字符串方法

1、基础的入门方法
这个比较常见的是C库函数提供的几个转换函数:
itoa():将整型值转换为字符串
ltoa():将长整型值转换为字符串
ultoa():将无符号长整型值转换为字符串
gcvt():将浮点型数转换为字符串,取四舍五入
ecvt():将双精度浮点型值转换为字符串,转换结果中不包含十进制小数点
fcvt():指定位数为转换精度,其余同ecvt()
在c++11后,STD提供了一个std::to_string(val)的方法。需要注意,itoa函数并不是标准C函数,如果需要跨平台时,需要自己处理相关的事项。
看下面的小例子:

#include <iostream>
#include <charconv>
#include <stdlib.h>
#include <string>
//#include <strstream> //c++17前
#include <sstream>  //c++17
#include <bitset>
#include <stdio.h>
void baseTostr()
{
    std::cout << "基础类型转字符串..." << std::endl;
    int num = 123;
    double db = 1.233;
    int dec;
    int sign;

    char buf[10] = {0};
    _itoa_s(num, buf, 4,10);//vc
    std::string sn(buf);
    std::cout << "num to str is:"<<sn << std::endl;
    errno_t err = _ecvt_s(buf,10,db,4,&dec,&sign);
    std::cout << "double db to str is:" << buf << std::endl;
    err = _gcvt_s(buf,10,db,4);
    std::cout << "double db to str is:" << buf << std::endl;

    auto sd = std::to_string(num);
    auto sd1 = std::to_string(db);
    std::cout << "num is:" << sd << ",db is:" << sd1 << std::endl;

    std::string str = std::bitset<32>(num).to_string();
    std::cout << "bitset string is:" << str << std::endl;

}

其实在C库和C++中还有一些库可能没有顾及到,大家查找类似的即可。此处的例子是在VS2022运行的,所以都针对微软的要求进行了修改,如果使用Posix,则改回标准C库的用法即可。

2、char*(char[])基础类型转字符串
这种就相对简单一些,可以直接使用string的构造函数或赋值构造函数:

void charsTostr()
{
    std::cout << "char* or char[]转字符串..." << std::endl;
    const char* p = "this is test!";
    char buf[] = "my test!";
    std::string s1 = p;
    std::string s2 = buf;
    std::cout << "p or buf transfer string is:"<<s1<<","<<s2 << std::endl;
    std::string s3(p);
    std::string s4(buf);
    std::cout << "p or buf transfer string is:" << s3 << "," << s3 << std::endl;
}

3、使用流
见下文三节3中分析
4、格式化
见下文三节4中分析

三、字符串转基础类型方法

1、基础的方法
如同逆向运算一样,此处C/c++均提供了API,在c++中为:
stoi(s, p, b),stol(s, p, b),stod(s, p, b),stof(s, p, b),stold(s, p, b),stoul(s, p, b), stoll(s, p, b), stoull(s, p, b)等。看名字基本可以知道是字符串与整形,长整形等的转换。
C中提供的API: atof(),atoi(),atol(),strtod(),strtol(),strtoul()。同样,很容易理解,不做过多的解释。

void strTobase()
{
    std::cout << "字符串转基础类型..." << std::endl;
    std::string snum = "345678";
    int num = atoi(snum.data());
    std::string sfloat = "3.333";
    double df = atof(sfloat.data());
    std::cout << "num is:" << num <<",double df is:"<<df<< std::endl;

    num = stoi(snum);
    df = stod(sfloat);
    std::cout << "num is:" << num << ",double df is:" << df << std::endl;

    //C
    df = strtod(sfloat.data(),nullptr);
    std::cout << "c str to double:" << df << std::endl;
    std::string strd = "3.333 test";
    char* p = nullptr;
    df = strtod(strd.data(),&p);
    std::cout << "c str to double:" << df <<",other:" << p << std::endl;

}

2、std::string转char*(char[])
在c++中,经常性的要进行一些传统的C类型字符串和c++字符串的转换,即char*(char[])和string的转换:

void strTochars()
{
    std::cout << "字符串转char* or char[]..." << std::endl;
    std::string str = "test is 123!";
    const char* pstr = str.c_str();
    char* pstr1 = str.data();
    std::cout << "const char * str is:" << pstr<<",char *str is:"<<pstr1 << std::endl;
    char buf[30] = {0};
    str.copy(buf,std::string::npos);
    std::cout << "copy value is:" << buf << std::endl;
}

这里需要注意的是,一般来说,std::string转char[]都有一个拷贝的过程,而不是像char*一样直接转换。
说明:data()方法返回的数组和c_str()返回的数组区别在在于前者不会自动在末尾增加’\0’。

3、流的方法
流的方法在c++中处理有一个非常明显的好处,就是安全,不会出现有些问题字符转化出错导致的程序异常:

template<class T>
std::string base2str(const T val)
{
    std::string s;
   // std::stringstream ss;//C++17 pre
    std::stringstream ss;
    ss << val;
    ss >> s;
    return s;
}

int str2int(const std::string& s)
{
    int val;
    std::stringstream ss;
    ss << s;
    ss >> val;
    return val;
}
double str2d(const std::string& s)
{
    double val;
    std::stringstream ss;
    ss << s;
    ss >> val;
    return val;
}
void streamTransfer()
{
    std::cout << "字符串与其它类型的流式转换..." << std::endl;
    int num = 100;
    double d = 3.33;
    float f = 6.1f;

    std::cout << "base to str transfer value is:" << base2str(num) << "," << base2str(d) << "," << base2str(f) << std::endl;
    std::string snum = "123456";
    std::string sdouble = "123.456";
    std::cout << "snum to num is:" << str2int(snum) << "," << str2d(sdouble) <<  std::endl;

    std::ostringstream nstream;
    nstream << d;
    std::string ns = nstream.str();
    std::cout << "ostringstream to str is:"<<ns << std::endl;

    std::istringstream istream;
    istream.str(ns);
    double d1 = 0.0;
    istream >> d1;
    std::cout << "to int is:"<<d1 << std::endl;

}

这里只是举了几个例子,可以按照这些方法,把所有的基本数据类型的转换都写一遍。

4、格式化输出
除了传统的sscanf(),snsprintf()等函数,在c++17中提供了from_chars()等函数:

void formatTransfer()
{
    std::cout << "字符串与其它类型的格式化转换..." << std::endl;
    //sscanf snsprintf
    std::string snum = "36";
    std::string sfloat = "6.89";
    std::string sTime = "22:30";
    std::string sDate = "20240511";
    int num;
    sscanf_s(snum.c_str(), "%d", &num);
    float f;
    sscanf_s(sfloat.c_str(), "%f", &f);
    int hours, minutes;
    sscanf_s(sTime.c_str(), "%d:%d", &hours, &minutes);
    std::cout << "sscanf to base is:" << num << "," << f <<","<<hours << ","<<minutes << std::endl;
    int year, month, day;
    sscanf_s(sDate.c_str(), "%04d%02d%02d", &year, &month, &day);
    std::cout << "cur date is:" << year << "," << month << "," << day << std::endl;
    std::string sHex = "1dbc36";
    int y, m, d;
    sscanf_s(sHex.c_str(), "%02x%02x%02x", &y, &m, &d);
    std::cout << "cur HEX is:"<<std::hex << y << "," << m << "," << d << std::endl;

    num = 0x126b3c5d;
    char  sformat[30] = {0};
    snprintf(sformat,9,"%02x",num);
    std::cout << "cur hex is:" << sformat << std::endl;

    //c++17
    const std::string str{ "123456789" };
    int value = 0;
    const auto r0 = std::from_chars(str.data(), str.data() + 4, value);
    if (r0.ec == std::errc())
    {
        std::cout << value << ", step " << r0.ptr - str.data() << std::endl;
    }
    else if (r0.ec == std::errc::invalid_argument)
    {
       std::cout << "invalid pars,transfer err!" << std::endl;
    }
    const std::string str1 = std::string("123.36");
    double val = 0;
    const auto format = std::chars_format::general;
    auto r1 = std::from_chars(str1.data(), str1.data() + str1.size(), val, format);
    std::cout << "val is:" << val << std::endl;

    std::string str2 = std::string("abcd");
    const int v = 1234;
    auto r2 = std::to_chars(str2.data(), str2.data() + str2.size(), v);
    if (r2.ec == std::errc())
    {
        std::cout << "str2 is: " << str2 << std::endl;
    }
    else
    {
        std::cout << str2 << ", err " << r2.ptr - str2.data() << " characters " << std::endl;
    }  

}

有些输出函数如std::cout也有一些格式控制,但太简单,这里就不再专门说明。
格式的转换其实在某些场景下特别有用,比如在图像处理或者网络数据分析中,有时候需要DUMP下相关数据进行对比,这时候儿这种十六进制的处理就非常有优势。

四、例程

在上面的基础上,增加调用函数即可形成一个完整的测试例程:

int main()
{
    std::cout << "测试开始..." << std::endl;
    strTobase();
    baseTostr();
    strTochars();
    charsTostr();
    streamTransfer();
    formatTransfer();

    std::cout << "测试完成..." << std::endl;
    return 0;
}

使用上面的例子,特别是一些传统的C的接口,一定注意异常问题,比如在itoa中,如果不给一个数字字符,那么程序就异常了。而如流等的转换,包括c++17后的方法都可以提供一些安全的机制,保证转换错误也不会出现异常。整体的原则是尽量向着C++标准库靠拢。

五、总结

上面只是把常见的一些转化分析说明了一下,其实在不同的环境下,还有很多方法可以进行转换,比如使用字符一个个的处理来进行自定义,使用一些特定的函数(如各种库或框架的函数)等等。大家可以根据自己的实际情况来进行应用,不必拘泥于某种实现。但是需要引起注意的是,简单的方法往往不普遍适用,导致异常;普遍的方法往往不方便。鱼和熊掌不可得兼,请大家根据自己的场景来决定应用的方式。
unicode编码相关等以后有机会再总结分析!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值