视频讲解:
http://v.youku.com/v_show/id_XNjE2NzI3Mzgw.html
视频自己录的,自认为这个视频讲解的有点含糊,而且废话多、错误多- -,又有很多没有讲到,所以特给出文字讲解。
本文主要讲解了字符与字符串的内存分配、常量字符串的表示、指针、数据转换方面的内容.
1 编码
ASCII 可以存放所有的拉丁字符,一个字符占1个字节
ANSI ANSI码可以算是ASCII码的一种扩展,ANSI又包括了GB2312, BIG5, JIS等编码标准(在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码).
ANSI编码可以存放拉丁字符和非拉丁字符,GB2312编码中一个拉丁字符占1个字节,一个汉字相当于两个拉丁字符占2个字节.
纯文本文件包括代码文件(.c .cpp .java .html等)在内一般都是以ANSI编码存储.
Unicode 又称万国码、单一码,它可以存放世界上所有国家所使用的文字,每一个字符都占用2个字节,不管是拉丁字符还是非拉丁字符.
在C语言要表示一个字符或字符串常量是Unicode编码,可以使用一个宏:_T("") 定义于:tchar.h,存储Unicode编码数据可以使用 wchar_t 宽字符数据类型.wchar_t数据类型一般为16位或32位,不同的C或C++库有不同的规定.
2 所有字符串均以 '\0’ 结尾
在C语言当中,所以的以双引号括起来的字符串的末尾都默认加上了一个 \0.
如 "abcde” 实际上是 abcde\0 .
\0 表示了一个字符串的末尾,实际也占用了一个字节.
要注意的是,只有在字符串中才会默认加上 \0,如 'a' 像以单引号括起来的单个字符的后面是不会加 \0 的,而 "a" 这样用双引号括起来的不管是一个字符还是几个字符,都会加上 \0.
3 ‘ 单引号 与 " 双引号表代表的值
char ch = ‘A’; 其中 用 ' 单引号括起来的,实质上其实是代表的一个范围为0~255的整型数据,这个整型值对应着其字符在ASCII码中所对应的整型数据(详细可见MSDN-ASCII Character Codes).char 也是一个整型数据,和其它整型数据不同的是,char 数据默认是无符号的,占用1字节,数据大小范围是0~255.列如 char ch = 65; 在这里我们可以直接将一个整型数据赋值给 char 类型变量 ch.如果我们用printf(“%c\n”, ch); 那么不管是赋给ch的是 'A' 还是 65(65 在ASCII码中对应了字符 大写字母A),输出结果都是 A.当然我们也用 %d 以整型数据输出它.
例子:
#include <stdio.h>
int main(void)
{
char a = 65;char b = 'A';printf("%c, %d\n", a, a);printf("%c, %d\n", b, b);return 0;
}
/*
参考输出结果:
A, 65
A, 65
*/
char *string = “abcd”; 其中,我们可以将 "abcd" 赋值给一个字符指针的原因是: 以双引号括起来的字符串,整个表达式的值是一个指针.在这个语句中,字符串 abcd\0 是一个常量.在程序运行的时候会自动判断内存中是否已经存放了 abcd\0 这个数据,如果存放了双引号表达式就代表了字符串中第一个元素的地址,没有存放则创建.所以 "abcd" 这个双引号表达式代表的指针就指向了字符串中的第一个元素 a.因为字符串 abcd\0 是一个常量,所以我们不能通过 string 去修改它的值,但是我们可以去修改 string 指向的地址,如: string = “efg”; 这里的 "efg" 和 "abcd" 是同一个概念.
#include <stdio.h>
int main(void)
{
char *str = "abcd";printf("%s, %#x\n", str, str);return 0;
}
/*
参考输出结果:
abcd, 0x40c000
*/
4 char[] 字符数组
char string[5] = “abcd”; 这个语句表示的是将常量 abcd\0 复制到所分配的5字节内存空间中去,与char *string = “abcd”;不同之处就在于,前者是将一个内存区域中的数据复制到另外一个内存区域里去,后者是定义一个指针让它指向了这一个内存区域.
深入了解过数组的朋友应该也知道,不管是什么数组,定义的数组名其实都是一个指针.不过这个指针不同于普通指针的是,这一个指针指向的地址不能被改变,指针string是一个常量.这个指针指向了数组的第一个元素,程序以这一个指针来找到整个数组.在 char string[5] = “abcd”; 中,string 就指向了第一个元素的地址,也就是 abcd\0 中 a 的地址.
即使我们能够改变 string 的值也是不合理的,因为 string 指向的不是常量区的 abcd\0 ,而是程序自己分配了内存空间存入了的 abcd\0 指针,如果我们修改 string 的值,我们就无法找到这个字符串变量所在的内存地址了.而我们如果不调用外部函数要改变一个char [] 字符串的值的话,就只能通过下标来一个一个修改每一个字符的值从而修改整个字符串,如: string[0] = ‘e’; string[1] = ‘f’; …… 当然如果这样修改起来将会非常的繁琐,所以我们可以用到一个C语言中给我们提供的用于 char 字符串复制的函数: strcpy.
strcpy 函数原型:char *strcpy( char *strDestination, const char *strSource );
参数1:strDestination 指向目标字符串的首地址;
参数2:strSource 指向源字符串的首地址.
返回值:返回指向 strDestination 的指针.
对于数据类型 const char * 的讲解可见下一章.
这个函数的功能就是将 strSource 中的数据复制到 strDestination 中,其返回值我们一般用不到,可忽略.这个函数的功能等价于 char string[5] = “abcd”; 中的 = 号,而与这整个语句不同的是,strcpy 不会分配新的内存空间.
函数使用示例: 改变 string 的值为 efg:
strcpy(string, “efg");
更多的 char 字符字符串操作函数可以查阅 MSDN-String Manipulation Routines
char 数组的性质和其它数据类型的数组一样,也可以通过一个指向第一个元素的非常量指针进行递增从而修改元素的值:
char string[5] = “abcd”;
char *pChar = string;
*pChar = ‘e’; // 修改第一个元素
*(++pChar) = 'f'; // 修改第二个元素 这里的括号可有可无,只是为了观察方便
5 const char * 与 char *const
const char * a; 这个语句定义了一个常量指针 a,也就是指向常量的指针,意思是说不能通过 a 修改它指向内存地址中的值.要注意这里是不能通过 a 修改它指向地址中的值,并不代表 a 只能指向常量而不能指向变量.只是 a 指向了变量之后,不能通过 a 去修改这个变量的值.另外 char const *a; 语句,与其等价.
char *const b; 这个语句定义了一个指针常量 b,也就是一个指针的常量,意思是说 b 是一个常量,b 的值不能被改变,即 b 的指向的地址不能被改变,可以通过 b 去修改它指向内存中的值.这里同样要注意, b 可以指向常量也可以指向变量,只是说 b 指向谁是不能被改变的.而如果 b 指向了一个常量,那么当然通过 b 去修改这个常量的值还是错误的.因为这里定义的是一个常量的变量,所以必须在定义的时候就给赋值.
例子:
const char *a = "abcd"; // OK
char str[5] = "abcd"; // OK
char *const b = str; // OK
const char *c = str; // OK
a = "fff"; // OK
*a = 'f'; // error 不能通过 常量指针 a 修改它指向内存地址中的值
b = "abcd"; // error 指针常量 b 是常量,不能被改变
*b = 'f'; // OK
*++b = 'f'; // error ++ 自增会对指针常量 b 的值进行更改
b[1] = 'f'; //OK
*c = 'f'; // error 不能通过常量指针 c 修改它指向内存地址中的值,尽管它指向的内存地址中的数据是变量
如果我们要使用 malloc 函数来为一个数组分配内存空间,并且让它和直接定义一个数组一样,指向第一个元素的指针的值不能被改变,那么我们就可以这样来定义:
char *const string = (char *)malloc(5);
6 字符、字符串与数值间的转换
int i = (ch-48); 这个语句完成了单个字符 ch 转化为数字并存入整型变量 i 的功能.
因为字符 0 在 ASCII 码中对应了 48,后面的数值也是以 1 递增,所以用它对应的ASCII码减去48就是这单个字符的整数形式.
如此,我们也可以反过来,实现将整型转换成字符: char ch = (i+48);
字符串之间的转换,我们不能同时将一个字符串中的所有字符进行转换,不调用外部函数的话,我们只能利用上述这一特性把字符串一个一个的转换,代码示例:
#include <stdio.h> int StringInt(const char *str) // 将字符串转换为int类型并返回 { int val = 0; int index = ( strlen(str) - 1 ); // 取索引值 int pn = 1; // 表示是正数还是非正数, 正数表示为1, 非正数表示为0 int f = 1, i = 1; // 用于后面的循环 const char *pChar = str; if ('-' == *pChar) { // 判断字符串第一个元素是否为负号 index--; pn = 0; *pChar++; } while (index >= 0) { f = 1; for (i=1; i<=index; i++) f *= 10; val += ( (*pChar++) - 48 ) * f; index--; } if (0 == pn) val *= -1; // 转换为负数 return val; } int main(void) { printf("%d\n", StringInt("333")); printf("%d\n", StringInt("0")); printf("%d\n", StringInt("-0")); printf("%d\n", StringInt("-321")); return 0; }
/*
参考输出结果:
333
0
0
-321
*/
上述代码中的 StringInt 函数便可以把一个字符串转换成 整型数据并返回,如果我们每个人都要去自己写一个用于这一转换的函数那么是会浪费很多时间的,在C语言中给我们提供了以下几个函数,可以十分方便地让我们完成数值与字符间的转换
字符串->数值:
int atoi( const char *string ); 将一个字符串转换以整型返回,功能与上述代码的 StringInt 完全相同.
double atof( const char *string ); 将一个字符串以双精度浮点型数据并返回
__int64 _atoi64( const char *string ); 将一个字符串以 64 位整型返回
long atol( const char *string ); 将一个字符串以长整型返回
数值->字符串:
char *_itoa( int value, char *string, int radix ); 将一个整型数据 value 转换为以 radix 进制表示的数据字符串存入 string 并返回指向 string 的指针.
char *_i64toa( __int64 value, char *string, int radix ); 同上,这里只是value的数据类型不同,功能相同.
char * _ui64toa( unsigned _int64 value, char *string, int radix ); 同上.
wchar_t * _itow( int value, wchar_t *string, int radix ); 将一个整型数据 value 转换为以 radix 进制表示的数据字符串存入宽字符串 string 并返回指向 string 的指针.
wchar_t * _i64tow( __int64 value, wchar_t *string, int radix ); 同上.
wchar_t * _ui64tow( unsigned __int64 value, wchar_t *string, int radix ); 同上.
更多的C语言数据转换函数可查阅MSDN-Data Conversion
格式化数据操作函数sprintf 与 sscanf :
int sprintf( char *buffer, const char *format [, argument] ... ); 格式化存放数据到字符串.第一个参数为缓冲区,第二个是格式控制字符串,第三个是输出表列,第二第三个参数和printf中的第一第二个参数的用法是一样的.返回值是存入 buffer 字符串的长度.要注意,sprintf只是够使用以下转换字符:
%% 印出百分比符号,不转换。
%c 整数转成对应的 ASCII 字元。
%d 整数转成十进位。
%f 倍精确度数字转成浮点数。
%o 整数转成八进位。
%s 整数转成字符串。
%x 整数转成小写十六进位。
%X 整数转成大写十六进位。
int swprintf( wchar_t *buffer, const wchar_t *format [, argument] ... ); 和 sprintf 相同,但是 swprintf 可以使用所有的转换字符
用法示例:
#include <stdio.h> int main(void) { char str[6] = "abc"; int i = 3; sprintf(str, "%s,%d", str, i); printf("%s\n", str); return 0; }
/*
参考输出结果:
abc,3
*/
int sscanf( const char *buffer, const char *format [, argument ] ... ); 将字符串 buffer 以格式化分割存入格式表列数据.第二第三个参数与 sscanf 的第一第二个参数用法一样.同时 sscanf 也和 sprintf 一样,只能使用相同的部分转换字符.
sscanf支持集合操作:
%[a-z] 表示匹配a到z中任意字符,贪婪性(尽可能多的匹配)
%[aB'] 匹配a、B、'中一员,贪婪性
%[^a] 匹配非a的任意字符,并且停止读入,贪婪性
int swscanf( const wchar_t *buffer, const wchar_t *format [, argument ] ... ); 和 sscanf 相同,可以使用所有转换字符
用法示例:
#include <stdio.h>
int main(void)
{
int a, b;
char str1[10], str2[10], str3[10];
sscanf("123, 456", "%d, %d", &a, &b);
printf("a = %d, b = %d\n", a, b);
sscanf("abchhdad 123 oofg", "%[a-z] %[1]", &str1, &str2);
printf("str1 = %s, str2 = %s\n", str1, str2);
return 0;
}
/*
参考输出结果:
a = 123, b = 456
str1 = abchhdad, str2 = 1
*/
有关 sscanf 的详细用法可参见百度百科:http://baike.baidu.com/view/1364018.htm