字符串

一、字符和ascll码

1、 字符类型

c语言中,用char表示字符型数据,指一个字符(有且只有一个字符),用单引号括起来,例如'a', '#'等。

2、字符的存储

所有数据在计算机里都存储为二进制数,为了能用二进制数表示字符,美国国家标准学会(ANSI)制订了ASCII编码(美国标准信息交换代码),用7位的二进制数来表示128个可能的字符。例如

字符空格的ASCII码为32
字符0-9 的ASCII码为48-57
字符A-Z的ASCII码为65-90
字符a-z的ASCII码为97-122

更多信息可查看: ASCII码表ASCII-百度百科

3、字符数字转换

由于字符的存储设计为ASCII码方式,于是字符数据与整型数据是可以通用的,互相赋值、互相运算等。

int n=65;
char c=n;
cout<<c; //'A'

n = c + 1;
cout<<n; //66

c='0' + 3;
cout<<c; //'3'

n = c - '0';
cout<<n; //3

4、大小写

在ASCII码里,小写字符比大写字符的ASCII码大32,所以转换成数字减32,然后转换回字符即成了大写。

char c = 'A';
c = c + 32;
cout<<c; //'a'

5、练习题

5.1 数字的和

输入长度为5的字符串(连续5个字符),每个字符都是数字字符,求所有单个数字字符的和。

例如:15673,和为1+5+6+7+3=22

输入样例-5个字符:

15673

输出样例-单个数字的和:

22

参考代码:

#include <iostream>
using namespace std;

int main()
{
    int sum = 0;// 累加器
    char c; // 定义字符
    for (int i = 0; i < 5;i++) // 循环5次,每次取一位
    {
        cin >> c;
        sum =sum + c - '0';
    }
    cout << sum << endl;    // 输出结果
    return 0;
}
5.2 凯撒加密

古罗马皇帝凯撒在打仗时使用字母错位的方法加密军事情报,这种加密方法会把ABC变成DEF。 编写程序,用c1, c2, c3存储输入的三位字符(大写),输出加密后的三位字符,加密方法是使用ASCII码加上3,然后再输出解密后的原三位字符。

输入样例1

A B C

输出样例1

DEF
ABC

参考代码:

#include <iostream>
#include <cstdio>
using namespace std;

int main()
{
    char c1, c2, c3;
    cin >> c1 >> c2 >> c3;
    c1 = c1 + 3;
    c1 = (c1-'A')% 26 ;
    c2 = c2 + 3;
    c3 = c3 + 3;
    cout << c1 << c2 << c3 << endl;
    //
    c1 = c1 - 3;
    c2 = c2 - 3;
    c3 = c3 - 3;
    cout << c1 << c2 << c3 << endl;
    return 0;
}
5.3 一本通p1129 统计数字字符个数

p1129

参考代码:

#include <iostream>
#include <cstdio>
using namespace std;

int main()
{
    char c;
    int cnt = 0;
    while(cin >> c)// 注意,在window,需要ctrl+Z 然后回车,linux是ctrl+D,然后回车,方可完成输入结束
    {
        if(c >= '0' && c <= '9')
        {
            cnt++;
        }        
    }
    cout << cnt;
    return 0;
}

二、字符数组

1、字符串常量

我们在一开始学习c++的时候,就学会了输出"hello world!",这种用双引号扩起来的一串内容,称之为字符串常量。

cout << "hello world!";

对应的,单引号扩起来的为单个字符常量。

2、字符数组

由字符元素组成的数组,称之为字符数组。

char a[5] = {'a', 'b', 'c', 'd', 'e'};

字符数组与整数数组一样的使用,也有一维、二维之分,同样可以使用下标访问。

3、字符串

c语言中,字符数组加上一个'\0'元素,用来表示一个字符串,程序中通过检测'\0'所在的位置判定字符串是否结束(字符串长度)。

char a[5] = {'m', 'i', 'k', 'e', '\0'};
下标 0 1 2 3 4 5
元素 m i k e \0 b

字符串长度为4,用strlen(s)函数可以计算得到。

4、字符数组初始化

除了用数组一样的初始化方式之外,字符数组还可以直接使用字符串常量进行初始化,例如:

