C语言细节 字符串

一.表示字符串
1.字符串字面量(String Literal):

"字符串字面量"(String Literal),也称"字符串常量"(String Constant),是使用双引号扩起的字符序列.编
译器会自动在结尾加入空字符('\0')作为结束的标志.从ANSI C开始,如果多个字符串字面量间没有间隔,或只有空
白字符,会被视为1个字符串字面量,如下面2行代码是等价的:
char greeting[50]="Hello,and"" how are"  " you"	" today?"
char greeting[50]="Hello,and how are you today?"
字符串字面量属于"静态存储类别"(Static Storage Class),这说明如果在函数中使用字符串字面量,该字符串只
会被存储1,且其在整个程序的生命周期内都存在,故即使函数被多次调用,使用的也都是同1个字符串字面量.另外,
字符串字面量被视为指向该字符串存储位置的指针,这类似于把数组名作为指向该数组(或数组首元素)的指针,:
printf("%s,%p,%p,%c\n","aaa","aaa","aaa",*"aaa");//结果:aaa,0000000000404000,0000000000404000,a
printf("%p,%p\n","aaa","bbb");//结果:0000000000404000,0000000000404011

2.字符串数组:

可以使用char类型的数组存储字符串.编译器会自动加上结尾的空字符('\0'),故数组大小应至少
为[字符串长度+1],:
  //注:'\0'的ASCII码是0
char words[20]="Hello World!";
或者也可以使用普通的字符数组存储字符串,:
char words2[20]={'H','e','l','l','o',' ','W','o','r','l','d','!','\0'};
注意:如果使用普通的字符数组存储字符串,最后1个字符应该是'\0',否则存储的就不是1个字符
串,而只是1个字符数组
未被初始化的元素会被自动设为'0'.也可以让编译器自动确定数组大小,:
char words3[]="Good Bye.";
不过,如果要让编译器自动确定数组大小,就必须初始化

3.字符串与指针:

可以使用指针表示法创建字符串,:
const char * pwords="Never mind.";
在这种情况下,带双引号的字符串本身决定了预留给字符串的存储空间

字符串字面量被视为const数据,而pwords指向该const数据,所以应该把pwords声明为指向const数据的指针(不过,
去除const标识也是可以的)

//关于字符串指针的拷贝:
#include <stdio.h>
int main(void) {
	const char * a="Don't be a fool";
	const char * b=a;
	const char * c;
	c=a;
	printf("%s,%p,%p\n",a,&a,a);
	printf("%s,%p,%p\n",b,&b,b);
	printf("%s,%p,%p\n",c,&c,c);
}
//结果:
Don't be a fool,000000000062FE18,0000000000404000
Don't be a fool,000000000062FE10,0000000000404000
Don't be a fool,000000000062FE08,0000000000404000
//3个指针存储在不同位置,但都指向同1个str,这是为了提高效率并节约资源

4.指针与数组的区别:

指针表示法和数组表示法在创建字符串上并不完全相同:
1.数组形式在内存中分配为数组,每个元素被初始化为字符串字面量中与之对应的字符(注意:比明示的字符多1'\0'
).通常,字符串都作为可执行文件的一部分存储在数据段中.把程序载入内存时,也载入了程序中的字符串.字符串存储
在"静态存储"(Static Memory).但是,程序在开始运行时才会为该数组分配内存,这时才将字符串拷贝到数组中.
此时字符串有2个副本,1个是在静态存储中的字符串字面量,1个是存储在数组中的字符串.此后,编译器便把数组名识
别为该数组首元素地址的别名.在数组形式中,数组名是"地址常量",不能修改,否则就改变了数组的存储位置(即地
址).可以进行[数组名+1]这样操作,表示数组的下1个元素;但不能进行[++<数组>]这样的操作

2.指针形式也使得编译器为每个元素留出空间.此外,一旦开始执行程序,其会为指针变量留出1个存储位置,并把字符串
的地址存储在指针变量中.指针变量最先指向字符串的首字符,但其值可以改变,如进行[++<指针>]操作后指针指向下1
个字符

3.初始化数组把静态存储区的str拷贝到数组中,而初始化指针只会把str的地址拷贝给指针:
#define MSG "I'm special'"
#include <stdio.h>
int main(void) {
	char ar[]=MSG;
	const char * pt=MSG;
	printf("%p\n","I'm special");//静态内存
	printf("%p\n",ar);//动态内存
	printf("%p\n",pt);//静态内存
	printf("%p\n",MSG);//静态内存
	printf("%p\n","I'm special");//静态内存
}
//结果:
000000000040400D
000000000062FE00
0000000000404000
0000000000404000//在某些编译器中和第1行不同
000000000040400D//在某些编译器中和第1行不同、

