有基础,进阶用,个人查漏补缺
-
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和ar都表示5个字符串
- 使用一个下标时都分别表示一个字符串,如pt[0]和ar[0]
- 使用两个下标时都分别表示一个字符,如pt[1][2]表示pt数组中第2个指针所指向的字符串的第3个字符,ar[1][2]表示ar数组的第2个字符串的第3个字符
- 两者初始化方式一样
-
pt是一个内含5个指针的数组,在系统中共占40字节
-
ar是一个内含5个数组的数组,每个数组内含40个char类型的数据,共占用200个字节
-
虽然pt[0]和ar[0]都分别表示一个字符串,但是pt和ar的类型并不同
- pt中的指针指向初始化时所用的字符串常量的位置,这些常量被储存在静态内存中
- ar中的数组则被储存着字符串常量的副本,所以每个字符串都被储存了两次
-
pt可看做一个不规则数组,每行长度不一样(pt数组的指针元素所指向的字符串不必储存在连续的内存中);ar可看作一个矩形二维数组,每行长度都是40字节
-
综上所述,如果要用数组表示一系列待显示的字符串,就使用指针数组,效率更高,但是指针指向的字符串常量不能修改;
而字符串数组可以修改,如果要改变字符串或为字符串输入预留空间,不要使用指向字符串常量的指针
-
字符串输入
要把一个字符串读入程序,要先预留储存该字符串的空间,然后用输入函数获取该字符串
-
分配空间
如果未分配空间,如
char *name; scanf("%s", name);
会通过编译,但是在读入的过程中,name可能会擦写掉程序中的数据或代码。因为scanf()要把信息拷贝到参数指定的地址上,但此时该参数是一个未初始化的指针,name可能会指向任何地方。
解决方法:在声明时显式指明数组大小,或使用C库函数分配内存(12章介绍)
char name[81];
-
gets()(切勿使用)
- 读取整行输入,直至遇到换行符,然后丢弃换行符,储存其余字符,并在末尾添加\0使其成为C字符串。
- 常和puts()配对使用,该函数用于显示字符串,并在末尾添加换行符。即gets丢弃,puts添加。
- gets()只知道数组开始处,无法检查数组是否装得下输入行,如果输入过长,则会导致缓冲区溢出,即多余字符超出了指定的目标空间。
- 如果这些多余的字符只是占用了尚未使用的内存,就不会立刻出现问题
- 如果它们擦写掉程序中的其他数据,会导致程序异常终止
- 不建议使用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 <stdio.h> #define STLEN 14 int main(void) {char words[STLEN];//该函数用于显示字符串,并在末尾添加\nputs("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()输出,末尾会添加\napple pie ————fputs()输出,末尾不会添加\n
-
-
举例代码:虽然STLEN被设置为10,但是该程序在处理过长的输入时完全没有问题。
- 程序中的fgets()一次读入STLEN-1 = 9个字符,故一开始只读入了”By the wa”(10个字符),并储存为By the wa\0
- 接着fputs()打印该字符,末尾不会添加\n(本此迭代没有),不会换行
- while循环进入下一轮迭代,读入”y, the ge” ,并储存为y, the ge\0
- 直至读入”tion\n”,由于字符串中的\n被输出,光标被移至下一行开始处
- 在按下enter之前,输入都被储存在缓冲区,按下后输入了一个\n,并把整行输入发送给fgets()。fputs()把字符发送给另一个缓冲区,当发送换行符的时候,缓冲区的内容被发送到屏幕。
#include <stdio.h> #define STLEN 10 int main(void) {char words[STLEN];puts("Enter a string.");//该函数会在末尾添加\n//当读到文件末尾或者读到空字符,或者读入了一行中的第一个字符为\nwhile(fgets(words, STLEN, stdin) != NULL && words[0] != '\n')fputs(words, stdout);puts("Done.");//该函数会在末尾添加\nreturn 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 <stdio.h> #define STLEN 10 int main(void) {char words[STLEN];int i;puts("Enter a string.");//该函数会在末尾添加\n//当读到文件末尾或者读到空字符,或者读入了一行中的第一个字符为\nwhile(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部分便丢弃输入行的剩余字符elsewhile(getchar() != '\n')//读到\0就会结束continue;}puts("Done.");//该函数会在末尾添加\nreturn 0; } /* 输出: Enter a string. **This ————输入,存为**This\0 This **program seems ————输入,存为**program s\0 program s **unwilling to accept long line ————输入,存为**unwilling\0 unwillingdone
-
-
- char *gets_s( char *str, rsize_t n )
-
-
和fgets()类似,区别如下:
- gets_s()只从标准输入中读取数据,故不需要第3个参数
- 如果gets_s()读到换行符,会丢弃它而不是储存它
- 如果gets_s()读到最大字符数都没有读到换行符,会执行:
- 首先把目标数组中的首字符设置为空字符
- 读取并丢弃随后的输入,直至读到换行符或文件结尾
- 最后返回空指针
-
char *gets_s( char *str, rsize_t n );
- 读stdin入指向的字符数组,str直到找到换行符或发生文件结束。在读入数组的最后一个字符后立即写入空字符。换行符被丢弃,但不存储在缓冲区中。
- 读取字符,stdin直到找到换行符或发生文件结束。只将大部分n-1字符写入指向的数组中str,并始终写入终止空字符(除非str是空指针)。换行符(如果找到)将被丢弃,并且不计入写入缓冲区的字符数。在运行时检测到以下错误并调用当前安装的约束处理函数:
- n是零
- n大于RSIZE_MAX
- str 是一个空指针
- 将n-1字符存储到缓冲区后,不会遇到endline或eof 。
无论如何,在调用约束处理程序之前,gets_s首先完成读取和放弃字符,stdin直到换行符,文件结束条件或读取错误。
返回值
成功时str,失败NULL。
- gets()、fgets()、gets_s()的适用性比较
- 当输入行太长,gets()会擦写现有数据,gets_s()会丢弃多余字符,故fgets()最好
- 故首选fgets()
-
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 <stdio.h> 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 <stdio.h> 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)
- 是puts()针对文件定制的版本,区别如下:
- fputs()第2个参数指明要写入数据的文件,如果要打印在显示器上,可以用定义在stdio.h中的stdout
- 不会自动换行
函数 特点 函数 特点 gets()(不使用) 丢弃\n puts() 添加\n,会自动换行 fgets() 保留\n fputs() 不添加\n,不会自动换行
- 是puts()针对文件定制的版本,区别如下:
-
printf()
- 不会自动换行
- 和puts相比,计算机执行时间更长
-
-
字符串函数: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<str2,则返回值<0; str1>str2,则返回值>0;str1=str2,则返回值=0 比较的是字符串内容,而不是地址,也不是整个数组,因为数组大小和字符串大小不一定相同。当字符串长度不一致时,多的部分比较的是空字符 strncmp() int strncmp(const char *str1, const char *str2, size_t n) 把 str1 和 str2 进行比较,最多比较前 n 个字符。 str1<str2,则返回值<0; str1>str2,则返回值>0;str1=str2,则返回值=0 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 <stdio.h> int main() {char str[80];sprintf(str, "Pi 的值 = %f", 3.1415);puts(str);return(0); } /*输出: Pi 的值 = 3.1415
-