一、字符指针
字符指针(Character Pointers)是指向字符型数据的指针变量。
每个字符串在内存中都占用一段连续的存储空间,并有唯一确定的首地址。因此,只要将字符串的首地址赋值给字符指针,即可让字符指针指向一个字符串。对于字符串字面量而言,字符串字面量本身代表的就是存放它的常量存储区的首地址,是一个地址常量。例如:
char *ptr = “Hello”;
与 char *ptr;
ptr = “Hello”;//将保存在常量存储区中的“Hello”的首地址赋值给ptr//
这两种表达方式时等价的,都表示定义一个字符指针变量ptr,并用字符串字面量“Hello”在常量存储区中的首地址为其初始化,即让ptr指向字符串自字面量“Hello”。注意,这里不能理解为将字符串赋值给ptr。
因字符串“Hello”保存在只读的常量存储区中,所以此时可修改指针变量ptr的值(即ptr的指向),但不能对ptr所指向的存储单元进行写操作。
例如,此执行如下操作就是非法的:
*ptr = ‘w’;
//不能修改ptr指向的常量存储区中的字符,因为它是只读的
但如果字符串“Hello”保存在一个数组中,然后再用一个字符指针指向它,即
char str [10] = “Hello”;
char *ptr = str ;(第二句)
那么此时由于数组名代表数组的首地址,因此将str赋值给ptr,就是让ptr指向数组str中存储的字符串“Hello”。其中,上面第二条语句相当于
char *ptr;
ptr = str;
因为数组名是一个地址常量,所以str的值是不可修改的,但ptr的值(即ptr的指向)可以被修改,ptr所指向的字符串也可以被修改。
例如,若要将ptr所指向的字符串中的第一个字符修改为‘W’,则可使用下面的语句:
*ptr = ‘W’;//等价于ptr[0] = ‘W’ ; 相当于str[0]=‘W’
总之,正确使用字符指针,必须明确字符串被保存到了哪里以及字符指针指向了哪里。
#include <stdio.h>
char str[]="Hello!";
char *ptr;
char *sstr="Hello!";
int main(void)
{
int i;
int n=0;
do{
n++;
}while(str[n]!=0);
printf("%d\n",i);
for(i=0;i<n;i++)
printf("%c",str[i]);
printf("\n");
ptr=str;
*ptr='W';
for(i=0;i<n;i++)
printf("%c",str[i]);
printf("\n\n");
// *sstr='w';
for(i=0;i<6;i++)
printf("%c",*sstr++);
printf("\n");
}
此程序根据上述内容进行编写。
二、字符串的访问和输入/输出
1、如何访问字符串中的单个字符
和其他类型的数组一样,可以使用下标方式来访问存放于字符数组中的每个字节。
例如,在前面定义的字符数组str中,str[0]就表示第1个字符’H’,str[1]表示第2个字符’e’,依此类推,可以通过下标为i的元素str[i]来访问存放于数组中的第i+1个字符。
此外,还可通过字符指针间接访问存放于数组中的字符串,例如,若字符指针ptr指向了字符数组str的首地址,既可通过
(ptr+i)来引用字符串中的第i+1个字符,(ptr+i)相当于*(str+i),即str[i],也可通过ptr++操作,即移动指针ptr,使ptr指向字符串中的某个字符。
注意:对于数组名str,不能使用str++操作使其指向字符串中的某个字符,因为数组名是一个地址常量,其值是不能被改变的。
2、字符串的输入/输出
以下三种方法均可实现字符数组str的输入/输出。
(1)、按c格式符,一个字符一个字符地单独输入/输出。
例如:
#include <stdio.h>
int main()
{
char str[10];
for(int i=0;i<10;i++)
{
scanf("%c",&str[i]);
}
for(int i=0;i<10;i++)
{
printf("%c",str[i]);
}
}
由于字符串地长度与字符数组地大小通常并不是完全一致的,因此很少使用上面这种方式输出字符数组中的字符串,更常用的方式是借助字符串结束标志’\0’,识别字符串的结束,进而结束字符串的输出操作,即:
for(i=0;str[i]!='\0';i++)
{
printf("%c",str[i]);//输出字符串
}
该语句在输出时,依次检查数组中的每个元素str[i]受否为’\0’,若是,则停止输出,否则继续输出下一个字符。这种方法非常灵活,无论字符串中的字符数是已知还是未知,都可采用。
(2)、按s格式符,将字符串作为一个整体输入/输出。
例如:
scanf("%s",str);
表示读入一个字符串,直到遇空白字符(空格、回车符或制表符)为止。而
printf("%s",str);
表示输出一个字符串,直到遇到字符串结束标志为止。这里,由于字符数组名str本身代表该数组中存放的字符串的首地址,所以数组名str前面不能在加取地址符。
例题:从键盘输入一个人名,并把它显示再屏幕上。
#include <stdio.h>
int main()
{
char str[]={};
printf("Enter your name:");
scanf("%s",str);
printf("Hello %s !",str);
printf("\n");
}
第一次调试:
第二次调试:
可以发现第二次调试,遇到空格之后,就结束了读入字符的操作。余下的字符被保留再缓冲区中,因此,我们可以对程序做如下修改:
#include <stdio.h>
int main()
{
char str[12];
printf("Enter your name:");
scanf("%s",str);
printf("Hello %s !\n",str);
scanf("%s",str);//再次读入输入时空格后的字符
printf("Hello %s !",str);
printf("\n");
}
修改后的调试结果:
第一个scanf语句将输入字符串中空格前面的字符串读到数组name中,然后由scanf后的第一个printf打印出第一个“Hello”信息。第9行将输入缓冲区中余下的上次未被读走的空格后面的字符串重新读到数组name中,然后由第10行语句打印出第二个“Hello”信息。
用%d输入数字或%s输入字符串时,忽略空格、回车或制表符等空白字符(被作为数据的分隔符),读到这些字符时,系统认为数据读入结束,因此用scanf()按s格式符不能输入带空格的字符串。
(3)使用字符串处理函数gets(),可以输入带空格地字符串,因为空格和制表符都是字符串地一部分。
此外,函数gets()与scanf()对回车符的处理也不同。gets()以回车符作为字符串的终止符,同时将回车符从的输入缓冲区读走,但不作为字符串的一部分。而scanf()不读走回车符,回车符仍留在输入缓冲区中。
例题:使用gets(),从键盘输入一个带有空格的人名,然后把它显示在屏幕上。
#include <stdio.h>
#define N 12
int main(void)
{
char name[N];
char nake[N];
printf("Enter your name: ");
gets(name);
printf("Hello %s !\n",name);
puts(name);
}
函数puts()也可以输出字符串。
函数puts()用于从括号内的参数给出的地址开始,依次输出存储单元中的字符,当遇到第一个‘\0’时输出结束,并且自动输出一个换行符。函数puts()输出字符串简洁方便,唯一不足就是不能向函数printf()那样在输出行中增加其他字符信息。
由于gets()和puts()都是C语言的标准输入输出库函数,因此,在使用时只要在程序开始时将头文件<stdio.h>包含到源文件中即可。
例题2:程序还可以使用字符指针来编程实现。
#include <stdio.h>
#define N 12
int main(void)
{
char name[N];
char *ptrName = name;
printf("Enter your name:");
gets(ptrName);
printf("Hello %s !\n",ptrName);
return 0;
}
用字符指针输入字符串时,必须确保字符指针事先已经指向一个数组的首地址,如本例字符指针变量ptrName指向了数组name。但如果删掉第5行语句,并将第6行语句修改为:
char *ptrName;
那么由于指针变量ptrName尚未指向一个确定的存储单元,就把输入的字符串存入其中,会导致非法内存访问错误,因此程序在Visual C++ 6.0(DEv C++)下运行后会发生异常终止。
其实,在前面几个程序都存在一种容易被忽略的问题,即如果用户没有听从括号内的提示信息的指示,键入的字符数超过了数组name的大小12,那么多出来的那些字符就有可能重写内存的其他区域,导致程序出错。
其根本原因在于,gets函数不能限制输入字符的长度,很容易引起缓冲区溢出。同样scanf()函数也存在同样的问题。即使使用了带格式控制的形式,如scanf(“%12s”,name),也不可能真正解决这个问题。
所以建议使用能限制字符串长度的函数,即:
fgets(name, sizeof(name),stdin);
将更有利于设计安全可靠的程序。
例题修改:
#include <stdio.h>
#define N 12
int main(void)
{
char name[N];
printf("Enter your name:");
fgets(name , sizeof(name),stdin);
printf("Hello %s !\n",name);
return 0;
}
输入字符产包含双引号:
例题3:从键盘输入一个带有空格的人名,然后在显示人名的前面显示“hello”,I said to。
#include <stdio.h>
#define N 12
int main(void)
{
char name[N];
char str[]="\"Hello\",I said to";
printf("Enter your name :");
fgets(name,sizeof(name),stdin);
printf("%s %s.",str,name);
}