目录
10.2.1 单字符的输入输出
putchar
int putchar(int c); 输出的不是char类型而是int类型,但是不能接收4字符
向标准输出写一个字符(在标准输出里输出一个字符)
返回写了几个字符,EOF(-1)表示写失败,返回1为正常
getchar
int getchar(void);
从标准输入读入一个字符
返回其读到的字符。返回类型也是int,这是为了返回EOF(-1),来表示输入结束。我们来解释一下
#include<stdio.h>
int main(int argc,char const *argv[])
{
int ch;
while((ch = getchar()) != EOF)
{
putchar(ch);
}
printf("EOF\n");
return 0;
}

(Mac上是CtrlD,而Windows是CtrlZ)
而既然getchar只读一个字符,我们输入一个字符之后它并没有自动终止读入,而是在用户按下回车之后才终止。
原因如图:

在我们的程序和我们输入与屏幕之间有shell代为处理数据,用户输入之后,shell会将输入的内容填入缓冲区,然后进行行编辑处理。shell把缓冲区里的东西输进程序。
输入的数据会在不同程序下对其进行不同的处理。其中的CtrlC会让shell直接关闭程序,而CtrlD在shell收到后,在末尾填一个-1,程序读到-1,程序就正常结束了。遇到\0,则等待用户继续输入,因此程序并不会停止读入。
string.h
strlen
size_t strlen (const char *s):可以返回s的字符串长度(不算结尾的0,sizeof会计算0)。
里边的const表示这不会对字符串进行任何更改。
#include<stdio.h>
#include<string.h>
int main(int argv,char const *argv[])
{
char a[] = "Hello";
printf("strlen = %lu\n",strlen(s));
printf("sizeof = %lu\n",sizeof(s));
return 0;
}
这个函数是怎么实现的?我们来写一个替代函数试一下!
strlen代替函数
#include<stdio.h>
#include<string.h>
//遍历数组,当数组元素为0时表示结束,用下标代替元素个数,得到字符串长度
size_t mylen(const char* s)
{
int index = 0;
while(s[index] != '\0')
{
index++;
}
return index;
}
int main(int argv,char const *argv[])
{
char a[] = "Hello";
printf("strlen = %lu\n",mylen(a));
printf("sizeof = %lu\n",sizeof(a));
return 0;
}结果和上面一样,证明了这样做可以替代strlen。
strcmp
- int strcmp(const char *s1,const char *s2); 用来比较两个字符串,返回值有三种情况
- 0:两字符串相等,s1==s2
- 1:前者较大,s1>s2
- -1:后者较大,s1<s2
#include<stdio.h>
#include<string.h>
int main(int argv,char const *argv[])
{
char s1[] = "abc";
char s2[] = "abc";
printf("strlen = %d\n",strcmp(s1,s2));
return 0;
}运行结果为0。但这个比较式不能直接用于if的条件判断,如:
if( strcmp(s1,s2) )
{ }因为if是当条件满足时才运行,返回一个0会直接跳过。因此需要这样做:
if( strcmp(s1,s2)==0 )
{ }那么是否能用这样的方式表示两个字符串相等呢?
#include<stdio.h>
#include<string.h>
int main(int argv,char const *argv[])
{
char s1[] = "abc";
char s2[] = "abc";
printf("%d\n",s1==s2);//可行吗?
printf("strlen = %d\n",strcmp(s1,s2));
return 0;
}当然不行,要知道,放在格式化输出符后面比较数组,实质是比较两个数组的地址是否相同。而两个数组的地址一定是不相同的。
我们再通过一些例子来看看这个函数的实质。
1.
char s1[] = "abc";
char s2[] = "bbc";比较结果为-1,表示后者大于前者。 也就表示a比b小,与ASCII码中的大小顺序相对应。
2.
char s1[] = "abc";
char s2[] = "Abc";比较结果为32,表示前者大于后者。也就是a比A大32,ASCII码 'a'-'A'=32。对应正确。
3.
char s1[] = "abc";
char s2[] = "abc ";//(多一个空格)比较结果为-32,表示后者大于前者。这是为什么?我们知道空格的ASCII码为32,和这个有关吗?
当然是有的。其原理如图:

因此其原理是:将两个数组中第n个元素ASCII码相减(第一个数组减第二个数组),返回结果。
所以我们可以写出替代函数:
strcmp代替函数
#include<stdio.h>
#include<string.h>
//遍历两个数组,下标相同的元素相减并返回得到的结果。
int mycmp(const char* s1,const char* s2)
{
int idx = 0;
while(1)
{
//当二者不相等时,跳出循环,直接返回差值。
if(s1[idx] != s2[idx])
{
break;
}
//当二者相等,但s1已经结束,也要跳出循环,直接返回差值
else if(s1[idx] == '\0')
{
break;
}
//相等的情况下,idx递增,继续判断
idx++;
}
return s1[idx] - s2[idx];
}
int main(int argv,char const *argv[])
{
char s1[] = "abc";
char s2[] = "abc";
printf("%d\n",s1==s2);//可行吗?
printf("strlen = %d\n",mycmp(s1,s2));
return 0;
}改进一下程序:
int mycmp(const char* s1,const char* s2)
{
int idx = 0;
//当比较的元素不相等,且s1没结束,就不退出循环,将之前的条件反过来并且合并
while( s1[idx] == s2[idx] && s1[idx] != '\0')
{
idx++;
}
return s1[idx] - s2[idx];
}甚至可以扔掉额外参数
int mycmp(const char* s1,const char* s2)
{
//条件是一样的,舍弃掉额外变量之后,需要用指针来控制遍历和比较。
//而且传进来的就是指针,可以直接用
while( *s1 == *s2 && *s1 != '\0')
{
s1++;
s2++;
}
return *s1 - *s2;
}因此在处理字符串时,要么把它当作数组,把参数作为下标,要么就把它当作指针,移动指针实现自增,指针加减实现返回值。
strcpy
- char* strcpy(char *resitrict dst, const char *restrict src); 作用是将src的字符串拷贝到dst
- 可以看到参数表里,目的地址在前而源地址在后
- restrict表示src和dst不重叠(C99)
- 返回dst的目的:让strcpy能够再参与别的运算,链接代码
用strcpy复制一个字符串:
//因为动态分配内存不清楚要复制的内容大小,用一个strlen函数获取
//因为strlen得到的是内容长度,不包含结尾的0,所以再加一就可以得到正确的长度
char* dst = (char*)malloc(strlen(src)+1);
strcpy(dst,src);strcpy代替函数
#include<stdio.h>
#include<string.h>
//复制字符串,需要将固定位置的源地址复制到定义的位置
char* mycpy(char* dst,const char* src)
{
int idx=0;
//挨个将源地址的每个字符复制到新地址,因此当源地址的值为0时退出循环
//条件也可以写成 while(src[idx]),最后一位是0符合退出条件,自动退出
while(src[idx] != '\0')
{
dst[idx] = src[idx];
idx++;
}
//当源地址复制到最后一位就已经退出,0还没有复制过去,因此退出后再补上
dst[idx] = '\0';
return dst;
}
int main(int argc,char const *argv[])
{
char s1[] = "abc";
char s2[] = "abc";
strcpy(s1,s2);
return 0;
}指针版(仅函数部分改动)
char* mycpy(char* dst,const char* src)
{
char* rst = *dst;
//一样,也可以写成while(*src)
//甚至再简化一下,整个循环只剩 while( *dst++ = *src++ )再加一个分号
while(*src != '\0')
{
*dst = *src;
*dst++;
*src++;
}
*dst = '\0';
//此时不能再返回dst,因为dst在过程中累加,值变化了
return rst;
}strcat
- char* strcat(char* restrict s1, const char *restrict s2);
- 作用:把s2拷贝到s1后面,接成一个长的字符串
- 返回s1

strcpy和strcat都有安全问题:目的地数组都可能会空间不足
安全版本
//n代表了能拷贝过去的最大字符数量限制
char* strncpy(char* restrict dst, const char *restrict src,size_t n);
//n代表了能连上的最大字符数量限制
char* strncat(char* restrict s1, const char *restrict s2,size_t n);
//n可以限制比较的个数,n代表前n个
int strncmp(const char *s1,const char *s2,size_t n);
strchr
- char *strchr(const char*s, int c); 用来在字符串里找字符,返回一个指针指向你找的字符(就是参数表里的c)
- char *strrchr(const char *s,int c); 用来从右往左找字符
- 返回NULL表示没找到
举个例子,在hello中寻找l,l出现了两次,如何分别寻找第一个和第二个?
我们先寻找一下‘l’这个字符
#include<stdio.h>
#include<string.h>
int main()
{
char s[] = "hello";
char *p = strchr(s,'l');
printf("%s\n",p);
return 0;
}将*p打印出来,结果是‘llo’,依据定义可以知道,返回的指针p指向了l,所以此时输出p会将l之后的字符打印出来
因此想要查找第二个l,只需要让指针移动一位
#include<stdio.h>
#include<string.h>
int main()
{
char s[] = "hello";
char *p = strchr(s,'l');
//这里是在查找出来的llo中继续查找l,但是指定字符串为p+1,也就是在lo中查找
p = strchr(p+1,'l');
printf("%s\n",p);
return 0;
}此时输出的就是lo
那么查找到需要的字符串之后,想要将其复制到别处,该如何做?
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(int argc,char const *argv[])
{
char s[] = "hello";
char *p = strchr(s,'l');
//新建一个t指针,把查找到的l之后的内容赋给t
char *t = (char*)malloc(strlen(p)+1);
//从p复制到t
strcpy(t,p);
printf("%s\n",t);
free(t);
//将malloc动态分配来的指针释放,free函数包含在stdlib中
return 0;
}如果我们想要得到查找到的字符之前的一段,该如何做?这里有一个小技巧:先把查找到的字符位置处更换为0,此时输出这个字符串,就得到需要的部分,然后再将其换回原来的字符,其他功能仍然可以正常运行,数组改动前后也没有变化。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(int argc,char const *argv[])
{
char s[] = "hello";
char *p = strchr(s,'l');
//建立一个临时变量c,指针指向了'l',赋给c,所以c='l'
char c = *p;
*p = '\0';
//*p = '0'之后,此时s这个字符串只有he'\0',将其拷贝到t再输出t就得到he
char *t = (char*)malloc(strlen(p)+1);
strcpy(t,p);
*p = 'c';
//将p还原回去,打印t
printf("%s\n",t);
free(t);
return 0;
}
strstr
- char *strstr(const char *s1,const char *s2);作用是在字符串里找字符串
- char *strcasestr(const char *s1,const char *s2); 和上面一样,但是忽略大小写