一、字符数组、指针和字符串的初始化作用 二、 strlen...
DESCRIPTION
第 11 章 字符和内存处理. 一、字符数组、指针和字符串的初始化作用 二、 strlen 函数确定字符串有效长度 三、 strcpy 函数拷贝字符串. 一、字符数组、指针和字符串的初始化作用 1. 字符数组、指针和字符串的初始化作用 C++ 把类型 char 、 signed char 和 unsigned char 视 为重载意义上不同的类型。 char 用于定义字符变量,字符变量本身又可提升为整 型变量,为了与整型变量接口的需要,字符便分成有符号 signed char 的和无符号 unsigned char 的。 - PowerPoint PPT PresentationTRANSCRIPT
1
一、字符数组、指针和字符串的初始化作用
二、strlen函数确定字符串有效长度
三、strcpy函数拷贝字符串
2
一、字符数组、指针和字符串的初始化作用
1. 字符数组、指针和字符串的初始化作用 C++ 把类型 char 、 signed char 和 unsigned char
视为重载意义上不同的类型。 char 用于定义字符变量,字符变量本身又可提升为整型变量,为了与整型变量接口的需要,字符便分成有符号signed char 的和无符号 unsigned char 的。
有符号整数可带符号进行高位扩展,无符号数扩展时高位以 0 进行数据填充。
3
char 型变量是有符号还是无符号的, C 和 C++ 语言并未对此明确规定。 因此一般不将 char 变量当作一字节的整型变量进行算术运算。在微软的编译器中 char 视为有符号的。 字符是构成计算机信息交流的基本单位,程序是用一序列串在一起的有意义的字符构成的,由此形成文本字符串。 字符在编程的意义上是通过关键字 char 定义的变量,称为字符变量,字符数组是每一个元素都是字符构成的数组。
在大多数情形下 char 、 signed char 和 unsigned char
定义的变量具有相同的特性,但在实际编程中依然习惯采用char 类型处理字符、字符数组和字符串。
4
字符数组本身最后一个元素不必是其值为 0 的元素。例如: char c[ ]={33,34,35,36,37};
局部范围内可以等价地写为: char c[5]; c[0 ]=‘!’ ; c[1]=‘\“’ ;
c[2 ]=‘#’ ; c[3 ]=‘$’ ; c[4]=‘%’ ;
也可以用单引号一个一个的初始化字符数组,但此种用法略嫌繁琐,但这种方法可以输入不可见字符。例如: char nodisplay [ ]= {'\a','\n','\t','\r','\0'};
// 响铃、换行、水平制表、回车与空字符可等价地改写为: char nodisplay[5 ]={7,10,9,13,0};
5
值得注意不以 0 结尾的字符数组在参入系统提供的字符串操作函数运算时可能会出现一些意外,因为这些函数基本上内嵌如下的循环代码: while (*dest=*source) {;...;dest++; source++;...;}
循环的控制条件是以 0 为基准的,遇 0 就结束循环,不然则继续循环,因此记住这一点就可以放心地使用字符数组是否要以零结尾。 C/C++ 的字符串是双引号括起来的字符序列。例如字符串"abc" 只有 3 个字符,但存储这个字符串需要 4 个字节的内存,在 c 字符之后添补一个结束字符‘ \0’ ,字符‘ \0’ 的 ASC
II 码值是 0 ,它是一个不可显示的字符,作为结束判断的过滤条件。字符 '0' 的 ASCII 码值是 48 。
6
[ 例 ] 无结尾字符 '\0' 的数组输出显示的结果是不确定的
# include<stdio.h>
void main(void)
{ char c[ ]="abc";
printf ("c->%p->%s,sizeof (c)=%d\n", c,c,sizeof (c));
}
// 输出: c->0065FDF4->abcd8??,sizeof(c)=4
字符串常数可以初始化字符指针,也可初始化字符数组,但作用不同。
7
[ 例 ] 字符串初始化指针和数组的差异
# include<stdio.h>
void main (void)
{ char* s="123456789"; printf ("s->%p:%s,%s sizeof (s)=%d\n",s,s,s+4,sizeof(s));
char c[ ]="abcdefghi"; printf ("c->%p: %s, %s sizeof (c) = %d\t",c,c,c+7,sizeof(c));
for (int k=0;k<9;k++) printf ("%c,",s[k]);
printf ("%d\n",s[9]);
} // 程序运行输出结果 :
s->00420FA0:123456789,56789 sizeof (s)=4
c->0065FDE8:abcdefghi,hi sizeof(c)=10 1,2,3,4,5,6,7,8,9,0
8
s= 00420FA0 s+4
全局数据区
c= 0065FDE8 c+7
堆栈数据区
局部字符指针 s 的定义和初始化语句 {char*s="123456789";}
可以分解为 :
char* s; s="123456789";
1 2 3 4 5 6 7 8 9 ‘\0’
a b c d e f g h I ‘\0’
定义语句 char* s=“123456789”; 的内存分配
定义语句 char c[ ]=“abcdefghi”; 的内存分配
9
表示 s 是一个仅占 4 字节内存的局部变量,指针 s 可以指
向任意的字符变量。 字符串常数“ 123456789” 需要 10 字节的内存安放。
编译器对于这样的字符串专门安排在全局数据区,这样的字符串常数区为 const Data 区。 指针 s获得该字符串的首地址 00420FA0 。 此时隐含着: s[0]= '1', s[1]= '2', s[2]= '3', s[3]= '4', ...,
s[8]= '9', s[9]= '\0'
定义语句 {char c[ ]="abcdefghi";} 等价于: char c[ ]={'a','b','c','d','e','f','g','h','i','\0'};
10
字符数组 c 是局部数组,字符数组在 main 函数的堆栈空
间,其首地址为 0065FDE8 。 相似的字符串初始化形式对应的含义是不同的: 初始化字符数组的字符串是初始化列表的洗练形式,字符数组 c 的内存区域可以更新。 初始化字符指针的字符串是只读字符串,指针指向字符串的首地址。 只读字符串占住的全局 const Data 区不应遭到覆盖的违规操作。
11
定义语句 {char c[10 ]="abcd";}
相当于数组的截断初始化形式: char c[ ]={'a','b','c','d', '\0',0,0,0,0,0};
不能写成 : char c[10 ]; c[10]= "abcd";
字符元素相当于一字节的整型变量 , 字符串对应一个地址 , 等号两边数据类型是不平级的。
12
[ 例 ] 从屏幕读取文本串到字符数组中 %s转换说明符要求 char* 型的指针参数,格式字符 s读取直到下一个空白字符的所有字符 , 在后面添加‘ \0’ 字符。为
存储读取的文本串和终止字符‘ \0’ ,字符数组应足够长。
#include<stdio.h>
void main (void)
{ char a[12],b[6],*p=a;
printf (" 键入文本串 \n");
scanf ("%s %s", a,b);
printf ("%s, %s\n", a,b);
scanf ("%s ",p); printf ("%s \n",a);
}
13
程序运行交互结果: 程序另一次运行交互结果:键入文本串 键入文本串12345 6789 12345 678912345, 6789 12345,678912345 6789 12345 678912345 12345 空格前的字符串存入“ 12345” 存入数组 a, 空格后的字符
串“6789” 存入数组 b 。相当于初始赋值: char a[12]= "12345"; char b[6]= "6789"; scanf ("%s",p)从屏幕读取文本串放入 p 指针指向的充裕的内存空间。如果 p 指针指向的数组内存不够容纳键入的文本串或 p 指针指向系统为字符串常数分配的静态 const 区域,运行产生不可预料的结果。
14
[ 例 ] 字符串初始化二维字符数组
#include <stdio.h>void show (char*p) // 输出以 p 定位的其后 5 个字符的十进制数{ int k=0; // 不要求实参字符指针指向 '\0' 结尾的字符串 while( k<5) printf ("%d,",p[k++]); } void main (void){ char a[ ][5]={"1","23"}; char b[2][5]= {49,0,0,0,0,50,51,0,0,0};
show (a[0]); show (a[1]); printf ("\n"); // 输出 49,0,0,0,0,50,51,0,0,0 ,
show (b[0]); show (b[1]); // 输出 49,0,0,0,0,50,51,0,0,0 , }
15
[ 例 ] 二维数组初始化完整形式
# include<stdio.h>
extern void f (char s[3][5],int n);
void main (void)
{ char s[ ][5]={{' ',' ','*',' ',' '},{' ','*',' ','*',' '}, {'*','*','*','*','*'}};
f (s,0);
printf ("\n"); f(s,1);
}// 结果如下 :
32,32,42,32,32,32,42,32,42,32,42,42,42,42,42
*
* *
*****
16
void f (char s[3][5], int n) { for (int j=0; j<3; j++) { for (int k=0; k<5; k++)
if (n==1) printf ("%c", s[ j ][ k ]);
else printf ("%d,", s[ j ][ k ]);
if (n==1) printf ("\n");
} }
17
[ 例 ] 二维数组初始化截断形式
# include<stdio.h>extern void f (char s[3][5],int n);void main (void){ char b[ ][5]= {{' ',' ','*'},{' ','*',' ','*'}, {'*','*','*','*','*'}};
f (b,0); printf ("\n");f(b,1);} //设执行文件为 a.exe结果如下 :32,32,42,0,0,32,42,32,42,0,42,42,42,42,42, * * * *****
18
[ 例 ] 字符串初始化字符指针数组和二维字符数组# include<stdio.h> void main(void) { char *pa[ ]= {"a","bc","def","higk"}; int n=sizeof (pa)/sizeof (pa[0]); int k;
for (k=0;k<n;k++) printf ("%p->%s ",pa [ k ], pa [ k ]); printf ("\nsizeof(pa)=%d, sizeof (pa[0]) = %d\n", sizeof(pa),sizeof(pa[0]));
char ca[ ][5] = {"1","23","456","7890"}; n = sizeof (ca)/sizeof (ca [0]); printf ("sizeof(ca)=%d,sizeof(ca[0])=%d\n", sizeof(ca),sizeof(ca[0])); for ( k=0;k<n;k++) printf ("%p->%s ",ca [ k ],ca[ k ]);}
19
全局内存区的数据是可索引的,局部变量对于上层主控函数是不可操纵的。 [ 例 ] 字符串初始化局部二维字符数组与生存期 # include<stdio.h> char* sa (int n) //返回 char* 型指针值的函数 {//static char ca[ ][5]={"1","23","456","7890"}; return ca [n];
} void main(void) { for (int k=0;k<4;k++) printf ("%p->%s ", sa(k), sa(k)); }
20
该程序运行输出的结果是残缺的(输出局部数组的地址,局部数组的内容是不确定的): 0065FD84-> *e 0065FD89->
0065FD8E-> 0065FD93->
如果在局部变量 ca前加上 static 关键字则输出结果为: 004232F8->1 004232FD->23
00423302->456 00423307->7890
21
[ 例 ] 字符串初始化指针数组与生存期 # include<stdio.h> char* ga(int i) { char *pa[ ]={"a","bc","def","higk"}; return pa[i]; } void main(void) { for (int k=0; k<4; k++) printf ("%p->%s ",ga (k), ga (k)); }// 程序运行输出结果: 00420080->a 0042007C->bc 00420070->def 00420FD4->higk
22
[ 例 ] 二维字符数组行地址初始化指针数组# include<stdio.h>
char* ca(int i)
{ char c[ ][5]={"a","bc","def","higk"};
char* pa[ ]={c[0],c[1],c[2],c[3]};
return pa[i];
}
void main (void)
{ for (int k=0;k<4;k++) printf ("%p->%s ",ca(k),ca(k));
}
程序输出结果是残缺的:0065FD84-> *e 0065FD89-> 0065FD8E-> 0065FD93->
23
[ 例 ] 返回 char** 指针值的函数 # include<stdio.h>
char** ppn (char *pp[ ],int n) { return pp+n; }
void main (void)
{ char *pa[4]= { "a","bc","def","ghijk“ };
for (int k=0; k<4; k++)
printf ("%s",*ppn(pa,k));
}
// 输出 :abcdefghijk
24
二、 strlen 函数确定字符串有效长度 函数原型 : size_t strlen(const char*s); 作用 : 计算入口字符串的净长度 ( 结尾字符 '\0'前的字符个数 )
unsigned int strlen1(const char*s) /** 函数的要点说明 **/ { unsigned int length=0; // length纪录字符串的个数
while(*s!='\0') {s++;length++;} // 长度 length 中不含 '\0'
return length; //返回不含结尾符的字符串长度 } // 字符数组若无结尾字符则结果是游移
的
25
#include<string.h> // 字符串处理函数的原型在头文件 string.h
中 #include<stdio.h> void main (void) { char c[ ]="ab\0d"; printf ("strlen (c)=%d,sizeof (c)=%d,%s\n", strlen1 (c),sizeof(c),c); c[2]='c'; printf ("strlen(c)=%d,%s\n",strlen(c),c); c[4]='e'; printf ("strlen (c)=%d,%s\n",strlen(c),c); printf ("strlen (ab\053d)=%d,%s\n", strlen ("ab\053d"),"ab\053d");
}
26
// 输出结果:strlen (c)=2,sizeof(c)=5,ab
// 由 sizeof获得字符数组的长度strlen (c)=4,abcd
// 对于 char c[ ]="abcd"; 存在 sizeof(c) = strlen(c)+1
strlen (c)=11,abcde??????
// 结果 strlen(c)=11 是不确定的strlen (ab+d)=4,ab+d // \053 是字符 + 的八进制 ASCII 码 对于 {char d[10]="12345"; char *s="12345678";}
存在 strlen(d)=5,sizeof(d)=10 , strlen(s)=8,
sizeof(s) = sizeof (char *) = 4 或 2 。当若干字符串用空格分隔的时候,相邻的字符串合为一体。
27
这种合并相邻字符串的方式可将一行写不下的长字符串分散到多行。例如: char c[ ]="12 34" "56\n";
相当于 char c[ ]="12 3456\n";
char* s="abcd" " ef\n" ;
相当于 char* s="abcd ef\n" ;
因此: printf ("%d,%d,%s",strlen (c),sizeof (c),c);
输出 : 8,9,12 3456
printf ("%d,%d,%s",strlen (s),sizeof (s),s);
输出 : 8,4,abcd ef
28
三、 strcpy 函数拷贝字符串函数原型: char* strcpy(char *dst,const char*src); 作用: 将源串 src 中的字符拷贝到目标串 dst 中
char* strcpy1(char *dst,const char*s) { char * p=dst; while ((*p=*s) !='\0') {p++; s++;} *p='\0'; return dst; }
#include<string.h>#include<stdio.h>void main (void){ char b[ ]="while Hsiang river is flowing northward" ; char * c=new char [sizeof(b)]; printf (“%s\n”,strcpy1 (c,b)); delete [ ] c; }
29
说明: 上面 while((*p=*s) !=‘\0') {p++; s++;} 循环流程是明确
的无歧义的,先将原字符串中的字符拷贝给目标内存的相应位置,判断是否遇到结尾字符,如果未遇到结尾字符 则继续拷贝。这个循环可改为 [while(*s!=‘\0’) *p++=*s++;] ,语句 [*p++=*s++;]
分解为 {*p=*s;p++;s++}
但不应改为下面的循环: while((*p++=*s++) !='\0') ;
// 循环体为空语句
30
不同的编译器对循环语句中条件判断中的表达式*p++=*s++ 有不同的分解方法。 [while((*p++=*s++) !='\
0');]
未必分解为 while((*p=*s) !=‘\0’) {p++; s++;}
例如: vc6.0 中对空语句表示的循环运行时弹出 Debug
Error错误。因此不要在循环语句的条件表达式中出现形如while (p[k++]=s[i++]),for(;*p++;),while(*p++=*s++) 等分解次序含糊的表达式 s[k++],*p++ 等。 while (e!=0) 等价于 while(e) , while (*s!=‘\0’) 可以改为 while(*s) 。
31