c++字符串与c字符串


你编写的每个应用程序都会使用某种类型的字符串。使用老式C语言时,没有太多的选择,只
能使用普通的以null结尾的字符数组来表示字符串。遗憾的是,这种表示方式会导致很多问题,例 如会导致安全攻击的缓冲区溢出。C++ STL包含了一个安全易用的std::string类,这个类没有这些 缺点。

1. C风格的字符串

在C语言中,字符串表示为字符的数组。字符串中的最后一个字符是空字符(\0’)
C++包含一些来自C语言的字符串操作函数,它们在< cstring >头文件中定义。

函数名称说明
strlen()返回字符串长度,不包含空字符‘\0’的一字节,字符串长度不等于字符个数
strcpy()字符串拷贝

C和C++中的sizeof操作符可用于获得给定数据类型或变量的大小。例如,sizeof(char)返回1,
因为char的大小是1字节。但是,在C风格的字符串中,sizeof()和strlen()是不同的。绝对不要通过 sizeof()获得字符串的大小。如果C风格的字符串存储为char[],则sizeof()返回字符串使用的实际内存,包括‘\0’字符。例如:

char text[]= "abcdef";
size_t s1= sizeof(text); // is 7
size_t s2 = strlen(text); // is 6

但是,如果C风格的字符串存储为char*, sizeof()就返回指针的大小!例如:

const char* text2 = "abcdef";
size_t s3 = sizeof(text2); // is platform-dependent
size_t s4 = strlen(text2); // is 6

在32位模式编译时,s3的值为4,而在64位编译时,s3的值为8,因为这返回的是指针const char*
的大小。可在<cstring>头文件中找到操作字符串的C函数的完整列表。

在Microsoft Visual Studio中使用C风格的字符串函数时,编译器可能会给 出安全相关的警告甚或错误,说明这些函数已经被废弃了。使用其他C标准库函数可以避免这些警告,例如
strcpy_s()和strcat_s(),这些函数是“安全C库” (ISO/IEC TR 24731)标准的一部分。然而,最好的解决方案是切换到C++的 string 类。

2. 字符串字面量

注意,C++程序中编写的字符串要用引号包围。例如,下面的代码输出字符串"hello",这段代码
包含这个字符串本身,而不是一个包含这个字符串的变量:

cout <<"hello" << endl;

在上面的代码中,"hello”是一个字符串字面量(string literal),因为这个字符串以值的形式写出,
而不是一个变量。与字符串字面量关联的真正内存在内存的只读部分中。通过这种方式,编译器可 以重用等价字符串字面量的引用,来优化内存的使用。也就是说,即使一个程序使用了 500次"hello"字符串字面量,编译器也只在内存中创建一个hello实例。这种技术称为字面量池
(literal pooling)。
字符串字面量可以赋值给变量,但因为字符串字面量位于内存的只读部分,且使用了字面量池,
所以这样做会产生风险。C++标准正式指出:字符串字面量的类型为“n个const char的数组”,然而为了向后兼容较老的不支持const的代码,大部分编译器不会强制程序将字符串字面量赋值给 const char类型的变量。这些编译器允许将字符串赋值给不带有const的char,而且整个程序可以 正常运行,除非试图修改字符串。一般情况下,试图修改字符串的行为是没有定义的。它可能会导 致程序崩溃;可能使程序继续执行,看起来没有什么莫名其妙的副作用;可能不加通告地忽略修改 行为;可能修改行为是有效的,这完全取决于编译器。例如,下面的代码展示了未定义的行为:

char* ptr = "hello"; // Assign the string literal to a variable.
ptr[1] = 'a ';       // Undefined behavior!

一种更安全的编码方法是在引用字符串常量时,使用指向 const 字符的指针。下面的代码包含
同样的 bug,但由于这段代码将字符串字面量赋值给 const char* 所以编译器会捕捉到任何写入只 读内存的企图。

const char* ptr = "hello";  // Assign the string literal to a variable.
ptr[ 1] = 'a';              // Error! Attempts to write to read-only memory

