字符数组与字符串

Copyright

本页面贡献者:水之幡米拉。 本页面内容遵循 MIT 协议,转载请附上原文出处链接和本声明。

内容

  • 计算机如何储存变量
  • char数组与string

计算机是如何储存数字的

用二进制存储数字

因为计算机的构造原理,计算机只能储存0,1这两种状态(低电位代表0,高电位代表1)

那么如何表示一个我们日常生活中的数呢?这就要用到二进制

假如一段二进制数为 1010,那么它转化为10进制就是

0 * 2^0 + 1 * 2^1 + 0 * 2^2 + 1 * 2^3= 10

内存单元

每一个0/1数字占一位,而一个内存单元有8位,称为字节,即bit,它的单位是byte。 所以,理论上1 byte能表示的最大数是(11111111)_{(2)} =\sum_{i=0}^{7}2^i =2^8 - 1

C++中的变量

C++中 int 类型的变量占用四个字节,也就是有32个0/1

那么理论上它能表示的最大的数应该是2^{32}-1

但是!!

问题在于,它该怎么表示负数?

为了表示负数,C++中把int的最高位,即代表2^{31}的那一位作为符号位,如果它是0,那就是正数,如果它是1,那就是负数,所以它可以表示 [-(2^{31}-1),2^{31}-1]

同样的,long long是8个字节,它的最高位也是表示正负,所以它可以表示 [-(2^{63}-1), 2^{63}-1]

所以说,在爆int的时候,有时候结果就会变成负数

unsigned 类型变量

使用unsigned int/unsigned long long时,它就将所有0/1位都用来表示数值,所以它无法表示负数,但是表示范围扩大了。

且发生算术溢出时,就相当于自动对最高位取模。

比如unsigned int ,发生溢出时,高于最高位的所有数被丢弃,就相当于对2^{31}取模。

计算机如何储存字符

我们可以用二进制来表示数字,但是该如何去表示字母呢?

字母与数字可以说是完全没有关系,所以肯定无法某些运算来得到

所以为了解决这个问题,大家规定,用特定数值来表示某个字母

即,建立数值与字母一一对应关系。

比如说,用65表示A,用66表示B。

ASCLL码

因为用特定数值表示特定字母这种方式是人为规定的,不同地区会有不同的规定,所以为了防止混乱,现在一般采用ASCII码这种对应规则。

美国信息交换标准代码是由美国国家标准学会(American National Standard Institute , ANSI )制定的,是一种标准的单字节字符编码方案,用于基于文本的数据。它最初是美国国家标准,供不同计算机在相互通信时用作共同遵守的西文字符编码标准,后来它被国际标准化组织(International Organization for Standardization, ISO)定为国际标准,称为ISO 646标准。适用于所有拉丁文字字母。

我们发现,大写字母、小写字母、数字的ASCII码是分别连续的。

即, A的ASCII码下一位就是B,c的ASCLL码下一位就是d。

具体应用

我们可以尝试以下程序

1
printf("%d %c\n", 65, 65);

那么具体发生了什么?

  • %d是告诉计算机,接下来要输出的内容,是按照int类型来输出的,所以,计算机会把65对应的值,也就是65本身,输出。
  • %c是告诉计算机,接下来要输出的内容,是按照char类型来输出的,所以,计算机会把65作为ASCII码,来输出其对应的字符,也就是‘A’,输出。
1
printf("%d %c\n", 'A', 'A');

%d是把'A'的ASCII码输出,%c就是输出'A'本身 因为'A'本身在内存中储存形式,其实就是数值,即65,只是根据输出方式不同,而采取不同的表现形式。

1
2
3
4
for(int i = 0; i <= 25; ++i) 
    printf("%c ", 'A' + i);
for(int i = 0; i <= 25; ++i) 
    printf("%c ", 'a' + i);
利用字母ASCII码连续的性质,输出'A'到'Z','a'到'z'的所有字母。

1
2
3
char x = '2';
int t = x - '0';
printf("%d\n", t);
通过减去'0',就可以将字符型数字,转化为数值。

转义符

假如我们想让计算机输出一个空格,或者换行,怎么实现?又或者说,空格和换行,在计算机中是怎么保存的?

这就用到了转义符,在C++中,它是'\'。

比如: * 空格: '\0' * 换行符'\n'

当读入到'\'时,计算机就不会输出'\'了,而是会再读取它后面一位,以此来确定内容。

那么怎么输出一个'\'?

1
printf("\\");

字符数组

如果我们需要储存一串字符的话,比如'ldstql',那么我们就需要使用字符数组

定义

1
char s[10005];
之后的实例中,默认s数组已经被声明过了。

读入

1
scanf("%s", s);
注意,仅在读入字符串,即'%s'时,不需要在变量前加'&'。

输出

1
printf("%s\n", s);

读入

1
scanf("%s", s);
这种方法就是将读入的字符串从s[0]开始,依次往后复制,一直读入到空格/换行符才会停止。 比如你想读入两个字符串,用空格把它们分割,那么当你敲下空格时,计算机看到的其实是'\0',然后它就知道这个字符串结束了。

顺带一提,当你建立一个char数组时,它的每一位默认为是'\0'。

当我们用上述方法读入字符串'ldstql'时,得到的结果就是

