原创 字符串的指针和指向字符串的指针变量

2009-9-10 15:39 5835 1 1 分类: 工程师职场
 

§8.4 字符串的指针和指向字符串的指针变量


8.4.1 字符串的表现形式


C程序中,可以用两种方法实现一个字符串。


1. 用字符数组实现。


[8.11]


void main(void)


{static char string [ ]="I Love China!";


printf("%s\n",string);


}


运行时输出: I Love China!


和前面介绍的数组属性一样,string是数组名,它代表字符数组的首地址,(见图8.17)。string[4]代表数组中序号为4的元素(v),实际上string[4]就是*(string+4)string+4是指向字符“v”指针。


2. 用字符指针实现。


可以不定义字符数组,而定义一个字符指针。用字符指针指向字符串中的字符。


[8.12]


void main(void)


{char *string="I Love China!";


printf("%s\n",string);


}


在这里没有定义字符数组,但C语言对字符串常量是按字符数组处理的,实际上在内存开辟了一个字符数组用来存放字符串数组。在程序中定义了一个字符指针变量string。并把字符串首地址(即存放字符串的字符数组的首地址)赋给它(见图8.18)。有人认为string是一个字符串变量,以为定义时把"I Love China!"赋给该字符串变量,这是不确切的。定义string的部分:


char *string="I Love China!";


等价于下面两行:


char *string;


string="I Love China!";


可以看到:string被定义为一个指针变量,它指向字符型数据,请注意只能指向一个字符变量或其它字符类型数据,不能同时指向多个字符数据,更不是把"I Love China!"这些字符存放到string中。只是把"I Love China!"的首地址赋给指针变量string(不是把字符串赋给*string)。因此不要认为上述定义行等价于:


char *string;


*string="I Love China!";


在输出时,用


printf("%s\n",string);


%s表示输出一个字符串,给出字符指针变量名string,则系统先输出它所指向的一个字符数据,然后自动使string1,使之指向下一个字符,然后再输出一个字符,……,如此直到遇到字符串结束标志‘\0’为止。注意,在内存中,字符串的最后被自动加了一个‘\0’(如图8.18所示),因此在输出时能确定字符串的终止位置。


通过字符数组名或字符指针变量可以输出一个字符串。而对一个数值型数组,是不能企图用数组名输出它的全部元素的。如:


int i[10]


:


printf("%d\n",i);


是不行的,只能逐个元素输出。显然,可以把字符串看作为一个整体来处理,可以对一个字符串进行整体的输入输出。


对字符串中字符的存取,可以用下标方法,也可以用指针方法。


[8.13]将字符串a复制得字符串b


void main(void)


{char a[ ]= "I am a boy. ",b[20];


int i;


for(i=0;*(a+i)!=‘\0’;i++)


*(b+i)=*(a+i);


*(b+i)=‘\0’;


printf("string a is:%s\n",a);


printf("string b is: ");


for(i=0;b!=‘\0’;i++)


printf("%c",b);


printf("\n");


}


程序运行结果为:


string a is:I am a boy.


string b is:I am a boy.


c817.jpg (28464 bytes)


程序中ab都定义为字符数组,可以用地址方法表示数组元素。在for语句中,先检查a是否为’\0’(今a是以*(a+i)形式表示的)。如果不等于’\0’,表示字符串尚未处理完,就将a的值赋给b,即复制一个字符。在for循环中将a串全部复制给了b串。最后还应将’\0’复制过去,故有:*(b+i)=‘\0’;


此时的i的值是字符串有效字符的个数n1。第二个for循环中用下标法表示一个数组元素(即一个字符)。也可以设指针变量,用它的值的改变来指向字符串中的不同的字符。


[8.14] 用指针变量来处理例8.13问题。


void main(void)


{char a[ ]= "I am a boy. ",b[20],*p1,*p2;


int i;


p1=a;p2=b;


for(;*p1!=‘\0’;p1++,p2++)


*p2=*p1;


*p2=‘\0’;


printf("string a is:%s\n",a);


printf("string b is: ");


for(i=0;b!=‘\0’;i++)


printf("%c",b);


printf("\n");


}


p1p2是指针变量,它指向字符型数据,先使p1p2的值分别为字符串ab的首地址。*p1最初的值为‘i’,赋值语句“*p2=*p1;”的作用是将字符’I’a串中第一个字符)赋给p2所指向的元素,直到 *p1的值为’\0’止。注意p1p2的值是不断在改变的,程序必须保证使p1p2同步移动。


8.4.2 字符串指针作函数参数


将一个字符串从一个函数传递到另一个函数,可以用地址传递的办法,即用字符数组名作参数或用指向字符串的指针作参数。在被调用的函数中可以改变字符串的内容,在主调函数中可以得到改变了的字符串。