还可以将字符串字面量用作字符数组(char®的初始值。这种情况下,编译器会创建一个足以放 下这个字符串的数组,然后将字符串复制到这个数组。因此,编译器不会将字面量放在只读的内存中,也不会进行字面量的池操作。

char arr [] = "hello"; // Compiler takes care of creating appropriate sized 
					   // character array arr.
arr[ 1] = 'a ';        // The contents can be modified.

3.C++ std::string 类

C++提供了一个得到极大改善的字符串概念,并作为标准库的一部分提供了这个字符串的实现。 在C++中,std::string是一个类(实际上是basic string模板类的一个实例),这个类支持
<cstring >中提 供的许多功能,还能自动管理内存分配。string类在std名称空间的< string >头文件中定义。

3.1 C风格字符串的优势和劣势

优势劣势
很简单,底层使用了基本的字符类型和数组结构为了模拟一等字符串数据类型,需要付出很多努力
量级轻,如果使用得当,只会占用所需的内存使用难度大,而且很容易产生难以找到的内存bug
很低级,因此可以按操作原始内存的方式轻松操作和复制字符串没有利用C++的面向对象特性
能够很好地被C语言程序员理解要求程序员了解底层的表示方式

3.2 使用string类

尽管string是一个类,但是几乎总是可以把string当做内建类型使用。事实上,把string想象为
简单类型更容易发挥string的作用。通过运算符重载的神奇作用,C++的string使用起来比C字符串容易得多。

3.2.1 std::string 字面量

源代码中的字符串字面量通常解释为const char*。使用用户定义的标准字面量”s”可以把字符串
字面量解释为std::string。例如:

auto stringl = "Hello World"; // stringl will be a const char*
auto string2 = "Hello World"s; // string2 will be an std::string

3.2.2 c++字符串的数值转换

数值转字符串字符串转数值
to_string(int val)int stoi(const string& str, size_t *idx=0, int base=10)
to_string(unsigned val)long stol(const string& str, size_t *idx=0, int base=10)
to_string(long val)unsigned long stoul(const string& str, size_t *idx=0, int base=10)
(1)字符串转数值

int stoi(const string& str, size_t *idx=0, int base=10)

stoi将n进制字符串转为十进制,第二个参数是字符串起始位置,第三个参数表示n进制

也可以直接用重载的 int stoi(const string& str),默认字符串为十进制,起始位置为0,制

#include<string>
#include <iostream>
#include <exception>
using namespace std;
int main()
{
	int i=0;
	try
	{
		i = stoi("FEEF", 0, 16);
		//int i = stoi("我的"); 输入非法时,可以捕获异常"invalid stoi argument"
	}
	catch (exception e)
	{
		cout << e.what() << endl;
	}
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述

(2)数值转字符串

转十进制可以用to_string,但是低版本的编译器可能不支持

转多进制可以用_itoa_s,但似乎没有安全机制,导致无法捕获异常

	char cstr[20];
	_itoa_s(100,cstr,2);
	cout << cstr << endl;

转多进制还可以用 stringstream

#include<string>
#include <iostream>
#include <sstream>
#include <bitset>
using namespace std;
int main()
{

   int num = 1234;
   stringstream stream;

   //转二进制
   stream << bitset<16>(num);
   cout << stream.str() <<endl;
   stream.str(""); //清空缓存,如果注释掉,那么会输出所有的历史结果
   //转8进制
   stream << oct << num;
   cout << stream.str() << endl;
   stream.str("");
   //转十六进制
   stream << hex << num;
   cout << stream.str() << endl;
   stream.str("");
   system("pause");
   return 0;
}

结果:
在这里插入图片描述

如果需要把格式化后的字符串通过>>输出到字符串, 必须每次都调用clear()方法

3.2.3 c++常用字符串函数

函数功能
append将字符添加到字符串的末尾
at返回字符串中的指定位置处的元素的引用
c_str将字符串的内容转换为 C 样式空终止字符串
data转换为字符数组的字符串的内容
empty测试是否该字符串包含的字符
erase从指定位置字符串中移除元素或某个范围的元素
find与指定的字符序列匹配的子字符串的第一个匹配项的向前搜索字符串
find_first_not_of搜索与指定任何的字符串元素相匹配的第一个字符的字符串
find_first_of搜索与指定任何的字符串元素相匹配的第一个字符的字符串
rfind向后方向中的首次出现的与指定的字符序列匹配的子字符串搜索字符串
pop_back清除该字符串的最后一个元素
push_back该字符串的末尾添加一个元素
insert将某个元素的数目或某个范围的元素插入到指定位置的字符串
length返回字符串中的元素的当前数目
replace替换指定的字符或从其他区域或字符串或 C 字符串复制的字符在字符串中位于指定位置处的元素
substr从指定位置的字符串开头的复制的子字符串的最大字符数

3.2.4 原始字符串字面量

原始字符串字面量(raw string literal)是可以横跨多行代码的字符串字面量,不需要转义嵌入的双引号,像\t和\1!这种转义序列不按照转义序列的方式处理,而是按照普通文本的方式处理。如果像下面这样编写普通的字符串字面量,那么会收到一个编译器错误,因为 字符串包含了未转义的引号:

string str = "Hello "World"!// Error!

对于普通的字符串,必须转义双引号,如下所示:

string str = "Hello \"World\"!";

对于原始字符串字面量,就不需要转义引号了。
原始字符串字面量的写法:

string str = R"(Hello "World"!)";

原始字符串字面量可以跨越多行代码。例如,如果像下面这样编写普通的字符串字面量,那么会收到一个编译器错误,因为普通的字符串字面量不能跨越多行:

string str = "Line 1
Line 2 with \t"; // Error!

可使用如下原始字符串字面量来替代:

string str = R"(Line 1
Line 2 with \t)";

这也说明,使用原始字符串字面量时,\t转义字符没有替换为实际的制表符字符,而是按照字面形式保存。将str写入控制台得到的输出如下所示:
Line 1
Line 2 with \t
因为原始字符串字面量以)“结尾,所以使用这个语法时,不能在字符串中嵌入)”。例如,下面的 字符串是不合法的,因为在这个字符串中间包含了一个)”:

string str = R"(The characters )"n are embedded in this string)" // Error!

如果需要嵌入)",则需要使用扩展的原始字符串字面量语法,如下所示:

R"d-char-sequence(r-char-sequence)d-char-sequence"

r-char-sequence是实际的原始字符串。d-char-sequence是可选的分隔符序列,原始字符串首尾的分隔符序列应该一致。分隔符序列最多能有16个字符。应选择未出现在原始字符串字面量中的序列作为分隔符序列。上面的例子可以改用唯一的分隔符序列:

string str = R"-(The characters )" are embedded in this string)-";

在操作数据库查询字符串和正则表达式等字符串时,原始字符串字面量可以令程序的编写更加方便。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页