C语言字符串Cheatsheet

字符串定义

定义字符串可以使用数组形式或字符指针形式,通常用char[]或char*表示。

字符串常量

字符串常量,是指位于一对双引号中的任何字符,双引号里的字符加上编译器自动提供的的结束标志\0字符,作为一个字符串被存储在内存里。定义字符串常量时可以使用字符数组或字符指针形式,也可以使用#define来定义字符串常量。

1
2
3
char string1[10] = "hello1";
char* string2 = "hello2";
#define string2 = "hello3";

对于字符串常量,整个引号中的内容作为指向该字符串存储位置的指针。如下代码:

1
2
3
4
5
6
#include <stdio.h>
int main(void)
{
printf("%s, %p, %c\n", "hello1", "hello2", *"hello3");
return 0;
}

输出结果为:hello1, 0x100000ff3, h分别输出的是字符串"hello1",字符串"hello2"的地址,字符串"hello3"的地址所指向的内容即首个字符h

数组形式

使用数组形式字符串时,必须告知编译器所需的空间。可以使用一个较大的数组来容纳该字符串。

1
char string1[20];

下边两种定义方式相同:

1
2
const char string1[20] = "hello world";
const char string1[] = {'h','e','l','l','o',' ','w','o','r','l','d','\0'};

第一种方式较为简便,注意使用第一种方法时,数组的大小至少要比字符串的长度多1(用于存放’\0’),数组中未被使用的字符元素均被初始化为’\0’。

也可以使用一种让编译器根据字符串长度决定数组尺寸的方法:

1
const char string2[] = "hello world again";

字符数组名也是数组首元素的地址,对于上边的代码,以下均为真:

1
2
3
string2 == &string2[0];
*string2 == 'h';
*(string2 + 1) == string2[1];

字符指针形式

使用指针形式来定义字符串:

1
2
char* string1;
char* string2 = "world";

使用字符数组和使用字符指针来表示字符串,有一些共同点也有很多区别,下边详细讨论这个问题。

字符数组和字符指针

数组和指针的对比

数组和指针的一些区别见下表

数组 指针
保存数据 保存数据的地址
直接访问数据 间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据
a[i]即以a+i为地址取得数据 如果指针有下标[i],就把指针的内容加i作为地址,从中提取数据
通常用于存储固定数目且数据类型相同的元素 通常用于动态数据结构
隐式分配和删除 相关函数为malloc(),free()
自身为数据名 通常指向匿名数据

字符数组和字符指针

在本段中,首先会说明两种字符串的相同和不同点,并会结合下边具体的例子来分析:

1
2
char string_a[] = "string by array!";
char* string_p = "string by pointer!";

此处string_a是个常量,string_p是变量。指向字符串的指针实际上指向的是字符串的第一个字符。

共同点

首先,在声明或定义函数时,函数形参可以使用字符数组或字符指针,两个函数声明方式是等效的:

1
2
void func(char* string1);
void func(char[] string1);

对于上边声明的字符数组和字符指针,二者都可以通过数组符号来访问:

1
2
3
4
5
6
7
for(i = 0; i < 6; i++)
putchar(string_a[i]);
putchar('\n');

for(i = 0; i < 6; i++)
putchar(string_p[i]);
putchar('\n');

均可以输出string

不同点

在声明和定义时,二者的作用不能互换,主要是取决于以下的不同点。

只有指针才可以使用增量运算符(数组名是常量):

1
2
while (*(string_p) != '\0')
putchar(*(string_p++));

如果运行上边的代码,输出结果将是string by pointer!,但是对于数组型字符串不能这样操作。

为使二者相等,可以将string_a赋值给string_p,但反之不行,即:

1
2
string_p = string_a ;
//string_a = string_p ; ERROR

string_a数组名是常量,但是数组中的元素是变量(没有const修饰的情况下),因此可以使用下边的操作修改字符串中的内容:

1
string_a[6] = '_' ;

等同于:

1
*(string_a + 6) = '_';

但是对于由指针初始化的字符串,部分编译器也支持这样的操作:

1
2
char* string_p2 = "string2 by pointer!";
string_p2[6] = 's';

这种操作是充满隐患的,不要使用

除了以上的区别之外,还有一些其他的不同之处:

数组作为sizeof()的操作数,返回的是整个数组的大小。如果使用指针形式,则返回指向的第一个元素的大小;

使用&操作符来对数组名取地址时,得到的是数组的地址,即string_a&string_a将输出相等的地址值。

字符串函数Cheatsheet

字符串操作

ANSI C文件string.h和stdio.h 给出了一些字符串相关函数的原型:

strlen

1
size_t strlen ( const char * str );

传入字符串,返回字符串的长度。

strcat

1
char * strcat ( char * destination, const char * source );

传入两个字符串,将source追加到destination后边,并返回新的destination,原destination末尾的'\0'source的第一个字符覆盖,同时在新生成的字符串末尾补一个'\0',传入的两个字符串不能有重叠。

strncat

1
char * strncat ( char * destination, const char * source, size_t num );

strcat相似,将source的前num个字符追加到destination后边,并在新字符串末尾补一个'\0'。如果source的长度比num小,则只将source追加到destination后边。返回新的destination

strcmp

1
int strcmp ( const char * str1, const char * str2 );

传入两个字符串,从两者的第一个字符开始比较,如果相同则继续,直到出现不同字符或者读取到了'\0'为止,如果两个字符串完全一致,返回0;对于遇到的首个不同的字符,如果str1的字符小于str2,返回一个负数,反之返回正数。

strncmp

1
int strncmp ( const char * str1, const char * str2, size_t num );

strcmp相似,仅比较两个字符串的前num个字符。

strcpy

1
char * strcpy ( char * destination, const char * source );