[8.15] 用函数调用实现字符串的复制。


(1)用字符数组作参数


void copy_string (char from[ ],char to[ ])


{int i="0";


while (from!=‘\0’)


{to=from;i++;}


to=‘\0’;


}


void main(void)


{char a[ ]= "I am a teacher. ";


char b[ ]= "You are a student. ";


printf("string_a=%s\n string_b=%s\n",a,b);


copy_string(a,b);


printf("\nstring_a=%s\n string_b=%s\n",a,b);


}


程序运行结果如下:


string_a=I am a teacher.


string_b=You are a student.


 


string_a=I am a teacher.


string_b=I am a teacher.


ab是字符数组。初值如图8.19(a)所示。copy_string函数的作用是将from赋给to,直到from的值为’\0’为止。在调用copy_string函数时,将ab的首地址分别传递给形参数组formto。因此froma是同一个单元,tob是同一个单元。程序执行完以后,b数组的内容如图8.19(b)所示。可以看出,由于b数组原来的长度大于a数组,因此在a数组复制到b数组后,未能全部覆盖b数组原有内容。b数组最后三个元素仍保留原状。在输出b时由于按%s(字符串)输出,遇’\0’即告结束,因此第一个’\0’后的字符不输出。如果不采取%s格式输出而用%c逐个字符输出是可以输出后面这些字符的。


main函数中也可以不定义字符数组,而用字符型指针变量。main函数可改写如下:


void main(void)


{char *a=“I am a teacher.”;


char *b= “You are a student.”;


printf(“string_a=%s\n


string_b=%s\n”,a,b);


copy_string(a,b);


printf(“\nstring_a=%s\n


string_b=%s\n”,a,b);


}


与上面程序运行结果相同。


(2)形参用字符指针变量


程序如下:


void copy_string(char *from,char *to)


{


for(;*from!=‘\0’;from++,to++)


*to=*from;


*to=‘\0’;


}


 


void main(void)


{char *a="I am a teacher. ";


char *b= "You are a student. ";


printf("string_a=%s\n


string_b=%s\n",a,b);


copy_string(a,b);


printf("\nstring_a=%s\n string_b=%s\n",a,b);


}


c818.jpg (27303 bytes)


形参formto是字符指针变量。它们相当于例8.19中的p1p2。算法也与例8.19完全相同。在调用copy_string时,将数组a的首地址传给from,把数组b的首地址传给to。在函数copy_string中的for循环中,每次将*from赋给*to,第1次就是将a数组中第1个字符赋给b数组的第1个字符。在执行from++to++以后,fromto就分别指向a[1]b[1]。再执行*to=*from,就将a[1]赋给b[1],……。最后将’\0’赋给*to,注意此时to指向哪个单元。


(3)copy_string函数还可作简化


1. copy_string函数改写为:


void copy_string (char *from,char *to)


{


while ((*to=*from)!=‘\0’)


{to++;from++;}


}


请与上面一个程序对比。在本程序中将“*to=*from;”的操作放在while语句的表达式中,把赋值运算和判断是否为’\0’的运算放在表达式中,先赋值后判断。在循环体中tofrom增值,指向下一个元素,……,直到*from的值为’\0’为止。


2. copy_string函数的函数体还可改为:


{


while((*to++=*from++)!=‘\0’);


}


把上面程序的to++from++运算与*to=*from合并,它的执行过程是:先将*from赋给*to,然后使tofrom增值。显然这又简化了。


3. 函数体还可写成:


{


while(*from!=‘\0’)


*to++=*from++;


*to=‘\0’;


}


*from不为’\0’时,使*from赋给*to,然后使tofrom增值。


字符可以用其ASCII代码来代替。例如:”ch=‘a’”可以用”ch=97”代替,”while(ch!=‘a’)”可以用”while(ch!=97)”代替。因此,”while(*from!=‘\0’)”可以用”while(*from!=0)代替(’\0’ASCII代码为0)。而关系表达式”*from!=0”又可简化为”*from”,这是因为若*form的值不等于0,则表达式”*from”为真,同时”*from!=0”也为真。因此”while(*from!=0)””while(*from)”是等价的。所以函数体可简化为:


{while(*from)


*to++=*from++;


*to=‘\0’;}


4. 上面的while语句还可以进一步简化为下面的while语句:


while(*to++=*from++)


它与下面语句等价:


while((*to++=*from++)!=‘\0’);


*from赋给*to,如果赋值后的*to值等于’\0’,则循环终止(’\0’已赋给*to)。


5. 函数体中while语句也可以改用for语句:


for(;(*to++=*from++)!=0;);


for(;*to++=*from;);


6. 也可用指针变量,函数copy_string可写为:


void copy_string( char from[ ],char to[ ])


{char *p1,*p2;


p1=from;p2=to;


while((*p2++=*p1++)!=‘\0’);


}


以上各种用法,变化多端,使用十分灵活,初看起来不太习惯,含义不直观。初学者会有些困难,也容易出错。但对C熟练之后,以上形式的使用是比较多的,读者应逐渐熟悉它,掌握它。


归纳起来,作为函数参数,有以下几种情况:



1.


2. 字符指针变量


3. 字符指针变量 字符指针变量


4. 字符指针变量


8.4.3 字符指针变量与字符数组


虽然用字符数组和字符指针变量都能实现字符串的存储和运算,但它们二者之间是有区别的,不应混为一谈,主要有以下几点:


1. 字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串的首地址),决不是将字符串放到字符指针变量中。


