有基础,进阶用,个人查漏补缺
puts()只显示字符串,而且自动在末尾加上换行符
字符串定义(字符串有字符串常量、char类型数组、指向char的指针)
字符串常量:
用双括号括起来,双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中。
属于静态存储类别,说明如果在函数中使用字符串常量,该字符串常量只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次。
用双括号括起来的内容被视为指向该字符串储存位置的指针。
printf("%s, %p, %c\n", "We", "are", *"space farers");
//输出:We, 0x100000f61, s
//%s代表字符串型格式符,%p用于以十六进制的形式打印地址(指针地址),%c代表字符型格式符
//%x、%X和%p的相同点都是16进制,但是%p会按编译器位数长短(32位/64位)输出地址,不够的补零
//printf()根据%s打印We
//根据%p打印地址(如果“are”代表一个地址,printf()将会打印该字符串的首字符地址)
//*"space farers"表示该字符串所指向地址上储存的值,为该字符串首字符
//字符串常量之间没有间隔或者用空格分割,C语言会将其视为串连起来的字符串常量
char a[50] = "hello, and"" how are" " you"" today!";
//等价于
char a[50] = "hello, and how are you today!";
char类型数组:
在指定数组大小时,要确保数组元素个数至少比字符串长度多1(为了容纳\0),所有未被使用的元素会被自动初始化为0(指的是char形式的空字符\0,不是数字字符0)
如果忽略数组初始化声明中的数组大小,编译器会自动根据初始化内容进行计算
const char b1[] = "ni hao";//char数组初始化
const char b2[7] = {'n', 'i', ' ', 'h', 'a', 'o', '\0'};//标准的数组初始化,复杂
//如果没有最后的\0,则是数组;有\0,则是字符串
指向char的指针
//这两个声明形式几乎一致
//初始化数组把静态存储区的字符串拷贝到数组中
const char arl[] = "something is pointing at me.";//28个字符+\0=29个
//初始化指针只把字符串的地址拷贝给指针
const char * pt1 = "something is pointing at me.";
数组形式中,arl为该数组首元素地址的别名(&arl[0]),所以arl是地址常量。不能更改arl,如果改变,则意味着改变了数组的储存位置(即地址)。可进行arl+1的操作,但是不能++arl。递增运算符不能用于常量。
指针形式中,编译器会为指针变量pt1留一个储存位置,把字符串的地址储存在指针变量中。该变量最初指向该字符的首字符,但是它的值可以改变。因此,可以使用递增运算符,++pt1指向第二个字符。
由于pt1指向const数据,所以应该把pt1声明为指向const数据的指针,这意味着不能用pt1改变它所指向的数据,但是仍然可以改变pt1的值(即pt1指向的位置)。
如果不修改字符串,就不要用指针指向字符串常量,即建议在把指针初始化为字符串变量时使用const限定符
//下面语句都引用了字符串“klingon”的内存位置
char * p1 = "klingon";
p1[0] = 'f'; //由于是非const,允许修改为f,所以该地址对应的值变成了f
printf("klingon");//printf()使用的是内存里的值,故"klingon"对应地址里,k变成了f
printf(": beware the %s!\n", "klingon");//同上
//输出:
//flingon: beware the flingon!
//当要打印"klingon"时,都会打印"flingon"
//建议在把指针初始化为字符串变量时使用const限定符
const char * p1 = "klingon";
数组和指针的区别
两者最主要的区别是:数组名ar是常量,指针名pt是变量
char ar[] = "I love Tim."; //数组的元素是变量,但数组名不是变量
const char *pt = "I love Sam.";
实际使用上:
都可以使用数组表示法:
//均输出I love
for(i=0; i<6; i++)
{
putchar(ar[i]);
}
for(i=0; i<6; i++)
{
putchar(pt[i]);
}
都可以进行指针加法操作
//均输出I love
for(i=0; i<6; i++)
{
putchar( *(ar + i) );
}
for(i=0; i<6; i++)
{
putchar( *(pt + i) );
}
只有指针法可以进行递增操作
while( *(pt) != '\0' ) //在字符串末尾处停止
putchar( *(pt++) ); //打印字符,指针指向下一个位置
//输出I love Sam.
如果想让数组和指针统一,则
pt = ar;//pt现在指向数组ar的首元素
//不能这么做
ar = pt;//非法构造,赋值运算符左侧必须可以修改(数组元素是变量,但数组名不是变量)
//但是这么做不会导致pt指向的字符串消失,只是改变了储存在pt中的地址
可以改变a数组中的元素的信息
ar[7] = 'm';
*(ar + 7) = 'm';
字符串数组:
const char *pt[5] = {"one", "tow", "three", "four", "five"};
char ar[5][40] = {"ONE", "TOW", "THREE", "FOUR", "FIVE"};
pt是一个内含5个指针的数组,在系统中共占40字节
ar是一个内含5个数组的数组,每个数组内含40个char类型的数据,共占用200个字节
虽然pt[0]和ar[0]都分别表示一个字符串,但是pt和ar的类型并不同
pt可看做一个不规则数组,每行长度不一样(pt数组的指针元素所指向的字符串不必储存在连续的内存中);ar可看作一个矩形二维数组,每行长度都是40字节
综上所述,如果要用数组表示一系列待显示的字符串,就使用指针数组,效率更高,但是指针指向的字符串常量不能修改;
而字符串数组可以修改,如果要改变字符串或为字符串输入预留空间,不要使用指向字符串常量的指针
字符串输入
要把一个字符串读入程序,要先预留储存该字符串的空间,然后用输入函数获取该字符串
分配空间
如果未分配空间,如
char *name;
scanf("%s", name);
会通过编译,但是在读入的过程中,name可能会擦写掉程序中的数据或代码。因为scanf()要把信息拷贝到参数指定的地址上,但此时该参数是一个未初始化的指针,name可能会指向任何地方。
解决方法:在声明时显式指明数组大小,或使用C库函数分配内存(12章介绍)
char name[81];
gets()(切勿使用)
gets()代替品:
C11不一定支持,专门用于处理文件输入
fgets()
char *fgets(char *str, int n, FILE *stream)
str – 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
n – 这是要读取的最大字符数(包括最后的空字符),所以将读入n-1个字符或者读到第一个换行符为止。通常是使用以 str 传递的数组长度。
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。即指明要读入的文件。如果读入从键盘输入的数据,则以stdin作为数据,该标识符定义在stdio.h。
如果成功,该函数返回char指针,与传入的第一个参数str相同。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针(NULL)。如果发生错误,返回一个空指针。
fgets会读入换行符,puts则不会添加换行符。与gets和puts相反。
举例代码:
#include
#define STLEN 14
int main(void)
{
char words[STLEN];
//该函数用于显示字符串,并在末尾添加\n
puts("Enter a string.");
//输入apple pie,apple pie\n\0会被存入
fgets(words, STLEN, stdin);
puts(words); //会丢弃换行符
fputs(words, stdout);
return 0;
}
输出:
Enter a string.————puts()输出,末尾会添加\n
**apple pie** ————fgets()输入,会把\n存入,即apple pie\n\0
****apple pie ————puts()输出,末尾会添加\n
apple pie ————fputs()输出,末尾不会添加\n
举例代码:虽然STLEN被设置为10,但是该程序在处理过长的输入时完全没有问题。
#include
#define STLEN 10
int main(void)
{
char words[STLEN];
puts("Enter a string.");//该函数会在末尾添加\n
//当读到文件末尾或者读到空字符,或者读入了一行中的第一个字符为\n
while(fgets(words, STLEN, stdin) != NULL && words[0] != '\n')
fputs(words, stdout);
puts("Done.");//该函数会在末尾添加\n
return 0;
}
输出:
Enter a string. ————puts()输出,末尾会添加\n
**By the way, the gets() function** ————fgets()输入,会把\n存入
By the way, the gets() function ————fputs()输出,末尾会丢弃\n
**Also enter another string** ————输入
Also enter another string **** ————输出
————输入前,光标就在该行,然后输入\n
Done. 则该行第一个字符为\n,结束
如何处理换行符?解决方法:
#include
#define STLEN 10
int main(void)
{
char words[STLEN];
int i;
puts("Enter a string.");//该函数会在末尾添加\n
//当读到文件末尾或者读到空字符,或者读入了一行中的第一个字符为\n
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部分便丢弃输入行的剩余字符
else
while(getchar() != '\n')//读到\0就会结束
continue;
}
puts("Done.");//该函数会在末尾添加\n
return 0;
}
/*
输出:
Enter a string.
**This ————输入,存为**This\0
This
**program seems ————输入,存为**program s\0
program s
**unwilling to accept long line ————输入,存为**unwilling\0
unwilling
done
和fgets()类似,区别如下:
char *gets_s( char *str, rsize_t n );
无论如何,在调用约束处理程序之前,gets_s首先完成读取和放弃字符,stdin直到换行符,文件结束条件或读取错误。
返回值
成功时str,失败NULL。
scanf()
若fgets()、gets()函数像获取字符串函数,则scanf()是获取单词函数
scanf()如果使用%s转换说明,以下一个空白字符(空行、空格、制表符、换行符)作为字符串结束(字符串不包括空白字符)
字符宽度和scanf():口表示空格
输入语句 | 原输入序列 | name中的内容 | 剩余输入序列 |
---|---|---|---|
scanf(’%s’, name); | fleebert口hup | fleebert | 口hup |
scanf(’%5s’, name); | fleebert口hup | fleeb | ert口hup |
scanf(’%5s’, name); | ann口ular | ann | 口ular |
返回值为成功读取的项数或者EOF(读到文件结尾)
举例代码
#include
int main(void)
{
char name1[11], name2[11];
int count;
printf("Enter 2 names.\n");
count = scanf("%5s %10s", name1, name2);
printf("I read the %d names %s and %s.\n", count, name1, name2);
return 0;
}
/*输出
1.两个名字的字符个数都没有超过字段宽度,第1个单词刚好能被完全存入缓冲区
Enter 2 names.
**jesse jukes**
I read the 2 names jesse and jukes.
2.前一个单词字符个数没有超过限制5,故**遇到空格就算结束**,第2个名字只读入了前10个字符
Enter 2 names.
**liza applebottham**
I read the 2 names liza and applebotth.
**3.portensia的后4个被存入name2中,
因为第2次调用scanf()时从上一次调用结束的地方继续读取数据**
Enter 2 names.
**portensia callowit**
I read the 2 names porte and nsia.
字符串输出:puts()、fputs()、pinrtf()——337页最后一段话有误,应该写puts()
puts()
传入字符串地址作为参数即可,遇到空字符结束,会自动换行
#include
int main(void)
{ //用双引号括起来的是字符串常量
//并且会被视为该字符串的地址
char str1[80] = "I love you.";
const char * str2 = "You love me.";
puts("Please say.");
puts(str1);
puts(str2);
puts(&str1[5]);
puts(str2 + 4);
return 0;
}
输出:
Please say.
I love you.
You love me.
e you.
——&str1[5]是str1数组的第6个元素(e),从该元素开始输出
love me.
——str2 + 4指向l,从该元素开始输出
fputs()
int fputs(const char *str, FILE *stream)
函数 | 特点 | 函数 | 特点 |
---|---|---|---|
gets()(不使用) | 丢弃\n | puts() | 添加\n,会自动换行 |
fgets() | 保留\n | fputs() | 不添加\n,不会自动换行 |
printf()
字符串函数:strlen()、strcat()、strcmp()、strncmp()、strcpy()、strncpy()
函数 | 完整函数声明 | 作用 | 返回值 | 备注 |
---|---|---|---|---|
strlen() | size_t strlen(const char *str) | 用于统计字符串长度 | 返回字符串的长度 | |
strcat() | char *strcat(char *dest, const char *src) | 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。 | 返回一个指向最终的目标字符串 dest 的指针(地址) | 无法检查第一个数组是否能容纳第2个数组,可能导致溢出问题,和gets()类似 |
strncat() | char *strncat (char *dest, const char *src, size_t n) | 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止 | 返回一个指向最终的目标字符串 dest 的指针(地址) | 解决上述strcat()问题 |
strcmp() | int strcmp(const char *str1, const char *str2) | 把 str1 所指向的字符串和 str2 所指向的字符串进行比较 | str1 |
比较的是字符串内容,而不是地址,也不是整个数组,因为数组大小和字符串大小不一定相同。当字符串长度不一致时,多的部分比较的是空字符 |
strncmp() | int strncmp(const char *str1, const char *str2, size_t n) | 把 str1 和 str2 进行比较,最多比较前 n 个字符。 | str1 |
|
strcpy() | char *strcpy(char *dest, const char *src) | 把 src(源字符串) 所指向的字符串复制到 dest(目标字符串) | 返回一个指向最终的目标字符串 dest 的指针(地址) | 第一个指针应指向一个数据对象,如数组,并且该对象有足够的空间。无法检查第一个数组是否能容纳第2个数组,可能导致溢出问题,和gets()类似 |
strncpy() | char *strncpy(char *dest, const char *src, size_t n) | 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。 | 返回最终复制的字符串 | 当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。 |
strcpy()、strncpy()注意:
声明数组将分配储存数据的空间,而声明指针只分配储存一个地址的空间
目标字符串必须先初始化
char * str;
strcpy(str, "hello");//错误,str未被初始化,该字符串会被拷贝到任何地方!!!
第一个参数不必指向数组开始
const char * orig = "beast";
char copy[40] = "be the best that you can be.";
char * ps;
puts(orig);//输出beast
puts(copy);//输出be the best that you can be.
ps = strcpy(copy + 7, orig);
puts(copy);//输出be the beast,因为copy+7指向best中的b,从此处开始粘贴orig
puts(ps);//输出beast,strcpy()返回一个指向最终的目标字符串 dest 的指针(地址)
strncpy(dest, src, n)中,如果拷贝到第n个字符时还未拷贝完整个源字符串,就不会拷贝空字符,所以拷贝的副本中不一定有空字符。
鉴于此,可以把n设置为比目标数组大小小1,然后把数组最后一个元素设置为空字符:
#define TARGSIZE 7
strncpy(dest, str, TARGSIZE-1);
dest[TARGSIZE-1] = '\0';
sprintf()
int sprintf(char *str, const char *format, …)
和printf()类似,这个是把数据写入字符串中,而不是打印在显示器,即发送格式化输出到 str 所指向的字符串
str – 这是指向一个字符数组的指针,该数组存储了 C 字符串。
format – 这是字符串,包含了要被写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier:
如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。
例子:
#include
int main()
{
char str[80];
sprintf(str, "Pi 的值 = %f", 3.1415);
puts(str);
return(0);
}
/*输出:
Pi 的值 = 3.1415