1
2
s[0] = 'l'; s[1] = 'd'; s[2] = 's';
s[3] = 't', s[4] = 'q', s[5] = 'l';

那么假如我习惯数组下标是从1开始的,怎么样才能读入的时候,s[1]开始赋值呢?

很简单,只需要在数组名后加上你想要从第几位开始的数字即可。

1
scanf("%s", s + 1);
这样做,就是从第1位开始读入。
1
2
s[1] = 'l'; s[2] = 'd'; s[3] = 's';
s[4] = 't', s[5] = 'q', s[6] = 'l';

输出

输出整个字符串

1
printf("%s\n", s);
它的输出规则是和读入一样的,从第0位开始依次往后输出,一直读入到空格/换行符才会停止

注意!!!! 上述规则意味着,当你读入时,如果是从第一位读入的话,那么输出的时候,也要从第一位开始,因为第0位是空格,即'\0',所以假如还是从第0位输出的话,那么它什么都不会输出。

1
printf("%s", s + 1); // 从第$1位开始输出。

字符串中单个字符

s[i]可以访问字符串中第i个字符,此时可以把它当作字符处理。

1
s[1]='l'; s[2]='d';s[3]='s'
同样的,也可以按照单个字符的形式输出
1
printf("%c", s[1]);

cstring库

C++中有一个专门用来帮助我们处理字符串的库,它就叫

1
#include<cstring>
里面提供了很多类、函数可供我们使用

注意

当你使用库中内容时,必须要include这个库。

strlen()

strlen()这个函数,会返回给定的字符串的长度

1
int n = strlen(s);
它的遍历字符串方式,和scanf/printf是一样的,所以如果字符串是从第一位读入的话,那么如果想要得到正确结果,也必须把第一位传入进去。

1
int n = strlen(s + 1);

我们以后读入了一个字符串,就可以用这个函数来快速获取它的长度了。

1
2
3
4
scanf("%s", s + 1);
int n =  strlen(s + 1);
for(int i = 1; i <= n; i++)
    printf("%c ", s[i]);

strcpy()

strcpy(x, y)的作用是把y字符串拷贝给x

1
2
3
char s1[21], s2[21];
scanf("%s", s1);
strcmp(s2, s1);
同样的,它的遍历数组方式与strlen一样。 所以如果是从第一位开始读入的,复制的时候也要从第一位
1
2
3
4
char s1[21], s2[21];
scanf("%s", s1 + 1);
strcmp(s2, s1 + 1); //从s2的第0位开始粘贴
strcmp(s2 + 1, s1 + 1) //从s2的第1位开始粘贴

string

cstring为我们提供了一个类(现在可以认为是个又特殊功能的变量),叫做 string,它本身就是一个字符串。

声明

1
string s;

读入

1
cin >> s;
string类型变量只能用cin来进行读入,不能使用scanf,且只能从第0位开始读入。 string是不需要声明大小的,它是‘动态’的,即会根据输入的内容自己控制大小。

string其余部分,与char数组一致。

1
2
cin >> s[1]; //读入第$1$位
cout << s[1]; //然后输出

string还自带很多功能。

1
2
3
4
int n = s.length() // 返回s的字符串长度
for(int i = 0; i < s.length(); ++i) // 这样遍历s
    cout << s[i] << " ";
s.clear() // 清除s内的所有内容

两个string类型之间可以直接相加,相当于把后面string的加到前面string的后面。

1
2
string a = "lds", b = "tql";
cout << a + b << endl;

但是,string类型之间是没有减法运算的 : (

一些细节

不管是string,还是char数组,当你对整个数组进行读入时,会直接覆盖掉之前的所有字符,哪怕之前字符串长度是114514,你对整个字符串输入了一个字符串,那就会清除掉之前所有的字符。但如果你对单个位置进行读入,那么是不会覆盖掉其余部分的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
char f[10005];
string s;

cin >> s; //第一次读入
cin >> s; //会覆盖掉所有第一次读入的内容
cin >> s[0] >> s[1] >> s[2]; //只会覆盖掉0,1,2这三个位置

cin >> f; //第一次读入
cin >> f; //会覆盖掉所有第一次读入的内容
cin >> f[0] >> f[1] >> f[2]; //只会覆盖掉0,1,2这三个位置

string数组

string 也是可以开成数组形式的。

1
2
3
4
5
6
string s[10]; // 声明10个string类型的数组
cin >> s[0] //向第0个string中进行读入
cout << s[0][0] // 输出第0个string的第0个字符
int n = s[0].length()//获得第0个string的长度
for(int i = 0; i < s[0].length(); i++) //遍历这个第0个string
    cout << s[0][i] << "0";

例题 判断回文串

给定一个由小写英文字母组成的字符串,判断它是否为回文串。

回文串的定义为:这个字符串从左往右读和从右往左着读是一样的,比如'cucuc'是回文串,而'niconiconi'就不是回文串。

提示

对于一个字符串,假如它的长度为n,我们从第1位开始读入,那么我们怎么知道与它对应的字符呢?

比如第i位,与之对应的就是n - i + 1

1
2
3
4
5
6
7
bool succ = true;
for(int i = 1; i < (n / 2); ++i) {
    if(s[i] != s[n - i + 1]) {
        succ = false;
        break; //既然已经对应不上了,那么它肯定不是回文串,那么就没有继续判断的必要了
    }
}