2. 赋初值的方式。对数组赋初值要用static存储类别,如


static str[ ]={ "I love China! ");


而对字符指针变量不必加static存储类型,如


char *a="I love China! ";


这是因为并没有对数组初始化,只是对指针变量初始化。


3. 赋值方式。对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值。


char str[14];


str="I love China! ";


而对字符指针变量,可以采用下面方法赋值:


char *a;


a="I love China! ";


但注意赋给a的不是字符,而是字符串的首地址。


4. 赋初值时,对以下的变量定义和赋初值:


char *a="I love China! ";


等价于:


char *a;


a="I love China! ";


而对数组初始化时:


static char str[14]={ "I love China! "};


不是等价于


char str[14];


str[ ]= "I love China! ";


即数组可以在变量定义时整体赋初值,但不能在赋值语句中整体赋值。


5. 在定义一个数组时,在编译时即已分配内存单元,有确定的地址。而定义一个字符指针变量时,给指针变量分配内存单元,在其中可以放一个地址值,也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋以一个地址位,则它并未具体指向哪一个字符数据。如:


char str[10];


scanf("%s",str);


是可以的,而常有人用下面的方法:


char *a;


scanf("%s",a);


目的是输入一个字符串,虽然一般也能运行,但这种方法是危险的,不宜提倡。因为编译时虽然分配给指针变量a一个单元,a的地址(即&a)是已指定了,但a的值并未指定,在a单元中是一个不可预料的值。因此在scanf函数中要求将一个字符串输入到a的值(地址)开始的存储区(这是好的情况),也有可能指向已存放指令或数据的内存段,这就会破坏了程序,甚至会造成严重的后果。在程序规模小时,由于空白地带多,往往可以正常运行,而程序规模大时,出现上述“冲突”的可能性就大多了。应当这样:


char *a,str[10];


a=str;


scanf("%s",a);


先使a有确定值,也就是使a指向一个数组的开头,然后输入字符串到该地址开始的若干单元中。


6. 指针变量的值是可以改变的,如:


[8.16]


void main(void)


{char *a="I love China! ";


a=a+7;


printf("%s",a);


}


运行结果如下:


China!


指针变量a的值可以变化,输出字符串从a当时所指向的单元开始输出各个字符,直到遇’\0’为止。而数组名虽然代表地址,但它的值是不能改变的。下面是错的:


char str[ ]={ "I love China! "};


str=str+7;


printf("%s",str);


需要说明:若定义了一个指针变量,使它指向一个字符串后,可以用下标形式引用指针变量所指的字符串中的字符。如:


[8.17]


void main(void)


{char *a="I LOVE CHINA. ";


int i;


printf("The sixth charcter is %c\n",a[5]);


for(i=0;a!=‘\0’;i++)


printf("%c",a);


}


运行结果如下:


The sixth charcter is E


I LOVE CHINA.


程序中虽然并未定义数组a,但字符串在内存中是以字符数组形式存放的。a[5]*(a+5)执行,即从a当前所指向的元素下移5个元素的位置,取出其单元中的值。


7. 用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。如:


char *format;


format="a=%d,b=%f\n";


printf(format,a,b);


它相当于


printf("a=%d,b=%f\n",a,b);


因此只要改变指针变量format所指向的字符串,就可以改变输入输出的格式。这种printf函数称为可变格式输出函数。


也可以用字符数组。如:


char format[ ]= "a=%d,b=%f\n";


printf(format,a,b);


但由于不能采取赋值语句对数组整体赋值的形式,如:


char format[ ];


format="a=%d,b=%f\n";


因此用指针变量指向字符串的方式更为方便。


 


 


 


 


 


 


 


 

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
1
关闭 站长推荐上一条 /3 下一条