第五课.容器(二)

字符串简介

字符串变量与常量

字符串变量需要满足两个条件:
1.一个数组,其元素均是字符;
2.最后一个元素以空字符'\0'结束(C++中单引号内是字符,双引号内是字符串);
需要注意,定义字符串变量时,应当留意为空字符预留空间,使空字符自动加入到分配的空间中:

//注意必须是双引号
char str[11]={"helloworld"};
//或者简写
char str[]={"helloworld"};

如果写成char str[10]={"helloworld"};,在visual studio编辑窗口中,会自动提示错误:const char[11]类型的值不能用于初始化char [10]类型的实体;
字符串常量是一对双引号括起来的字符序列,字符串中每个字符作为一个数组元素存储,比如字符串:"helloworld"
字符串常量同样以空字符结尾:
fig1

关于字符表示的说明

对于0 '\0' '0'
在机器中,表示为:

char c1=0; //0x00
char c2='\0'; //0x00
char c3='0'; //0x30

虽然0是int类型的数字,然而在内存中,由于char的限制,依然只使用一字节:00,另外,观察机器码可以发现 c 1 c_{1} c1 c 2 c_{2} c2是一样的;
对于 c 3 c_{3} c3,保存的是字符0,字符0的ascii码为48,十六进制为30,注意ascii码用于字符编码,补码专用于数值编码,两者并没有关系;


ascii码是基于拉丁字母的编码方式,主要用于英语,使用指定的7位或8位二进制数组合表示128或256种可能字符;

'0'0x30
NULL0x00
'A'0x41
'a'0x61
DEL:0x7F

现在看来,ascii已经不能满足全球使用,所以诞生了Unicode编码,Unicode编码的初衷就是把世界上的文字都映射到一套字符空间;

Unicode编码

为了表示Unicode字符集合,诞生了3种编码方式:

  • 1.UTF-8:
    一个字节(byte)表示字符,兼容ascii码;存储效率高,为了表示其他国家的语言,则将英语对应的utf-8重新组合去编码,比如汉语一个汉字由两个byte组合表示,UTF-8也是python的默认编码;
  • 2.UTF-16:
    UTF-8为了表示其他语言,是不定长的,当汉语英语混合时,机器需消耗资源先判断字符是什么类别,所以需要定长的UTF-16编码,UTF-16是定长的2byte;

需要注意一个问题,当使用大于等于两个byte的编码时,需要考虑字节序,当两个机器进行交互(比如网络传输)时,如果不说明字节序,将会出现乱码;
编码错误的根本在于编码与解码方式不统一


  • 3.UTF-32:
    用4个byte定长表示字符,与UTF-16相比,扩充了表达范围,但依然面临字节序问题,事实上,UTF-32使用并不广泛;

由于需要解决字节序问题,Windows的文件会携带BOM(byte order mark),如果要在其他平台正确读该文件,需要去除BOM,比如Linux下:

dos2unix

编码实例

首先基于Notepad++指定编码:
fig2
ANSI就是最经典的ascii编码,UCS-2实际上相当于UTF-16;
为了便于读取文件,将文件保存到工程所在目录,Visual Studio中查看方式为:
右击文件,选择文件所在目录:
fig3
文件内容为,编码方式为UTF-16大端法:

helloworld

在解决方案资源管理器"添加"->“现有项”,加入之前保存好的文本文件,加入之后在解决方案资源管理器中右击文件,选择打开方式为二进制编辑器:
fig4
编码看似复杂,其实仔细观察会发现,0x68正是h的ascii码,由于是UTF-16,所以扩充为两字节0x0068,大端法的高位在最前,如果是小端法则为0x6800,FE FF也是告知系统,编码为大端法,同理,FF FE则代表小端法;
(00000000和00000010是Visual Studio的原因,可以忽略)如果文件携带BOM,则最开头会出现EF BB BF之类的3个连续字节,以告知系统编码方式与字节序的选择;
可见,去除BOM的方法也很简单,只需要写一个程序删除前3个字节就行;


html,xml等文件可以用于浏览器,因为它们自身就携带编码信息,从而便于解码,便得以在不同平台间广泛使用


字符串的指针表示

这里补充两个与指针相关的单目运算符:*(定义指针变量)和&(取地址),指针中存储的内容本质就是地址,指针变量用于存储其他变量的地址;
字符串的指针表示方法为:

char* pst="helloworld";

指针变量pstr指向字符类型,"helloworld"是一个字符串常量,即pstr指向内存中的字符串常量"helloworld"所在的地址,字符串常量"helloworld"以空字符结尾(保存在数组中),pstr指向首字符’h’,存储了’h’的地址:
fig5
回顾本篇开始的字符串变量定义,使用了数组char […]的方式,char [ ]与char*有以下例子;
比如 char [ ] 方式:

char strhello[11] = { "helloworld" };

//注意不要将10写成11,虽然不报错,但字符串的结束符默认不要操作
for (int index = 0; index < 10; ++index)
{
	strhello[index] += 1;
	cout << strhello[index] << endl;
}

当使用 char* 方式时:

char* pstrhello = "helloworld";

for (int index = 0; index < 10; ++index)
{
	pstrhello[index] += 1;
	cout << pstrhello[index] << endl;
}