source对应的字符串复制到destination,包括'\0',为防止溢出,destination指向的数组应足够长,能够容纳整个source字符串以及'\0',二者在内存中不应有重叠。返回值为destination

strncpy

1
char * strncpy ( char * destination, const char * source, size_t num );

strcpy相似,仅拷贝source的前num个字符。如果source中的'\0'位于第num个字符之前,则在拷贝整个source之后剩余少于num的字符数将由'\0'来补满;如果source长度大于num,则需要在执行完strncpy后手动为destination添加一个'\0'

strchr

1
char * strchr ( char * str, int character );

返回字符串str中首次出现字符character的指针,如果没有该字符则返回空指针。

strrchr

1
char * strrchr ( char * str, int character );

strchr相似,返回最后一次出现字符character的指针。

strpbrk

1
char * strpbrk ( char * str1, const char * str2 );

返回在str1中首次出现的str2中包含的所有字符的位置,如果str1中不包含任何str2中的字符,则返回空指针,查询过程中不包括'\0'。用一段示例程序说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* strpbrk example */
#include <stdio.h>
#include <string.h>

int main ()
{
char str[] = "This is a sample string";
char key[] = "aeiou";
char* pch;

pch = strpbrk (str, key);

printf ("Vowels in '%s': ",str);
while (pch != NULL)
{
printf ("%c " , *pch);
pch = strpbrk (pch+1,key);
}
printf ("\n");
return 0;
}

输出结果为:Vowels in 'This is a sample string': i i a a e i

strstr

1
char * strstr ( char * str1, const char * str2 );

返回str1中首次出现str2的指针,如果str1不包含str2,则返回空指针。

sprintf

1
int sprintf ( char * str, const char * format, ... );

按照format规定的格式构造一个字符串,并存入strstr的尺寸应足够大以容纳格式输出字符串。在格式输出内容之后会自动加'\0'。返回值为不包含'\0'的成功写入str的字符个数。如果写入失败则返回负值。

sscanf

1
int sscanf ( const char * s, const char * format, ...);

从字符串s中读取数据,并按照格式要求保存到各参数。返回值为列出的参数中根据格式成功填充的个数,如果转换失败返回EOF。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* sscanf example */
#include <stdio.h>

int main ()
{
char sentence []="Rudolph is 12 years old";
char str [20];
int i;

sscanf (sentence,"%s %*s %d",str,&i);
printf ("%s -> %d\n",str,i);

return 0;
}

输出结果为:Rudolph -> 12

字节操作

string.h包含的一些字节操作函数:

memcpy

1
void * memcpy ( void * destination, const void * source, size_t num );

source指向的内存复制num个字节的数据到destination指向的内存,sourcedestination指向的数组应该至少有num个字节,并且不能有重叠。

memcmp

1
int memcmp ( const void * ptr1, const void * ptr2, size_t num );

比较ptr1ptr2指向的内存的前num个字节,如果相等则返回0,对于出现的第一个不同的字节,如果ptr1小于ptr2则返回负值,否则返回正值。

memset

1
void * memset ( void * ptr, int value, size_t num );

ptr指向的内存的前num个字节设置为value的值,value会被转化为unsigned char。

举例说明:

1
2
3
4
5
6
7
8
9
10
11
/* memset example */
#include <stdio.h>
#include <string.h>

int main ()
{
char str[] = "almost every programmer should know memset!";
memset (str,'-',6);
puts (str);
return 0;
}

输出结果为------ every programmer should know memset!

memmove

1
void * memmove ( void * destination, const void * source, size_t num );

source指向的内存复制num个字节的数据到destination指向的内存,sourcedestination指向的数组应该至少有num个字节。与memcpy不同的是,使用memmove时允许sourcedestination指向的内存区域重叠。

字符串转数字

stdlib.h中包含的将字符串转为数字的函数:

atoi

1
int atoi (const char * str);

将字符串str转化为整数并返回,会自动去除str中数字位前边的空格以及数字位后边的非数字字符。如果首位是非数字字符,或str为空,或str只包含空格,则也会返回0。可以的话推荐使用更稳健的strtol()

atol

1
long int atol ( const char * str );

atoi相似,转化为长整形。如果可以的话推荐使用更稳健的strtol()

atof

1
double atof (const char* str);

atoi相似,转化为双精度浮点型。如果可以的话推荐使用更稳健的strtod()

strtol

1
long int strtol (const char* str, char** endptr, int base);

atoi相似,将字符串str转化为长整形数值并返回,使用base进制,如果endptr不是空指针的话,函数会将endptr指向str中数字结束后的第一个非数值字符。参考示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* strtol example */
#include <stdio.h> /* printf */
#include <stdlib.h> /* strtol */

int main ()
{
char szNumbers[] = "2001 60c0c0 -1101110100110100100000 0x6fffff";
char * pEnd;
long int li1, li2, li3, li4;
li1 = strtol (szNumbers,&pEnd,10);
li2 = strtol (pEnd,&pEnd,16);
li3 = strtol (pEnd,&pEnd,2);
li4 = strtol (pEnd,NULL,0);
printf ("The decimal equivalents are: %ld, %ld, %ld and %ld.\n", li1, li2, li3, li4);
return 0;
}

输出结果为The decimal equivalents are: 2001, 6340800, -3624224 and 7340031

strtoul

1
unsigned long int strtoul (const char* str, char** endptr, int base);

strtol相似,转化为无符号长整形。

strtod

1
double strtod (const char* str, char** endptr);

strtol相似,转化为十进制双精度浮点型。

REFERENCE

《C Primer Plus 第五版》
《C专家编程》
http://www.cplusplus.com/reference/clibrary/
https://msdn.microsoft.com/en-us/library/ms859613.aspx