用std::string取代char*

本文主要针对那些有C语言背景知识,而现在开始使用C++语言编程的程序员。事实上,C++继承了大多数C语言的功能,但有些方面还是不得不要留意的,如new和delete取代了malloc和free,且C++还使用了STL容器类来静态或动态地分配数组。本文中要讲的是用std::string来取代char*,将会演示C风格数组带来的一系列问题,及如何使用std::string来避免这些问题。

避免“病态”的char数组声明

当声明一个char数组时,许多程序员都会这样做:

char* name = "marius";

乍看起来好像没什么问题,但如果想让字符串首字符大写,最简单的实现方法是:

name[0] = 'M';

代码生成时没有问题,但在运行时会崩溃,因为这是未定义的行为,且依赖于编译器的实现(在VS2005中,可通过编译,但在运行时会崩溃)。对此的解答是:“marius”是一个文字上的字符串,且存储于程序的数据区,“name”只是一个指向数组的指针,因为存储字符串的数据区为只读,所以不允许你修改它。正确的声明形式应该像下面这样:

const char* name = "marius";

这样一来,只要试图修改其中的一个字符,都会被编译器发现,并抛出一个错误:cannot modify a constant variable。

“令人讨厌”的C风格方法

可用char[]来定义一个定长的字符数组:

char name[] = "marius";
name[0] = 'M';

在本例中,name是一个7字符的数组(包括终止符),其由字符串“marius”进行初始化,具有读写权限。
 现在,试着用strcat()衔接一个字符串:

char name[] = "marius";
strcat(name, " bancila");

但程序只要一运行就会崩溃,因为strcat不能确定缓冲区是否可以装下追加的字符串,导致数组越界破坏了内存。
 当然了,你也可声明一个更大的数组来解决这个问题,只要保证它能放下所有的字符就行了,比如说,50个字符长度应该可以放下一个英文名了:

char name[50] = "marius";
strcat(name, " bancila");

这就行了,但如果有Carlos Marìa Eduardo García de la Cal Fernàndez Leal Luna Delgado Galván Sanz这样的名字呢,而且这只是单个西班牙名,另外还有内存空间浪费的问题,如果声明了100个字符长度,平均使用只有20,那一份十万个名字的列表,要浪费800万字节了。

动态分配内存

那么接下来就是寻找动态分配内存最合适的方法:

char* name = new char[strlen("marius")+1];
strcpy(name, "marius");

在此例中,你可重新分配所需的内存,如下所示:

char* temp = new char[strlen(name) + strlen(" bancila") + 1];
strcpy(temp, name);
strcat(temp, " bancila");

delete [] name;
name = temp;

这需要编写及维护更多的代码,另外,在涉及到类时,情况会变得更加复杂。

确保类中内存的正确处理

如果有一个Person类,它存储了人名,你的第一个反应它可能会像下面这样:

class Person
{
   char* name;
};

好像看上去没什么问题,但这个类还应有:

 一个构造函数,它可以接受一个字符串来初始化name;
 一个自定义的拷贝构造函数,以确保深拷贝(默认的拷贝构造函数由编译器提供,它是浅拷贝,也就是说,当从一个对象中复制全部属性的值到一个对象时,它只复制了指针,而不是指向的所有对象)。
 一个自定义的 operator=
 一个析构函数,负责清理动态分配的内存

把这些整合起来之后,Person类就会像下面这样:

class Person
{
   char* name;
public:
   Person(const char* str)
   {
      name = new char [strlen(str)+1];
      strcpy(name, str);
   }

Person(const Person& p)
   {
      name = new char [strlen(p.name)+1];
   }

Person& operator=(const Person& p)
   {
      if(this != &p)
      {
         delete [] name;

name = new char [strlen(p.name)+1];
         strcpy(name, p.name);
      }

return *this;
   }

~Person()
   {
      delete [] name;
   }
};

还是std::string省事

标准模板库(STL)提供了一个std::string类,其是std::basic_string的一个特化,它是一个容器类,可把字符串当作普通类型来使用,并支持比较、连接、遍历、STL算法、复制、赋值等等操作,这个类定义在<string>头文件中。

使用std::string的好处在于:

1、 易于分配、复制及连接。

std::string name = "marius";  // 由赋值进行初始化
name += " bancila";           // 连接
std::string copy = name;      // 复制

2、 可用length()或size()方法确定字符串的长度,这两个方法是一样的,第二个方法只是为了保持STL容器类的一致性。

