C —— 字符串

在 C 语言中,字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。

字符

关键字 char 可以用来声明一个字符变量。char 变量既是一种整数(最小的整数类型),也是一种特殊的字符类型。如下所示:

#include<stdio.h>

int main() {
	char c = 'A';
	printf("c=%d, c=%c", c, c);
	return 0;
}

执行结果:

c=65, c=A

可以发现,字符 A 在输出的时候,既可以使用数字格式输出,也可以使用字符格式输出,原因是它的数据类型是 char 类型,是一种既是整型又是字符的数据类型。既然是整型,那就能进行算数运算。如下例:

#include<stdio.h>

int main() {
	char c = 'A' + 1;
	printf("c=%d, c=%c", c, c);
	return 0;
}

执行结果:

c=66, c=B

实际上,char 能将字符 A 进行算数运算的原因是它的每个字符都有对应的无符号整型的数据与之对应。从这里的例子中可以知道,字符 A 对应的整数是 65,字符 B 对应的整数就是 66,如果进行 'Z' - 'A' 的运算,能得到与整数 26对应的字符。

在计算机早期,所有的数据在存储和运算时都要使用二进制数表示(因为计算机用高电平和低电平分别表示1和0),例如,像a、b、c、d这样的52个字母(包括大写)以及0、1等数字还有一些常用的符号(例如*、#、@等)在计算机中存储时也要使用二进制数来表示。而具体用哪些二进制数字表示哪个符号的问题,美国国家标准学会(American National Standard Institute , ANSI)制定了一套标准的单字节字符编码方案,用于基于文本的数据。它最初是美国国家标准,供不同计算机在相互通信时用作共同遵守的西文字符编码标准,后来它被国际标准化组织(International Organization for Standardization, ISO)定为国际标准,称为ISO 646标准。适用于所有拉丁文字字母。
—— 摘自百度百科

这种字符和整数对应关系的标准被称之为 ASCII ((American Standard Code for Information Interchange): 美国信息交换标准代码),ASCII 码表的具体内容如下:

二进制八进制十进制十六进制缩写/字符解释
0000 0000000x00NUL(null)空字符
0000 0001110x01SOH(start of headline)标题开始
0000 0010220x02STX (start of text)正文开始
0000 0011330x03ETX (end of text)正文结束
0000 0100440x04EOT (end of transmission)传输结束
0000 0101550x05ENQ (enquiry)请求
0000 0110660x06ACK (acknowledge)收到通知
0000 0111770x07BEL (bell)响铃
0000 10001080x08BS (backspace)退格
0000 10011190x09HT (horizontal tab)水平制表符
0000 101012100x0ALF (NL line feed, new line)换行键
0000 101113110x0BVT (vertical tab)垂直制表符
0000 110014120x0CFF (NP form feed, new page)换页键
0000 110115130x0DCR (carriage return)回车键
0000 111016140x0ESO (shift out)不用切换
0000 111117150x0FSI (shift in)启用切换
0001 000020160x10DLE (data link escape)数据链路转义
0001 000121170x11DC1 (device control 1)设备控制1
0001 001022180x12DC2 (device control 2)设备控制2
0001 001123190x13DC3 (device control 3)设备控制3
0001 010024200x14DC4 (device control 4)设备控制4
0001 010125210x15NAK (negative acknowledge)拒绝接收
0001 011026220x16SYN (synchronous idle)同步空闲
0001 011127230x17ETB (end of trans. block)结束传输块
0001 100030240x18CAN (cancel)取消
0001 100131250x19EM (end of medium)媒介结束
0001 101032260x1ASUB (substitute)代替
0001 101133270x1BESC (escape)换码(溢出)
0001 110034280x1CFS (file separator)文件分隔符
0001 110135290x1DGS (group separator)分组符
0001 111036300x1ERS (record separator)记录分隔符
0001 111137310x1FUS (unit separator)单元分隔符
0010 000040320x20(space)空格
0010 000141330x21!叹号
0010 001042340x22"双引号
0010 001143350x23#井号
0010 010044360x24$美元符
0010 010145370x25%百分号
0010 011046380x26&和号
0010 011147390x27闭单引号
0010 100050400x28(开括号
0010 100151410x29)闭括号
0010 101052420x2A*星号
0010 101153430x2B+加号
0010 110054440x2C,逗号
0010 110155450x2D-减号/破折号
0010 111056460x2E.句号
0010 111157470x2F/斜杠
0011 000060480x300字符0
0011 000161490x311字符1
0011 001062500x322字符2
0011 001163510x333字符3
0011 010064520x344字符4
0011 010165530x355字符5
0011 011066540x366字符6
0011 011167550x377字符7
0011 100070560x388字符8
0011 100171570x399字符9
0011 101072580x3A:冒号
0011 101173590x3B;分号
0011 110074600x3C<小于
0011 110175610x3D=等号
0011 111076620x3E>大于
0011 111177630x3F?问号
0100 0000100640x40@电子邮件符号
0100 0001101650x41A大写字母A
0100 0010102660x42B大写字母B
0100 0011103670x43C大写字母C
0100 0100104680x44D大写字母D
0100 0101105690x45E大写字母E
0100 0110106700x46F大写字母F
0100 0111107710x47G大写字母G
0100 1000110720x48H大写字母H
0100 1001111730x49I大写字母I
1001010112740x4AJ大写字母J
0100 1011113750x4BK大写字母K
0100 1100114760x4CL大写字母L
0100 1101115770x4DM大写字母M
0100 1110116780x4EN大写字母N
0100 1111117790x4FO大写字母O
0101 0000120800x50P大写字母P
0101 0001121810x51Q大写字母Q
0101 0010122820x52R大写字母R
0101 0011123830x53S大写字母S
0101 0100124840x54T大写字母T
0101 0101125850x55U大写字母U
0101 0110126860x56V大写字母V
0101 0111127870x57W大写字母W
0101 1000130880x58X大写字母X
0101 1001131890x59Y大写字母Y
0101 1010132900x5AZ大写字母Z
0101 1011133910x5B[开方括号
0101 1100134920x5C\反斜杠
0101 1101135930x5D]闭方括号
0101 1110136940x5E^脱字符
0101 1111137950x5F_下划线
0110 0000140960x60`开单引号
0110 0001141970x61a小写字母a
0110 0010142980x62b小写字母b
0110 0011143990x63c小写字母c
0110 01001441000x64d小写字母d
0110 01011451010x65e小写字母e
0110 01101461020x66f小写字母f
0110 01111471030x67g小写字母g
0110 10001501040x68h小写字母h
0110 10011511050x69i小写字母i
0110 10101521060x6Aj小写字母j
0110 10111531070x6Bk小写字母k
0110 11001541080x6Cl小写字母l
0110 11011551090x6Dm小写字母m
0110 11101561100x6En小写字母n
0110 11111571110x6Fo小写字母o
0111 00001601120x70p小写字母p
0111 00011611130x71q小写字母q
0111 00101621140x72r小写字母r
0111 00111631150x73s小写字母s
0111 01001641160x74t小写字母t
0111 01011651170x75u小写字母u
0111 01101661180x76v小写字母v
0111 01111671190x77w小写字母w
0111 10001701200x78x小写字母x
0111 10011711210x79y小写字母y
0111 10101721220x7Az小写字母z
0111 10111731230x7B{开花括号
0111 11001741240x7C|垂线
0111 11011751250x7D}闭花括号
0111 11101761260x7E~波浪号
0111 11111771270x7FDEL (delete)删除

所以,char 类型的数据对应的整型数据的取值范围应该在 [0, 127] 之间。使用循环可以将其全部输出并查看:

#include<stdio.h>

int main() {
	for(char c = 0; c < 128; c++) {
		printf("c=%d, c=%c\n", c, c);
		if(c < 0) {
			break;
		}
	}
	return 0;
}

因为 char 的最大值是 127, 如果当变量 c 是 127 的时候,再执行自增操作,则 c 的最高位会被进位为 1,就被解释为负数了,故而,需要在循环体中做判断 c < 0 就跳出循环的操作,否则该循环将成为无限循环。

C 的字符串

很多资料在描述 C 的字符串的时候,说在 C 中,字符数组就是字符串,其实不完全正确。根据定义,字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。换句话说,如果一个字符数组中的最后一个元素不是 NULL 字符,那这个字符数组不能称之为字符串。示例如下:

#include<stdio.h>

int main() {
	char a[] = {'H', 'e', 'l', 'l', 'o'};
	char b[] = {'H', 'e', 'l', 'l', 'o', '\0'};
	char c[] = "Hello";
	printf("sizeof(a) is %d, &a=%p, a=%s\n", sizeof(a), &a, a);
	printf("sizeof(b) is %d, &b=%p, b=%s\n", sizeof(b), &b, b);
	printf("sizeof(c) is %d, &c=%p, c=%s\n", sizeof(c), &c, c);
	return 0;
}

执行结果:

sizeof(a) is 5, &a=0022FF3B, a=Hello
sizeof(b) is 6, &b=0022FF35, b=Hello
sizeof(c) is 6, &c=0022FF2F, c=Hello

这里的变量 a, b, c 都是字符数组,但变量 a 是不能被称之为是 C 的字符串的,因为根据定义,它的最后一个元素的不是 NULL 字符。

那为什么变量 c 能被称之为是字符串呢?原因是在编写源程序的时候,不需要把 null 字符放在字符串常量的末尾。C 编译器会在初始化数组时,自动把 '\0' 放在字符串的末尾。也就是说,C 在编译的时候,将字符串字面量 "Hello" 分解成了一个以 NULL 字符结尾的字符数组。这也就是为什么使用 sizeof 运算变量 c 的时候,得到的值是 6 的原因,因为在字符 o 的后面还有一个 NULL 字符。可以简单的理解为 "Hello" 等价于 {'H', 'e', 'l', 'l', 'o', '\0'}

在之前以 指针 为主题的文章中说:在 C 中,数组变量就是一个特殊常量指针,也称之为数组指针。那也就是说在上面的例子中,变量 a, b, c 也都是指针。换句话说,字符串变量也是一个指针变量

同样的,把之前数组指针的案例修改下,更改数据类型为 char,然后增加输出格式 %c。得到如下代码:

#include<stdio.h>

#define SIZE 6

int main() {

	char cs[SIZE] = {'H', 'e', 'l', 'l', 'o', '\0'};
	char *p = cs;
	
	// cs 和 p 都能使用运算符 *
	printf("*p=%d, *cs=%d, *cs=%c\n", *p, *cs, *cs);
	printf(" p=%p, cs=%p\n", p, cs);
	printf("&p=%p, &cs=%p\n", &p, &cs);
	
	// cs 和 p 都能使用逻辑运算符
	printf("p <= &cs[1] is %s\n", p <= &cs[1] ? "true" : "false");
	printf("cs < &cs[1] is %s\n", cs < &cs[1] ? "true" : "false");
	
	// cs 和 p 都能使用下标来操作元素
	for(int i = 0; i < SIZE; i++) {
		printf("cs[%d]=%d, cs[%d]=%c, &cs[%d]=%p, p[%d]=%d, &p[%d]=%p\n", i, cs[i], i, cs[i], i, &cs[i], i, p[i], i, &p[i]);
	}
	
	// cs 是常量指针,故而不能做自运算
	while(p < &cs[SIZE - 1]) {
		p++;
		// error: lvalue required as increment operand
		// cs++;
		printf("p=%p, *p=%d, cs=%p, *cs=%d, *cs=%c\n", p, *p, cs, *cs, *cs);
	}
	
	return 0;
}

执行结果:

*p=72, *cs=72, *cs=H
 p=0022FF26, cs=0022FF26
&p=0022FF20, &cs=0022FF26
p <= &cs[1] is true
cs < &cs[1] is true
cs[0]=72, cs[0]=H, &cs[0]=0022FF26, p[0]=72, &p[0]=0022FF26
cs[1]=101, cs[1]=e, &cs[1]=0022FF27, p[1]=101, &p[1]=0022FF27
cs[2]=108, cs[2]=l, &cs[2]=0022FF28, p[2]=108, &p[2]=0022FF28
cs[3]=108, cs[3]=l, &cs[3]=0022FF29, p[3]=108, &p[3]=0022FF29
cs[4]=111, cs[4]=o, &cs[4]=0022FF2A, p[4]=111, &p[4]=0022FF2A
cs[5]=0, cs[5]= , &cs[5]=0022FF2B, p[5]=0, &p[5]=0022FF2B
p=0022FF27, *p=101, cs=0022FF26, *cs=72, *cs=H
p=0022FF28, *p=108, cs=0022FF26, *cs=72, *cs=H
p=0022FF29, *p=108, cs=0022FF26, *cs=72, *cs=H
p=0022FF2A, *p=111, cs=0022FF26, *cs=72, *cs=H
p=0022FF2B, *p=0, cs=0022FF26, *cs=72, *cs=H

从这个简单的案例中证明了,字符串变量也是一个指针变量。原因是因为数组是一个常量指针。那也就是说可以使用声明指针的形式来声明一个字符串。示例如下:

#include<stdio.h>

int main() {
	char *a = "Hello";
	printf("sizeof(a) is %d, &a=%p, a=%s\n", sizeof(a), &a, a);
	return 0;
}

执行结果:

sizeof(a) is 4, &a=0022FF3C, a=Hello

那如果声明两个相同的字符串指针变量,他们的值相同吗?代码如下:

#include<stdio.h>
#include<string.h>

int main() {
	char *a = "Hello";
	char *b = "Hello";
	if(a == b) {
		printf("a == b.\n");
	} else {
		printf("a != b.\n");
	}
	printf("a=%s, a=%p, &a[0]=%p, *a=%c, &a=%p\n", a, a, &a[0], *a, &a);
	printf("b=%s, b=%p, &b[0]=%p, *b=%c, &b=%p\n", b, b, &b[0], *b, &b);
	return 0;
}

执行结果:

a == b.
a=Hello, a=00405044, &a[0]=00405044, *a=H, &a=0022FF3C
b=Hello, b=00405044, &b[0]=00405044, *b=H, &b=0022FF38

从这个例子中可以知道的是,两个字符指针指向的地址是同一个地址,也就是说源程序中的两个字面量 Hello 在内存中使用同一个字符数组来进行存储的。换句话讲,这个例子中的变量 a 和变量 b 不是两个长得一样,而是它们就是同一个字符串。

综上可知,字符串的特点有:

  1. 字符串变量是使用 NULL 字符作为最后一个元素的一维字符数组
  2. 字符串变量也是一个指针变量
  3. 字符串指针指向的是字符数组的第一个元素的地址
  4. 两个相同的字符串字面量在内存中使用同一个字符数组来进行存储

您还知道 C 的字符串有哪些特点,评论区留言给我吧!

字符串函数

在 C 的标准库中,提供了关于字符串的函数,在使用之前需要引入头文件 string.h。关于这个头文件中包含的函数说明请参考:http://www.cplusplus.com/reference/cstring/

strcpy

函数签名char * strcpy ( char * destination, const char * source );
函数描述:将 source 所指的 C 字符串复制到 destination 所指的数组中,包括终止的空字符(并在该点停止)。
名称来源: Copy string
使用示例

#include<stdio.h>
#include<string.h>

int main() {
	char a[] = "Hello";
	char b[] = "World";
	
	// strcpy(a, b) 等价于 a = b
	strcpy(a, b);
	
	printf("a=%s\n", a);
	printf("b=%s\n", b);
	
	return 0;
}

执行结果:

a=World
b=World

因为数组是一个常量指针,所以它不能像普通变量那样直接将变量 b 的值赋予给变量 a。这里使用到 C 标准库中提供的 strcpy 函数将 b 的值赋予给 a。当然,如果使用指针变量声明的字符串完成就可以不需要 strcpy 函数了。示例如下:

#include<stdio.h>
#include<string.h>

int main() {
	char *a = "Hello";
	char *b = "World";
	a = b;
	printf("a=%s\n", a);
	printf("b=%s\n", b);
	return 0;
}

执行结果:

a=World
b=World

结果虽然一样,但是使用指针的做法实际上就是将 b 的指针指向的地址值赋予了 a,使得 a 的指向发生变化而已。

strcat

函数签名char * strcat ( char * destination, const char * source );
函数描述: 将 source 字符串的副本追加到 destination 字符串。destination 中的终止空字符被 source 的第一个字符覆盖,并且在由 destination 中的两个字符串联而成的新字符串的末尾包含一个空字符。
名称来源: Concatenate strings
使用示例

#include<stdio.h>
#include<string.h>

int main() {
	char a[] = "Hello";
	char b[] = "World";
	
	strcat(strcat(a, " "), b);
	
	printf("a=%s\n", a);
	printf("b=%s\n", b);
	return 0;
}

执行结果:

a=Hello World
b=World

strlen

函数签名size_t strlen ( const char * str );
函数描述: 返回 C 字符串 str 的长度。
名称来源: Get string length
使用示例

#include<stdio.h>
#include<string.h>

int main() {
	char a[] = "Hello";
	char b[] = "World";
				
	printf("a=%s, a's length is %d\n", a, strlen(a));
	printf("b=%s, b's length is %d\n", b, strlen(b));
	return 0;
}

执行结果:

a=Hello, a's length is 5
b=World, b's length is 5

strcmp

函数签名int strcmp ( const char * str1, const char * str2 );
函数描述: 如果每个字符都一致,则返回零,否则比较第一个不同的字符的 ASCII 值。
名称来源: Compare two strings
使用示例

#include<stdio.h>
#include<string.h>

int main() {

	char a[] = "Hello";
	char b[] = "World";
	int c = strcmp(a, b);
	printf("strcmp(\"%s\", \"%s\") is %d\n", a, b, c);
	return 0;
}

执行结果:

strcmp("Hello", "World") is -1

函数 strcmp 比较 HelloWorld 的结果是 -1,也就是 a < b。那是因为 World 的第一个字符的 ASCII 值比 Hello 的大。如果将 World 更改为 Happy,那就是比较第二个字符的 ASCII 值,结果就是 a > b 了。关于它的更多介绍,可以参考 http://www.cplusplus.com/reference/cstring/strcmp/

strchr

函数签名char * strchr ( const char * str, int character);
函数描述: 返回指向 C 字符串 str 中 character 的第一个匹配项的指针。
名称来源: Locate first occurrence of character in string
使用示例

#include<stdio.h>
#include<string.h>

int main() {

	char a[] = "Hello World!";
	char *c = strchr(a, 'l');
	printf("c=%p, c=%c, c=%d, c=%s", c, *c, *c, c);
	
	return 0;
}

执行结果:

c=0022FF31, c=l, c=108, c=llo World!

返回的指针 c 指向的是原字符串的一个第一个 l 的字符,可以使用指针运算,统计出该字符串中某个字符的总数。如下例所示:

#include<stdio.h>
#include<string.h>

int main() {
	char a[] = "Hello World!";
	char *c = a, ch = 'l';
	int count = 0;
	while(c) {
		c = strchr(c, ch);
		if(!c) {
			// 如果找不到对应的字符,则返回空指针
			break;
		}
		// printf("c=%p, c=%c, c=%d, c=%s\n", c, *c, *c, c);
		c++;
		count++;
	}
	printf("count of '%c' is %d.\n", ch, count);
	return 0;
}

执行结果:

count of 'l' is 3.

strstr

函数签名char * strstr ( char * str1, const char * str2 );
函数描述: 返回指向 str1 中第一个 str2 的指针,如果 str2 不是 str1 的一部分,则返回空指针。
使用示例

#include<stdio.h>
#include<string.h>

int main() {
	char a[] = "The matching process does not include the terminating null-characters, but it stops there.";
	char *c = strstr(a, "null");
	if(c) {
		// c is not null pointer
		printf("c=%p, c=%c, c=%d, c=%s\n", c, *c, *c, c);
	}
	return 0;
}

执行结果:

c=0022FF07, c=n, c=110, c=null-characters, but it stops there.

C++ 的字符串

在 C++ 中,字符串的声明和使用都比 C 中来得简单。示例代码如下:

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

int main() {
	string str = "Hello World";
	cout << "str's length is " << str.size() << endl;
	str += "!";
	cout << "str's length is " << str.size() << endl;
	cout << str << endl;
	// 在字符串从左开始查找第一次出现指定字符串的位置,并返回下标
	int index = str.find("l");
	cout << "index is " << index << endl;
	// 在字符串从右开始查找第一次出现指定字符串的位置,并返回下标
	index = str.rfind(" ");
	cout << "index is " << index << endl;
	// 从指定下标开始截取字符串,截取指定的长度
	string substring = str.substr(index + 1, 2);
	cout << "substring is " << substring << endl;
	// 从指定下标开始截取字符串,截取到末尾
	substring = str.substr(index + 1);
	cout << "substring is " << substring << endl;
	substring = str.replace(index + 1, 5, "C++");
	cout << "substring is " << substring << endl;
	return 0;
}

执行结果:

str's length is 11
str's length is 12
Hello World!
index is 2
index is 5
substring is Wo
substring is World!
substring is Hello C++!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:书香水墨 设计师:CSDN官方博客 返回首页
评论

打赏作者

奇妙的代码

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值