因此,推荐在将指针初始化为str字面量时使用const限定符,否则进行修改可能影响多个相同的str或导致其他问题
通常来说,如果准备修改str,就不要使用指针表示法

#include <stdio.h>
int main(void) {
	const char a[3][40]={//是1个3×40的字符串数组,占用12B
	//每个字符串字面量需要被存储2遍;由于每个元素都必须能储存下最长的字符串,因此会浪费一部分内存
	    "I am your father",
	    "We die in the darkness",
	    "so that you can live in the light"
	};
	const char * b[3]={//包含3个指针,每个都指向1个str,占用24B
	//每个字符串字面量只需存储1遍,指针只存储str的地址
		"I am your father",
	    "We die in the darkness",
		"so that you can live in the light"
	};
	printf("%s\n",a);
	printf("%s\n",*b);
}
//结果:
I am your father
I am your father

因此,如果要用数组表示一系列待显示的字符串,通常使用指针数组;不过,如果要修改字符串或为字符串输入留出空间,
不要使用指向字符串字面量的指针

4.数组名是常量(但数组元素是变量),指针名是变量,因此
①只有指针表示法可以进行自增/减运算
const char * p="aaa";
char a[]="bbb";
p++;
//a++;//不合法
②可以给指针重新赋值,不能给(整个)数组重新赋值:
p=a;//注意:这样做后"aaa"仍然留在内存中,如果不提前保存其地址,之后会无法访问该str
//a=p;//不合法
③可以改变数组中的元素:
a[1]='c';
④编译器可能允许通过指针修改str,但这种行为是未定义的:\
p[1]='c';//编译器可能允许,但可能导致内存访问错误,也可能直接报错

二.字符串的输入
1.分配空间:

计算机在读取字符串时不会顺便计算其长度再分配内存空间,因此需要手动分配内存空间:
char *name;
上述代码可能可以通过编译(可能给出警告),但在输入字符串时可能擦写掉程序中的数据,从而导致程序中止 
故应使用下述代码:
char name[8];
也可使用C语言的相关库函数来进行动态内存分配

2.gets()及其替代品

如果输出行小于等于为其分配的内存空间的大小,下述各函数均是安全的,不过有的会保留换行符,另一些则不会

如果输入超出了为其分配的内存空间的大小,gets()可能擦除已有数据,因而是不安全的;gets_s()是安全的,但可
能造成程序中止/退出,并需要直到然后编写特殊的"处理函数",并且即使用户不希望如此,也会清空剩余的输入;相比
较而言,fegts()是最方便的

(1)gets():

stdin读取整行输入:gets(<var>);
  //即从stdin中读取数据,直到遇到换行符,然后丢弃换行符并在结尾添加1个空字符('\0')
  //通常和puts()配对使用
  //参数说明:
    var:用于存储读取到的字符串的变量

//实例
#include <stdio.h>
int main(void) {
	char words[20];
	gets(words);//输入:lkslaskals
	puts(words);//结果:lkslaskals
	return 0;
}

//问题:
有些编译器可能会(在要求输入前)输出1个警告:
warning: this program uses gets(), which is unsafe
另一些编译器可能在编译时给出警告或不在任何位置给出警告.这是因为gets()无法检测数组是否足以容纳输入的内
容,如果输入的内容过长,会导致缓冲区溢出(Buffer Overflow),即多余的字符超出了指定的目标空间,这可能只会
使用尚未使用的内存;但也可能擦写掉程序中的其他数据从而导致程序中止;或导致其他问题.C99标准建议不要使用该
函数,C11标准则直接删除了该函数,不过为了能兼容旧代码,多数编译器仍支持使用该函数.如果要编写新程序,可以使
用下述函数来代替gets()

(2)fgets():

读取有长度上限的整行内容:[<p>=]fgets(<var>,<len>,<pos>);
  //该函数被设计为专门用于处理文件输入,所以在其他情况下可能并不好用
  //如果读取到上限为止都没有遇到换行符,则读取前<len>-1个字符;否则读取整行(直到换行符).然后在结尾加上空字符('\0')
  //注意:如果fgets()读取到1个换行符,会将其储存在str中(因此通常与fputs()配对使用),这点与gets()不同
  //通常和fputs()配对使用
  //参数说明:
    var:用于存储读取到的字符串的变量
    len:指定读入字符的最大数量
      //由于需要在结尾直接1个空字符,因此指定上限为<len>时,只会读取前<len>-1个字符
    pos:指定从哪里读取输入;可为stdin/文件路径/文件指针
      //标识符stdin被定义在stdio.h中
    p:如何没有超出上限,返回<var>的地址;否则,返回"空指针"(Null Pointer)
      空指针不指向任何有效的数据,用于表示这种特殊情况
      在C语言中,空指针用宏NULL(如果读取错误,也返回NULL)来代替,不过也可以用0来代替