std::string name = "marius";
std::cout << "length=" << name.length() << std::endl;
std::cout << "length=" << name.size()   << std::endl;

3、 检查是否为空值。

std::string name;
if(name.empty())
   std::cout << "empty string";

4、 支持比较。

if(name == "marius")
{
}

if(name.compare("marius") == 0)
{
}
 方法campre进行大小写敏感的比较,以确定两个字符串是否相等,或其中一个在词典顺序上小于另一个。它的返回值与strcmp()的返回值代表的意义一样:负值表示操作数小于参数字符串,而正值表示操作系统数大于它,0表示相等。另外,还有6个重载版本可允许比较字符串的某一部分:

if(name.compare(0, 3, "mar") == 0)
{
   std::cout << "match";
}

5、 重载操作符 << 和 >>,可从流中读写字符串。

std::string name;
std::cin  >> name;    // 从控制台中读name
std::cout << name;    // 向控制台写name

6、 易于访问字符串中的字符。

std::string name = "marius";
name[0] = 'M';
name[name.length()-1] = 'S';

7、 遍历所有字符,这可由C风格的索引或STL迭代子来完成(如果无需修改,应使用const_iterator)。

std::string name = "marius";

for(size_t i = 0; i < name.length(); ++i)
   std::cout << name[i];

for(std::string::const_iterator cit = name.begin(); cit != name.end(); ++cit)
   std::cout << *cit;

for(std::string::iterator it = name.begin();it != name.end(); ++it)
   *it = toupper(*it);

8、 删除字符串的某一部分。

std::string name = "marius bancila";
// 删除第6个元素之后的所有东西
name.erase(6, name.length() - 6);

9、 在指定位置插入字符串或字符。

std::string name = "marius";
// 在结尾插入
name.insert(name.length(), " bancila");
name.insert(name.length(), 3, '!');

10、在字符串结尾插入其他元素。

std::string name = "marius";
name.push_back('!');

11、两个字符串值的快速交换。

std::string firstname = "bancila";
std::string lastname = "marius";
firstname.swap(lastname);

std::cout << firstname << ' ' << lastname << std::endl;

12、使用c_str()方法只读访问其内部字符数组缓冲区,可在接受字符指针(是否const都行)作参数的函数中使用std::string对象。

void print(const char* name)
{
   std::cout << name << std::endl;
}

std::string name = "marius";
print(name.c_str());

void makeupper(char* array, int len)
{
   for(int i = 0; i < len; ++i)
      array[i] = toupper(array[i]);
}

std::string name = "marius";
makeupper(&name[0], name.length());

13、使用STL算法

std::string name = "marius";
// 使字符串全为大写
std::transform(name.begin(), name.end(), name.begin(),toupper);
std::string name = "marius";
// 升序排列字符串
std::sort(name.begin(), name.end());
std::string name = "marius";
// 反转字符串
std::reverse(name.begin(), name.end());
bool iswhitespace(char ch)
{
   return  ch == ' ' || ch == '/t' || ch == '/v' ||
           ch == '/r' || ch == '/n';
}

std::string name = " marius  ";
// 删除空白字符
std::string::iterator newend = std::remove_if(name.begin(), name.end(), iswhitespace);
name.erase(newend);

14、也可用头文件<sstream>中的std::stringstream来构建字符串。

std::stringstream strbuilder;
strbuilder << "1 + 1 = " << 1+1;
std::string str = strbuilder.str();

来回顾一下前面的Person类,如果用std::string替换了char*,那么剩下的工作只需编写一个构造函数就行了,其他的由编译器来完成,在本例中,复制字符串时使用了浅拷贝,这足够了,因为这个动作触发了std::string的operator=,它会正确地复制字符串。

class Person
{
   std::string name;
public:
   Person(const std::string& str)
   {
      name = str;
   }
};

Person p1("marius");
// works because std::string has a constructor that takes a const
// char*

Person p2("bancila");
p1 = p2;

结论

本文既不是std::string的文档,也不是其辅导书,只是恳求大家使用std::string。用标准模板库中的std::string来取代C风格数组可使代码看上去更简洁、更自然、更易于阅读及维护,也不必担心动态内存分配等问题,由此可忽略一些不必要的细节问题(如内存管理),而集中精力于编程的重要方面,试下吧。

