字符串定义
定义字符串可以使用数组形式或字符指针形式,通常用char[]或char*表示。
字符串常量
字符串常量,是指位于一对双引号中的任何字符,双引号里的字符加上编译器自动提供的的结束标志\0
字符,作为一个字符串被存储在内存里。定义字符串常量时可以使用字符数组或字符指针形式,也可以使用#define
来定义字符串常量。
1 | char string1[10] = "hello1"; |
对于字符串常量,整个引号中的内容作为指向该字符串存储位置的指针。如下代码:
1 |
|
输出结果为:hello1, 0x100000ff3, h
分别输出的是字符串"hello1"
,字符串"hello2"
的地址,字符串"hello3"
的地址所指向的内容即首个字符h
。
数组形式
使用数组形式字符串时,必须告知编译器所需的空间。可以使用一个较大的数组来容纳该字符串。
1 | char string1[20]; |
下边两种定义方式相同:
1 | const char string1[20] = "hello world"; |
第一种方式较为简便,注意使用第一种方法时,数组的大小至少要比字符串的长度多1(用于存放’\0’),数组中未被使用的字符元素均被初始化为’\0’。
也可以使用一种让编译器根据字符串长度决定数组尺寸的方法:
1 | const char string2[] = "hello world again"; |
字符数组名也是数组首元素的地址,对于上边的代码,以下均为真:
1 | string2 == &string2[0]; |
字符指针形式
使用指针形式来定义字符串:
1 | char* string1; |
使用字符数组和使用字符指针来表示字符串,有一些共同点也有很多区别,下边详细讨论这个问题。
字符数组和字符指针
数组和指针的对比
数组和指针的一些区别见下表
数组 | 指针 |
---|---|
保存数据 | 保存数据的地址 |
直接访问数据 | 间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据 |
a[i]即以a+i为地址取得数据 | 如果指针有下标[i],就把指针的内容加i作为地址,从中提取数据 |
通常用于存储固定数目且数据类型相同的元素 | 通常用于动态数据结构 |
隐式分配和删除 | 相关函数为malloc(),free() |
自身为数据名 | 通常指向匿名数据 |
字符数组和字符指针
在本段中,首先会说明两种字符串的相同和不同点,并会结合下边具体的例子来分析:
1 | char string_a[] = "string by array!"; |
此处string_a
是个常量,string_p
是变量。指向字符串的指针实际上指向的是字符串的第一个字符。
共同点
首先,在声明或定义函数时,函数形参可以使用字符数组或字符指针,两个函数声明方式是等效的:
1 | void func(char* string1); |
对于上边声明的字符数组和字符指针,二者都可以通过数组符号来访问:
1 | for(i = 0; i < 6; i++) |
均可以输出string
。
不同点
在声明和定义时,二者的作用不能互换,主要是取决于以下的不同点。
只有指针才可以使用增量运算符(数组名是常量):
1 | while (*(string_p) != '\0') |
如果运行上边的代码,输出结果将是string by pointer!
,但是对于数组型字符串不能这样操作。
为使二者相等,可以将string_a
赋值给string_p
,但反之不行,即:
1 | string_p = string_a ; |
string_a
数组名是常量,但是数组中的元素是变量(没有const修饰的情况下),因此可以使用下边的操作修改字符串中的内容:
1 | string_a[6] = '_' ; |
等同于:
1 | *(string_a + 6) = '_'; |
但是对于由指针初始化的字符串,部分编译器也支持这样的操作:
1 | char* string_p2 = "string2 by pointer!"; |
这种操作是充满隐患的,不要使用。
除了以上的区别之外,还有一些其他的不同之处:
数组作为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 | /* strpbrk example */ |
输出结果为: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
规定的格式构造一个字符串,并存入str
。str
的尺寸应足够大以容纳格式输出字符串。在格式输出内容之后会自动加'\0'
。返回值为不包含'\0'
的成功写入str
的字符个数。如果写入失败则返回负值。
sscanf
1 | int sscanf ( const char * s, const char * format, ...); |
从字符串s
中读取数据,并按照格式要求保存到各参数。返回值为列出的参数中根据格式成功填充的个数,如果转换失败返回EOF。
1 | /* sscanf example */ |
输出结果为:Rudolph -> 12
。
字节操作
string.h包含的一些字节操作函数:
memcpy
1 | void * memcpy ( void * destination, const void * source, size_t num ); |
从source
指向的内存复制num
个字节的数据到destination
指向的内存,source
和destination
指向的数组应该至少有num
个字节,并且不能有重叠。
memcmp
1 | int memcmp ( const void * ptr1, const void * ptr2, size_t num ); |
比较ptr1
和ptr2
指向的内存的前num
个字节,如果相等则返回0,对于出现的第一个不同的字节,如果ptr1
小于ptr2
则返回负值,否则返回正值。
memset
1 | void * memset ( void * ptr, int value, size_t num ); |
将ptr
指向的内存的前num
个字节设置为value
的值,value
会被转化为unsigned char。
举例说明:
1 | /* memset example */ |
输出结果为------ every programmer should know memset!
。
memmove
1 | void * memmove ( void * destination, const void * source, size_t num ); |
从source
指向的内存复制num
个字节的数据到destination
指向的内存,source
和destination
指向的数组应该至少有num
个字节。与memcpy
不同的是,使用memmove
时允许source
和destination
指向的内存区域重叠。
字符串转数字
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 | /* strtol example */ |
输出结果为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