//实例:
#include <stdio.h>
int main(void) {
	char words[10];
	fgets(words,5,stdin);
	puts(words);
	fputs(words,stdout);
	printf("aaa\n");
	return 0;
}
//结果1:
dsfas//读入"dsfa",储存的是"dsfa\0"
dsfa//输出"dsfa\0\n"
dsfaaaa//输出:①"dsfa\0" ②"aaa\n\0"

//结果2:
sa//读入"sa\n",储存的是"sa\n\0"
sa//输出"sa\n\0\n"

sa//输出"sa\n\0"
aaa//输出:"aaa\n\0"

  • 特殊情况的处理:
//将结尾的换行符转化为空字符:
int i=0;
while (words[i]!='\n') {
    i++;
}
words[i]='\0';

//丢弃输入行中超出上限的部分:
while (getchar()!='\n') {
    continue;
}

//实例:
#include <stdio.h>
#define STLEN 10 
int main(void) {
	char words[STLEN];
	int i;
	while (fgets(words,STLEN,stdin)!=NULL&&words[0]!='\n') {
		i=0;
		while (words[i]!='\n'&&words[i]!='\0') {
			i++;
		}
		if (words[i]=='\n') {
			words[i]='\0';
		} else {
			while (getchar()!='\n') {
				continue;
			}
		}
		puts(words);
	}
	return 0;
}
//结果:
This
This
program seems
program s
unwilling to accept long lines
unwilling
But it doesn't get stuck on long
But it do
lines either
lines eit

(3)gets_s():

该函数是C11标准中新增的,gets()接近,可以替换现有代码中的gets();不过,该函数是可选扩展项,所以支持C11
标准的编译器也不一定支持该函数

从标准输入读取有长度上限的整行:gets_s(<var>,<len>);
  //只从stdin读取输入,因此不需要fgets()中的第3个参数
  //如果没有达到上限,与gets()几乎相同(读取输入直到遇到换行符,并将换行符转换为空字符)
  //如果达到上限时还未读取到换行符,会在结尾增加1个空字符,读取并丢弃随后的输入直至读到换行符/文件结尾,
  //然后返回空指针,接着调用依赖实现的"处理函数"或选择的其他函数,可能会中止或退出程序
  //参数说明:同fgets()

(4)s_gets():

可以自定义1个函数,其在没有到达上限时读取整行输入并将换行符转换为空字符;否则读取一部分并丢弃输入行的剩余
部分(即 二.2.(2).特殊情况的处理 部分中的实例实现的功能)

char * s_gets(char * st,int n) {
    char * ret_val;
    int i=0;
    ret_val=fgets(st,n,stdin);
    if (ret_val) {//相当于ret_val!=NULL
        while (st[i]!='\n'&&st[i]!='\0') {
            i++;
        }
        if (st[i]=='\n') {
            st[i]='\0';
        } else {
			while (getchar()!='\n') {
				continue;
			}
		}
    }
    return ret_vaal;
}

//注意:
1.丢弃多余的输入是为了保证多余的内容不留在缓冲区中,否则可能导致下1条读取语句崩溃
2.s_gets()仍有问题,如在丢弃多余的字符时,既不通知程序也不告知用户

3.scanf():

参见 C语言基础.常用命令,运算符,控制符.3.(1) 部分

与gets()系列函数相比,scanf()更像是"获取单词"而不是"获取字符串",因为其只读取到空白字符(空格/制表符/
换行符/空行;不含空白字符).如果需要1次读取多个单词,那么使用fgets()更合适;scanf()通常用于读取指定了格
式的混合数据类型的输入,如输入工具名(str)+库存量(int)+单价(float),这时如果使用fgets()就需要自定义1
个函数,进行一系列输入检查来判断输入是否符合格式

scanf()也有一些缺点:如果输入行过长,也会导致溢出,不过在%s转换说明中指定字段宽度可防止这一点

三.字符串的输出
1.puts():

输出字符串到stdout:puts(<str_p>);
  //会自动在结尾加上换行符
  //通常和gets()配对使用
  //参数说明:
    str_p:要输出的字符串的地址

//实例:
#include <stdio.h>
#define WORDS "Hello World!"
int main(void) {
	char awords[20]="Hello World!";
	const char * pwords="Hello World!";

	puts(awords);//结果:Hello World!
	puts(pwords);//结果:Hello World!
	puts(WORDS);//结果:Hello World!
	puts("Hello World!");//结果:Hello World!//说明字符串字面量被视为该str的地址

	puts(&awords[3]);//结果:lo World!//说明从指定的地址处开始输出,直到遇到空字符就结束
	puts(pwords+2);//结果:llo World!
}