char a[] = "hello";
char b[] = {"tom"};

当使用字符串常量进行初始化时,系统会自动给数组后面加上'\0',所以长度要多1个。

5、输入字符串

当从终端读入字符串时,可以根据场景使用不同的方法: char a[100];

  • cin >> a: 当遇到空格、tab、换行就会停止。
  • gets(a):可以读入空格、tab,遇见换行停止,超过不会报错。
  • cin.getline(a, len):读入指定长度的字符串,长度为len-1(因为还有一个'\0')。

6、扩展理解

6.1、转义符

'\0'是一个特殊字符,其ASCII码为0,''称之为转义符,有很多类似的特殊字符,例如:

字符形式 含义
\n 换行,位置移到下一行开头
\r 回车,位置移到本行开头
\0 字符串结束符号
\t 制表符,tab键
| 反斜杠字符
' 单引号字符
" 双引号字符
6.2. c字符串

在c++中,提供了string类用来表示字符串,为了区分字符数组和string,一般把字符数组以'\0'结束的称之为c字符串。

三、字符数组函数

  1. strlen-长度
  2. strcpy-拷贝
  3. strcmp-比较
  4. strcat - 连接
  5. strlwr-大小写

1. 长度

1.1 数组长度

如果定义的时候没有声明长度,可以采用sizeof的方法:

char a[] = "hello";
  int len = sizeof(a) / sizeof(a[0]); //6
sizeof介绍

sizeof是一个运算符,并不是函数,它是用来计算所占内存空间的大小,单位是字节,主要是针对我们所给的 数据类型分析得出的。

int a = 2;
char b = '3';
short c = 2;
long long d = 2;

通过sizeof计算得到: a 的大小是 4,因为 a 的数据类型是 int,int 是4个字节; b 的大小是 1,因为 b 的数据类型是 char,char 是4个字节; c 的大小是 2,因为 c 的数据类型是 short,short 是2个字节; d 的大小是 8,因为 d 的数据类型是 longlong,longlong 是8个字节 所以我们不难看出的是:

sizeof计算所占内存的大小,就是看我们传入的数据,它的数据类型是几个字节,sizeof算出来的就是几个字节。

sizeof与数值型数组

int main()
{int arr[5] = {1,2,3,4,5};
printf("%d\n",sizeof (arr));
printf("%d\n",sizeof (arr[0]));
return 0;}

我们首先定义了一个一维数值型数组,数组arr有五个元素,我们这里 sizeof(arr),计算的是整个数组的大小,该数组元素有5个,都是int类型,整个数组大小就是 5 * 4 = 20,也就是20个字节。

而 sizeof(arr[0]) 计算的是我们arr数组的第一个元素,下标是0,arr[0] 的数据类型是 int,所以是4个字节。 那么对于其他种类的数值型数组,大家可以自己动手来观察一下。

(记住sizeof是根据你给的数据的数据类型来计算的哦)

sizeof与字符数组

int main()
 {
     char arr[5] = {'a','b','c','d','e'};
     printf("%d\n",sizeof (arr));
     printf("%d\n",sizeof (arr[0]));
     return 0;
 }

我们看到 sizeof(arr) 得到的是 5,因为这里的 arr 数组名是代表的整个数组,数组一共有五个元素,每个元素是 char类型,所以整个数组大小是5*1 = 5个字节,那么sizeof(arr[0])里面的 arr[0] 数据类型是 char,char是 1 个字节,所以 sizeof 得到就是1个字节。

这跟上面数值型数据是一样的道理,都是看sizeof里面的数据类型是什么。

sizeof与字符串

int main()
 {
     char arr[] = "abcde";
     printf("%d\n",sizeof (arr));
     printf("%d\n",sizeof (arr[0]));
     return 0;
 }

在这里我们看到了不一样的结果了,跟以往不同,我们明明是给了字符串 5 个元素,那为什么sizeof(arr) 却得到了 6 呢?

这是因为我们在给字符串赋值的时候,字符串末尾自动补上了一个 ‘\0’

所以 sizeof 在计算大小的时候也会将 ‘\0’ 计算在里面,也就是说,在 sizeof(arr) 里认为,有6个元素,分别是 abcde\0,每个元素都是char类型,就是6个字节。