此时,会发生中断,因为pstrhello指向的是没有写权限的区域,因为指针变量指向的是字符串常量,常量是不能修改的,如果让指针指向字符串变量(数组形式),则可以进行修改:

char* pstrhello = strhello;

for (int index = 0; index < 10; ++index)
{
	pstrhello[index] += 1;
	cout << pstrhello[index] << endl;
}

可见,指针变量指向的内容是否可变取决于该内容是常量还是变量;
指针变量在运行过程中,可以改变指向:

char strhello[11]={"helloworld"};
char* pstrhello="helloworld";
pstrhello=strhello

而数组char [ ]在运行中,不能改变地址(指向),如果写strhello=pstrhello就会出错;


数组一旦声明,地址就不能再改变,指针可以改变指向是因为指针变量是一个变量而不是地址


字符串的基本操作(一)

获取字符串的长度:

#include<string.h>

char* s="helloworld";
strlen(s);
//返回字符串s的长度,10,不包含'\0'

字符串比较,两个字符串从左向右逐个字符按照ascii的值比较,直到出现不同的字符或遇到'\0'为止:

strcmp(s1,s2);
/*
如果s1和s2相同,返回0;
如果s1<s2,返回值小于0;
如果s1>s2,返回值大于0
*/
char* s1 = "Apple";
char* s2 = "Bank";
cout << strcmp(s1, s2) << endl; //-1

比如s1="Apple"与s2=“Bank”,字符A与B相比,已经不同且A小于B,所以直接返回小于0的数;
字符串拷贝与拼接(个人认为这是早期C++遗留下来的不太好的函数):

strcpy(s1,s2);
//将s2拷贝到s1,(从s1的首字符开始),注意s1数组的空间要大于等于s2
strcat(s1,s2);
//需要确保s1数组的空余空间可以容纳s2

查找字符串:

strchr(s1,ch);
//ch在s1中首次出现的位置
cout << strchr(s1, 'l') << endl;
// le

strstr(s1,s2);
//s2在s1中首次出现的位置
cout << strstr(s1, "pp") << endl;
// pple

字符串的基本操作(二)

在字符串的基本操作(一)中,strcat实际上是一个很危险的函数,虽然说对于strcat(s1,s2);需要确保s1数组的空余空间可以容纳s2,但这需要靠开发者自觉,如果在s1的结束符后开始拼接也是可以的,不注意这个问题的话,可能会导致内存中紧跟着s1后的内容被s2覆盖,这会暗中修改了机器的逻辑,极其危险;
这是真实发生过无数次的教训,曾经一个黑客就是利用strcat的方式将机器中用作判断权限的语句完整覆盖,直接获得权限操控机器,盗取了管理员看守的秘密信息;这种方式也就是计算机历史上第一个病毒:蠕虫病毒;其本质就是缓冲区溢出,暗中覆盖原有的逻辑;
从这个角度看,能很好表现出C++的特点,开发者必须时刻关注内存的管理,尤其注意边界检查;
为了修正安全问题,C++已经有了安全的api,只需要在以前的函数名后加_s()_s()的意思是security,比如strcat_s(),通过观察其实现,其实就是在原有基础上增加了边界检查功能;
使用strlen_s,strcpy_s,strcat_s才更安全
安全版本的api在调用上略有不同,以strcat_s为例,在visual studio下,点击函数名后按F1可以自动跳转api的网页文档,阅读文档有助于我有效调用函数:

errno_t strcat_s(
   char *strDestination,
   size_t numberOfElements,
   const char *strSource
);

参数说明为:
fig6
实例如下:

const int STR_LEN = 16;
char cat1[STR_LEN] = { "hello" };
char* cat2 = "world";

strcat_s(cat1, STR_LEN, cat2);

cout << cat1 << endl; //helloworld

如果将STR_LEN改为5,将会中断报错,报错信息为Buffer is too small;


字符串操作的开源库Redis,高效是基于空间换时间,Redis下的字符串设计:
fig7
Redis链接
sdshdr是一个动态的字符串对象


C++的新型字符串string

C++标准库提供了string类型专门处理字符串:

#include<string>

使用string可以更方便和安全地管理字符串;
定义字符串变量与初始化方式:

string s; //定义空字符串
string s="hello"; //定义并初始化
string s("hello");
string s=string("hello");

相关函数
获取字符串长度:

s.length();
s.size(); //等价于s.length()

s.capacity(); //返回的是这个容器的真正长度

字符串的比较:

string s1="hello";
string s2="world";
cout<<(s1==s2)<<endl;
cout<<(s1!=s2)<<endl;

实例如下:

string mys = "hello";
cout << mys.size() << endl; //5
cout << mys.length() << endl; //5
cout << mys.capacity() << endl; //15

查看变量mys,包含有以下对象:
fig8常用操作
string类型转为传统C风格:

string s="hello";
const char* cpp_str=s.c_str();

关键字const距离char近,代表定义一个指向字符常量的指针;
随机访问:

string s="hello";
s[0]='m';
s; //mello

字符串拷贝(方便且安全):

string s1="hello";
string s2=s1;

字符串连接:

string s1="hello",s2="world";
string s3=s1+s2; //拼接结果在s3,即s3:helloworld
s1+=s2; //拼接结果在s1,即s1:helloworld

string的出现极大方便了字符串的使用,但没有传统C语言的字符串精简高效,适用于对性能要求不高的情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值