//注意:
#include <stdio.h>
#define WORDS "Hello World!"
int main(void) {
	char dont[]={'W','O','W','!'};
	char side_b[]="side B";
	puts(dont);//结果:WOW!
	puts(dont+1);//结果:OW!//这是Dev-C++ 5.11中的结果,编译器是TDM-GCC 4.9.2 64-bit Release
	//但是在某些情况下puts(dont)会一直输出直到遇到'\0',比如输出:WOW!side B
	//如果删掉side_b,会不断输出内存中的数据直到遇到'\0'(内存中通常有许多'\0')
}

2.fputs():

这是puts()针对文件定制的版本

向指定位置输出字符串:fputs("<str>",<pos>);
  //不会自动在结尾添加换行符
  //通常和fgets()配对使用
  //参数说明:
    str:要输出的字符串
    pos:指定要输出到哪里;可为stdout/文件路径/文件指针
      //标识符stdout被定义在stdio.h中

//实例:
#include <stdio.h>
int main(void) {
	char words[10];
	fgets(words,5,stdin);
	puts(words);
	fputs(words,stdout);
	printf("aaa\n");
	return 0;
}
//结果1:
dsfas//读入"dsfa",储存的是"dsfa\0"
dsfa//输出"dsfa\0\n"
dsfaaaa//输出:①"dsfa\0" ②"aaa\n\0"

//结果2:
sa//读入"sa\n",储存的是"sa\n\0"
sa//输出"sa\n\0\n"

sa//输出"sa\n\0"
aaa//输出:"aaa\n\0"

3.printf():

参见 C语言基础.常用命令,运算符,控制符.1.(1) 部分

注意:printf()的效率低于puts(),而且需要输入更多代码;printf()使打印多个字符串更简单,并且可以格式化不
同的数据类型

四.字符串函数

详情参见 C语言基础.库函数.4 部分

拷贝字符串:char * strcpy(char * restrict <tstr>,const char * restrict <fstr>);
进行有上限的字符串拷贝:char * strcpy(char * restrict <tstr>,const char * restrict <fstr>,size_t <n>);

拼接字符串:char * strcat(char * restrict <str1>,const char * restrict <str2>);
进行有上限的字符串拼接:char * strncat(char * restrict <str1>,const char * restrict <str2>,size_t <n>);

字符串整体比较:int strcmp(const char * <str1>,const char * <str2>);
字符串的局部比较:int strncmp(const char * <str1>,const char * <str2>,size_t <n>);

查找首个指定字符:char * strchr(const char * <s>,int c);
查找首个指定的多个字符:char * strpbrk(const char <str1>,const char * <str2>);
查找尾个指定字符:char * strchr(const char * <s>,char c);
查找尾个指定的多个字符:char * strpbrk(const char <str1>,const char * <str2>);

//注意:
1.不能在函数中修改那些使用了const标识符的参数
2.restrict标识符限制了函数参数的用法,如不能把字符串拷贝给本身
3.size_t是在string.h中通过typedef定义的某个无符号整数类型的别名,在不同系统中不同

//#######################################################################################

详情参见 C语言基础.库函数.6 部分

将str转换为整数:int atoi(const char * restrict <nptr>);
将str转换为double:double atof(const char * restrict <nptr>);
将str转换为long:long atol(const char * restrict <nptr>);
检查并将str转换为long:long strtol(const char * restrict <nptr>,char ** restrict <endptr>,int <base>);
检查并将str转换为unsigned long:unsigned long strtoul(const char * restrict <nptr>,char ** restrict <endptr>,int <base>);
检查并将str转换为double:double strtod(const char * restrict <nptr>,char ** restrict <endptr>);
将整数转换成str:char * itoa(int <i>);
将浮点数转换成str:char * itoa(double <f>);

//#######################################################################################

详情参见 C语言基础.库函数.5 部分

是否是字母或数字:isalnum(const char * <ptr>);
是否是字母:isalpha(const char * <ptr>);
是否是标准的空白字符或本地化指定为空白的字符:isblank(const char * <ptr>);
是否是控制字符(如Ctrl+B):iscntrl(const char * <ptr>);
是否是数字:isdigit(const char * <ptr>);
是否是除空格外的任意可打印字符:isgraph(const char * <ptr>);
是否是小写字母:islower(const char * <ptr>);
是否是可打印字符:isprint(const char * <ptr>);
是否是标点符号:ispunct(const char * <ptr>);
是否是空白字符:isspace(const char * <ptr>);
是否是大写字母:isupper(const char * <ptr>);
是否是16进制数字符:isxdigit(const char * <ptr>);
将大写字母转换成小写字母:char * tolower(const char * <ptr>);
将小写字母转换成大写字母:char * toupper(const char * <ptr>);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值