1.2 字符串长度

对于'\0'结束的字符数组,用sizeof算出的是分配空间的长度,而字符串长度可用strlen函数计算:

char a[10] = "hello";
int len = strlen(a); //5

2. 拷贝

2.1 strcpy函数

strcpy(dest, src):将src的内容(包括结束符'\0'),拷贝到dest中, 函数返回dest。

char dest[10];
strcpy(dest, "hello"); //dest: hello\0

注意

  1. 此函数内部实现是以结束符'\0'为循环结束条件的,如果src不是'\0'结束的,会出现意外危险。
  2. 如果src长度超过dest,则会出现溢出危险,即其它地址的内容被修改。
2.2 strncpy函数

strcpy(dest, src, len):将src的内容的前len位,拷贝到dest中, 函数返回dest。

char dest[10] = "123456789";
strncpy(dest, "hello", 3); //dest: ``hel456789`

3. 连接

strcat(dest, src):将src连接到dest后面,src的第一个元素替代了dest的结束符'\0'。

char s1[20] = "i am";
char s2[] = "a boy";
strcat(s1,s2); //s1-->i am a boy\0

注意:如果s1的长度不够的话,也会产生溢出。

4. 比较

strcmp(s1, s2): 比较两个字符串是否相等:

  • 相等:返回0
  • s1 > s2:返回正整数(第一个不等的字符的差值)
  • s1 < s2:返回负整数(第一个不等的字符的差值)
char pwd[] = "123456";
int b = strcmp(pwd,"123456"); //b == 0

5. 大小写

5.1 字符大转小

tolower(c):将一个字符转为小写,返回值为 int(ASCII码)。

char c = tolower('A');  //'a'
5.2 字符小转大

toupper(c):将一个字符转为大写,返回值为 int(ASCII码)。

char c = toupper('b'); //'B'
5.3 字符串大小写

c++(c)语言标准库没有实现字符串大小写转换的函数,需要自己实现。

四、string类

  1. string类
  2. 变量声明和实例化
  3. 常用运算符
  4. 输入输出
  5. 下标访问
  6. 字符串数组

1、string类

c语言使用'\0'结束的字符数组来表示字符串,为了更方便的使用字符串,c++中专门提供了一个string类,包含在命名空间std中,类库为cstring。

#include <cstring>

string s = "hello world!";

2、变量声明和实例化

string类型的变量声明和基本类型(int、short、char、bool等)一样:

string s;

但是string是一个类,其变量初始化需要经过实例化,即调用构造函数来初始化。

以下几种典型的实例化方法:

string s("hello");  //将c字符串"hello"作为s的值
string s(str1);     //拷贝str1字符串变量的值作为s的值
string s(10, 'a');  //生成10个'a'组成的字符串作为s的值
stirng s = "hello";

3、常用运算符

为了更方便的对字符串变量进行操作,string类重载了一些运算符,使用起来更加一致。

string s = "hello world!";  //赋值
string s2 = s + " 1234";  //连接成"hello world! 1234"
if (s2 == s1)  //是否相等

4、输入输出

string类型变量的输入,可以使用几种:

  • cin>>s:遇见空格、tab、回车就停止。
  • getline(cin, s):读入一行,遇见回车停止。
  • getline(cin, s, ch):读入一行,遇见ch(字符)停止。

如果想输入带空格的语句,使用getline函数。

输出字符串变量跟原来一样,如果想输出地址,使用&地址符。

5、下标访问

string类也提供了下标运算符[],可以使用下标来访问字符元素。

s = "hello";
cout << s[0] << endl;  //'h'

s[0] = 'H';
cout << s << endl; //"Hello"

6、字符串数组

每个元素都是一个字符串,例如:

string color[3] = {"red", "blue", "yellow"};

五、string类字符串函数

  1. 赋值:assign
  2. 拼接:push_back、append
  3. 查找:find
  4. 插入:insert
  5. 删除:erase
  6. 替换:replace
  7. 子串:substr
  8. 长度:length、size

1、拼接

1.1 拼接字符
  • s.push_back(ch):时间复杂度为O(1),最适合循环里调用
  • s = s + ch:时间复杂度介于O(1)和O(n)之间。