用std::string取代char*相关推荐

  1. 小白提问:C++ 不存在从 “std::string“ 到 “char *“ 的适当转换函数

    提问 看不懂错误,把char nam[ ]换成string,strcpy(name1, nam)换成name1= nam就可以(我知道为什么可以,不知道为什么不可以@TOC #include #inc ...

  2. 如何将std :: string转换为const char *或char *?

    如何将<code>std::string转换为char*或const char* ? #1楼 看看这个: string str1("stackoverflow"); c ...

  3. 脱了裤子放屁之std::string

    一个天天跟c#奋斗的苦逼c++程序猿 改自己曾经代码的时候发现有例如以下几行. char szPath[MAX_PATH] = {0}; GetModuleFileNameA(NULL,szPath, ...

  4. 关于std::string 在 并发场景下 __grow_by_and_replace free was not allocated 的异常问题

    使用string时发现了一些坑. 我们知道stl 容器并不是线程安全的,所以在使用它们的过程中往往需要一些同步机制来保证并发场景下的同步更新. 应该踩的坑还是一个不拉的踩了进去,所以还是记录一下吧. ...

  5. ATL::CStringA和std::string之间转换的一些误区

    对于刚做windows下VC的开发同学,类型转换应该是一个令其很苦恼的问题.我刚写工作的时候,也为这类问题不停的在网上搜索转换方法.最近工作中遇到一个"神奇"的bug(一般&quo ...

  6. std::string::assign 崩溃的问题

    最近遇到了一个assign 崩溃的问题, 代码的话 其实就是去assign,莫名其妙就崩溃,是在一个自动化测试的时候发生的,可能手动测试的时候不会发现! 猜了一下里面的assign的逻辑,基本是这样的 ...

  7. 关于std::string和 C-style string的一些知识点备忘

    C++ 中的std::string和 C-style string 是两种不同的字符串,前者是标准库中定义的一个类,后者是字符数组的别名. C-style string:通常都以\0作为结尾. std ...

  8. linux 共享内存 出错,共享内存的std :: string给出了分段错误(linux)

    我目前正在尝试在Linux上的2个进程之间的共享内存中放置结构.我没有问题共享bool或int但是当尝试共享字符串时,std :: string或char我有一个分段错误错误. 现在我的代码是: #i ...

  9. C++ 字符串、string、char *、char[]、const char*的转换和区别

    1.字符串 字符串本质就是一串字符,在C++中大家想到字符串往往第一反应是std::string(后面简称string) 字符串得从C语言说起,string其实是个类,C语言是没有class的,所以C ...

最新文章

  1. 用Azure VM + Azure Database for MySQL搭建Web服务
  2. Github代码版本控制可视化教程—Git Gui的使用
  3. ajax java对象返回前台少了属性_AJAX常见提交数据的三种方式
  4. Table——高淇JAVA300讲笔记之Guava
  5. Qt 编译时报错“退出,退出代码2”的原因
  6. css3 shapes是什么意思,如何在Web中使用CSS Shapes
  7. c语言topk函数并获取下标,C语言函数语法大全(一)
  8. java调用机器上的shell脚本
  9. 六级词汇打卡第三天(三)
  10. 什么是阻塞和非阻塞?
  11. python123用户登录的三次机会_用户登录三次机会(PYTHON)
  12. sql表达式_SQL表达式
  13. tablayout 增加数字小标_Android中TabLayout添加小红点的示例代码
  14. [渝粤教育] 西南科技大学 机械设计基础 在线考试复习资料
  15. matlab 快速傅里叶反变换函数(ifft)编写
  16. 最基础的傅里叶变换公式推导
  17. 解析几何 直线与平面 直线与平面(1.2)
  18. 雨课堂和微助教比较分析
  19. ArcGis-制图(简单点、线、面符号的制作、保存)
  20. python统计汉字个数是_Python入门(一):一句话统计文章不重复汉字数

热门文章

  1. 内存泄漏检测神器valgrind
  2. Oracle plsql开发ppt,oraclePLSQL基础功能培训.ppt
  3. 人民币金额转换成大写格式(java)
  4. 致敬区块链创业者,Neutrino 开放独立办公间申请
  5. 狼的智慧和处世的天性
  6. iphone二手价格表
  7. ffmpeg 常用命令:视频拼接、裁剪、转图片
  8. 亚马逊新品推广的方法
  9. 南京航空航天大学计算机学院保研,南京航空航天大学2020届保研率17%,主要保研本校、国科大、华五...
  10. 语音增强TFLite模型的安卓部署