1.2 拼接字符串
  • s.append(t):时间复杂度为O(n)。
  • s = s + t: 时间复杂度为O(n)

2、查找

  • s.find(t):在s中查找t,返回t第一个出现的位置,如果找不到返回string::npos(2^64-1)
  • s.find(t, x): 从第x位开始查找
  • s.find_first_of(t, x): 从x位置开始找,第一个t里的字符出现的位置。
  • s.find_first_not_of(t, x):从x位置开始找,第一个不在t里的字符出现的位置。
  • s.rfind(t): 从后往前找
  • s.find_last_of(t, x):从后往前找,第一个t里的字符
  • s.find_last_not_of(t, x):从后往前找,第一个不在t里的字符
s = "hello world!";
int pos = s.find("world"); //6

3、插入

s.insert(x, t):在s的第x位上插入字符串t。

s = "01234";
s.insert(3, "abc"); //012abc34

注意:x一定要是合法的下标!

4、删除

  • s.erase(x): 从第x位开始删除到最后
  • s.erase(x, len):从第x位开始删除len位
s = "abcdefg";
s.erase(2, 3); // cde被删除了,剩下abf

注意:x一定要是合法的下标!

5、替换

s.replace(x, len, t):用t替换s中从x开始长度len的一段子串。

s = "abcdefg";
s.replace(2, 1, 123); //ab123def:cd被替换成了123

注意

  • x -> x+len-1 都要是合法的下标
  • 替换可以换成删除+插入实现

6、子串

s.substr(x, len):取出从第x位开始,长度为len 的子串。

s = "abcdefg";
string s2 = s.substr(3, 2); //s2 -> de

注意

  • x一定要是合法的下标。
  • 如果x + len -1越界,则只取合法内容。

7.长度

string s;
cin >>s;
//这个字符串的长度可以用以下两个函数来求
s.length();
s.size();

六、综合练习

1.一本通:1130:找第一个只出现一次的字符

1130:找第一个只出现一次的字符

参考代码:(思路1:暴力做法)

#include <iostream>

#include <cstring>
using namespace std;

int main()
{
    string s; //定义c++字符串变量s
    cin >> s; // 输入的内容放在变量s中
    int flagG = 0; // 是否找到只出现一次的标志变量,0表示没找到,1表示找到了
    // 枚举s中的每一个字符,
    for (int i = 0; i < s.length(); ++i) // 1e5
    {
        int flag = 0;// 有没有重复标志变量,0表示没有重,1表示重复
        // 枚举的每一个字符s[i],与其他的每一个字符比较,相同则重复
        for (int j = 0; j < s.length(); ++j) // 1e5
        {
            if(i == j) // 不能为同一个字符
                continue; // 是则不执行后面的语句,继续循环
            if(s[i] == s[j]) // 如果相同了,则表示重复
            {
                flag = 1; 
            }
        }
        if(flag == 0) // 检查s[i]这个字符没有重复
        {
            cout << s[i] << endl; // 输出这个字符
            flagG = 1; // 找到了不重复的字符,标志变量设为1
            break; // 找到就退出循环
        }
    }
    if(!flagG) //没找到只出现一次的字符,要输出no
        cout << "no";
    return 0;
     
}

参考代码2(桶数组的思路,可降低时间复杂度为O(n))

#include <iostream>

#include <cstring>
using namespace std;

int main()
{
    string s; //定义c++字符串变量s
    cin >> s; // 输入的内容放在变量s中
    int flagG = 0; // 是否找到只出现一次的标志变量,0表示没找到,1表示找到了
    int to[200] = {0}; // 桶数组,下标表示的是字符的ASII码,内容表示这个字符有几个,注意要初始化数组内容全为0
    //
    for (int i = 0; i < s.length(); ++i)
    {
        to[s[i]]++;
    }
    for (int i = 0; i < s.length(); ++i)
    {
        //这个s[i],在桶中的数量为1,则说明是不重复的
        if(to[s[i]] == 1)
        {
            flagG = 1;
            cout << s[i] << endl;
            break;
        }
    }
    if(!flagG)
        cout << "no";
    return 0;
}